Преглед изворни кода

Updates stacking for common statuses (#6807)

* Fixes #6798.
* Updates the Fail, End, and EndReturn lists for OPT1 and OPT2 statuses.
* Removes the hardcoded OPT1 overwrite prevention check.
* OPT1 that have RemoveOnDamaged flag should not get applied again in the same attack.
* Fixes Stone status not properly being inflicted by the bAddEff, bAddEff2, bAddEffWhenHit, and bAddEffOnSkill item bonuses.
* Fixes Stone status not properly being inflicted by The Hanged Man from Tarot Card of Fate.
Thanks to @Playtester!
Aleos пре 3 година
родитељ
комит
53bc2376a6
6 измењених фајлова са 139 додато и 80 уклоњено
  1. 18 19
      db/pre-re/status.yml
  2. 15 16
      db/re/status.yml
  3. 2 1
      doc/status.txt
  4. 51 8
      src/map/skill.cpp
  5. 50 36
      src/map/status.cpp
  6. 3 0
      src/map/status.hpp

+ 18 - 19
db/pre-re/status.yml

@@ -70,7 +70,6 @@ Body:
       Stun: true
       Sleep: true
       Burning: true
-      #Undead: true
     End:
       Aeterna: true
     EndReturn:
@@ -89,7 +88,6 @@ Body:
       Stun: true
       Sleep: true
       Burning: true
-      #Undead: true
     EndReturn:
       StoneWait: true
       Stone: true
@@ -115,8 +113,13 @@ Body:
       Inspiration: true
       Warmer: true
       Gvg_Freez: true
+      Stone: true
+      StoneWait: true
+      Freeze: true
+      Stun: true
+      Sleep: true
+      Burning: true
     End:
-      Dancing: true
       Aeterna: true
   - Status: Stun
     DurationLookup: NPC_STUNATTACK
@@ -135,8 +138,7 @@ Body:
       Refresh: true
       Inspiration: true
       Gvg_Stun: true
-    End:
-      Dancing: true
+      Stun: true
   - Status: Sleep
     DurationLookup: NPC_SLEEPATTACK
     States:
@@ -155,8 +157,7 @@ Body:
       Refresh: true
       Inspiration: true
       Gvg_Sleep: true
-    End:
-      Dancing: true
+      Sleep: true
   - Status: Poison
     DurationLookup: NPC_POISON
     CalcFlags:
@@ -172,6 +173,8 @@ Body:
     Fail:
       Refresh: true
       Inspiration: true
+      Poison: true
+      Dpoison: true
   - Status: Curse
     DurationLookup: NPC_CURSEATTACK
     CalcFlags:
@@ -189,6 +192,7 @@ Body:
       Refresh: true
       Inspiration: true
       Gvg_Curse: true
+      Curse: true
   - Status: Silence
     DurationLookup: NPC_SILENCEATTACK
     States:
@@ -204,16 +208,16 @@ Body:
       Refresh: true
       Inspiration: true
       Gvg_Silence: true
+      Silence: true
   - Status: Confusion
     DurationLookup: NPC_WIDECONFUSE
     Flags:
       BossResist: true
       StopWalking: true
-      RemoveOnDamaged: true
-      OverlapFail: true
     Fail:
       Refresh: true
       Inspiration: true
+    EndReturn:
       Confusion: true
   - Status: Blind
     DurationLookup: NPC_BLINDATTACK
@@ -231,6 +235,7 @@ Body:
       Inspiration: true
       Fear: true
       Gvg_Blind: true
+      Blind: true
   - Status: Bleeding
     Icon: EFST_BLOODING
     DurationLookup: NPC_BLEEDING
@@ -243,7 +248,6 @@ Body:
       BossResist: true
       NoSave: true
       NoClearance: true
-      OverlapFail: true
     Fail:
       Refresh: true
       Inspiration: true
@@ -257,7 +261,6 @@ Body:
     Flags:
       SendOption: true
       BossResist: true
-      OverlapFail: true
     Fail:
       Refresh: true
       Inspiration: true
@@ -415,6 +418,8 @@ Body:
     Flags:
       BossResist: true
       TaekwonAngel: true
+    EndReturn:
+      Curse: true
   - Status: Signumcrucis
     Icon: EFST_CRUCIS
     DurationLookup: AL_CRUCIS
@@ -2840,7 +2845,6 @@ Body:
     Flags:
       BossResist: true
       StopWalking: true
-      OverlapFail: true
       Debuff: true
     Fail:
       Inspiration: true
@@ -2849,6 +2853,8 @@ Body:
   - Status: Burning
     Icon: EFST_BURNT
     DurationLookup: RK_DRAGONBREATH
+    CalcFlags:
+      Mdef: true
     Opt1: Burning
     Flags:
       SendOption: true
@@ -2861,8 +2867,6 @@ Body:
     Fail:
       Refresh: true
       Inspiration: true
-    CalcFlags:
-      Mdef: true
   - Status: Freezing
     Icon: EFST_FROSTMISTY
     DurationLookup: WL_FROSTMISTY
@@ -3076,13 +3080,8 @@ Body:
       SetStand: true
       StopWalking: true
       StopAttacking: true
-      OverlapFail: true
     End:
-      Dancing: true
-      Burning: true
       Freezing: true
-      Freeze: true
-      Stone: true
   - Status: Marshofabyss
     Icon: EFST_MARSHOFABYSS
     DurationLookup: WL_MARSHOFABYSS

+ 15 - 16
db/re/status.yml

@@ -71,7 +71,6 @@ Body:
       Stun: true
       Sleep: true
       Burning: true
-      #Undead: true
     End:
       Aeterna: true
     EndReturn:
@@ -91,7 +90,6 @@ Body:
       Stun: true
       Sleep: true
       Burning: true
-      #Undead: true
     EndReturn:
       StoneWait: true
       Stone: true
@@ -117,8 +115,14 @@ Body:
       Inspiration: true
       Warmer: true
       Gvg_Freez: true
+      Whiteimprison: true
+      Stone: true
+      StoneWait: true
+      Freeze: true
+      Stun: true
+      Sleep: true
+      Burning: true
     End:
-      Dancing: true
       Aeterna: true
   - Status: Stun
     DurationLookup: NPC_STUNATTACK
@@ -137,8 +141,7 @@ Body:
       Refresh: true
       Inspiration: true
       Gvg_Stun: true
-    End:
-      Dancing: true
+      Stun: true
   - Status: Sleep
     DurationLookup: NPC_SLEEPATTACK
     States:
@@ -157,8 +160,7 @@ Body:
       Refresh: true
       Inspiration: true
       Gvg_Sleep: true
-    End:
-      Dancing: true
+      Sleep: true
   - Status: Poison
     DurationLookup: NPC_POISON
     CalcFlags:
@@ -175,6 +177,8 @@ Body:
     Fail:
       Refresh: true
       Inspiration: true
+      Poison: true
+      Dpoison: true
   - Status: Curse
     DurationLookup: NPC_WIDECURSE
     CalcFlags:
@@ -193,6 +197,7 @@ Body:
       Refresh: true
       Inspiration: true
       Gvg_Curse: true
+      Curse: true
   - Status: Silence
     DurationLookup: NPC_SILENCEATTACK
     States:
@@ -209,17 +214,17 @@ Body:
       Refresh: true
       Inspiration: true
       Gvg_Silence: true
+      Silence: true
   - Status: Confusion
     DurationLookup: NPC_WIDECONFUSE
     Flags:
       BossResist: true
       StopWalking: true
-      RemoveOnDamaged: true
-      OverlapFail: true
       SpreadEffect: true
     Fail:
       Refresh: true
       Inspiration: true
+    EndReturn:
       Confusion: true
   - Status: Blind
     DurationLookup: NPC_BLINDATTACK
@@ -238,6 +243,7 @@ Body:
       Inspiration: true
       Fear: true
       Gvg_Blind: true
+      Blind: true
   - Status: Bleeding
     Icon: EFST_BLOODING
     DurationLookup: NPC_BLEEDING
@@ -250,7 +256,6 @@ Body:
       BossResist: true
       NoSave: true
       NoClearance: true
-      OverlapFail: true
       SpreadEffect: true
     Fail:
       Refresh: true
@@ -265,7 +270,6 @@ Body:
     Flags:
       SendOption: true
       BossResist: true
-      OverlapFail: true
     Fail:
       Refresh: true
       Inspiration: true
@@ -2951,7 +2955,6 @@ Body:
     Flags:
       BossResist: true
       StopWalking: true
-      OverlapFail: true
       Debuff: true
     Fail:
       Inspiration: true
@@ -3188,12 +3191,8 @@ Body:
       StopWalking: true
       StopAttacking: true
       StopCasting: true
-      OverlapFail: true
     End:
-      Burning: true
       Freezing: true
-      Freeze: true
-      Stone: true
   - Status: Marshofabyss
     Icon: EFST_MARSHOFABYSS
     DurationLookup: WL_MARSHOFABYSS

+ 2 - 1
doc/status.txt

@@ -3,7 +3,7 @@
 //===== By: ==================================================
 //= rAthena Dev Team
 //===== Last Updated: ========================================
-//= 20220406
+//= 20220414
 //===== Description: =========================================
 //= Explanation of the status.yml file and structure.
 //============================================================
@@ -113,6 +113,7 @@ Opt1: Special effect when status is active (Aegis: BODYSTATE_*). This option is
 
 	None         - No effect (Default)
 	Stone        - Stone curse effect
+	StoneWait    - Stone curse incubation effect
 	Freeze       - Freeze effect
 	Stun         - Stun effect
 	Sleep        - Sleep effect

+ 51 - 8
src/map/skill.cpp

@@ -1302,11 +1302,24 @@ int skill_additional_effect(struct block_list* src, struct block_list *bl, uint1
 				type = it.sc;
 				time = it.duration;
 
+				int32 val1 = 7, val2, val3;
+
+				if (type == SC_STONEWAIT) {
+					val2 = src->id;
+					val3 = skill_get_time(status_db.getSkill(type), 7);
+				} else {
+					val2 = 0;
+					if (type == SC_BURNING)
+						val3 = src->id;
+					else
+						val3 = 0;
+				}
+
 				if (it.flag&ATF_TARGET)
-					status_change_start(src,bl,type,rate,7,0,(type == SC_BURNING)?src->id:0,0,time,SCSTART_NONE);
+					status_change_start(src,bl,type,rate,val1,val2,val3,0,time,SCSTART_NONE);
 
 				if (it.flag&ATF_SELF)
-					status_change_start(src,src,type,rate,7,0,(type == SC_BURNING)?src->id:0,0,time,SCSTART_NONE);
+					status_change_start(src,src,type,rate,val1,val2,val3,0,time,SCSTART_NONE);
 			}
 		}
 
