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!
This commit is contained in:
Aleos 2022-04-21 09:31:27 -04:00 committed by GitHub
parent b17b0c7a0b
commit 53bc2376a6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 139 additions and 80 deletions

View File

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

View File

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

View File

@ -3,7 +3,7 @@
//===== By: ================================================== //===== By: ==================================================
//= rAthena Dev Team //= rAthena Dev Team
//===== Last Updated: ======================================== //===== Last Updated: ========================================
//= 20220406 //= 20220414
//===== Description: ========================================= //===== Description: =========================================
//= Explanation of the status.yml file and structure. //= 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) None - No effect (Default)
Stone - Stone curse effect Stone - Stone curse effect
StoneWait - Stone curse incubation effect
Freeze - Freeze effect Freeze - Freeze effect
Stun - Stun effect Stun - Stun effect
Sleep - Sleep effect Sleep - Sleep effect

View File

@ -1302,11 +1302,24 @@ int skill_additional_effect(struct block_list* src, struct block_list *bl, uint1
type = it.sc; type = it.sc;
time = it.duration; 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) 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) 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; type = it.sc;
time = it.duration; 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) 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) 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] //"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) { 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; type = it.sc;
time = it.duration; 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) 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)) 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 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; 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)); 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; break;
} }
case 9: // DEATH - curse, coma and poison case 9: // DEATH - curse, coma and poison

View File

@ -1000,8 +1000,15 @@ int status_damage(struct block_list *src,struct block_list *target,int64 dhp, in
for (const auto &it : status_db) { for (const auto &it : status_db) {
sc_type type = static_cast<sc_type>(it.first); 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); status_change_end(target, type, INVALID_TIMER);
}
} }
if ((sce=sc->data[SC_ENDURE]) && !sce->val4) { if ((sce=sc->data[SC_ENDURE]) && !sce->val4) {
/** [Skotlex] /** [Skotlex]
@ -8640,6 +8647,8 @@ void status_change_init(struct block_list *bl)
struct status_change *sc = status_get_sc(bl); struct status_change *sc = status_get_sc(bl);
nullpo_retv(sc); nullpo_retv(sc);
memset(sc, 0, sizeof (struct status_change)); memset(sc, 0, sizeof (struct status_change));
sc->lastEffect = SC_NONE;
sc->lastEffectTimer = INVALID_TIMER;
} }
/*========================================== [Playtester] /*========================================== [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 // Check failing SCs from list
if (!scdb->fail.empty()) { if (!scdb->fail.empty()) {
for (const auto &it : scdb->fail) { 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; return 0;
} }
} }
@ -9624,6 +9634,16 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
break; 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. // Before overlapping fail, one must check for status cured.
std::vector<sc_type> endlist; 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. // List of hardcoded status cured.
switch (type) { 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: case SC_BLESSING:
// !TODO: Blessing and Agi up should do 1 damage against players on Undead Status, even on PvM // !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] // !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); pc_bonus_script_clear(sd, BSF_REM_ON_MADOGEAR);
break; break;
default: default:
// If new SC has OPT1 while unit has OPT1, fail it!
if (sc->opt1 && scdb->opt1)
return 0;
break; break;
} }
@ -12012,14 +12023,13 @@ int status_change_start(struct block_list* src, struct block_list* bl,enum sc_ty
unit_stop_attack(bl); unit_stop_attack(bl);
} }
break; break;
case SC_WHITEIMPRISON:
case SC_DEEPSLEEP:
case SC_CRYSTALIZE:
case SC_FREEZE: case SC_FREEZE:
case SC_STUN: case SC_STUN:
case SC_GRAVITYCONTROL: case SC_STONE:
if (sc->data[SC_DANCING]) if (sc->data[SC_DANCING]) {
unit_stop_walking(bl, 1); unit_stop_walking(bl, 1);
status_change_end(bl, SC_DANCING, INVALID_TIMER);
}
break; break;
default: default:
if (!unit_blown_immune(bl,0x1)) 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 if (sce->timer != INVALID_TIMER) // Could be a SC with infinite duration
delete_timer(sce->timer,status_change_timer); 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)--; (sc->count)--;
@ -14495,6 +14485,29 @@ static TIMER_FUNC(status_natural_heal_timer){
return 0; 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 * Check if status is disabled on a map
* @param type: Status Change data * @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_change_timer,"status_change_timer");
add_timer_func_list(status_natural_heal_timer,"status_natural_heal_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(); initDummyData();
status_readdb(); status_readdb();
natural_heal_prev_tick = gettick(); natural_heal_prev_tick = gettick();

View File

@ -3055,6 +3055,8 @@ struct status_change {
unsigned short opt1;// body state unsigned short opt1;// body state
unsigned short opt2;// health state (bitfield) unsigned short opt2;// health state (bitfield)
unsigned char count; 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. //! TODO: See if it is possible to implement the following SC's without requiring extra parameters while the SC is inactive.
struct { struct {
uint8 move; 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); 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_buffs(struct block_list* bl, uint8 type);
void status_change_clear_onChangeMap(struct block_list *bl, struct status_change *sc); 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_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) #define status_calc_pet(pd, opt) status_calc_bl_(&(pd)->bl, status_db.getSCB_ALL(), opt)