diff --git a/changelog.txt b/changelog.txt index e02571f44..43c045ce8 100644 --- a/changelog.txt +++ b/changelog.txt @@ -11,8 +11,12 @@ Kayen: Implemented SE_IllusionOther - Allows next Illusion buff (self only) cast Kayen: Update SE_AETaunt - Base value will now determine AE taunt range (This will not result in any change to currently used spells). Kayen: Udpated SE_ReclaimPet - Correct forumla for mana returned to properly return 75% of actual pet spell mana cost. Kayen: Implemented SE_ImprovedReclaimEnergy - Modifies % mana returned from SE_ReclaimPet. +Kayen: Implemented SE_HeadShot, SE_HeadShotLevel - Defines headshot damage and level requirements. +Revised HeadShot mechanic so damage now recieves all archery bonuses, proc chance can be set to either (lives new Proc Per minute +system, or flat chance based on dex (formula updated). Required SQL: utils/sql/git/required/2014_06_25_AA_Update.sql +Optional SQL: utils/sql/git/optiional/2014_06_29_HeadShotRules.sql == 06/17/2014 == diff --git a/common/ruletypes.h b/common/ruletypes.h index 70e4ba0d2..6f5fe0769 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -333,6 +333,8 @@ RULE_REAL ( Combat, AvgProcsPerMinute, 2.0) RULE_REAL ( Combat, ProcPerMinDexContrib, 0.075) RULE_REAL ( Combat, BaseProcChance, 0.035) RULE_REAL ( Combat, ProcDexDivideBy, 11000) +RULE_BOOL ( Combat, AdjustSpecialProcPerMinute, true) //Set PPM for special abilities like HeadShot (Live does this as of 4-14) +RULE_REAL ( Combat, AvgSpecialProcsPerMinute, 2.0) //Unclear what best value is atm. RULE_REAL ( Combat, BaseHitChance, 69.0) RULE_REAL ( Combat, NPCBonusHitChance, 26.0) RULE_REAL ( Combat, HitFalloffMinor, 5.0) //hit will fall off up to 5% over the initial level range diff --git a/common/spdat.h b/common/spdat.h index bb3951019..65bb9f692 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -364,7 +364,7 @@ typedef enum { #define SE_MaxHPChange 214 // implemented #define SE_PetAvoidance 215 // implemented[AA] - increases pet ability to avoid melee damage #define SE_Accuracy 216 // implemented -//#define SE_HeadShot 217 // not implemented as bonus - ability to head shot (base2 = damage) +#define SE_HeadShot 217 // implemented - ability to head shot (base2 = damage) #define SE_PetCriticalHit 218 // implemented[AA] - gives pets a baseline critical hit chance #define SE_SlayUndead 219 // implemented - Allow extra damage against undead (base1 = rate, base2 = damage mod). #define SE_SkillDamageAmount 220 // implemented @@ -492,8 +492,8 @@ typedef enum { #define SE_ImmuneFleeing 342 // implemented - stop mob from fleeing #define SE_InterruptCasting 343 // implemented - % chance to interrupt spells being cast every tic. Cacophony (8272) #define SE_ChannelChanceItems 344 // implemented[AA] - chance to not have ITEM effects interrupted when you take damage. -//#define SE_AssassinationLevel 345 // not implemented as bonus - AA Assisination max level to kill -//#define SE_HeadShotLevel 346 // not implemented as bonus - AA HeadShot max level to kill +#define SE_AssassinateLevel 345 // implemented as bonus - AA Assisination max level to kill +#define SE_HeadShotLevel 346 // implemented[AA] - HeadShot max level to kill #define SE_DoubleRangedAttack 347 // implemented - chance at an additional archery attack (consumes arrow) #define SE_LimitManaMin 348 // implemented #define SE_ShieldEquipHateMod 349 // implemented[AA] Increase melee hate when wearing a shield. @@ -586,7 +586,7 @@ typedef enum { //#define SE_BeneficialCountDownHold 436 // not used ( 23491 | ABTest Buff Hold) //#define SE_TeleporttoAnchor 437 // *not implemented - Teleport Guild Hall Anchor(33099) //#define SE_TranslocatetoAnchor 438 // *not implemented - Translocate Primary Anchor (27750) -//#define SE_IncreaseAssassinationLvl 439 // *not implemented[AA] - increases the maximum level of humanoid that can be affected by assassination +#define SE_Assassinate 439 // implemented[AA] - Assassinate damage #define SE_FinishingBlowLvl 440 // implemented[AA] - Sets the level Finishing blow can be triggered on an NPC #define SE_DistanceRemoval 441 // implemented - Buff is removed from target when target moves X amount of distance away from where initially hit. #define SE_TriggerOnReqTarget 442 // implemented - triggers a spell which a certain criteria are met (below X amount of hp,mana,end, number of pets on hatelist) diff --git a/utils/sql/git/optional/2014_06_29_HeadShotRules.sql b/utils/sql/git/optional/2014_06_29_HeadShotRules.sql new file mode 100644 index 000000000..ad3de19b6 --- /dev/null +++ b/utils/sql/git/optional/2014_06_29_HeadShotRules.sql @@ -0,0 +1,2 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:AdjustSpecialProcPerMinute', 'false', 'Allow PPM for special abilities HeadShot, Assassinate, Decap ect.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:AvgSpecialProcsPerMinute', '2.0', 'Set PPM for special abilities HeadShot, Assassinate, Decap ect. (Unknown what value live uses) .'); \ No newline at end of file diff --git a/utils/sql/git/required/2014_06_25_AA_Updates..sql b/utils/sql/git/required/2014_06_25_AA_Updates..sql index 13522e93c..c9ae42237 100644 --- a/utils/sql/git/required/2014_06_25_AA_Updates..sql +++ b/utils/sql/git/required/2014_06_25_AA_Updates..sql @@ -9,5 +9,9 @@ UPDATE aa_actions SET spell_id = 5227, nonspell_action = 0 WHERE aaid = 643; -- AA Improved Reclaim Energy INSERT INTO `aa_effects` (`aaid`, `slot`, `effectid`, `base1`, `base2`) VALUES ('180', '1', '241', '95', '0'); +-- AA Headshot +INSERT INTO `aa_effects` (`aaid`, `slot`, `effectid`, `base1`, `base2`) VALUES ('644', '1', '217', '0', '32000'); +INSERT INTO `aa_effects` (`aaid`, `slot`, `effectid`, `base1`, `base2`) VALUES ('644', '2', '346', '46', '0'); + -- spells_new update ALTER TABLE `spells_new` CHANGE `field175` `numhits_type` INT(11) NOT NULL DEFAULT '0'; \ No newline at end of file diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index be847b280..345f602f8 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -637,7 +637,7 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) continue; _log(AA__BONUSES, "Applying Effect %d from AA %u in slot %d (base1: %d, base2: %d) on %s", effect, aaid, slot, base1, base2, this->GetCleanName()); - + uint8 focus = IsFocusEffect(0, 0, true,effect); if (focus) { @@ -1279,6 +1279,23 @@ void Client::ApplyAABonuses(uint32 aaid, uint32 slots, StatBonuses* newbon) newbon->ImprovedReclaimEnergy = base1; break; } + + case SE_HeadShot: + { + if(newbon->HeadShot[1] < base2){ + newbon->HeadShot[0] = base1; + newbon->HeadShot[1] = base2; + } + break; + } + + case SE_HeadShotLevel: + { + if(newbon->HSLevel < base1) + newbon->HSLevel = base1; + break; + } + } } } @@ -2760,6 +2777,38 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne break; } + case SE_HeadShot: + { + if(newbon->HeadShot[1] < base2){ + newbon->HeadShot[0] = effect_value; + newbon->HeadShot[1] = base2; + } + break; + } + + case SE_HeadShotLevel: + { + if(newbon->HSLevel < effect_value) + newbon->HSLevel = effect_value; + break; + } + + case SE_Assassinate: + { + if(newbon->Assassinate[1] < base2){ + newbon->Assassinate[0] = effect_value; + newbon->Assassinate[1] = base2; + } + break; + } + + case SE_AssassinateLevel: + { + if(newbon->AssassinateLevel < effect_value) + newbon->AssassinateLevel = effect_value; + break; + } + //Special custom cases for loading effects on to NPC from 'npc_spels_effects' table if (IsAISpellEffect) { @@ -4173,7 +4222,27 @@ void Mob::NegateSpellsBonuses(uint16 spell_id) aabonuses.Metabolism = effect_value; itembonuses.Metabolism = effect_value; break; - + + case SE_ImprovedReclaimEnergy: + spellbonuses.ImprovedReclaimEnergy = effect_value; + aabonuses.ImprovedReclaimEnergy = effect_value; + itembonuses.ImprovedReclaimEnergy = effect_value; + break; + + case SE_HeadShot: + spellbonuses.HeadShot[0] = effect_value; + aabonuses.HeadShot[0] = effect_value; + itembonuses.HeadShot[0] = effect_value; + spellbonuses.HeadShot[1] = effect_value; + aabonuses.HeadShot[1] = effect_value; + itembonuses.HeadShot[1] = effect_value; + break; + + case SE_HeadShotLevel: + spellbonuses.HSLevel = effect_value; + aabonuses.HSLevel = effect_value; + itembonuses.HSLevel = effect_value; + } } } diff --git a/zone/client_mods.cpp b/zone/client_mods.cpp index 7ffc1e797..3bf2217cb 100644 --- a/zone/client_mods.cpp +++ b/zone/client_mods.cpp @@ -1864,7 +1864,7 @@ uint16 Mob::GetInstrumentMod(uint16 spell_id) const break; } - effectmodcap += aabonuses.songModCap + spellbonuses.songModCap; + effectmodcap += aabonuses.songModCap + spellbonuses.songModCap + itembonuses.songModCap; if (effectmod < 10) effectmod = 10; diff --git a/zone/common.h b/zone/common.h index 421474eec..95f1df783 100644 --- a/zone/common.h +++ b/zone/common.h @@ -422,7 +422,12 @@ struct StatBonuses { int8 StunBashChance; // chance to stun with bash. int8 IncreaseChanceMemwipe; // increases chance to memory wipe int8 CriticalMend; // chance critical monk mend - int16 ImprovedReclaimEnergy; // Modifies amount of mana returned from reclaim energy + int16 ImprovedReclaimEnergy; // Modifies amount of mana returned from reclaim energy + int32 HeadShot[2]; // Headshot AA (Massive dmg vs humaniod w/ archery) 0= ? 1= Dmg + uint8 HSLevel; // Max Level Headshot will be effective at. + int32 Assassinate[2]; // Assassinate AA (Massive dmg vs humaniod w/ assassinate) 0= ? 1= Dmg + uint8 AssassinateLevel; // Max Level Assassinate will be effective at. + }; typedef struct diff --git a/zone/mob.h b/zone/mob.h index 61a2220d6..b5e21c932 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -141,7 +141,7 @@ public: virtual void TryCriticalHit(Mob *defender, uint16 skill, int32 &damage, ExtraAttackOptions *opts = nullptr); void TryPetCriticalHit(Mob *defender, uint16 skill, int32 &damage); virtual bool TryFinishingBlow(Mob *defender, SkillUseTypes skillinuse); - virtual bool TryHeadShot(Mob* defender, SkillUseTypes skillInUse); + uint32 TryHeadShot(Mob* defender, SkillUseTypes skillInUse); virtual void DoRiposte(Mob* defender); void ApplyMeleeDamageBonus(uint16 skill, int32 &damage); virtual void MeleeMitigation(Mob *attacker, int32 &damage, int32 minhit, ExtraAttackOptions *opts = nullptr); @@ -991,6 +991,7 @@ protected: void ExecWeaponProc(const ItemInst* weapon, uint16 spell_id, Mob *on); virtual float GetProcChances(float ProcBonus, uint16 weapon_speed = 30, uint16 hand = 13); virtual float GetDefensiveProcChances(float &ProcBonus, float &ProcChance, uint16 weapon_speed = 30, uint16 hand = 13); + virtual float GetSpecialProcChances(uint16 hand = 13); int GetWeaponDamage(Mob *against, const Item_Struct *weapon_item); int GetWeaponDamage(Mob *against, const ItemInst *weapon_item, uint32 *hate = nullptr); int GetKickDamage(); diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index f9f324b6d..55e919bc3 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -861,8 +861,12 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item } else { mlog(COMBAT__RANGED, "Ranged attack hit %s.", other->GetName()); - if(!TryHeadShot(other, SkillArchery)) - { + + bool HeadShot = false; + uint32 HeadShot_Dmg = TryHeadShot(other, SkillArchery); + if (HeadShot_Dmg) + HeadShot = true; + int32 TotalDmg = 0; int16 WDmg = 0; int16 ADmg = 0; @@ -882,6 +886,9 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item uint32 MaxDmg = (RuleR(Combat, ArcheryBaseDamageBonus)*(WDmg+ADmg)*GetDamageTable(SkillArchery)) / 100; int32 hate = ((WDmg+ADmg)); + if (HeadShot) + MaxDmg = HeadShot_Dmg; + uint16 bonusArcheryDamageModifier = aabonuses.ArcheryDamageModifier + itembonuses.ArcheryDamageModifier + spellbonuses.ArcheryDamageModifier; MaxDmg += MaxDmg*bonusArcheryDamageModifier / 100; @@ -938,7 +945,9 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item hate += (2*((GetLevel()-25)/3)); } - other->AvoidDamage(this, TotalDmg, false); + if (!HeadShot) + other->AvoidDamage(this, TotalDmg, false); + other->MeleeMitigation(this, TotalDmg, minDmg); if(TotalDmg > 0) { @@ -957,8 +966,11 @@ void Mob::DoArcheryAttackDmg(Mob* other, const ItemInst* RangeWeapon, const Item else TotalDmg = -5; + if (HeadShot) + entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); + other->Damage(this, TotalDmg, SPELL_UNKNOWN, SkillArchery); - } + } //try proc on hits and misses @@ -2074,30 +2086,73 @@ void Mob::InstillDoubt(Mob *who) { } } -bool Mob::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { - bool Result = false; +uint32 Mob::TryHeadShot(Mob* defender, SkillUseTypes skillInUse) { - if(defender && skillInUse == SkillArchery) { - if(GetAA(aaHeadshot) && defender->GetBodyType() == BT_Humanoid) { - if((GetLevelCon(GetLevel(), defender->GetLevel()) == CON_LIGHTBLUE || GetLevelCon(GetLevel(), defender->GetLevel()) == CON_GREEN) && defender->GetLevel() <= 60 && !defender->IsClient()) { - // WildcardX: These chance formula's below are arbitrary. If someone has a better formula that is more - // consistent with live, feel free to update these. - int AttackerChance = 20 + ((GetLevel() - 51) / 2) + (itembonuses.HeroicDEX / 10); - int DefenderChance = MakeRandomInt(0, 100); - if(AttackerChance > DefenderChance) { - mlog(COMBAT__ATTACKS, "Landed a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance); - entity_list.MessageClose_StringID(this, false, 200, MT_CritMelee, FATAL_BOW_SHOT, GetName()); - defender->Damage(this, 32000, SPELL_UNKNOWN, skillInUse); - Result = true; - } - else { - mlog(COMBAT__ATTACKS, "FAILED a headshot: Attacker chance was %f and Defender chance was %f.", AttackerChance, DefenderChance); - } + //Only works on YOUR target. + if(defender && (skillInUse == SkillArchery) && (GetTarget() == defender)) { + + int32 HeadShot_Dmg = aabonuses.HeadShot[1] + spellbonuses.HeadShot[1] + itembonuses.HeadShot[1]; + + uint8 HeadShot_Level = 0; //Get Highest Headshot Level + HeadShot_Level = aabonuses.HSLevel; + if (HeadShot_Level < spellbonuses.HSLevel) + HeadShot_Level = spellbonuses.HSLevel; + else if (HeadShot_Level < itembonuses.HSLevel) + HeadShot_Level = itembonuses.HSLevel; + + if(HeadShot_Dmg && defender->GetBodyType() == BT_Humanoid) { + if(HeadShot_Level && (defender->GetLevel() <= HeadShot_Level) && !defender->IsClient()){ + + float ProcChance = GetSpecialProcChances(11); + if(ProcChance > MakeRandomFloat(0,1)) + return HeadShot_Dmg; + } } } - return Result; + return 0; +} + +float Mob::GetSpecialProcChances(uint16 hand) +{ + int mydex = GetDEX(); + + if (mydex > 255) + mydex = 255; + + uint16 weapon_speed; + float ProcChance = 0.0f; + float ProcBonus = 0.0f; + + switch (hand) { + case 13: + weapon_speed = attack_timer.GetDuration(); + break; + case 14: + weapon_speed = attack_dw_timer.GetDuration(); + break; + case 11: + weapon_speed = ranged_timer.GetDuration(); + break; + } + + if (weapon_speed < RuleI(Combat, MinHastedDelay)) + weapon_speed = RuleI(Combat, MinHastedDelay); + + if (RuleB(Combat, AdjustSpecialProcPerMinute)) { + ProcChance = (static_cast(weapon_speed) * + RuleR(Combat, AvgSpecialProcsPerMinute) / 60000.0f); + ProcBonus += static_cast(mydex/35) + static_cast(itembonuses.HeroicDEX / 25); + ProcChance += ProcChance * ProcBonus / 100.0f; + } else { + /*PRE 2014 CHANGE Dev Quote - "Elidroth SOE:Proc chance is a function of your base hardcapped Dexterity / 35 + Heroic Dexterity / 25.” + Kayen: Most reports suggest a ~ 6% chance to Headshot which consistent with above.*/ + + ProcChance = (static_cast(mydex/35) + static_cast(itembonuses.HeroicDEX / 25))/100.0f; + } + + return ProcChance; } void Mob::DoMeleeSkillAttackDmg(Mob* other, uint16 weapon_damage, SkillUseTypes skillinuse, int16 chance_mod, int16 focus, bool CanRiposte) diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 8eab0b329..51979eddc 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -2922,6 +2922,8 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) case SE_CombatStability: case SE_AddSingingMod: case SE_SongModCap: + case SE_HeadShot: + case SE_HeadShotLevel: case SE_PetAvoidance: case SE_GiveDoubleRiposte: case SE_Ambidexterity: