From 53bc2376a60f01bfcde568fa1eaff144fb3c4fdc Mon Sep 17 00:00:00 2001 From: Aleos Date: Thu, 21 Apr 2022 09:31:27 -0400 Subject: [PATCH] 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! --- db/pre-re/status.yml | 37 ++++++++++--------- db/re/status.yml | 31 ++++++++-------- doc/status.txt | 3 +- src/map/skill.cpp | 59 +++++++++++++++++++++++++----- src/map/status.cpp | 86 +++++++++++++++++++++++++------------------- src/map/status.hpp | 3 ++ 6 files changed, 139 insertions(+), 80 deletions(-) diff --git a/db/pre-re/status.yml b/db/pre-re/status.yml index d98c5038ad..a411858fcf 100644 --- a/db/pre-re/status.yml +++ b/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 diff --git a/db/re/status.yml b/db/re/status.yml index 00069a5089..58961b0ee6 100644 --- a/db/re/status.yml +++ b/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 diff --git a/doc/status.txt b/doc/status.txt index 0ac48e99aa..d27addbb55 100644 --- a/doc/status.txt +++ b/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 diff --git a/src/map/skill.cpp b/src/map/skill.cpp index ab432ceb3a..c071dcff55 100755 --- a/src/map/skill.cpp +++ b/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 diff --git a/src/map/status.cpp b/src/map/status.cpp index a04fb386da..18275b5d1a 100644 --- a/src/map/status.cpp +++ b/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(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 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(); diff --git a/src/map/status.hpp b/src/map/status.hpp index 79989de7ab..dd1e92d311 100644 --- a/src/map/status.hpp +++ b/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)