Bard instrument mods should be more consistent with live

Changes:
	Mods are now saved for in the DB so they are loaded on zone
	This allows long duration buffs from bards that get mods to keep their mods
	Ex. Selo's, Symphony of Battle

	Instrument mods are applied to basically anything that is an instrument skill
	The only exception to this is discs (ex. Puretone is Singing but always 10)

	Singing spells from procs (Ex. Storm Blade) that are instrument skills should
	inherit their buffs instrument mod. Doom effects should also. This isn't
	implemented yet.
This commit is contained in:
Michael Cook (mackal) 2015-05-20 02:01:43 -04:00
parent 2ef0fc9342
commit ea5a1dd6f1
18 changed files with 493 additions and 510 deletions

View File

@ -1,5 +1,9 @@
EQEMu Changelog (Started on Sept 24, 2003 15:50) EQEMu Changelog (Started on Sept 24, 2003 15:50)
------------------------------------------------------- -------------------------------------------------------
== 05/20/2015 ==
demonstar55: Bard instrument mods should be more consistent with live. Zoning will keep instrument mod for long duration buffs (selo's)
Still need to have procs/doom effects to inherit the instrument mods from their source buff/whatever
== 05/18/2015 == == 05/18/2015 ==
KLS: Changed how fishing locates water to hopefully be a bit more accurate at the expense of a bit more cpu power per line cast. KLS: Changed how fishing locates water to hopefully be a bit more accurate at the expense of a bit more cpu power per line cast.

View File

@ -92,3 +92,17 @@ float EQEmu::GetSkillMeleePushForce(SkillUseTypes skill)
return 0.0f; return 0.0f;
} }
} }
bool EQEmu::IsBardInstrumentSkill(SkillUseTypes skill)
{
switch (skill) {
case SkillBrassInstruments:
case SkillSinging:
case SkillStringedInstruments:
case SkillWindInstruments:
case SkillPercussionInstruments:
return true;
default:
return false;
}
}

View File

@ -271,6 +271,7 @@ namespace EQEmu {
bool IsTradeskill(SkillUseTypes skill); bool IsTradeskill(SkillUseTypes skill);
bool IsSpecializedSkill(SkillUseTypes skill); bool IsSpecializedSkill(SkillUseTypes skill);
float GetSkillMeleePushForce(SkillUseTypes skill); float GetSkillMeleePushForce(SkillUseTypes skill);
bool IsBardInstrumentSkill(SkillUseTypes skill);
} }
#endif #endif

View File

@ -447,7 +447,7 @@ bool IsTGBCompatibleSpell(uint16 spell_id)
bool IsBardSong(uint16 spell_id) bool IsBardSong(uint16 spell_id)
{ {
if (IsValidSpell(spell_id) && spells[spell_id].classes[BARD - 1] < 255) if (IsValidSpell(spell_id) && spells[spell_id].classes[BARD - 1] < 127 && !spells[spell_id].IsDisciplineBuff)
return true; return true;
return false; return false;

View File

@ -30,7 +30,7 @@
Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt Manifest: https://github.com/EQEmu/Server/blob/master/utils/sql/db_update_manifest.txt
*/ */
#define CURRENT_BINARY_DATABASE_VERSION 9077 #define CURRENT_BINARY_DATABASE_VERSION 9078
#define COMPILE_DATE __DATE__ #define COMPILE_DATE __DATE__
#define COMPILE_TIME __TIME__ #define COMPILE_TIME __TIME__
#ifndef WIN32 #ifndef WIN32

View File

@ -331,6 +331,7 @@
9075|2015_02_02_logsys_packet_logs_with_dump.sql|SELECT * FROM `logsys_categories` WHERE `log_category_description` LIKE 'Packet: Server -> Client With Dump'|empty| 9075|2015_02_02_logsys_packet_logs_with_dump.sql|SELECT * FROM `logsys_categories` WHERE `log_category_description` LIKE 'Packet: Server -> Client With Dump'|empty|
9076|2015_02_04_average_coin.sql|SHOW COLUMNS FROM `loottable` WHERE Field = 'avgcoin'|contains|smallint 9076|2015_02_04_average_coin.sql|SHOW COLUMNS FROM `loottable` WHERE Field = 'avgcoin'|contains|smallint
9077|2015_02_12_zone_gravity.sql|SHOW COLUMNS FROM `zone` LIKE 'gravity'|empty| 9077|2015_02_12_zone_gravity.sql|SHOW COLUMNS FROM `zone` LIKE 'gravity'|empty|
9078|2015_05_20_BuffInstrumentMod.sql|SHOW COLUMNS FROM `character_buffs` LIKE `instrument_mod`|empty|
# Upgrade conditions: # Upgrade conditions:
# This won't be needed after this system is implemented, but it is used database that are not # This won't be needed after this system is implemented, but it is used database that are not

View File

@ -0,0 +1 @@
ALTER TABLE `character_buffs` ADD COLUMN `instrument_mod` int(10) DEFAULT 10 NOT NULL;

View File

@ -1449,7 +1449,7 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon)
int buff_count = GetMaxTotalSlots(); int buff_count = GetMaxTotalSlots();
for(i = 0; i < buff_count; i++) { for(i = 0; i < buff_count; i++) {
if(buffs[i].spellid != SPELL_UNKNOWN){ if(buffs[i].spellid != SPELL_UNKNOWN){
ApplySpellsBonuses(buffs[i].spellid, buffs[i].casterlevel, newbon, buffs[i].casterid, 0, buffs[i].ticsremaining,i); ApplySpellsBonuses(buffs[i].spellid, buffs[i].casterlevel, newbon, buffs[i].casterid, 0, buffs[i].ticsremaining, i, buffs[i].instrument_mod);
if (buffs[i].numhits > 0) if (buffs[i].numhits > 0)
Numhits(true); Numhits(true);
@ -1472,7 +1472,8 @@ void Mob::CalcSpellBonuses(StatBonuses* newbon)
if (GetClass() == BARD) newbon->ManaRegen = 0; // Bards do not get mana regen from spells. if (GetClass() == BARD) newbon->ManaRegen = 0; // Bards do not get mana regen from spells.
} }
void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* new_bonus, uint16 casterId, uint8 WornType, uint32 ticsremaining, int buffslot, void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses *new_bonus, uint16 casterId,
uint8 WornType, uint32 ticsremaining, int buffslot, int instrument_mod,
bool IsAISpellEffect, uint16 effect_id, int32 se_base, int32 se_limit, int32 se_max) bool IsAISpellEffect, uint16 effect_id, int32 se_base, int32 se_limit, int32 se_max)
{ {
int i, effect_value, base2, max, effectid; int i, effect_value, base2, max, effectid;
@ -1511,7 +1512,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne
AdditiveWornBonus = true; AdditiveWornBonus = true;
effectid = spells[spell_id].effectid[i]; effectid = spells[spell_id].effectid[i];
effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, caster, ticsremaining); effect_value = CalcSpellEffectValue(spell_id, i, casterlevel, instrument_mod, caster, ticsremaining);
base2 = spells[spell_id].base2[i]; base2 = spells[spell_id].base2[i];
max = spells[spell_id].max[i]; max = spells[spell_id].max[i];
} }

