diff --git a/changelog.txt b/changelog.txt index 4f71af6d5..7647ea8ec 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 08/14/2016 == +mackal: Implement Linked Spell Reuse Timers + - For whatever reason this is a bit unfriendly, but that's how it is on live. + - Titanium is especially unfriendly with large differences in reuse times (ex higher canni and the first 4) + - Unsure when this went live for spells, but canni was at least linked at OoW launch + == 08/13/2016 == Kinglykrab: Implemented optional avoidance cap rules. - Serves to eliminate God-like characters on custom servers with high item stats diff --git a/common/emu_oplist.h b/common/emu_oplist.h index 8b913095b..6b7c58841 100644 --- a/common/emu_oplist.h +++ b/common/emu_oplist.h @@ -289,6 +289,7 @@ N(OP_LFGuild), N(OP_LFPCommand), N(OP_LFPGetMatchesRequest), N(OP_LFPGetMatchesResponse), +N(OP_LinkedReuse), N(OP_LoadSpellSet), N(OP_LocInfo), N(OP_LockoutTimerInfo), diff --git a/common/eq_packet_structs.h b/common/eq_packet_structs.h index fc1c9f9e7..12a08fa36 100644 --- a/common/eq_packet_structs.h +++ b/common/eq_packet_structs.h @@ -390,6 +390,18 @@ uint32 scribing; // 1 if memorizing a spell, set to 0 if scribing to book, 2 if uint32 reduction; // lower reuse }; +/* +** Linked Spell Reuse Timer +** Length: 12 +** Comes before the OP_Memorize +** Live (maybe TDS steam) has an extra DWORD after timer_id +*/ +struct LinkedSpellReuseTimer_Struct { + uint32 timer_id; // Timer ID of the spell + uint32 end_time; // timestamp of when it will be ready + uint32 start_time; // timestamp of when it started +}; + /* ** Make Charmed Pet ** Length: 12 Bytes diff --git a/common/ptimer.h b/common/ptimer.h index 7194ab49b..d7398bea3 100644 --- a/common/ptimer.h +++ b/common/ptimer.h @@ -37,10 +37,12 @@ enum { //values for pTimerType pTimerSenseTraps = 12, pTimerDisarmTraps = 13, pTimerDisciplineReuseStart = 14, - pTimerDisciplineReuseEnd = 24, + pTimerDisciplineReuseEnd = 24, // client actually has 20 ids, but still no disc go that high even on live pTimerCombatAbility = 25, pTimerCombatAbility2 = 26, // RoF2+ Tiger Claw is unlinked from other monk skills, generic in case other classes ever need it pTimerBeggingPickPocket = 27, + pTimerLinkedSpellReuseStart = 28, + pTimerLinkedSpellReuseEnd = 48, pTimerLayHands = 87, //these IDs are used by client too pTimerHarmTouch = 89, //so dont change them diff --git a/utils/patches/patch_RoF.conf b/utils/patches/patch_RoF.conf index 00682d57f..aefcd8dbc 100644 --- a/utils/patches/patch_RoF.conf +++ b/utils/patches/patch_RoF.conf @@ -172,6 +172,7 @@ OP_BeginCast=0x17ff OP_ColoredText=0x41cb OP_ConsentResponse=0x183d OP_MemorizeSpell=0x2fac +OP_LinkedReuse=0x3ac0 OP_SwapSpell=0x4736 OP_CastSpell=0x1cb5 OP_Consider=0x4d8d diff --git a/utils/patches/patch_RoF2.conf b/utils/patches/patch_RoF2.conf index 9237a0238..19ef8d842 100644 --- a/utils/patches/patch_RoF2.conf +++ b/utils/patches/patch_RoF2.conf @@ -171,6 +171,7 @@ OP_BeginCast=0x318f OP_ColoredText=0x43af OP_ConsentResponse=0x384a OP_MemorizeSpell=0x217c +OP_LinkedReuse=0x1619 OP_SwapSpell=0x0efa OP_CastSpell=0x1287 OP_Consider=0x742b diff --git a/utils/patches/patch_SoD.conf b/utils/patches/patch_SoD.conf index 4944b0ef3..08e072d33 100644 --- a/utils/patches/patch_SoD.conf +++ b/utils/patches/patch_SoD.conf @@ -171,6 +171,7 @@ OP_BeginCast=0x0d5a # C OP_ColoredText=0x569a # C OP_ConsentResponse=0x6e47 # C OP_MemorizeSpell=0x8543 # C +OP_LinkedReuse=0x6ef9 OP_SwapSpell=0x3fd2 # C OP_CastSpell=0x3582 # C OP_Consider=0x6024 # C diff --git a/utils/patches/patch_SoF.conf b/utils/patches/patch_SoF.conf index 113038e49..d758daea9 100644 --- a/utils/patches/patch_SoF.conf +++ b/utils/patches/patch_SoF.conf @@ -169,6 +169,7 @@ OP_BeginCast=0x5A50 #SEQ 12/04/08 OP_ColoredText=0x3BC7 #SEQ 12/04/08 OP_ConsentResponse=0x4D30 #SEQ 12/04/08 OP_MemorizeSpell=0x6A93 #SEQ 12/04/08 +OP_LinkedReuse=0x2c26 OP_SwapSpell=0x1418 #SEQ 12/04/08 OP_CastSpell=0x7F5D #SEQ 12/04/08 OP_Consider=0x32E1 #SEQ 12/04/08 diff --git a/utils/patches/patch_Titanium.conf b/utils/patches/patch_Titanium.conf index a48de92b5..23f07109f 100644 --- a/utils/patches/patch_Titanium.conf +++ b/utils/patches/patch_Titanium.conf @@ -170,6 +170,7 @@ OP_Save=0x736b # ShowEQ 10/27/05 OP_Camp=0x78c1 # ShowEQ 10/27/05 OP_EndLootRequest=0x2316 # ShowEQ 10/27/05 OP_MemorizeSpell=0x308e # ShowEQ 10/27/05 +OP_LinkedReuse=0x6a00 OP_SwapSpell=0x2126 # ShowEQ 10/27/05 OP_CastSpell=0x304b # ShowEQ 10/27/05 OP_DeleteSpell=0x4f37 diff --git a/utils/patches/patch_UF.conf b/utils/patches/patch_UF.conf index 4b650176f..f41b62740 100644 --- a/utils/patches/patch_UF.conf +++ b/utils/patches/patch_UF.conf @@ -173,6 +173,7 @@ OP_BeginCast=0x0d5a # C OP_ColoredText=0x71bf # C OP_ConsentResponse=0x0e87 # C OP_MemorizeSpell=0x3887 # C +OP_LinkedReuse=0x1b26 OP_SwapSpell=0x5805 # C OP_CastSpell=0x50c2 # C OP_Consider=0x3c2d # C diff --git a/zone/client.h b/zone/client.h index dc04fbc1e..df7c1fce3 100644 --- a/zone/client.h +++ b/zone/client.h @@ -894,6 +894,9 @@ public: void SendDisciplineTimer(uint32 timer_id, uint32 duration); bool UseDiscipline(uint32 spell_id, uint32 target); + void SetLinkedSpellReuseTimer(uint32 timer_id, uint32 duration); + bool IsLinkedSpellReuseTimerReady(uint32 timer_id); + bool CheckTitle(int titleset); void EnableTitle(int titleset); void RemoveTitle(int titleset); diff --git a/zone/spells.cpp b/zone/spells.cpp index ea8067b8c..d310589b1 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -552,6 +552,10 @@ bool Mob::DoCastingChecks() } } + if (IsClient() && spells[spell_id].EndurTimerIndex > 0 && casting_spell_slot < CastingSlot::MaxGems) + if (!CastToClient()->IsLinkedSpellReuseTimerReady(spells[spell_id].EndurTimerIndex)) + return false; + casting_spell_checks = true; return true; } @@ -1308,8 +1312,11 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo { if(IsClient()) { - this->CastToClient()->CheckSongSkillIncrease(spell_id); - this->CastToClient()->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar); + Client *c = CastToClient(); + c->CheckSongSkillIncrease(spell_id); + if (spells[spell_id].EndurTimerIndex > 0 && slot < CastingSlot::MaxGems) + c->SetLinkedSpellReuseTimer(spells[spell_id].EndurTimerIndex, spells[spell_id].recast_time / 1000); + c->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar); } Log.Out(Logs::Detail, Logs::Spells, "Bard song %d should be started", spell_id); } @@ -1321,6 +1328,8 @@ void Mob::CastedSpellFinished(uint16 spell_id, uint32 target_id, CastingSlot slo SendSpellBarEnable(spell_id); // this causes the delayed refresh of the spell bar gems + if (spells[spell_id].EndurTimerIndex > 0 && slot < CastingSlot::MaxGems) + c->SetLinkedSpellReuseTimer(spells[spell_id].EndurTimerIndex, spells[spell_id].recast_time / 1000); c->MemorizeSpell(static_cast(slot), spell_id, memSpellSpellbar); // this tells the client that casting may happen again @@ -5680,3 +5689,25 @@ void Mob::ConeDirectional(uint16 spell_id, int16 resist_adjust) ++iter; } } + +// duration in seconds +void Client::SetLinkedSpellReuseTimer(uint32 timer_id, uint32 duration) +{ + if (timer_id > 19) + return; + GetPTimers().Start(pTimerLinkedSpellReuseStart + timer_id, duration); + auto outapp = new EQApplicationPacket(OP_LinkedReuse, sizeof(LinkedSpellReuseTimer_Struct)); + auto lr = (LinkedSpellReuseTimer_Struct *)outapp->pBuffer; + lr->timer_id = timer_id; + lr->start_time = Timer::GetCurrentTime() / 1000; + lr->end_time = lr->start_time + duration; + FastQueuePacket(&outapp); +} + +bool Client::IsLinkedSpellReuseTimerReady(uint32 timer_id) +{ + if (timer_id > 19) + return true; + return GetPTimers().Expired(&database, pTimerLinkedSpellReuseStart + timer_id); +} +