PDA

View Full Version : Spell Stacking Block/Overwrite


KLS
10-04-2006, 08:21 AM
Been fiddling around with the server for a while now and I've turned my eye, briefly, on spell stacking.

I was going through the code trying to figure out why two spells that should stack, don't stack.

Hand of Virtue
1: Stacking: Block new spell if slot 3 is effect 'Max Hitpoints' and < 2405
2: Increase Max Hitpoints by 1405
3: Increase HP when cast by 1405
4: Increase AC by 72
5: Stacking: Overwrite existing spell if slot 3 is effect 'Max Hitpoints' and < 2405

Brell's Stalwart Shield
4: Increase Max Hitpoints by 330.

It puzzled me because the overwrite and block says nothing about slot 4 and the effects in slot 4 don't match at all. Looking for something that could be the problem I noticed two spots in Mob::CheckStackConflict():


if(effect1 == SE_StackingCommand_Block)
{
blocked_effect = sp1.base[i];
blocked_slot = sp1.formula[i] - 200;
blocked_below_value = sp1.max[i];

if(sp2.effectid[blocked_slot] == blocked_effect)
{

and

if(effect2 == SE_StackingCommand_Overwrite)
{
overwrite_effect = sp2.base[i];
overwrite_slot = sp2.formula[i] - 200;
overwrite_below_value = sp2.max[i];
if(sp1.effectid[overwrite_slot] == overwrite_effect)
{


This issue I believe after looking through lucy's raw data for the spells in question is the formula. The formula calc for a blocked or overwrite slot 3 effect is '203': 203-200 = 3, so we're referencing sp1.effectid[3] which would be slot 4. So instead of comparing the slot we're supposed to be we're comparing the slot+1 we're supposed to be. I think making these line changes:


if(sp2.effectid[blocked_slot-1] == blocked_effect)

&

if(sp1.effectid[overwrite_slot-1] == overwrite_effect)

would fix quite a few stacking issues, not all mind you but yeah, hope I was clear enough.

fathernitwit
10-05-2006, 11:31 AM
wow... good catch... obviously nobody ever tested that stacking code when they wrote it.

KLS
10-06-2006, 07:04 PM
Yeah it seemed very odd when I first caught it, something so small causes such a large issue. I've been looking at spell stacking more and took the liberty to tinker with some of it because I felt it was lacking somewhat.

-I changed how I solved the index problem, with just taking the formula and reducing by 201 instead of 200 because I saw no reason not too, and the value is plugged into a function below it which also works in a similar way.
-I let dots of the same resist type stack so long as they aren't the same spell, I logged into live to check this and this is indeed how they work now.
-I implemented dots of the same type stacking between different players.
-I made it so a beneficial effect and a detrimental of an effect don't conflict. Ex: snare will not overwrite sow any more and slow will not overwrite haste
-I made it so beneficial songs will stack with beneficial spells, ex: bard regen will stack with enchanter regen but bard slow will not stack on a mob with enchanter slow.
-I made it so you can snare things while they're rooted(why this is even in there /boggle)

The only large code change is to Mob::CheckStackConflict() (spells.cpp approx line 1758), change the function to:

int Mob::CheckStackConflict(int16 spellid1, int caster_level1, int16 spellid2, int caster_level2, Mob* caster1, Mob* caster2)
{
const SPDat_Spell_Struct &sp1 = spells[spellid1];
const SPDat_Spell_Struct &sp2 = spells[spellid2];

int i, effect1, effect2, sp1_value, sp2_value;
int blocked_effect, blocked_below_value, blocked_slot;
int overwrite_effect, overwrite_below_value, overwrite_slot;

/*
One of these is a bard song and one isn't and they're both beneficial so they should stack.
*/
if(IsBardSong(spellid1) != IsBardSong(spellid2))
{
if(!IsDetrimentalSpell(spellid1) && !IsDetrimentalSpell(spellid2))
{
mlog(SPELLS__STACKING, "%s and %s are beneficial, no action needs to be taken", sp1.name, sp2.name);
return (0);
}
}


// solar: check for special stacking block command in spell1 against spell2
for(i = 0; i < EFFECT_COUNT; i++)
{
effect1 = sp1.effectid[i];
if(effect1 == SE_StackingCommand_Block)
{
/*
The logic here is if you're comparing the same spells they can't block each other
from refreshing
*/
if(spellid1 == spellid2)
continue;

blocked_effect = sp1.base[i];
blocked_slot = sp1.formula[i] - 201;
blocked_below_value = sp1.max[i];

if(sp2.effectid[blocked_slot] == blocked_effect)
{
sp2_value = CalcSpellEffectValue(spellid2, blocked_slot, caster_level2);

mlog(SPELLS__STACKING, "%s (%d) blocks effect %d on slot %d below %d. New spell has value %d on that slot/effect. %s.",
sp1.name, spellid1, blocked_effect, blocked_slot+1, blocked_below_value, sp2_value, (sp2_value < blocked_below_value)?"Blocked":"Not blocked");

if(sp2_value < blocked_below_value)
return -1; // blocked
} else {
mlog(SPELLS__STACKING, "%s (%d) blocks effect %d on slot %d below %d, but we do not have that effect on that slot. Ignored.",
sp1.name, spellid1, blocked_effect, blocked_slot+1, blocked_below_value);
}
}
}

// check for special stacking overwrite in spell2 against effects in spell1
for(i = 0; i < EFFECT_COUNT; i++)
{
effect2 = sp2.effectid[i];
if(effect2 == SE_StackingCommand_Overwrite)
{
overwrite_effect = sp2.base[i];
overwrite_slot = sp2.formula[i] - 201;
overwrite_below_value = sp2.max[i];
if(sp1.effectid[overwrite_slot] == overwrite_effect)
{
sp1_value = CalcSpellEffectValue(spellid1, overwrite_slot, caster_level1);

mlog(SPELLS__STACKING, "%s (%d) overwrites existing spell if effect %d on slot %d is below %d. Old spell has value %d on that slot/effect. %s.",
sp2.name, spellid2, overwrite_effect, overwrite_slot+1, overwrite_below_value, sp1_value, (sp1_value < overwrite_below_value)?"Overwriting":"Not overwriting");

if(sp1_value < overwrite_below_value)
return 1; // overwrite spell if its value is less
} else {
mlog(SPELLS__STACKING, "%s (%d) overwrites existing spell if effect %d on slot %d is below %d, but we do not have that effect on that slot. Ignored.",
sp2.name, spellid2, overwrite_effect, overwrite_slot+1, overwrite_below_value);

}
}
}

bool sp1_detrimental = IsDetrimentalSpell(spellid1);
bool sp2_detrimental = IsDetrimentalSpell(spellid2);
bool sp_det_mismatch;

if(sp1_detrimental == sp2_detrimental)
sp_det_mismatch = false;
else
sp_det_mismatch = true;

// now compare matching effects
// abitrartion takes place if 2 spells have the same effect at the same
// effect slot, otherwise they're stackable, even if it's the same effect
bool will_overwrite = false;
for(i = 0; i < EFFECT_COUNT; i++)
{
if(IsBlankSpellEffect(spellid1, i))
continue;

effect1 = sp1.effectid[i];
effect2 = sp2.effectid[i];

/*
Quick check, are the effects the same, if so then
keep going else ignore it for stacking purposes.
*/
if(effect1 != effect2)
continue;

/*
If target is a npc and caster1 and caster2 exist
If Caster1 isn't the same as Caster2 and the effect is a DoT then ignore it.
*/
if(IsNPC())
{
if(caster1 && caster2)
{
if(caster1 != caster2)
{
if(effect1 == SE_CurrentHP)
{
if(sp1_detrimental && sp2_detrimental)
{
continue;
mlog(SPELLS__STACKING, "Both casters exist and are not the same, the effect is a detrimental dot, moving on");
}
}
}
}
}

/*
If the effects are the same and
sp1 = beneficial & sp2 = detrimental or
sp1 = detrimental & sp2 = beneficial
Then this effect should be ignored for stacking purposes.
*/
if(sp_det_mismatch)
{
mlog(SPELLS__STACKING, "The effects are the same but the spell types are not, passing the effect");
continue;
}

/*
If the spells aren't the same
and the effect is a dot we can go ahead and stack it
*/
if(spellid1 != spellid2)
{
if(effect1 == SE_CurrentHP)
{
if(sp1_detrimental && sp2_detrimental)
{
mlog(SPELLS__STACKING, "The spells are not the same and it is a detrimental dot, passing");
continue;
}
}
}

sp1_value = CalcSpellEffectValue(spellid1, i, caster_level1);
sp2_value = CalcSpellEffectValue(spellid2, i, caster_level2);

// some spells are hard to compare just on value. attack speed spells
// have a value that's a percentage for instance
if
(
effect1 == SE_AttackSpeed ||
effect1 == SE_AttackSpeed2 ||
effect1 == SE_AttackSpeed3
)
{
sp1_value -= 100;
sp2_value -= 100;
}

if(sp1_value < 0)
sp1_value = 0 - sp1_value;
if(sp2_value < 0)
sp2_value = 0 - sp2_value;

if(sp2_value < sp1_value) {
mlog(SPELLS__STACKING, "Spell %s (value %d) is not as good as %s (value %d). Rejecting %s.",
sp2.name, sp2_value, sp1.name, sp1_value, sp2.name);
return -1; // can't stack
}
//we dont return here... a better value on this one effect dosent mean they are
//all better...

mlog(SPELLS__STACKING, "Spell %s (value %d) is not as good as %s (value %d). We will overwrite %s if there are no other conflicts.",
sp1.name, sp1_value, sp2.name, sp2_value, sp1.name);
will_overwrite = true;
}

//if we get here, then none of the values on the new spell are "worse"
//so now we see if this new spell is any better, or if its not related at all
if(will_overwrite) {
mlog(SPELLS__STACKING, "Stacking code decided that %s should overwrite %s.", sp2.name, sp1.name);
return(1);
}

mlog(SPELLS__STACKING, "Stacking code decided that %s is not affected by %s.", sp2.name, sp1.name);
return 0;
}


Note that the declaration of the function in mob.h has changed, change it to:

int CheckStackConflict(int16 spellid1, int caster_level1, int16 spellid2, int caster_level2, Mob* caster1 = NULL, Mob* caster2 = NULL);


then in Mob::AddBuff() you'll find the following line:

ret = CheckStackConflict(curbuf.spellid, curbuf.casterlevel, spell_id, caster_level);

Just change it to:

ret = CheckStackConflict(curbuf.spellid, curbuf.casterlevel, spell_id, caster_level, entity_list.GetMobID(curbuf.casterid), caster);


Then in spells.cpp at approx line 2610 you'll find:

//this is a crap load of work to say "a movement speed increase cannot land if your rooted."
int8 buffslot = GetBuffSlotFromType(SE_MovementSpeed);
if((FindType(SE_Root) && IsEffectInSpell(spell_id, SE_MovementSpeed))
|| (buffslot!=255 && IsValidSpell(buffs[buffslot].spellid)
&& IsDetrimentalSpell(buffs[buffslot].spellid) && IsBeneficialSpell(spell_id)))
{
caster->Message_StringID(MT_Shout,CANNOT_AFFECT_PC);
return true;
}

That is a lot of work to be doing something that's not live like behavior at all, just remove it.

I tested this stuff and it appears to work, if you see any problems with it please point them out to me.

vales
10-06-2006, 08:47 PM
Damn. That's some freaking nice work there, KLS! :D

Wish I knew how to dive into it and take it apart like you, but alas, the years have been creeping up way too fast for me.

Thanks again for the fixes! Much appreciated! ;)

Dralanna
10-07-2006, 01:28 AM
Dear lord this rocks.... thanks for this tremendous fix :)

John Adams
10-07-2006, 03:28 PM
Awesome, KLS. Please let us know if you open any AA code for a quick peek. ~grins~