View File

@ -9092,8 +9092,8 @@ bool Bot::SpellEffect(Mob* caster, uint16 spell_id, float partial) {
return Result; return Result;
} }
void Bot::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caster_level, Mob* caster) { void Bot::DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster) {
Mob::DoBuffTic(spell_id, slot, ticsremaining, caster_level, caster); Mob::DoBuffTic(buff, slot, caster);
} }
bool Bot::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_time, int32 mana_cost, uint32* oSpellWillFinish, uint32 item_slot, int16 *resist_adjust) { bool Bot::CastSpell(uint16 spell_id, uint16 target_id, uint16 slot, int32 cast_time, int32 mana_cost, uint32* oSpellWillFinish, uint32 item_slot, int16 *resist_adjust) {

View File

@ -310,7 +310,7 @@ public:
virtual int32 GetActSpellDuration(uint16 spell_id, int32 duration); virtual int32 GetActSpellDuration(uint16 spell_id, int32 duration);
virtual float GetAOERange(uint16 spell_id); virtual float GetAOERange(uint16 spell_id);
virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100); virtual bool SpellEffect(Mob* caster, uint16 spell_id, float partial = 100);
virtual void DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caster_level, Mob* caster = 0); virtual void DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster = nullptr);
virtual bool CastSpell(uint16 spell_id, uint16 target_id, uint16 slot = USE_ITEM_SPELL_SLOT, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, int16 *resist_adjust = nullptr); virtual bool CastSpell(uint16 spell_id, uint16 target_id, uint16 slot = USE_ITEM_SPELL_SLOT, int32 casttime = -1, int32 mana_cost = -1, uint32* oSpellWillFinish = 0, uint32 item_slot = 0xFFFFFFFF, int16 *resist_adjust = nullptr);
virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar); virtual bool SpellOnTarget(uint16 spell_id, Mob* spelltar);
virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster); virtual bool IsImmuneToSpell(uint16 spell_id, Mob *caster);

View File