@@ -1321,10 +1334,23 @@ int skill_additional_effect(struct block_list* src, struct block_list *bl, uint1
 				type = it.sc;
 				time = it.duration;
 
+				int32 val1 = 7, val2, val3;
+
+				if (type == SC_STONEWAIT) {
+					val2 = src->id;
+					val3 = skill_get_time(status_db.getSkill(type), 7);
+				} else {
+					val2 = 0;
+					if (type == SC_BURNING)
+						val3 = src->id;
+					else
+						val3 = 0;
+				}
+
 				if (it.target&ATF_TARGET)
-					status_change_start(src,bl,type,it.rate,7,0,0,0,time,SCSTART_NONE);
+					status_change_start(src,bl,type,it.rate,val1,val2,val3,0,time,SCSTART_NONE);
 				if (it.target&ATF_SELF)
-					status_change_start(src,src,type,it.rate,7,0,0,0,time,SCSTART_NONE);
+					status_change_start(src,src,type,it.rate,val1,val2,val3,0,time,SCSTART_NONE);
 			}
 			//"While the damage can be blocked by Pneuma, the chance to break armor remains", irowiki. [Cydh]
 			if (dmg_lv == ATK_BLOCK && skill_id == AM_ACIDTERROR) {
@@ -2519,11 +2545,24 @@ int skill_counter_additional_effect (struct block_list* src, struct block_list *
 			type = it.sc;
 			time = it.duration;
 
+			int32 val1 = 7, val2, val3;
+
+			if (type == SC_STONEWAIT) {
+				val2 = src->id;
+				val3 = skill_get_time(status_db.getSkill(type), 7);
+			} else {
+				val2 = 0;
+				if (type == SC_BURNING)
+					val3 = src->id;
+				else
+					val3 = 0;
+			}
+
 			if (it.flag&ATF_TARGET && src != bl)
-				status_change_start(src,src,type,rate,7,0,0,0,time,SCSTART_NONE);
+				status_change_start(src,src,type,rate,val1,val2,val3,0,time,SCSTART_NONE);
 
 			if (it.flag&ATF_SELF && !status_isdead(bl))
-				status_change_start(src,bl,type,rate,7,0,0,0,time,SCSTART_NONE);
+				status_change_start(src,bl,type,rate,val1,val2,val3,0,time,SCSTART_NONE);
 		}
 	}
 
