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 175

, change the function to:
Code:
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:
Code:
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:
Code:
ret = CheckStackConflict(curbuf.spellid, curbuf.casterlevel, spell_id, caster_level);
Just change it to:
Code:
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:
Code:
//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.