@ -1974,101 +1974,87 @@ int32 Client::CalcATK()
uint32 Mob::GetInstrumentMod(uint16 spell_id) const uint32 Mob::GetInstrumentMod(uint16 spell_id) const
{ {
if (GetClass() != BARD) { if (GetClass() != BARD || spells[spell_id].IsDisciplineBuff) // Puretone is Singing but doesn't get any mod
return 10; return 10;
}
uint32 effectmod = 10; uint32 effectmod = 10;
int effectmodcap = RuleI(Character, BaseInstrumentSoftCap); int effectmodcap = RuleI(Character, BaseInstrumentSoftCap);
// this should never use spell modifiers... // this should never use spell modifiers...
// if a spell grants better modifers, they are copied into the item mods // if a spell grants better modifers, they are copied into the item mods
// because the spells are supposed to act just like having the intrument. // because the spells are supposed to act just like having the intrument.
// item mods are in 10ths of percent increases // item mods are in 10ths of percent increases
// clickies (Symphony of Battle) that have a song skill don't get AA bonus for some reason
// but clickies that are songs (selo's on Composers Greaves) do get AA mod as well
switch (spells[spell_id].skill) { switch (spells[spell_id].skill) {
case SkillPercussionInstruments: case SkillPercussionInstruments:
if (itembonuses.percussionMod == 0 && spellbonuses.percussionMod == 0) { if (itembonuses.percussionMod == 0 && spellbonuses.percussionMod == 0)
effectmod = 10; effectmod = 10;
} else if (GetSkill(SkillPercussionInstruments) == 0)
else if (GetSkill(SkillPercussionInstruments) == 0) {
effectmod = 10; effectmod = 10;
} else if (itembonuses.percussionMod > spellbonuses.percussionMod)
else if (itembonuses.percussionMod > spellbonuses.percussionMod) {
effectmod = itembonuses.percussionMod; effectmod = itembonuses.percussionMod;
} else
else {
effectmod = spellbonuses.percussionMod; effectmod = spellbonuses.percussionMod;
} if (IsBardSong(spell_id))
effectmod += aabonuses.percussionMod; effectmod += aabonuses.percussionMod;
break; break;
case SkillStringedInstruments: case SkillStringedInstruments:
if (itembonuses.stringedMod == 0 && spellbonuses.stringedMod == 0) { if (itembonuses.stringedMod == 0 && spellbonuses.stringedMod == 0)
effectmod = 10; effectmod = 10;
} else if (GetSkill(SkillStringedInstruments) == 0)
else if (GetSkill(SkillStringedInstruments) == 0) {
effectmod = 10; effectmod = 10;
} else if (itembonuses.stringedMod > spellbonuses.stringedMod)
else if (itembonuses.stringedMod > spellbonuses.stringedMod) {
effectmod = itembonuses.stringedMod; effectmod = itembonuses.stringedMod;
} else
else {
effectmod = spellbonuses.stringedMod; effectmod = spellbonuses.stringedMod;
} if (IsBardSong(spell_id))
effectmod += aabonuses.stringedMod; effectmod += aabonuses.stringedMod;
break; break;
case SkillWindInstruments: case SkillWindInstruments:
if (itembonuses.windMod == 0 && spellbonuses.windMod == 0) { if (itembonuses.windMod == 0 && spellbonuses.windMod == 0)
effectmod = 10; effectmod = 10;
} else if (GetSkill(SkillWindInstruments) == 0)
else if (GetSkill(SkillWindInstruments) == 0) {
effectmod = 10; effectmod = 10;
} else if (itembonuses.windMod > spellbonuses.windMod)
else if (itembonuses.windMod > spellbonuses.windMod) {
effectmod = itembonuses.windMod; effectmod = itembonuses.windMod;
} else
else {
effectmod = spellbonuses.windMod; effectmod = spellbonuses.windMod;
} if (IsBardSong(spell_id))
effectmod += aabonuses.windMod; effectmod += aabonuses.windMod;
break; break;
case SkillBrassInstruments: case SkillBrassInstruments:
if (itembonuses.brassMod == 0 && spellbonuses.brassMod == 0) { if (itembonuses.brassMod == 0 && spellbonuses.brassMod == 0)
effectmod = 10; effectmod = 10;
} else if (GetSkill(SkillBrassInstruments) == 0)
else if (GetSkill(SkillBrassInstruments) == 0) {
effectmod = 10; effectmod = 10;
} else if (itembonuses.brassMod > spellbonuses.brassMod)
else if (itembonuses.brassMod > spellbonuses.brassMod) {
effectmod = itembonuses.brassMod; effectmod = itembonuses.brassMod;
} else
else {
effectmod = spellbonuses.brassMod; effectmod = spellbonuses.brassMod;
} if (IsBardSong(spell_id))
effectmod += aabonuses.brassMod; effectmod += aabonuses.brassMod;
break; break;
case SkillSinging: case SkillSinging:
if (itembonuses.singingMod == 0 && spellbonuses.singingMod == 0) { if (itembonuses.singingMod == 0 && spellbonuses.singingMod == 0)
effectmod = 10; effectmod = 10;
} else if (itembonuses.singingMod > spellbonuses.singingMod)
else if (itembonuses.singingMod > spellbonuses.singingMod) {
effectmod = itembonuses.singingMod; effectmod = itembonuses.singingMod;
} else
else {
effectmod = spellbonuses.singingMod; effectmod = spellbonuses.singingMod;
} if (IsBardSong(spell_id))
effectmod += aabonuses.singingMod + spellbonuses.Amplification; effectmod += aabonuses.singingMod + spellbonuses.Amplification;
break; break;
default: default:
effectmod = 10; effectmod = 10;
break; return effectmod;
} }
effectmodcap += aabonuses.songModCap + spellbonuses.songModCap + itembonuses.songModCap; effectmodcap += aabonuses.songModCap + spellbonuses.songModCap + itembonuses.songModCap;
if (effectmod < 10) { if (effectmod < 10)
effectmod = 10; effectmod = 10;
} if (effectmod > effectmodcap)
if (effectmod > effectmodcap) {
effectmod = effectmodcap; effectmod = effectmodcap;
} Log.Out(Logs::Detail, Logs::Spells, "%s::GetInstrumentMod() spell=%d mod=%d modcap=%d\n", GetName(), spell_id,
Log.Out(Logs::Detail, Logs::Spells, "%s::GetInstrumentMod() spell=%d mod=%d modcap=%d\n", effectmod, effectmodcap);
GetName(), spell_id, effectmod, effectmodcap);
return effectmod; return effectmod;
} }

View File