@@ -4848,10 +4887,14 @@ static int skill_tarotcard(struct block_list* src, struct block_list *target, ui
 	}
 	case 8: // THE HANGED MAN - stop, freeze or stoned
 	{
-		enum sc_type sc[] = { SC_STOP, SC_FREEZE, SC_STONE };
+		enum sc_type sc[] = { SC_STOP, SC_FREEZE, SC_STONEWAIT };
 		uint8 rand_eff = rnd() % 3;
 		int time = ((rand_eff == 0) ? skill_get_time2(skill_id, skill_lv) : skill_get_time2(status_db.getSkill(sc[rand_eff]), 1));
-		sc_start(src, target, sc[rand_eff], 100, skill_lv, time);
+
+		if (sc[rand_eff] == SC_STONEWAIT)
+			sc_start4(src, target, SC_STONEWAIT, 100, skill_lv, src->id, skill_get_time(status_db.getSkill(SC_STONEWAIT), 1), 0, time);
+		else
+			sc_start(src, target, sc[rand_eff], 100, skill_lv, time);
 		break;
 	}
 	case 9: // DEATH - curse, coma and poison

+ 50 - 36
src/map/status.cpp

@@ -1000,8 +1000,15 @@ int status_damage(struct block_list *src,struct block_list *target,int64 dhp, in
 			for (const auto &it : status_db) {
 				sc_type type = static_cast<sc_type>(it.first);
 
-				if (sc->data[type] && it.second->flag[SCF_REMOVEONDAMAGED])
+				if (sc->data[type] && it.second->flag[SCF_REMOVEONDAMAGED]) {
+					// A status change that gets broken by damage should still be considered when calculating if a status change can be applied or not (for the same attack).
+					// !TODO: This is a temporary solution until we refactor the code so that the calculation of an SC is done at the start of an attack rather than after the damage was applied.
+					if (sc->opt1 > OPT1_NONE && sc->lastEffectTimer == INVALID_TIMER) {
+						sc->lastEffectTimer = add_timer(gettick() + 10, status_clear_lastEffect_timer, target->id, 0);
+						sc->lastEffect = type;
+					}
 					status_change_end(target, type, INVALID_TIMER);
+				}
 			}
 			if ((sce=sc->data[SC_ENDURE]) && !sce->val4) {
 				/** [Skotlex]
@@ -8640,6 +8647,8 @@ void status_change_init(struct block_list *bl)
 	struct status_change *sc = status_get_sc(bl);
 	nullpo_retv(sc);
 	memset(sc, 0, sizeof (struct status_change));
+	sc->lastEffect = SC_NONE;
+	sc->lastEffectTimer = INVALID_TIMER;
 }
 
 /*========================================== [Playtester]
@@ -9262,7 +9271,8 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 	// Check failing SCs from list
 	if (!scdb->fail.empty()) {
 		for (const auto &it : scdb->fail) {
-			if (it && sc->data[it])
+			// Don't let OPT1 that have RemoveOnDamaged start a new effect in the same attack.
+			if (sc->data[it] || sc->lastEffect == it)
 				return 0;
 		}
 	}
@@ -9624,6 +9634,16 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 			break;
 	}
 
+	// Check for OPT1 stacking
+	if (sc->opt1 > OPT1_NONE && scdb->opt1 > OPT1_NONE) {
+		for (const auto &status_it : status_db) {
+			sc_type opt1_type = status_it.second->type;
+
+			if (sc->data[opt1_type] && status_it.second->opt1 > OPT1_NONE)
+				status_change_end(bl, opt1_type, INVALID_TIMER);
+		}
+	}
+
 	// Before overlapping fail, one must check for status cured.
 	std::vector<sc_type> endlist;
 
@@ -9652,12 +9672,6 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 
 	// List of hardcoded status cured.
 	switch (type) {
-		case SC_STONE:
-			if (sc->data[SC_DANCING]) {
-				unit_stop_walking(bl, 1);
-				status_change_end(bl, SC_DANCING, INVALID_TIMER);
-			}
-			break;
 		case SC_BLESSING:
 			// !TODO: Blessing and Agi up should do 1 damage against players on Undead Status, even on PvM
 			// !but cannot be plagiarized (this requires aegis investigation on packets and official behavior) [Brainstorm]
@@ -9698,9 +9712,6 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 				pc_bonus_script_clear(sd, BSF_REM_ON_MADOGEAR);
 			break;
 		default:
-			// If new SC has OPT1 while unit has OPT1, fail it!
-			if (sc->opt1 && scdb->opt1)
-				return 0;
 			break;
 	}
 
@@ -12012,14 +12023,13 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
 					unit_stop_attack(bl);
 				}
 				break;
-			case SC_WHITEIMPRISON:
-			case SC_DEEPSLEEP:
-			case SC_CRYSTALIZE:
 			case SC_FREEZE:
 			case SC_STUN:
-			case SC_GRAVITYCONTROL:
-				if (sc->data[SC_DANCING])
+			case SC_STONE:
+				if (sc->data[SC_DANCING]) {
 					unit_stop_walking(bl, 1);
+					status_change_end(bl, SC_DANCING, INVALID_TIMER);
+				}
 				break;
 			default:
 				if (!unit_blown_immune(bl,0x1))
@@ -12383,26 +12393,6 @@ int status_change_end_(struct block_list* bl, enum sc_type type, int tid, const
 		}
 		if (sce->timer != INVALID_TIMER) // Could be a SC with infinite duration
 			delete_timer(sce->timer,status_change_timer);
-		if (sc->opt1)
-			switch (type) {
-				// "Ugly workaround"  [Skotlex]
-				// delays status change ending so that a skill that sets opt1 fails to
-				// trigger when it also removed one
-				case SC_STONE:
-				case SC_STONEWAIT:
-					sce->val4 = -1; // Petrify time
-				case SC_FREEZE:
-				case SC_STUN:
-				case SC_SLEEP:
-					if (sce->val1) {
-						// Removing the 'level' shouldn't affect anything in the code
-						// since these SC are not affected by it, and it lets us know
-						// if we have already delayed this attack or not.
-						sce->val1 = 0;
-						sce->timer = add_timer(gettick()+10, status_change_timer, bl->id, type);
-						return 1;
-					}
-			}
 	}
 
 	(sc->count)--;
@@ -14495,6 +14485,29 @@ static TIMER_FUNC(status_natural_heal_timer){
 	return 0;
 }
 
+/**
+ * Clears the lastEffect value from a target
+ * @param tid: Timer ID
+ * @param tick: Current tick (time)
+ * @param id: Object ID
+ * @param data: data pushed through timer function
+ * @return 0
+ */
+TIMER_FUNC(status_clear_lastEffect_timer) {
+	block_list *bl = map_id2bl(id);
+
+	if (bl != nullptr) {
+		status_change *sc = status_get_sc(bl);
+
+		if (sc != nullptr) {
+			sc->lastEffect = SC_NONE;
+			sc->lastEffectTimer = INVALID_TIMER;
+		}
+	}
+
+	return 0;
+}
+
 /**
  * Check if status is disabled on a map
  * @param type: Status Change data
@@ -15184,6 +15197,7 @@ void do_init_status(void) {
 
 	add_timer_func_list(status_change_timer,"status_change_timer");
 	add_timer_func_list(status_natural_heal_timer,"status_natural_heal_timer");
+	add_timer_func_list(status_clear_lastEffect_timer, "status_clear_lastEffect_timer");
 	initDummyData();
 	status_readdb();
 	natural_heal_prev_tick = gettick();

+ 3 - 0
src/map/status.hpp

@@ -3055,6 +3055,8 @@ struct status_change {
 	unsigned short opt1;// body state
 	unsigned short opt2;// health state (bitfield)
 	unsigned char count;
+	sc_type lastEffect; // Used to check for stacking damageable SC on the same attack
+	int32 lastEffectTimer; // Timer for lastEffect
 	//! TODO: See if it is possible to implement the following SC's without requiring extra parameters while the SC is inactive.
 	struct {
 		uint8 move;
@@ -3229,6 +3231,7 @@ int status_change_timer_sub(struct block_list* bl, va_list ap);
 int status_change_clear(struct block_list* bl, int type);
 void status_change_clear_buffs(struct block_list* bl, uint8 type);
 void status_change_clear_onChangeMap(struct block_list *bl, struct status_change *sc);
+TIMER_FUNC(status_clear_lastEffect_timer);
 
 #define status_calc_mob(md, opt) status_calc_bl_(&(md)->bl, status_db.getSCB_ALL(), opt)
 #define status_calc_pet(pd, opt) status_calc_bl_(&(pd)->bl, status_db.getSCB_ALL(), opt)