diff --git a/changelog.txt b/changelog.txt index ac69f9d00..4657226a2 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,9 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 09/28/2014 == +demonstar55: Add support for post June 18, 2014 Hundred Hands Effect spells (they changed the formula and stuff) +set Spells:Jun182014HundredHandsRevamp to true if you're using a spell file from June 18, 2014+ + == 09/27/2014 == Kayen: Implemented perl function $mob->GetSpellStat(spell_id, identifier, slot); Note: identifier is the stat field in spells_new, slot is used for certain effects like effectid, base,base2, max ect. @@ -7,6 +11,10 @@ Example $mob->GetSpellStat(121, "range"); //Returns spell range Example $mob->GetSpellStat(121, "effectid", 1); //Returns the the value of effectid1 This will allow you to pull almost all the data for any spell in quest files. demonstar55: Move the client's SetAttackTimer to the end of Client::CalcBonuses to keep the haste in sync +demonstar55: Correct haste/slow "stacking" rules +demonstar55: Correct SE_AttackSpeed4 to respect unslowable +demonstar55: Make the haste be between 1-225 like the client (<100 = slow, >100 = haste) to ... +demonstar55: Correct Hundred Hands effect and use formula provided by devs == 09/24/2014 == Uleat: Re-ordered server opcodes and handlers to give them some predictability of location (I need this for the inventory re-enumeration.) diff --git a/common/ruletypes.h b/common/ruletypes.h index 058d5ce6f..c5004ffd1 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -322,6 +322,7 @@ RULE_INT ( Spells, AI_IdleNoSpellMinRecast, 500) // AI spell recast time(MS) che RULE_INT ( Spells, AI_IdleNoSpellMaxRecast, 2000) // AI spell recast time(MS) check when no spell is cast while chasing target. (max time in random) RULE_INT ( Spells, AI_IdleBeneficialChance, 100) // Chance while idle to do a beneficial spell on self or others. RULE_BOOL ( Spells, SHDProcIDOffByOne, true) // pre June 2009 SHD spell procs were off by 1, they stopped doing this in June 2009 (so UF+ spell files need this false) +RULE_BOOL ( Spells, Jun182014HundredHandsRevamp, false) // this should be true for if you import a spell file newer than June 18, 2014 RULE_CATEGORY_END() diff --git a/utils/sql/git/optional/2014_09_28_NewHundredHandsEffect.sql b/utils/sql/git/optional/2014_09_28_NewHundredHandsEffect.sql new file mode 100644 index 000000000..2c5acb205 --- /dev/null +++ b/utils/sql/git/optional/2014_09_28_NewHundredHandsEffect.sql @@ -0,0 +1 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:Jun182014HundredHandsRevamp', 'false', 'Set this to true if your spell file is from after June 18, 2014.'); diff --git a/zone/attack.cpp b/zone/attack.cpp index d52d2a6ba..feb10369f 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -4807,6 +4807,26 @@ void Mob::CommonBreakInvisible() improved_hidden = false; } +/* Dev quotes: + * Old formula + * Final delay = (Original Delay / (haste mod *.01f)) + ((Hundred Hands / 100) * Original Delay) + * New formula + * Final delay = (Original Delay / (haste mod *.01f)) + ((Hundred Hands / 1000) * (Original Delay / (haste mod *.01f)) + * Base Delay 20 25 30 37 + * Haste 2.25 2.25 2.25 2.25 + * HHE (old) -17 -17 -17 -17 + * Final Delay 5.488888889 6.861111111 8.233333333 10.15444444 + * + * Base Delay 20 25 30 37 + * Haste 2.25 2.25 2.25 2.25 + * HHE (new) -383 -383 -383 -383 + * Final Delay 5.484444444 6.855555556 8.226666667 10.14622222 + * + * Difference -0.004444444 -0.005555556 -0.006666667 -0.008222222 + * + * These times are in 10th of a second + */ + void Mob::SetAttackTimer() { attack_timer.SetAtTrigger(4000, true); @@ -4814,7 +4834,7 @@ void Mob::SetAttackTimer() void Client::SetAttackTimer() { - float PermaHaste = GetPermaHaste(); + float haste_mod = GetHaste() * 0.01f; //default value for attack timer in case they have //an invalid weapon equipped: @@ -4879,28 +4899,30 @@ void Client::SetAttackTimer() } } - int16 DelayMod = std::max(itembonuses.HundredHands + spellbonuses.HundredHands, -99); + int hhe = itembonuses.HundredHands + spellbonuses.HundredHands; int speed = 0; + int delay = 36; + float quiver_haste = 0.0f; //if we have no weapon.. if (ItemToUse == nullptr) { //above checks ensure ranged weapons do not fall into here // Work out if we're a monk - if ((GetClass() == MONK) || (GetClass() == BEASTLORD)) - speed = static_cast((GetMonkHandToHandDelay() * (100 + DelayMod) / 100) * PermaHaste); - else - speed = static_cast((36 * (100 + DelayMod) / 100) * PermaHaste); + if (GetClass() == MONK || GetClass() == BEASTLORD) + delay = GetMonkHandToHandDelay(); } else { //we have a weapon, use its delay - // Convert weapon delay to timer resolution (milliseconds) - //delay * 100 - speed = static_cast((ItemToUse->Delay * (100 + DelayMod) / 100) * PermaHaste); - if (ItemToUse->ItemType == ItemTypeBow || ItemToUse->ItemType == ItemTypeLargeThrowing) { - float quiver_haste = GetQuiverHaste(); - if (quiver_haste > 0) - speed *= quiver_haste; - } + delay = ItemToUse->Delay; + if (ItemToUse->ItemType == ItemTypeBow || ItemToUse->ItemType == ItemTypeLargeThrowing) + quiver_haste = GetQuiverHaste(); } + if (RuleB(Spells, Jun182014HundredHandsRevamp)) + speed = static_cast(((delay / haste_mod) + ((hhe / 1000.0f) * (delay / haste_mod))) * 100); + else + speed = static_cast(((delay / haste_mod) + ((hhe / 100.0f) * delay)) * 100); + // this is probably wrong + if (quiver_haste > 0) + speed *= quiver_haste; TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true); if (i == MainPrimary) @@ -4910,13 +4932,24 @@ void Client::SetAttackTimer() void NPC::SetAttackTimer() { - float PermaHaste = GetPermaHaste(); + float haste_mod = GetHaste() * 0.01f; //default value for attack timer in case they have //an invalid weapon equipped: attack_timer.SetAtTrigger(4000, true); Timer *TimerToUse = nullptr; + int hhe = itembonuses.HundredHands + spellbonuses.HundredHands; + + // Technically NPCs should do some logic for weapons, but the effect is minimal + // What they do is take the lower of their set delay and the weapon's + // ex. Mob's delay set to 20, weapon set to 19, delay 19 + // Mob's delay set to 20, weapon set to 21, delay 20 + int speed = 0; + if (RuleB(Spells, Jun182014HundredHandsRevamp)) + speed = static_cast(((attack_delay / haste_mod) + ((hhe / 1000.0f) * (attack_delay / haste_mod))) * 100); + else + speed = static_cast(((attack_delay / haste_mod) + ((hhe / 100.0f) * attack_delay)) * 100); for (int i = MainRange; i <= MainSecondary; i++) { //pick a timer @@ -4938,13 +4971,6 @@ void NPC::SetAttackTimer() } } - int16 DelayMod = std::max(itembonuses.HundredHands + spellbonuses.HundredHands, -99); - - // Technically NPCs should do some logic for weapons, but the effect is minimal - // What they do is take the lower of their set delay and the weapon's - // ex. Mob's delay set to 20, weapon set to 19, delay 19 - // Mob's delay set to 20, weapon set to 21, delay 20 - int speed = static_cast((attack_delay * (100 + DelayMod) / 100) * PermaHaste); TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true); } } diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index d14624f2d..89af2a244 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -1546,6 +1546,11 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_AttackSpeed4: { + // These don't generate the IMMUNE_ATKSPEED message and the icon shows up + // but have no effect on the mobs attack speed + if (GetSpecialAbility(UNSLOWABLE)) + break; + if (effect_value < 0) //A few spells use negative values(Descriptions all indicate it should be a slow) effect_value = effect_value * -1; @@ -2467,7 +2472,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne } break; } - + case SE_ManaAbsorbPercentDamage: { if (newbon->ManaAbsorbPercentDamage[0] < effect_value){ @@ -2488,7 +2493,7 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_ShieldBlock: newbon->ShieldBlock += effect_value; break; - + case SE_ShieldEquipHateMod: newbon->ShieldEquipHateMod += effect_value; break; @@ -2502,6 +2507,10 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne newbon->BlockBehind += effect_value; break; + case SE_Blind: + newbon->IsBlind = true; + break; + case SE_Fear: newbon->IsFeared = true; break; @@ -4081,6 +4090,10 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) itembonuses.BlockBehind = effect_value; break; + case SE_Blind: + spellbonuses.IsBlind = false; + break; + case SE_Fear: spellbonuses.IsFeared = false; break; diff --git a/zone/bot.cpp b/zone/bot.cpp index d001a0b88..4102a2af6 100644 --- a/zone/bot.cpp +++ b/zone/bot.cpp @@ -8349,11 +8349,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { if(!ca_time) return; - float HasteModifier = 0; - if (GetHaste()) - HasteModifier = 10000 / (100 + GetHaste()); - else - HasteModifier = 100; + float HasteModifier = GetHaste() * 0.01f; int32 dmg = 0; uint16 skill_to_use = -1; @@ -8585,7 +8581,7 @@ void Bot::DoClassAttacks(Mob *target, bool IsRiposte) { TryBackstab(target,reuse); } - classattack_timer.Start(reuse*HasteModifier/100); + classattack_timer.Start(reuse / HasteModifier); } bool Bot::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { @@ -8978,11 +8974,7 @@ int32 Bot::CalcMaxMana() { } void Bot::SetAttackTimer() { - float PermaHaste; - if (GetHaste()) - PermaHaste = 1 / (1 + (float)GetHaste()/100); - else - PermaHaste = 1.0f; + float haste_mod = GetHaste() * 0.01f; //default value for attack timer in case they have //an invalid weapon equipped: @@ -8991,117 +8983,77 @@ void Bot::SetAttackTimer() { Timer* TimerToUse = nullptr; const Item_Struct* PrimaryWeapon = nullptr; - for (int i=MainRange; i<=MainSecondary; i++) { - + for (int i = MainRange; i <= MainSecondary; i++) { //pick a timer if (i == MainPrimary) TimerToUse = &attack_timer; else if (i == MainRange) TimerToUse = &ranged_timer; - else if(i == MainSecondary) + else if (i == MainSecondary) TimerToUse = &attack_dw_timer; else //invalid slot (hands will always hit this) continue; const Item_Struct* ItemToUse = nullptr; ItemInst* ci = GetBotItem(i); - if(ci) + if (ci) ItemToUse = ci->GetItem(); //special offhand stuff - if(i == MainSecondary) { + if (i == MainSecondary) { //if we have a 2H weapon in our main hand, no dual - if(PrimaryWeapon != nullptr) { - if( PrimaryWeapon->ItemClass == ItemClassCommon - && (PrimaryWeapon->ItemType == ItemType2HSlash - || PrimaryWeapon->ItemType == ItemType2HBlunt - || PrimaryWeapon->ItemType == ItemType2HPiercing)) { - attack_dw_timer.Disable(); - continue; + if (PrimaryWeapon != nullptr) { + if (PrimaryWeapon->ItemClass == ItemClassCommon + && (PrimaryWeapon->ItemType == ItemType2HSlash + || PrimaryWeapon->ItemType == ItemType2HBlunt + || PrimaryWeapon->ItemType == ItemType2HPiercing)) { + attack_dw_timer.Disable(); + continue; } } //clients must have the skill to use it... - if(!GetSkill(SkillDualWield)) { + if (!GetSkill(SkillDualWield)) { attack_dw_timer.Disable(); continue; } } //see if we have a valid weapon - if(ItemToUse != nullptr) { + if (ItemToUse != nullptr) { //check type and damage/delay - if(ItemToUse->ItemClass != ItemClassCommon - || ItemToUse->Damage == 0 - || ItemToUse->Delay == 0) { + if (ItemToUse->ItemClass != ItemClassCommon + || ItemToUse->Damage == 0 + || ItemToUse->Delay == 0) { //no weapon - ItemToUse = nullptr; + ItemToUse = nullptr; } // Check to see if skill is valid - else if((ItemToUse->ItemType > ItemTypeLargeThrowing) && (ItemToUse->ItemType != ItemTypeMartial) && (ItemToUse->ItemType != ItemType2HPiercing)) { + else if ((ItemToUse->ItemType > ItemTypeLargeThrowing) && (ItemToUse->ItemType != ItemTypeMartial) && (ItemToUse->ItemType != ItemType2HPiercing)) { //no weapon ItemToUse = nullptr; } } - int16 DelayMod = itembonuses.HundredHands + spellbonuses.HundredHands; - if (DelayMod < -99) - DelayMod = -99; + int hhe = itembonuses.HundredHands + spellbonuses.HundredHands; + int speed = 0; + int delay = 36; //if we have no weapon.. if (ItemToUse == nullptr) { //above checks ensure ranged weapons do not fall into here // Work out if we're a monk - if ((GetClass() == MONK) || (GetClass() == BEASTLORD)) { - //we are a monk, use special delay - int speed = (int)( (GetMonkHandToHandDelay()*(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste); - // 1200 seemed too much, with delay 10 weapons available - if(speed < RuleI(Combat, MinHastedDelay)) //lower bound - speed = RuleI(Combat, MinHastedDelay); - TimerToUse->SetAtTrigger(speed, true); // Hand to hand, delay based on level or epic - } else { - //not a monk... using fist, regular delay - int speed = (int)((36 *(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste); - //if(speed < RuleI(Combat, MinHastedDelay) && IsClient()) //lower bound - // speed = RuleI(Combat, MinHastedDelay); - TimerToUse->SetAtTrigger(speed, true); // Hand to hand, non-monk 2/36 - } + if ((GetClass() == MONK) || (GetClass() == BEASTLORD)) + delay = GetMonkHandToHandDelay(); } else { //we have a weapon, use its delay - // Convert weapon delay to timer resolution (milliseconds) - //delay * 100 - int speed = (int)((ItemToUse->Delay*(100+DelayMod)/100)*(100.0f+attack_speed)*PermaHaste); - if(speed < RuleI(Combat, MinHastedDelay)) - speed = RuleI(Combat, MinHastedDelay); - - if(ItemToUse && (ItemToUse->ItemType == ItemTypeBow || ItemToUse->ItemType == ItemTypeLargeThrowing)) - { - /*if(IsClient()) - { - float max_quiver = 0; - for(int r = SLOT_PERSONAL_BEGIN; r <= SLOT_PERSONAL_END; r++) - { - const ItemInst *pi = CastToClient()->GetInv().GetItem(r); - if(!pi) - continue; - if(pi->IsType(ItemClassContainer) && pi->GetItem()->BagType == bagTypeQuiver) - { - float temp_wr = (pi->GetItem()->BagWR / 3); - if(temp_wr > max_quiver) - { - max_quiver = temp_wr; - } - } - } - if(max_quiver > 0) - { - float quiver_haste = 1 / (1 + max_quiver / 100); - speed *= quiver_haste; - } - }*/ - } - TimerToUse->SetAtTrigger(speed, true); + delay = ItemToUse->Delay; } + if (RuleB(Spells, Jun182014HundredHandsRevamp)) + speed = static_cast(((delay / haste_mod) + ((hhe / 1000.0f) * (delay / haste_mod))) * 100); + else + speed = static_cast(((delay / haste_mod) + ((hhe / 100.0f) * delay)) * 100); + TimerToUse->SetAtTrigger(std::max(RuleI(Combat, MinHastedDelay), speed), true); if(i == MainPrimary) PrimaryWeapon = ItemToUse; diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index ba8eae47e..2fb7ad9ca 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -1341,12 +1341,45 @@ int16 Client::CalcCHA() { return(CHA); } -int Client::CalcHaste() { - int h = spellbonuses.haste + spellbonuses.hastetype2; +int Client::CalcHaste() +{ + /* Tests: (based on results in newer char window) + * 68 v1 + 46 item + 25 over + 35 inhib = 204% + * 46 item + 5 v2 + 25 over + 35 inhib = 65% + * 68 v1 + 46 item + 5 v2 + 25 over + 35 inhib = 209% + * 75% slow + 35 inhib = 25% + * 35 inhib = 65% + * 75% slow = 25% + * Conclusions: + * the bigger effect in slow v. inhib wins + * slow negates all other hastes + * inhib will only negate all other hastes if you don't have v1 (ex. VQ) + */ + // slow beats all! Besides a better inhibit + if (spellbonuses.haste < 0) { + if (-spellbonuses.haste <= spellbonuses.inhibitmelee) + Haste = 100 - spellbonuses.inhibitmelee; + else + Haste = 100 + spellbonuses.haste; + return Haste; + } + + // No haste and inhibit, kills all other hastes + if (spellbonuses.haste == 0 && spellbonuses.inhibitmelee) { + Haste = 100 - spellbonuses.inhibitmelee; + return Haste; + } + + int h = 0; int cap = 0; - int overhaste = 0; int level = GetLevel(); + // we know we have a haste spell and not slowed, no extra inhibit melee checks needed + if (spellbonuses.haste) + h += spellbonuses.haste - spellbonuses.inhibitmelee; + if (spellbonuses.hastetype2 && level > 49) // type 2 is capped at 10% and only available to 50+ + h += spellbonuses.hastetype2 > 10 ? 10 : spellbonuses.hastetype2; + // 26+ no cap, 1-25 10 if (level > 25) // 26+ h += itembonuses.haste; @@ -1368,24 +1401,16 @@ int Client::CalcHaste() { // 51+ 25 (despite there being higher spells...), 1-50 10 if (level > 50) // 51+ - overhaste = spellbonuses.hastetype3 > 25 ? 25 : spellbonuses.hastetype3; + h += spellbonuses.hastetype3 > 25 ? 25 : spellbonuses.hastetype3; else // 1-50 - overhaste = spellbonuses.hastetype3 > 10 ? 10 : spellbonuses.hastetype3; + h += spellbonuses.hastetype3 > 10 ? 10 : spellbonuses.hastetype3; - h += overhaste; h += ExtraHaste; //GM granted haste. h = mod_client_haste(h); - if (spellbonuses.inhibitmelee) { - if (h >= 0) - h -= spellbonuses.inhibitmelee; - else - h -= ((100 + h) * spellbonuses.inhibitmelee / 100); - } - - Haste = h; - return(Haste); + Haste = 100 + h; + return Haste; } //The AA multipliers are set to be 5, but were 2 on WR diff --git a/zone/common.h b/zone/common.h index 0dbb68e0b..fb56b3f01 100644 --- a/zone/common.h +++ b/zone/common.h @@ -294,6 +294,7 @@ struct StatBonuses { int16 ResistFearChance; //i bool Fearless; //i bool IsFeared; //i + bool IsBlind; //i int16 StunResist; //i int16 MeleeSkillCheck; //i uint8 MeleeSkillCheckSkill; diff --git a/zone/fearpath.cpp b/zone/fearpath.cpp index 636422b3f..47fc53a96 100644 --- a/zone/fearpath.cpp +++ b/zone/fearpath.cpp @@ -31,12 +31,10 @@ #define snprintf _snprintf #endif - extern Zone* zone; #define FEAR_PATHING_DEBUG - //this is called whenever we are damaged to process possible fleeing void Mob::CheckFlee() { //if were allready fleeing, dont need to check more... @@ -55,7 +53,7 @@ void Mob::CheckFlee() { float ratio = GetHPRatio(); float fleeratio = GetSpecialAbility(FLEE_PERCENT); fleeratio = fleeratio > 0 ? fleeratio : RuleI(Combat, FleeHPRatio); - + if(ratio >= fleeratio) return; @@ -101,12 +99,13 @@ void Mob::CheckFlee() { } } - -void Mob::ProcessFlee() { +void Mob::ProcessFlee() +{ //Stop fleeing if effect is applied after they start to run. //When ImmuneToFlee effect fades it will turn fear back on and check if it can still flee. - if(flee_mode && (GetSpecialAbility(IMMUNE_FLEEING) || spellbonuses.ImmuneToFlee) && !spellbonuses.IsFeared){ + if (flee_mode && (GetSpecialAbility(IMMUNE_FLEEING) || spellbonuses.ImmuneToFlee) && + !spellbonuses.IsFeared && !spellbonuses.IsBlind) { curfp = false; return; } @@ -114,40 +113,42 @@ void Mob::ProcessFlee() { //see if we are still dying, if so, do nothing float fleeratio = GetSpecialAbility(FLEE_PERCENT); fleeratio = fleeratio > 0 ? fleeratio : RuleI(Combat, FleeHPRatio); - if(GetHPRatio() < fleeratio) + if (GetHPRatio() < fleeratio) return; //we are not dying anymore... see what we do next flee_mode = false; - //see if we are legitimately feared now - if(!spellbonuses.IsFeared) { - //not feared... were done... + //see if we are legitimately feared or blind now + if (!spellbonuses.IsFeared && !spellbonuses.IsBlind) { + //not feared or blind... were done... curfp = false; return; } } -float Mob::GetFearSpeed() { - if(flee_mode) { +float Mob::GetFearSpeed() +{ + if (flee_mode) { //we know ratio < FLEE_HP_RATIO float speed = GetBaseRunspeed(); float ratio = GetHPRatio(); float multiplier = RuleR(Combat, FleeMultiplier); - if(GetSnaredAmount() > 40) + if (GetSnaredAmount() > 40) multiplier = multiplier / 6.0f; speed = speed * ratio * multiplier / 100; //NPC will eventually stop. Snares speeds this up. - if(speed < 0.09) + if (speed < 0.09) speed = 0.0001f; - - return(speed); + + return speed; } - return(GetRunspeed()); + // fear and blind use their normal run speed + return GetRunspeed(); } void Mob::CalculateNewFearpoint() @@ -209,17 +210,3 @@ void Mob::CalculateNewFearpoint() } } - - - - - - - - - - - - - - diff --git a/zone/merc.cpp b/zone/merc.cpp index 2316c6af0..6f500b12e 100644 --- a/zone/merc.cpp +++ b/zone/merc.cpp @@ -4619,13 +4619,7 @@ void Merc::DoClassAttacks(Mob *target) { if(!ca_time) return; - float HasteModifier = 0; - if(GetHaste() > 0) - HasteModifier = 10000 / (100 + GetHaste()); - else if(GetHaste() < 0) - HasteModifier = (100 - GetHaste()); - else - HasteModifier = 100; + float HasteModifier = GetHaste() * 0.01f; int level = GetLevel(); int reuse = TauntReuseTime * 1000; //make this very long since if they dont use it once, they prolly never will @@ -4689,7 +4683,7 @@ void Merc::DoClassAttacks(Mob *target) { } } - classattack_timer.Start(reuse*HasteModifier/100); + classattack_timer.Start(reuse / HasteModifier); } bool Merc::Attack(Mob* other, int Hand, bool bRiposte, bool IsStrikethrough, bool IsFromSpell, ExtraAttackOptions *opts) diff --git a/zone/mob.cpp b/zone/mob.cpp index 8a4253c19..2441ca0bc 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -2735,12 +2735,29 @@ uint32 Mob::GetZoneID() const { return(zone->GetZoneID()); } -int Mob::GetHaste() { - int h = spellbonuses.haste + spellbonuses.hastetype2; +int Mob::GetHaste() +{ + // See notes in Client::CalcHaste + // Need to check if the effect of inhibit melee differs for NPCs + if (spellbonuses.haste < 0) { + if (-spellbonuses.haste <= spellbonuses.inhibitmelee) + return 100 - spellbonuses.inhibitmelee; + else + return 100 + spellbonuses.haste; + } + + if (spellbonuses.haste == 0 && spellbonuses.inhibitmelee) + return 100 - spellbonuses.inhibitmelee; + + int h = 0; int cap = 0; - int overhaste = 0; int level = GetLevel(); + if (spellbonuses.haste) + h += spellbonuses.haste - spellbonuses.inhibitmelee; + if (spellbonuses.hastetype2 && level > 49) + h += spellbonuses.hastetype2 > 10 ? 10 : spellbonuses.hastetype2; + // 26+ no cap, 1-25 10 if (level > 25) // 26+ h += itembonuses.haste; @@ -2760,21 +2777,13 @@ int Mob::GetHaste() { // 51+ 25 (despite there being higher spells...), 1-50 10 if (level > 50) // 51+ - overhaste = spellbonuses.hastetype3 > 25 ? 25 : spellbonuses.hastetype3; + h += spellbonuses.hastetype3 > 25 ? 25 : spellbonuses.hastetype3; else // 1-50 - overhaste = spellbonuses.hastetype3 > 10 ? 10 : spellbonuses.hastetype3; + h += spellbonuses.hastetype3 > 10 ? 10 : spellbonuses.hastetype3; - h += overhaste; h += ExtraHaste; //GM granted haste. - if (spellbonuses.inhibitmelee) { - if (h >= 0) - h -= spellbonuses.inhibitmelee; - else - h -= ((100 + h) * spellbonuses.inhibitmelee / 100); - } - - return(h); + return 100 + h; } void Mob::SetTarget(Mob* mob) { diff --git a/zone/mob.h b/zone/mob.h index d76fe5a8c..5f481d1f9 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -684,7 +684,6 @@ public: inline bool GetInvul(void) { return invulnerable; } inline void SetExtraHaste(int Haste) { ExtraHaste = Haste; } virtual int GetHaste(); - inline float GetPermaHaste() { return GetHaste() ? 100.0f / (1.0f + static_cast(GetHaste()) / 100.0f) : 100.0f; } uint8 GetWeaponDamageBonus(const Item_Struct* Weapon); uint16 GetDamageTable(SkillUseTypes skillinuse); @@ -769,6 +768,7 @@ public: inline void StartFleeing() { flee_mode = true; CalculateNewFearpoint(); } void ProcessFlee(); void CheckFlee(); + inline bool IsBlind() { return spellbonuses.IsBlind; } inline bool CheckAggro(Mob* other) {return hate_list.IsOnHateList(other);} float CalculateHeadingToTarget(float in_x, float in_y); diff --git a/zone/mob_ai.cpp b/zone/mob_ai.cpp index 125e1d85f..5f8ea2c11 100644 --- a/zone/mob_ai.cpp +++ b/zone/mob_ai.cpp @@ -1040,7 +1040,7 @@ void Mob::AI_Process() { // if(RuleB(Combat, EnableFearPathing)){ if(curfp) { - if(IsRooted()) { + if(IsRooted() || (IsBlind() && CombatRange(hate_list.GetClosest(this)))) { //make sure everybody knows were not moving, for appearance sake if(IsMoving()) { @@ -1087,7 +1087,9 @@ void Mob::AI_Process() { if (engaged) { - if (IsRooted()) + // we are prevented from getting here if we are blind and don't have a target in range + // from above, so no extra blind checks needed + if (IsRooted() || IsBlind()) SetTarget(hate_list.GetClosest(this)); else { diff --git a/zone/questmgr.cpp b/zone/questmgr.cpp index ec8fbd584..90f0d370d 100644 --- a/zone/questmgr.cpp +++ b/zone/questmgr.cpp @@ -2672,45 +2672,47 @@ const char* QuestManager::saylink(char* Phrase, bool silent, const char* LinkNam // Query for an existing phrase and id in the saylink table std::string query = StringFormat("SELECT `id` FROM `saylink` WHERE `phrase` = '%s'", escaped_string); auto results = database.QueryDatabase(query); - if(results.Success()) - { - if (results.RowCount() >= 1) + if (results.Success()) { + if (results.RowCount() >= 1) { for (auto row = results.begin();row != results.end(); ++row) sayid = atoi(row[0]); - else // Add a new saylink entry to the database and query it again for the new sayid number - { - query = StringFormat("INSERT INTO `saylink` (`phrase`) VALUES ('%s')", escaped_string); - results = database.QueryDatabase(query); - - if(!results.Success()) - LogFile->write(EQEMuLog::Error, "Error in saylink phrase queries", results.ErrorMessage().c_str()); - else if (results.RowCount() >= 1) - for(auto row = results.begin(); row != results.end(); ++row) - sayid = atoi(row[0]); - + } else { // Add a new saylink entry to the database and query it again for the new sayid number + std::string insert_query = StringFormat("INSERT INTO `saylink` (`phrase`) VALUES ('%s')", escaped_string); + results = database.QueryDatabase(insert_query); + if (!results.Success()) { + LogFile->write(EQEMuLog::Error, "Error in saylink phrase queries", results.ErrorMessage().c_str()); + } else { + results = database.QueryDatabase(query); + if (results.Success()) { + if (results.RowCount() >= 1) + for(auto row = results.begin(); row != results.end(); ++row) + sayid = atoi(row[0]); + } else { + LogFile->write(EQEMuLog::Error, "Error in saylink phrase queries", results.ErrorMessage().c_str()); + } + } } } safe_delete_array(escaped_string); - if(silent) + if (silent) sayid = sayid + 750000; else sayid = sayid + 500000; - //Create the say link as an item link hash - char linktext[250]; + //Create the say link as an item link hash + char linktext[250]; - if(initiator) - { + if (initiator) { if (initiator->GetClientVersion() >= EQClientRoF) sprintf(linktext,"%c%06X%s%s%c",0x12,sayid,"0000000000000000000000000000000000000000000000000",LinkName,0x12); else if (initiator->GetClientVersion() >= EQClientSoF) sprintf(linktext,"%c%06X%s%s%c",0x12,sayid,"00000000000000000000000000000000000000000000",LinkName,0x12); else sprintf(linktext,"%c%06X%s%s%c",0x12,sayid,"000000000000000000000000000000000000000",LinkName,0x12); - } - else // If no initiator, create an RoF saylink, since older clients handle RoF ones better than RoF handles older ones. + } else { // If no initiator, create an RoF saylink, since older clients handle RoF ones better than RoF handles older ones. sprintf(linktext,"%c%06X%s%s%c",0x12,sayid,"0000000000000000000000000000000000000000000000000",LinkName,0x12); + } strcpy(Phrase,linktext); return Phrase; diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index 904dc41b3..901456cd2 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -1412,11 +1412,7 @@ void NPC::DoClassAttacks(Mob *target) { if(!ca_time) return; - float HasteModifier = 0; - if (GetHaste()) - HasteModifier = 10000 / (100 + GetHaste()); - else - HasteModifier = 100; + float HasteModifier = GetHaste() * 0.01f; int level = GetLevel(); int reuse = TauntReuseTime * 1000; //make this very long since if they dont use it once, they prolly never will @@ -1568,7 +1564,7 @@ void NPC::DoClassAttacks(Mob *target) { } } - classattack_timer.Start(reuse*HasteModifier/100); + classattack_timer.Start(reuse / HasteModifier); } void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) @@ -1592,20 +1588,13 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) } int ReuseTime = 0; - int ClientHaste = GetHaste(); - int HasteMod = 0; + float HasteMod = GetHaste() * 0.01f; - if(ClientHaste >= 0){ - HasteMod = (10000/(100+ClientHaste)); //+100% haste = 2x as many attacks - } - else{ - HasteMod = (100-ClientHaste); //-100% haste = 1/2 as many attacks - } int32 dmg = 0; uint16 skill_to_use = -1; - if (skill == -1){ + if (skill == -1){ switch(GetClass()){ case WARRIOR: case RANGER: @@ -1677,8 +1666,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) } } - ReuseTime = BashReuseTime-1; - ReuseTime = (ReuseTime*HasteMod)/100; + ReuseTime = (BashReuseTime - 1) / HasteMod; DoSpecialAttackDamage(ca_target, SkillBash, dmg, 1,-1,ReuseTime); @@ -1705,8 +1693,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) if (min_dmg > max_dmg) max_dmg = min_dmg; - ReuseTime = FrenzyReuseTime-1; - ReuseTime = (ReuseTime*HasteMod)/100; + ReuseTime = (FrenzyReuseTime - 1) / HasteMod; //Live parses show around 55% Triple 35% Double 10% Single, you will always get first hit. while(AtkRounds > 0) { @@ -1781,7 +1768,7 @@ void Client::DoClassAttacks(Mob *ca_target, uint16 skill, bool IsRiposte) TryBackstab(ca_target,ReuseTime); } - ReuseTime = (ReuseTime*HasteMod)/100; + ReuseTime = ReuseTime / HasteMod; if(ReuseTime > 0 && !IsRiposte){ p_timers.Start(pTimerCombatAbility, ReuseTime); } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 9d7a93a7b..f5509ece2 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -1267,10 +1267,11 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) #ifdef SPELL_EFFECT_SPAM snprintf(effect_desc, _EDLEN, "Blind: %+i", effect_value); #endif - if (spells[spell_id].base[i] == 1) + // this should catch the cures + if (BeneficialSpell(spell_id) && spells[spell_id].buffduration == 0) BuffFadeByEffect(SE_Blind); - // handled by client - // TODO: blind flag? + else if (!IsClient()) + CalculateNewFearpoint(); break; } @@ -3995,6 +3996,11 @@ void Mob::BuffFadeBySlot(int slot, bool iRecalcBonuses) break; } + case SE_Blind: + if (curfp && !FindType(SE_Fear)) + curfp = false; + break; + case SE_Fear: { if(RuleB(Combat, EnableFearPathing)){ diff --git a/zone/zone.cpp b/zone/zone.cpp index 36cb3504a..446110a4a 100644 --- a/zone/zone.cpp +++ b/zone/zone.cpp @@ -433,8 +433,8 @@ void Zone::LoadTempMerchantData(){ npcid = ml.npcid; } ml.slot = atoul(row[1]); - ml.item = atoul(row[2]); - ml.charges = atoul(row[3]); + ml.charges = atoul(row[2]); + ml.item = atoul(row[3]); ml.origslot = ml.slot; cur->second.push_back(ml); }