@ -1494,7 +1494,7 @@ void Client::Handle_Connect_OP_ZoneEntry(const EQApplicationPacket *app)
for (int i = 0; i < max_slots; i++) { for (int i = 0; i < max_slots; i++) {
if (buffs[i].spellid != SPELL_UNKNOWN) { if (buffs[i].spellid != SPELL_UNKNOWN) {
m_pp.buffs[i].spellid = buffs[i].spellid; m_pp.buffs[i].spellid = buffs[i].spellid;
m_pp.buffs[i].bard_modifier = 10; m_pp.buffs[i].bard_modifier = buffs[i].instrument_mod;
m_pp.buffs[i].slotid = 2; m_pp.buffs[i].slotid = 2;
m_pp.buffs[i].player_id = 0x2211; m_pp.buffs[i].player_id = 0x2211;
m_pp.buffs[i].level = buffs[i].casterlevel; m_pp.buffs[i].level = buffs[i].casterlevel;

View File

@ -202,6 +202,7 @@ struct Buffs_Struct {
int32 caston_z; int32 caston_z;
int32 ExtraDIChance; int32 ExtraDIChance;
int16 RootBreakChance; //Not saved to dbase int16 RootBreakChance; //Not saved to dbase
uint32 instrument_mod;
bool persistant_buff; bool persistant_buff;
bool client; //True if the caster is a client bool client; //True if the caster is a client
bool UpdateClient; bool UpdateClient;

View File

@ -200,7 +200,7 @@ public:
bool IsBeneficialAllowed(Mob *target); bool IsBeneficialAllowed(Mob *target);
virtual int GetCasterLevel(uint16 spell_id); virtual int GetCasterLevel(uint16 spell_id);
void ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterID = 0, void ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* newbon, uint16 casterID = 0,
uint8 WornType = 0, uint32 ticsremaining = 0, int buffslot = -1, uint8 WornType = 0, uint32 ticsremaining = 0, int buffslot = -1, int instrument_mod = 10,
bool IsAISpellEffect = false, uint16 effect_id = 0, int32 se_base = 0, int32 se_limit = 0, int32 se_max = 0); bool IsAISpellEffect = false, uint16 effect_id = 0, int32 se_base = 0, int32 se_limit = 0, int32 se_max = 0);
void NegateSpellsBonuses(uint16 spell_id); void NegateSpellsBonuses(uint16 spell_id);
virtual float GetActSpellRange(uint16 spell_id, float range, bool IsBard = false); virtual float GetActSpellRange(uint16 spell_id, float range, bool IsBard = false);
@ -253,7 +253,7 @@ public:
//Buff //Buff
void BuffProcess(); void BuffProcess();
virtual void DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caster_level, Mob* caster = 0); virtual void DoBuffTic(const Buffs_Struct &buff, int slot, Mob* caster = nullptr);
void BuffFadeBySpellID(uint16 spell_id); void BuffFadeBySpellID(uint16 spell_id);
void BuffFadeByEffect(int effectid, int skipslot = -1); void BuffFadeByEffect(int effectid, int skipslot = -1);
void BuffFadeAll(); void BuffFadeAll();
@ -857,7 +857,7 @@ public:
virtual uint32 GetAA(uint32 aa_id) const { return(0); } virtual uint32 GetAA(uint32 aa_id) const { return(0); }
uint32 GetInstrumentMod(uint16 spell_id) const; uint32 GetInstrumentMod(uint16 spell_id) const;
int CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level = 1, Mob *caster = nullptr, int ticsremaining = 0); int CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level = 1, uint32 instrument_mod = 10, Mob *caster = nullptr, int ticsremaining = 0);
int CalcSpellEffectValue_formula(int formula, int base, int max, int caster_level, uint16 spell_id, int ticsremaining = 0); int CalcSpellEffectValue_formula(int formula, int base, int max, int caster_level, uint16 spell_id, int ticsremaining = 0);
virtual int CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, int caster_level2, Mob* caster1 = nullptr, Mob* caster2 = nullptr, int buffslot = -1); virtual int CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, int caster_level2, Mob* caster1 = nullptr, Mob* caster2 = nullptr, int buffslot = -1);
uint32 GetCastedSpellInvSlot() const { return casting_spell_inventory_slot; } uint32 GetCastedSpellInvSlot() const { return casting_spell_inventory_slot; }

View File

@ -2566,10 +2566,8 @@ void NPC::ApplyAISpellEffects(StatBonuses* newbon)
return; return;
for (int i = 0; i < AIspellsEffects.size(); i++) for (int i = 0; i < AIspellsEffects.size(); i++)
{ ApplySpellsBonuses(0, 0, newbon, 0, 0, 0, -1, 10, true, AIspellsEffects[i].spelleffectid,
ApplySpellsBonuses(0, 0, newbon, 0, 0, 0,-1, AIspellsEffects[i].base, AIspellsEffects[i].limit, AIspellsEffects[i].max);
true, AIspellsEffects[i].spelleffectid, AIspellsEffects[i].base, AIspellsEffects[i].limit,AIspellsEffects[i].max);
}
return; return;
} }

View File

@ -199,7 +199,7 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
continue; continue;
effect = spell.effectid[i]; effect = spell.effectid[i];
effect_value = CalcSpellEffectValue(spell_id, i, caster_level, caster ? caster : this); effect_value = CalcSpellEffectValue(spell_id, i, caster_level, buffslot > -1 ? buffs[buffslot].instrument_mod : 10, caster ? caster : this);
if(spell_id == SPELL_LAY_ON_HANDS && caster && caster->GetAA(aaImprovedLayOnHands)) if(spell_id == SPELL_LAY_ON_HANDS && caster && caster->GetAA(aaImprovedLayOnHands))
effect_value = GetMaxHP(); effect_value = GetMaxHP();
@ -3029,16 +3029,12 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial)
return true; return true;
} }
int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level, Mob *caster, int ticsremaining) int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level, uint32 instrument_mod, Mob *caster,
int ticsremaining)
{ {
int formula, base, max, effect_value; int formula, base, max, effect_value;
if if (!IsValidSpell(spell_id) || effect_id < 0 || effect_id >= EFFECT_COUNT)
(
!IsValidSpell(spell_id) ||
effect_id < 0 ||
effect_id >= EFFECT_COUNT
)
return 0; return 0;
formula = spells[spell_id].formula[effect_id]; formula = spells[spell_id].formula[effect_id];
@ -3050,27 +3046,27 @@ int Mob::CalcSpellEffectValue(uint16 spell_id, int effect_id, int caster_level,
effect_value = CalcSpellEffectValue_formula(formula, base, max, caster_level, spell_id, ticsremaining); effect_value = CalcSpellEffectValue_formula(formula, base, max, caster_level, spell_id, ticsremaining);
if(caster && IsBardSong(spell_id) && // this doesn't actually need to be a song to get mods, just the right skill
(spells[spell_id].effectid[effect_id] != SE_AttackSpeed) && if (EQEmu::IsBardInstrumentSkill(spells[spell_id].skill) &&
(spells[spell_id].effectid[effect_id] != SE_AttackSpeed2) && spells[spell_id].effectid[effect_id] != SE_AttackSpeed &&
(spells[spell_id].effectid[effect_id] != SE_AttackSpeed3) && spells[spell_id].effectid[effect_id] != SE_AttackSpeed2 &&
(spells[spell_id].effectid[effect_id] != SE_Lull) && spells[spell_id].effectid[effect_id] != SE_AttackSpeed3 &&
(spells[spell_id].effectid[effect_id] != SE_ChangeFrenzyRad) && spells[spell_id].effectid[effect_id] != SE_Lull &&
(spells[spell_id].effectid[effect_id] != SE_Harmony) && spells[spell_id].effectid[effect_id] != SE_ChangeFrenzyRad &&
(spells[spell_id].effectid[effect_id] != SE_CurrentMana)&& spells[spell_id].effectid[effect_id] != SE_Harmony &&
(spells[spell_id].effectid[effect_id] != SE_ManaRegen_v2)) spells[spell_id].effectid[effect_id] != SE_CurrentMana &&
{ spells[spell_id].effectid[effect_id] != SE_ManaRegen_v2) {
int oval = effect_value; int oval = effect_value;
int mod = caster->GetInstrumentMod(spell_id); int mod = ApplySpellEffectiveness(caster, spell_id, instrument_mod, true);
mod = ApplySpellEffectiveness(caster, spell_id, mod, true);
effect_value = effect_value * mod / 10; effect_value = effect_value * mod / 10;
Log.Out(Logs::Detail, Logs::Spells, "Effect value %d altered with bard modifier of %d to yeild %d", oval, mod, effect_value); Log.Out(Logs::Detail, Logs::Spells, "Effect value %d altered with bard modifier of %d to yeild %d",
oval, mod, effect_value);
} }
effect_value = mod_effect_value(effect_value, spell_id, spells[spell_id].effectid[effect_id], caster); effect_value = mod_effect_value(effect_value, spell_id, spells[spell_id].effectid[effect_id], caster);
return(effect_value); return effect_value;
} }
// generic formula calculations // generic formula calculations
@ -3365,7 +3361,7 @@ void Mob::BuffProcess()
{ {
if (buffs[buffs_i].spellid != SPELL_UNKNOWN) if (buffs[buffs_i].spellid != SPELL_UNKNOWN)
{ {
DoBuffTic(buffs[buffs_i].spellid, buffs_i, buffs[buffs_i].ticsremaining, buffs[buffs_i].casterlevel, entity_list.GetMob(buffs[buffs_i].casterid)); DoBuffTic(buffs[buffs_i], buffs_i, entity_list.GetMob(buffs[buffs_i].casterid));
// If the Mob died during DoBuffTic, then the buff we are currently processing will have been removed // If the Mob died during DoBuffTic, then the buff we are currently processing will have been removed
if(buffs[buffs_i].spellid == SPELL_UNKNOWN) if(buffs[buffs_i].spellid == SPELL_UNKNOWN)
continue; continue;
@ -3418,35 +3414,32 @@ void Mob::BuffProcess()
} }
} }
void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caster_level, Mob* caster) { void Mob::DoBuffTic(const Buffs_Struct &buff, int slot, Mob *caster)
{
int effect, effect_value; int effect, effect_value;
if(!IsValidSpell(spell_id)) if (!IsValidSpell(buff.spellid))
return; return;
const SPDat_Spell_Struct &spell = spells[spell_id]; const SPDat_Spell_Struct &spell = spells[buff.spellid];
if (spell_id == SPELL_UNKNOWN) if (IsNPC()) {
return;
if(IsNPC())
{
std::vector<EQEmu::Any> args; std::vector<EQEmu::Any> args;
args.push_back(&ticsremaining); args.push_back(&buff.ticsremaining);
args.push_back(&caster_level); args.push_back(&buff.casterlevel);
args.push_back(&slot); args.push_back(&slot);
int i = parse->EventSpell(EVENT_SPELL_BUFF_TIC_NPC, CastToNPC(), nullptr, spell_id, caster ? caster->GetID() : 0, &args); int i = parse->EventSpell(EVENT_SPELL_BUFF_TIC_NPC, CastToNPC(), nullptr, buff.spellid,
caster ? caster->GetID() : 0, &args);
if (i != 0) { if (i != 0) {
return; return;
} }
} } else {
else
{
std::vector<EQEmu::Any> args; std::vector<EQEmu::Any> args;
args.push_back(&ticsremaining); args.push_back(&buff.ticsremaining);
args.push_back(&caster_level); args.push_back(&buff.casterlevel);
args.push_back(&slot); args.push_back(&slot);
int i = parse->EventSpell(EVENT_SPELL_BUFF_TIC_CLIENT, nullptr, CastToClient(), spell_id, caster ? caster->GetID() : 0, &args); int i = parse->EventSpell(EVENT_SPELL_BUFF_TIC_CLIENT, nullptr, CastToClient(), buff.spellid,
caster ? caster->GetID() : 0, &args);
if (i != 0) { if (i != 0) {
return; return;
} }
@ -3457,50 +3450,47 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
if (IsClient()) if (IsClient())
CastToClient()->CheckAAEffect(aaEffectRampage); CastToClient()->CheckAAEffect(aaEffectRampage);
for (int i = 0; i < EFFECT_COUNT; i++) for (int i = 0; i < EFFECT_COUNT; i++) {
{ if (IsBlankSpellEffect(buff.spellid, i))
if(IsBlankSpellEffect(spell_id, i))
continue; continue;
effect = spell.effectid[i]; effect = spell.effectid[i];
// I copied the calculation into each case which needed it instead of // I copied the calculation into each case which needed it instead of
// doing it every time up here, since most buff effects dont need it // doing it every time up here, since most buff effects dont need it
switch(effect) switch (effect) {
{ case SE_CurrentHP: {
case SE_CurrentHP: effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod,
{ caster, buff.ticsremaining);
effect_value = CalcSpellEffectValue(spell_id, i, caster_level, caster, ticsremaining);
// Handle client cast DOTs here. // Handle client cast DOTs here.
if (caster && effect_value < 0) { if (caster && effect_value < 0) {
if (IsDetrimentalSpell(spell_id)){ if (IsDetrimentalSpell(buff.spellid)) {
if (caster->IsClient()) { if (caster->IsClient()) {
if (!caster->CastToClient()->GetFeigned()) if (!caster->CastToClient()->GetFeigned())
AddToHateList(caster, -effect_value); AddToHateList(caster, -effect_value);
} } else if (!IsClient()) // Allow NPC's to generate hate if casted on other
else if (!IsClient()) //Allow NPC's to generate hate if casted on other NPC's. // NPC's.
AddToHateList(caster, -effect_value); AddToHateList(caster, -effect_value);
} }
effect_value = caster->GetActDoTDamage(spell_id, effect_value, this); effect_value = caster->GetActDoTDamage(buff.spellid, effect_value, this);
caster->ResourceTap(-effect_value, spell_id); caster->ResourceTap(-effect_value, buff.spellid);
effect_value = -effect_value; effect_value = -effect_value;
Damage(caster, effect_value, spell_id, spell.skill, false, i, true); Damage(caster, effect_value, buff.spellid, spell.skill, false, i, true);
} else if (effect_value > 0) { } else if (effect_value > 0) {
// Regen spell... // Regen spell...
// handled with bonuses // handled with bonuses
} }
break; break;
} }
case SE_HealOverTime: case SE_HealOverTime: {
{ effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod);
effect_value = CalcSpellEffectValue(spell_id, i, caster_level);
if (caster) if (caster)
effect_value = caster->GetActSpellHealing(spell_id, effect_value); effect_value = caster->GetActSpellHealing(buff.spellid, effect_value);
HealDamage(effect_value, caster, spell_id); HealDamage(effect_value, caster, buff.spellid);
// healing aggro would go here; removed for now // healing aggro would go here; removed for now
break; break;
} }
@ -3510,11 +3500,12 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
break; break;
} }
case SE_BardAEDot: case SE_BardAEDot: {
{ effect_value =
effect_value = CalcSpellEffectValue(spell_id, i, caster_level, caster); CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod, caster);
if ((!RuleB(Spells, PreNerfBardAEDoT) && IsMoving()) || invulnerable || /*effect_value > 0 ||*/ DivineAura()) if ((!RuleB(Spells, PreNerfBardAEDoT) && IsMoving()) || invulnerable ||
/*effect_value > 0 ||*/ DivineAura())
break; break;
if (effect_value < 0) { if (effect_value < 0) {
@ -3522,11 +3513,10 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
if (caster) { if (caster) {
if (caster->IsClient() && !caster->CastToClient()->GetFeigned()) { if (caster->IsClient() && !caster->CastToClient()->GetFeigned()) {
AddToHateList(caster, effect_value); AddToHateList(caster, effect_value);
} } else if (!caster->IsClient())
else if(!caster->IsClient())
AddToHateList(caster, effect_value); AddToHateList(caster, effect_value);
} }
Damage(caster, effect_value, spell_id, spell.skill, false, i, true); Damage(caster, effect_value, buff.spellid, spell.skill, false, i, true);
} else if (effect_value > 0) { } else if (effect_value > 0) {
// healing spell... // healing spell...
HealDamage(effect_value, caster); HealDamage(effect_value, caster);
@ -3536,14 +3526,13 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
} }
case SE_Hate: { case SE_Hate: {
effect_value = CalcSpellEffectValue(spell_id, i, caster_level); effect_value = CalcSpellEffectValue(buff.spellid, i, buff.casterlevel, buff.instrument_mod);
if (caster) { if (caster) {
if (effect_value > 0) { if (effect_value > 0) {
if (caster) { if (caster) {
if (caster->IsClient() && !caster->CastToClient()->GetFeigned()) { if (caster->IsClient() && !caster->CastToClient()->GetFeigned()) {
AddToHateList(caster, effect_value); AddToHateList(caster, effect_value);
} } else if (!caster->IsClient())
else if(!caster->IsClient())
AddToHateList(caster, effect_value); AddToHateList(caster, effect_value);
} }
} else { } else {
@ -3558,12 +3547,11 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
break; break;
} }
case SE_WipeHateList: case SE_WipeHateList: {
{ if (IsMezSpell(buff.spellid))
if (IsMezSpell(spell_id))
break; break;
int wipechance = spells[spell_id].base[i]; int wipechance = spells[buff.spellid].base[i];
int bonus = 0; int bonus = 0;
if (caster) { if (caster) {
@ -3574,19 +3562,18 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
wipechance += wipechance * bonus / 100; wipechance += wipechance * bonus / 100;
if(zone->random.Roll(wipechance)) if (zone->random.Roll(wipechance)) {
{ if (IsAIControlled()) {
if(IsAIControlled())
{
WipeHateList(); WipeHateList();
} }
Message(13, "Your mind fogs. Who are my friends? Who are my enemies?... it was all so clear a moment ago..."); Message(13, "Your mind fogs. Who are my friends? Who are my enemies?... it was all so "
"clear a moment ago...");
} }
break; break;
} }
case SE_Charm: { case SE_Charm: {
if (!caster || !PassCharismaCheck(caster, spell_id)) { if (!caster || !PassCharismaCheck(caster, buff.spellid)) {
BuffFadeByEffect(SE_Charm); BuffFadeByEffect(SE_Charm);
} }
@ -3599,27 +3586,25 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
*/ */
if (zone->random.Roll(RuleI(Spells, RootBreakCheckChance))) { if (zone->random.Roll(RuleI(Spells, RootBreakCheckChance))) {
float resist_check = ResistSpell(spells[spell_id].resisttype, spell_id, caster, 0,0,0,0,true); float resist_check =
ResistSpell(spells[buff.spellid].resisttype, buff.spellid, caster, 0, 0, 0, 0, true);
if (resist_check == 100) if (resist_check == 100)
break; break;
else else if (!TryFadeEffect(slot))
if(!TryFadeEffect(slot))
BuffFadeBySlot(slot); BuffFadeBySlot(slot);
} }
break; break;
} }
case SE_Fear: case SE_Fear: {
{
if (zone->random.Roll(RuleI(Spells, FearBreakCheckChance))) { if (zone->random.Roll(RuleI(Spells, FearBreakCheckChance))) {
float resist_check = ResistSpell(spells[spell_id].resisttype, spell_id, caster); float resist_check = ResistSpell(spells[buff.spellid].resisttype, buff.spellid, caster);
if (resist_check == 100) if (resist_check == 100)
break; break;
else else if (!TryFadeEffect(slot))
if(!TryFadeEffect(slot))
BuffFadeBySlot(slot); BuffFadeBySlot(slot);
} }
@ -3627,7 +3612,8 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
} }
case SE_Hunger: { case SE_Hunger: {
// this procedure gets called 7 times for every once that the stamina update occurs so we add 1/7 of the subtraction. // this procedure gets called 7 times for every once that the stamina update occurs so we add
// 1/7 of the subtraction.
// It's far from perfect, but works without any unnecessary buff checks to bog down the server. // It's far from perfect, but works without any unnecessary buff checks to bog down the server.
if (IsClient()) { if (IsClient()) {
CastToClient()->m_pp.hunger_level += 5; CastToClient()->m_pp.hunger_level += 5;
@ -3637,44 +3623,37 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
} }
case SE_Invisibility: case SE_Invisibility:
case SE_InvisVsAnimals: case SE_InvisVsAnimals:
case SE_InvisVsUndead: case SE_InvisVsUndead: {
{ if (buff.ticsremaining > 3) {
if(ticsremaining > 3) if (!IsBardSong(buff.spellid)) {
{
if(!IsBardSong(spell_id))
{
double break_chance = 2.0; double break_chance = 2.0;
if(caster) if (caster) {
{ break_chance -= (2 * (((double)caster->GetSkill(SkillDivination) +
break_chance -= (2 * (((double)caster->GetSkill(SkillDivination) + ((double)caster->GetLevel() * 3.0)) / 650.0)); ((double)caster->GetLevel() * 3.0)) /
} 650.0));
else } else {
{ break_chance -=
break_chance -= (2 * (((double)GetSkill(SkillDivination) + ((double)GetLevel() * 3.0)) / 650.0)); (2 *
(((double)GetSkill(SkillDivination) + ((double)GetLevel() * 3.0)) /
650.0));
} }
if(zone->random.Real(0.0, 100.0) < break_chance) if (zone->random.Real(0.0, 100.0) < break_chance) {
{ BuffModifyDurationBySpellID(buff.spellid, 3);
BuffModifyDurationBySpellID(spell_id, 3);
} }
} }
} }
} }
case SE_Invisibility2: case SE_Invisibility2:
case SE_InvisVsUndead2: case SE_InvisVsUndead2: {
{ if (buff.ticsremaining <= 3 && buff.ticsremaining > 1) {
if(ticsremaining <= 3 && ticsremaining > 1)
{
Message_StringID(MT_Spells, INVIS_BEGIN_BREAK); Message_StringID(MT_Spells, INVIS_BEGIN_BREAK);
} }
break; break;
} }
case SE_InterruptCasting: case SE_InterruptCasting: {
{ if (IsCasting()) {
if(IsCasting()) if (zone->random.Roll(spells[buff.spellid].base[i])) {
{
if(zone->random.Roll(spells[spell_id].base[i]))
{
InterruptSpell(); InterruptSpell();
} }
} }
@ -3683,25 +3662,20 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
// These effects always trigger when they fade. // These effects always trigger when they fade.
case SE_CastOnFadeEffect: case SE_CastOnFadeEffect:
case SE_CastOnFadeEffectNPC: case SE_CastOnFadeEffectNPC:
case SE_CastOnFadeEffectAlways: case SE_CastOnFadeEffectAlways: {
{ if (buff.ticsremaining == 1) {
if (ticsremaining == 1) SpellOnTarget(spells[buff.spellid].base[i], this);
{
SpellOnTarget(spells[spell_id].base[i], this);
} }
break; break;
} }
case SE_LocateCorpse: case SE_LocateCorpse: {
{
// This is handled by the client prior to SoD. // This is handled by the client prior to SoD.
if (IsClient() && (CastToClient()->GetClientVersionBit() & BIT_SoDAndLater)) if (IsClient() && (CastToClient()->GetClientVersionBit() & BIT_SoDAndLater))
CastToClient()->LocateCorpse(); CastToClient()->LocateCorpse();
} }
case SE_TotalHP: case SE_TotalHP: {
{ if (spell.formula[i] > 1000 && spell.formula[i] < 1999) {
if (spell.formula[i] > 1000 && spell.formula[i] < 1999)
{
// These formulas can affect Max HP each tick // These formulas can affect Max HP each tick
// Maybe there is a more efficient way to recalculate this for just Max HP each tic... // Maybe there is a more efficient way to recalculate this for just Max HP each tic...
// CalcBonuses(); // CalcBonuses();
@ -3711,15 +3685,15 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
break; break;
} }
case SE_DistanceRemoval: case SE_DistanceRemoval: {
{
if (spellbonuses.DistanceRemoval) { if (spellbonuses.DistanceRemoval) {
int distance = ((int(GetX()) - buffs[slot].caston_x) * (int(GetX()) - buffs[slot].caston_x)) + int distance =
((int(GetY()) - buffs[slot].caston_y) * (int(GetY()) - buffs[slot].caston_y)) + ((int(GetX()) - buff.caston_x) * (int(GetX()) - buff.caston_x)) +
((int(GetZ()) - buffs[slot].caston_z) * (int(GetZ()) - buffs[slot].caston_z)); ((int(GetY()) - buff.caston_y) * (int(GetY()) - buff.caston_y)) +
((int(GetZ()) - buff.caston_z) * (int(GetZ()) - buff.caston_z));
if (distance > (spells[spell_id].base[i] * spells[spell_id].base[i])){ if (distance > (spells[buff.spellid].base[i] * spells[buff.spellid].base[i])) {
if (!TryFadeEffect(slot)) if (!TryFadeEffect(slot))
BuffFadeBySlot(slot, true); BuffFadeBySlot(slot, true);
@ -3728,8 +3702,7 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
} }
} }
case SE_AddHateOverTimePct: case SE_AddHateOverTimePct: {
{
if (IsNPC()) { if (IsNPC()) {
uint32 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base[i]) / 100; uint32 new_hate = CastToNPC()->GetHateAmount(caster) * (100 + spell.base[i]) / 100;
if (new_hate <= 0) if (new_hate <= 0)
@ -3740,9 +3713,7 @@ void Mob::DoBuffTic(uint16 spell_id, int slot, uint32 ticsremaining, uint8 caste
break; break;
} }
default: {
default:
{
// do we need to do anyting here? // do we need to do anyting here?
} }
} }

View File

@ -3189,6 +3189,7 @@ int Mob::AddBuff(Mob *caster, uint16 spell_id, int duration, int32 level_overrid
buffs[emptyslot].dot_rune = 0; buffs[emptyslot].dot_rune = 0;
buffs[emptyslot].ExtraDIChance = 0; buffs[emptyslot].ExtraDIChance = 0;
buffs[emptyslot].RootBreakChance = 0; buffs[emptyslot].RootBreakChance = 0;
buffs[emptyslot].instrument_mod = caster ? caster->GetInstrumentMod(spell_id) : 10;
if (level_override > 0) { if (level_override > 0) {
buffs[emptyslot].UpdateClient = true; buffs[emptyslot].UpdateClient = true;

View File

@ -2980,19 +2980,21 @@ void ZoneDatabase::SaveBuffs(Client *client) {
query = StringFormat("INSERT INTO `character_buffs` (character_id, slot_id, spell_id, " query = StringFormat("INSERT INTO `character_buffs` (character_id, slot_id, spell_id, "
"caster_level, caster_name, ticsremaining, counters, numhits, melee_rune, " "caster_level, caster_name, ticsremaining, counters, numhits, melee_rune, "
"magic_rune, persistent, dot_rune, caston_x, caston_y, caston_z, ExtraDIChance) " "magic_rune, persistent, dot_rune, caston_x, caston_y, caston_z, ExtraDIChance, "
"instrument_mod) "
"VALUES('%u', '%u', '%u', '%u', '%s', '%u', '%u', '%u', '%u', '%u', '%u', '%u', " "VALUES('%u', '%u', '%u', '%u', '%s', '%u', '%u', '%u', '%u', '%u', '%u', '%u', "
"'%i', '%i', '%i', '%i')", client->CharacterID(), index, buffs[index].spellid, "'%i', '%i', '%i', '%i', '%i')", client->CharacterID(), index, buffs[index].spellid,
buffs[index].casterlevel, buffs[index].caster_name, buffs[index].ticsremaining, buffs[index].casterlevel, buffs[index].caster_name, buffs[index].ticsremaining,
buffs[index].counters, buffs[index].numhits, buffs[index].melee_rune, buffs[index].counters, buffs[index].numhits, buffs[index].melee_rune,
buffs[index].magic_rune, buffs[index].persistant_buff, buffs[index].dot_rune, buffs[index].magic_rune, buffs[index].persistant_buff, buffs[index].dot_rune,
buffs[index].caston_x, buffs[index].caston_y, buffs[index].caston_z, buffs[index].caston_x, buffs[index].caston_y, buffs[index].caston_z,
buffs[index].ExtraDIChance); buffs[index].ExtraDIChance, buffs[index].instrument_mod);
QueryDatabase(query); QueryDatabase(query);
} }
} }
void ZoneDatabase::LoadBuffs(Client *client) { void ZoneDatabase::LoadBuffs(Client *client)
{
Buffs_Struct *buffs = client->GetBuffs(); Buffs_Struct *buffs = client->GetBuffs();
uint32 max_slots = client->GetMaxBuffSlots(); uint32 max_slots = client->GetMaxBuffSlots();
@ -3002,8 +3004,9 @@ void ZoneDatabase::LoadBuffs(Client *client) {
std::string query = StringFormat("SELECT spell_id, slot_id, caster_level, caster_name, ticsremaining, " std::string query = StringFormat("SELECT spell_id, slot_id, caster_level, caster_name, ticsremaining, "
"counters, numhits, melee_rune, magic_rune, persistent, dot_rune, " "counters, numhits, melee_rune, magic_rune, persistent, dot_rune, "
"caston_x, caston_y, caston_z, ExtraDIChance " "caston_x, caston_y, caston_z, ExtraDIChance, instrument_mod "
"FROM `character_buffs` WHERE `character_id` = '%u'", client->CharacterID()); "FROM `character_buffs` WHERE `character_id` = '%u'",
client->CharacterID());
auto results = QueryDatabase(query); auto results = QueryDatabase(query);
if (!results.Success()) { if (!results.Success()) {
return; return;
@ -3031,6 +3034,7 @@ void ZoneDatabase::LoadBuffs(Client *client) {
int32 caston_y = atoul(row[12]); int32 caston_y = atoul(row[12]);
int32 caston_z = atoul(row[13]); int32 caston_z = atoul(row[13]);
int32 ExtraDIChance = atoul(row[14]); int32 ExtraDIChance = atoul(row[14]);
uint32 instrument_mod = atoul(row[15]);
buffs[slot_id].spellid = spell_id; buffs[slot_id].spellid = spell_id;
buffs[slot_id].casterlevel = caster_level; buffs[slot_id].casterlevel = caster_level;
@ -3058,7 +3062,7 @@ void ZoneDatabase::LoadBuffs(Client *client) {
buffs[slot_id].ExtraDIChance = ExtraDIChance; buffs[slot_id].ExtraDIChance = ExtraDIChance;
buffs[slot_id].RootBreakChance = 0; buffs[slot_id].RootBreakChance = 0;
buffs[slot_id].UpdateClient = false; buffs[slot_id].UpdateClient = false;
buffs[slot_id].instrument_mod = instrument_mod;
} }
max_slots = client->GetMaxBuffSlots(); max_slots = client->GetMaxBuffSlots();