From 2cdd50b9e9493282eedbe5b60e68cfe80556d62e Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Thu, 3 Apr 2014 04:25:45 -0400 Subject: [PATCH 01/14] -Implemented live like spell projectiles (ie mage bolts). -See function in spells.cpp for more info on bolt behavior. -This works reasonably well, but still room for improvements. -Rules are for setting what item id is used for the projectile since live uses an item id from SOF+ I added alternate item graphic for titanium clients. -Note: Max number of projectiles (set at 10) is a made up value in most situations it would be nearly impossible to have more than 3 bolts in the air at the same time. This values gives enough wiggle room that no server should have an issue though. -Small fix to SE_CompleteHeal --- changelog.txt | 6 ++ common/ruletypes.h | 3 + .../2014_04_03_SpellProjectileRules.sql | 3 + zone/client_process.cpp | 3 + zone/common.h | 1 + zone/mob.cpp | 51 ++++++++++++++ zone/mob.h | 7 ++ zone/npc.cpp | 3 + zone/spell_effects.cpp | 8 ++- zone/spells.cpp | 66 ++++++++++++++++++- 10 files changed, 149 insertions(+), 2 deletions(-) create mode 100644 utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql diff --git a/changelog.txt b/changelog.txt index 6931fac1d..62e33d3e7 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/03/2014 == +Kayen: Implemented live like spell projectiles (ie. Mage Bolts). + +Optional SQL: utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql +Note: The rules in this SQL are for setting the item id for the graphic used by the projectile on different clients. + == 04/01/2014 == demonstar55: Implemented ability for a merchant to open and close shop. Lua quest functions: e.self:MerchantOpenShop() and e.self:MerchantCloseShop() diff --git a/common/ruletypes.h b/common/ruletypes.h index 32775f316..39886c9f5 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -304,6 +304,9 @@ RULE_BOOL ( Spells, BuffLevelRestrictions, true) //Buffs will not land on low le RULE_INT ( Spells, RootBreakCheckChance, 70) //Determines chance for a root break check to occur each buff tick. RULE_INT ( Spells, FearBreakCheckChance, 70) //Determines chance for a fear break check to occur each buff tick. RULE_INT ( Spells, SuccorFailChance, 2) //Determines chance for a succor spell not to teleport an invidual player +RULE_INT ( Spells, FRProjectileItem_Titanium, 1113) // Item id for Titanium clients for Fire 'spell projectile'. +RULE_INT ( Spells, FRProjectileItem_SOF, 80684) // Item id for SOF clients for Fire 'spell projectile'. +RULE_INT ( Spells, FRProjectileItem_NPC, 80684) // Item id for NPC Fire 'spell projectile'. RULE_CATEGORY_END() RULE_CATEGORY( Combat ) diff --git a/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql b/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql new file mode 100644 index 000000000..70d8594b6 --- /dev/null +++ b/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql @@ -0,0 +1,3 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_Titanium', '1113', 'Item id for Titanium clients for Fire spell projectile.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'FRProjectileItem_SOF', '80684', 'Item id for Titanium clients for Fire spell projectile.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'FRProjectileItem_NPC', '80684', 'Item id for Titanium clients for Fire spell projectile.'); diff --git a/zone/client_process.cpp b/zone/client_process.cpp index f1e25226a..c28affd28 100644 --- a/zone/client_process.cpp +++ b/zone/client_process.cpp @@ -570,6 +570,9 @@ bool Client::Process() { viral_timer_counter = 0; } + if(projectile_timer.Check()) + SpellProjectileEffect(); + if(spellbonuses.GravityEffect == 1) { if(gravity_timer.Check()) DoGravityEffect(); diff --git a/zone/common.h b/zone/common.h index 11b9f8919..ee87bd164 100644 --- a/zone/common.h +++ b/zone/common.h @@ -5,6 +5,7 @@ #include "../common/spdat.h" #define HIGHEST_RESIST 9 //Max resist type value +#define MAX_SPELL_PROJECTILE 10 //Max amount of spell projectiles that can be active by a single mob. /* solar: macros for IsAttackAllowed, IsBeneficialAllowed */ #define _CLIENT(x) (x && x->IsClient() && !x->CastToClient()->IsBecomeNPC()) diff --git a/zone/mob.cpp b/zone/mob.cpp index 6267f5bcc..d3199d0e6 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -276,6 +276,14 @@ Mob::Mob(const char* in_name, casting_spell_inventory_slot = 0; target = 0; + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_spell_id[i] = 0; } + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_target_id[i] = 0; } + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_increment[i] = 0; } + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_x[i] = 0; } + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_y[i] = 0; } + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { projectile_z[i] = 0; } + projectile_timer.Disable(); + memset(&itembonuses, 0, sizeof(StatBonuses)); memset(&spellbonuses, 0, sizeof(StatBonuses)); memset(&aabonuses, 0, sizeof(StatBonuses)); @@ -4358,6 +4366,49 @@ bool Mob::TryReflectSpell(uint32 spell_id) return false; } +void Mob::SpellProjectileEffect() +{ + bool time_disable = false; + + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { + + if (projectile_increment[i] == 0){ + continue; + } + + Mob* target = entity_list.GetMobID(projectile_target_id[i]); + + float dist = 0; + + if (target) + dist = target->CalculateDistance(projectile_x[i], projectile_y[i], projectile_z[i]); + + int increment_end = 0; + increment_end = (dist / 10) - 1; //This pretty accurately determines end time for speed for 1.5 and timer of 250 ms + + if (increment_end <= projectile_increment[i]){ + + if (target && IsValidSpell(projectile_spell_id[i])) + SpellOnTarget(projectile_spell_id[i], target, false, true, spells[projectile_spell_id[i]].ResistDiff, true); + + projectile_spell_id[i] = 0; + projectile_target_id[i] = 0; + projectile_x[i] = 0, projectile_y[i] = 0, projectile_z[i] = 0; + projectile_increment[i] = 0; + time_disable = true; + } + + else { + projectile_increment[i]++; + time_disable = false; + } + } + + if (time_disable) + projectile_timer.Disable(); +} + + void Mob::DoGravityEffect() { Mob *caster = nullptr; diff --git a/zone/mob.h b/zone/mob.h index c8453d301..03e160f12 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -252,6 +252,7 @@ public: void SendBuffsToClient(Client *c); inline Buffs_Struct* GetBuffs() { return buffs; } void DoGravityEffect(); + void SpellProjectileEffect(); void DamageShield(Mob* other, bool spell_ds = false); int32 RuneAbsorb(int32 damage, uint16 type); bool FindBuff(uint16 spellid); @@ -1040,6 +1041,12 @@ protected: uint8 bardsong_slot; uint32 bardsong_target_id; + Timer projectile_timer; + uint32 projectile_spell_id[MAX_SPELL_PROJECTILE]; + uint16 projectile_target_id[MAX_SPELL_PROJECTILE]; + uint8 projectile_increment[MAX_SPELL_PROJECTILE]; + float projectile_x[MAX_SPELL_PROJECTILE], projectile_y[MAX_SPELL_PROJECTILE], projectile_z[MAX_SPELL_PROJECTILE]; + float rewind_x; float rewind_y; float rewind_z; diff --git a/zone/npc.cpp b/zone/npc.cpp index 861a4359e..7a64b3cac 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -659,6 +659,9 @@ bool NPC::Process() viral_timer_counter = 0; } + if(projectile_timer.Check()) + SpellProjectileEffect(); + if(spellbonuses.GravityEffect == 1) { if(gravity_timer.Check()) DoGravityEffect(); diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 1292fab89..1901fcc09 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -326,7 +326,13 @@ bool Mob::SpellEffect(Mob* caster, uint16 spell_id, float partial) if(inuse) break; - Heal(); + int32 val = 0; + val = 7500*effect_value; + val = caster->GetActSpellHealing(spell_id, val, this); + + if (val > 0) + HealDamage(val, caster); + break; } diff --git a/zone/spells.cpp b/zone/spells.cpp index c8903c537..730971a97 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1830,7 +1830,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 } // check line of sight to target if it's a detrimental spell - if(spell_target && IsDetrimentalSpell(spell_id) && !CheckLosFN(spell_target) && !IsHarmonySpell(spell_id)) + if(spell_target && IsDetrimentalSpell(spell_id) && !CheckLosFN(spell_target) && !IsHarmonySpell(spell_id) && spells[spell_id].targettype != ST_TargetOptional) { mlog(SPELLS__CASTING, "Spell %d: cannot see target %s", spell_target->GetName()); Message_StringID(13,CANT_SEE_TARGET); @@ -1868,6 +1868,70 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 } } + /*For mage 'Bolt' line and other various spells. + -This is mostly accurate for how the modern clients handle this effect. + -It was changed at some point to use an actual projectile as done here (opposed to a particle effect in classic) + -The projectile graphic appears to be that of 'Ball of Sunlight' ID 80648 and will be visible to anyone in SoF+ + -There is no LOS check to prevent a bolt from being cast. If you don't have LOS your bolt simply goes into whatever barrier + and you lose your mana. If there is LOS the bolt will lock onto your target and the damage is applied when it hits the target. + -If your target moves the bolt moves with it in any direction or angle (consistent with other projectiles). + -The way this is written once a bolt is cast a timer checks the distance from the initial cast to the target repeatedly + and calculates at what predicted time the bolt should hit that target in client_process (therefore accounting for any target movement). + When bolt hits its predicted point the damage is then done to target. + Note: Projectile speed of 1 takes 3 seconds to go 100 distance units. Calculations are based on this constant. + Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units. + Pending Implementation: What this code can not do is prevent damage if the bolt hits a barrier after passing the initial LOS check + because the target has moved while the bolt is in motion. (it is rare to actual get this to occur on live in normal game play) + */ + if (spell_target && spells[spell_id].targettype == ST_TargetOptional){ + + uint8 anim = spells[spell_id].CastingAnim; + int bolt_id = -1; + + //Make sure there is an avialable bolt to be cast. + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { + if (projectile_spell_id[i] == 0){ + bolt_id = i; + break; + } + } + + if (bolt_id < 0) + return false; + + if (CheckLosFN(spell_target)) { + + projectile_spell_id[bolt_id] = spell_id; + projectile_target_id[bolt_id] = spell_target->GetID(); + projectile_x[bolt_id] = GetX(), projectile_y[bolt_id] = GetY(), projectile_z[bolt_id] = GetZ(); + projectile_increment[bolt_id] = 1; + projectile_timer.Start(250); + } + + //Only use fire graphic for fire spells. + if (spells[spell_id].resisttype == RESIST_FIRE) { + + if (IsClient()){ + if (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic. + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, 1.5); + else + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, 1.5); + } + else + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, 1.5); + + if (spells[spell_id].CastingAnim == 64) + anim = 44; //Corrects for animation error. + } + + //Pending other types of projectile graphics. (They will function but with a default arrow graphic for now) + else + ProjectileAnimation(spell_target,0, 1, 1.5); + + DoAnim(anim, 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); + return true; + } + // // Switch #2 - execute the spell // From 4b14ec53f1f2f242e28907713103b04cbf3300f9 Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Fri, 4 Apr 2014 01:59:55 -0400 Subject: [PATCH 02/14] Implemented Physical Resists consistent with live. SQL to add new column 'PhR' to npc_types Values to populate table based on extensive parsing. Fixes for spell projectile code. --- changelog.txt | 6 + common/spdat.h | 2 +- .../required/2014_04_04_PhysicalResist.txt | 7 + zone/StringIDs.h | 1 + zone/mob.h | 6 +- zone/npc.cpp | 3 + zone/spell_effects.cpp | 70 ++++++ zone/spells.cpp | 230 +++++++++--------- zone/zonedb.cpp | 2 + zone/zonedump.h | 1 + 10 files changed, 212 insertions(+), 116 deletions(-) create mode 100644 utils/sql/git/required/2014_04_04_PhysicalResist.txt diff --git a/changelog.txt b/changelog.txt index 62e33d3e7..d9a3c2106 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/04/2014 == +Kayen: Implemented 'Physical Resists' (Resist Type 9) to be consistent with live based on extensive parsing. + SQL will add new field to npc_types 'PhR' and fill in database with values consistent with observations. + +Optional SQL: utils/sql/git/optional/2014_04_04_PhysicalResists.sql + == 04/03/2014 == Kayen: Implemented live like spell projectiles (ie. Mage Bolts). diff --git a/common/spdat.h b/common/spdat.h index 99de59c83..fcbb02aaf 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -258,7 +258,7 @@ typedef enum { #define SE_Familiar 108 // implemented #define SE_SummonItemIntoBag 109 // implemented - summons stuff into container //#define SE_IncreaseArchery 110 // not used -#define SE_ResistAll 111 // implemented +#define SE_ResistAll 111 // implemented - Note: Physical Resists are not modified by this effect. #define SE_CastingLevel 112 // implemented #define SE_SummonHorse 113 // implemented #define SE_ChangeAggro 114 // implemented - Hate modifing buffs(ie horrifying visage) diff --git a/utils/sql/git/required/2014_04_04_PhysicalResist.txt b/utils/sql/git/required/2014_04_04_PhysicalResist.txt new file mode 100644 index 000000000..f748f0b9f --- /dev/null +++ b/utils/sql/git/required/2014_04_04_PhysicalResist.txt @@ -0,0 +1,7 @@ +ALTER TABLE `npc_types` ADD `PhR` smallint( 5 ) UNSIGNED NOT NULL DEFAULT '0' AFTER `Corrup`; + +-- Approximate baseline live npc values based on extensive parsing. +UPDATE npc_types SET PhR = 10 WHERE PhR = 0 AND level <= 50; +UPDATE npc_types SET PhR = (10 + (level - 50)) WHERE PhR = 0 AND (level > 50 AND level <= 60); +UPDATE npc_types SET PhR = (20 + ((level - 60)*4)) WHERE PhR = 0 AND level > 60; + diff --git a/zone/StringIDs.h b/zone/StringIDs.h index d34bb4148..95227ae07 100644 --- a/zone/StringIDs.h +++ b/zone/StringIDs.h @@ -262,6 +262,7 @@ #define DISCIPLINE_REUSE_MSG 5807 //You can use the ability %1 again in %2 hour(s) %3 minute(s) %4 seconds. #define DISCIPLINE_REUSE_MSG2 5808 //You can use the ability %1 again in %2 minute(s) %3 seconds. #define FAILED_TAUNT 5811 //You have failed to taunt your target. +#define PHYSICAL_RESIST_FAIL 5817 //Your target avoided your %1 ability. #define AA_NO_TARGET 5825 //You must first select a target for this ability! #define FORAGE_MASTERY 6012 //Your forage mastery has enabled you to find something else! #define GUILD_BANK_CANNOT_DEPOSIT 6097 // Cannot deposit this item. Containers must be empty, and only one of each LORE and no NO TRADE or TEMPORARY items may be deposited. diff --git a/zone/mob.h b/zone/mob.h index 03e160f12..4ee5da5a8 100644 --- a/zone/mob.h +++ b/zone/mob.h @@ -192,6 +192,7 @@ public: virtual int32 GetActSpellCasttime(uint16 spell_id, int32 casttime); float ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use_resist_override = false, int resist_override = 0, bool CharismaCheck = false, bool CharmTick = false, bool IsRoot = false); + int ResistPhysical(int level_diff, uint8 caster_level); uint16 GetSpecializeSkillValue(uint16 spell_id) const; void SendSpellBarDisable(); void SendSpellBarEnable(uint16 spellid); @@ -222,6 +223,8 @@ public: uint16 CastingSpellID() const { return casting_spell_id; } bool DoCastingChecks(); bool TryDispel(uint8 caster_level, uint8 buff_level, int level_modifier); + void SpellProjectileEffect(); + bool TrySpellProjectile(Mob* spell_target, uint16 spell_id); //Buff void BuffProcess(); @@ -252,7 +255,6 @@ public: void SendBuffsToClient(Client *c); inline Buffs_Struct* GetBuffs() { return buffs; } void DoGravityEffect(); - void SpellProjectileEffect(); void DamageShield(Mob* other, bool spell_ds = false); int32 RuneAbsorb(int32 damage, uint16 type); bool FindBuff(uint16 spellid); @@ -338,6 +340,7 @@ public: inline virtual int16 GetPR() const { return PR + itembonuses.PR + spellbonuses.PR; } inline virtual int16 GetCR() const { return CR + itembonuses.CR + spellbonuses.CR; } inline virtual int16 GetCorrup() const { return Corrup + itembonuses.Corrup + spellbonuses.Corrup; } + inline virtual int16 GetPhR() const { return PhR; } inline StatBonuses GetItemBonuses() const { return itembonuses; } inline StatBonuses GetSpellBonuses() const { return spellbonuses; } inline StatBonuses GetAABonuses() const { return aabonuses; } @@ -916,6 +919,7 @@ protected: int16 DR; int16 PR; int16 Corrup; + int16 PhR; bool moving; int targeted; bool findable; diff --git a/zone/npc.cpp b/zone/npc.cpp index 7a64b3cac..f83d1d31b 100644 --- a/zone/npc.cpp +++ b/zone/npc.cpp @@ -158,6 +158,7 @@ NPC::NPC(const NPCType* d, Spawn2* in_respawn, float x, float y, float z, float FR = d->FR; PR = d->PR; Corrup = d->Corrup; + PhR = d->PhR; STR = d->STR; STA = d->STA; @@ -2062,6 +2063,8 @@ void NPC::CalcNPCResists() { PR = (GetLevel() * 11)/10; if (!Corrup) Corrup = 15; + if (!PhR) + PhR = 10; return; } diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index 1901fcc09..ce299ed36 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -6020,3 +6020,73 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama return false; } +bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id){ + + /*For mage 'Bolt' line and other various spells. + -This is mostly accurate for how the modern clients handle this effect. + -It was changed at some point to use an actual projectile as done here (opposed to a particle effect in classic) + -The projectile graphic appears to be that of 'Ball of Sunlight' ID 80648 and will be visible to anyone in SoF+ + -There is no LOS check to prevent a bolt from being cast. If you don't have LOS your bolt simply goes into whatever barrier + and you lose your mana. If there is LOS the bolt will lock onto your target and the damage is applied when it hits the target. + -If your target moves the bolt moves with it in any direction or angle (consistent with other projectiles). + -The way this is written once a bolt is cast a timer checks the distance from the initial cast to the target repeatedly + and calculates at what predicted time the bolt should hit that target in client_process (therefore accounting for any target movement). + When bolt hits its predicted point the damage is then done to target. + Note: Projectile speed of 1 takes 3 seconds to go 100 distance units. Calculations are based on this constant. + Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units. + Pending Implementation: What this code can not do is prevent damage if the bolt hits a barrier after passing the initial LOS check + because the target has moved while the bolt is in motion. (it is rare to actual get this to occur on live in normal game play) + */ + + if (!spell_target) + return false; + + uint8 anim = spells[spell_id].CastingAnim; + int bolt_id = -1; + + //Make sure there is an avialable bolt to be cast. + for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { + if (projectile_spell_id[i] == 0){ + bolt_id = i; + break; + } + } + + if (bolt_id < 0) + return false; + + if (CheckLosFN(spell_target)) { + + projectile_spell_id[bolt_id] = spell_id; + projectile_target_id[bolt_id] = spell_target->GetID(); + projectile_x[bolt_id] = GetX(), projectile_y[bolt_id] = GetY(), projectile_z[bolt_id] = GetZ(); + projectile_increment[bolt_id] = 1; + projectile_timer.Start(250); + } + + //Only use fire graphic for fire spells. + if (spells[spell_id].resisttype == RESIST_FIRE) { + + if (IsClient()){ + if (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic. + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, 1.5); + else + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, 1.5); + } + + else + ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, 1.5); + + if (spells[spell_id].CastingAnim == 64) + anim = 44; //Corrects for animation error. + } + + //Pending other types of projectile graphics. (They will function but with a default arrow graphic for now) + else + ProjectileAnimation(spell_target,0, 1, 1.5); + + DoAnim(anim, 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); //Override the default projectile animation. + return true; +} + + diff --git a/zone/spells.cpp b/zone/spells.cpp index 730971a97..e00ece3c0 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -1868,70 +1868,6 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 } } - /*For mage 'Bolt' line and other various spells. - -This is mostly accurate for how the modern clients handle this effect. - -It was changed at some point to use an actual projectile as done here (opposed to a particle effect in classic) - -The projectile graphic appears to be that of 'Ball of Sunlight' ID 80648 and will be visible to anyone in SoF+ - -There is no LOS check to prevent a bolt from being cast. If you don't have LOS your bolt simply goes into whatever barrier - and you lose your mana. If there is LOS the bolt will lock onto your target and the damage is applied when it hits the target. - -If your target moves the bolt moves with it in any direction or angle (consistent with other projectiles). - -The way this is written once a bolt is cast a timer checks the distance from the initial cast to the target repeatedly - and calculates at what predicted time the bolt should hit that target in client_process (therefore accounting for any target movement). - When bolt hits its predicted point the damage is then done to target. - Note: Projectile speed of 1 takes 3 seconds to go 100 distance units. Calculations are based on this constant. - Live Bolt speed: Projectile speed of X takes 5 seconds to go 300 distance units. - Pending Implementation: What this code can not do is prevent damage if the bolt hits a barrier after passing the initial LOS check - because the target has moved while the bolt is in motion. (it is rare to actual get this to occur on live in normal game play) - */ - if (spell_target && spells[spell_id].targettype == ST_TargetOptional){ - - uint8 anim = spells[spell_id].CastingAnim; - int bolt_id = -1; - - //Make sure there is an avialable bolt to be cast. - for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { - if (projectile_spell_id[i] == 0){ - bolt_id = i; - break; - } - } - - if (bolt_id < 0) - return false; - - if (CheckLosFN(spell_target)) { - - projectile_spell_id[bolt_id] = spell_id; - projectile_target_id[bolt_id] = spell_target->GetID(); - projectile_x[bolt_id] = GetX(), projectile_y[bolt_id] = GetY(), projectile_z[bolt_id] = GetZ(); - projectile_increment[bolt_id] = 1; - projectile_timer.Start(250); - } - - //Only use fire graphic for fire spells. - if (spells[spell_id].resisttype == RESIST_FIRE) { - - if (IsClient()){ - if (CastToClient()->GetClientVersionBit() <= 4) //Titanium needs alternate graphic. - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, 1.5); - else - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, 1.5); - } - else - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, 1.5); - - if (spells[spell_id].CastingAnim == 64) - anim = 44; //Corrects for animation error. - } - - //Pending other types of projectile graphics. (They will function but with a default arrow graphic for now) - else - ProjectileAnimation(spell_target,0, 1, 1.5); - - DoAnim(anim, 0, true, IsClient() ? FilterPCSpells : FilterNPCSpells); - return true; - } - // // Switch #2 - execute the spell // @@ -1959,7 +1895,12 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 if (isproc) { SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, true); } else { - if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false)) { + if (spells[spell_id].targettype == ST_TargetOptional){ + if (!TrySpellProjectile(spell_target, spell_id)) + return false; + } + + else if(!SpellOnTarget(spell_id, spell_target, false, true, resist_adjust, false)) { if(IsBuffSpell(spell_id) && IsBeneficialSpell(spell_id)) { // Prevent mana usage/timers being set for beneficial buffs if(casting_spell_type == 1) @@ -1968,6 +1909,7 @@ bool Mob::SpellFinished(uint16 spell_id, Mob *spell_target, uint16 slot, uint16 } } } + if(IsPlayerIllusionSpell(spell_id) && IsClient() && CastToClient()->CheckAAEffect(aaEffectProjectIllusion)){ @@ -3504,8 +3446,15 @@ bool Mob::SpellOnTarget(uint16 spell_id, Mob* spelltar, bool reflect, bool use_r if(spell_effectiveness == 0 || !IsPartialCapableSpell(spell_id) ) { mlog(SPELLS__RESISTS, "Spell %d was completely resisted by %s", spell_id, spelltar->GetName()); - Message_StringID(MT_SpellFailure, TARGET_RESISTED, spells[spell_id].name); - spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name); + + if (spells[spell_id].resisttype == RESIST_PHYSICAL){ + Message_StringID(MT_SpellFailure, PHYSICAL_RESIST_FAIL,spells[spell_id].name); + spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name); + } + else { + Message_StringID(MT_SpellFailure, TARGET_RESISTED, spells[spell_id].name); + spelltar->Message_StringID(MT_SpellFailure, YOU_RESIST, spells[spell_id].name); + } if(spelltar->IsAIControlled()){ int32 aggro = CheckAggroAmount(spell_id); @@ -4254,67 +4203,83 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use } break; case RESIST_PHYSICAL: + { + if (IsNPC()) + target_resist = GetPhR(); + else + target_resist = 0; + } default: - //This is guessed but the others are right - target_resist = (GetSTA() / 4); + + target_resist = 0; } //Setup our base resist chance. int resist_chance = 0; + int level_mod = 0; //Adjust our resist chance based on level modifiers int temp_level_diff = GetLevel() - caster->GetLevel(); - if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff)) - { - int a = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel(); - if(a > 0) + + //Physical Resists are calclated using their own formula derived from extensive parsing. + if (resist_type == RESIST_PHYSICAL) { + level_mod = ResistPhysical(temp_level_diff, caster->GetLevel()); + } + + else { + + if(IsNPC() && GetLevel() >= RuleI(Casting,ResistFalloff)) { - temp_level_diff = a; - } - else - { - temp_level_diff = 0; - } - } - - if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15) - { - temp_level_diff = 15; - } - - if(IsNPC() && temp_level_diff < -9) - { - temp_level_diff = -9; - } - - int level_mod = temp_level_diff * temp_level_diff / 2; - if(temp_level_diff < 0) - { - level_mod = -level_mod; - } - - if(IsNPC() && (caster->GetLevel() - GetLevel()) < -20) - { - level_mod = 1000; - } - - //Even more level stuff this time dealing with damage spells - if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17) - { - int level_diff; - if(GetLevel() >= RuleI(Casting,ResistFalloff)) - { - level_diff = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel(); - if(level_diff < 0) + int a = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel(); + if(a > 0) { - level_diff = 0; + temp_level_diff = a; + } + else + { + temp_level_diff = 0; } } - else + + if(IsClient() && GetLevel() >= 21 && temp_level_diff > 15) { - level_diff = GetLevel() - caster->GetLevel(); + temp_level_diff = 15; + } + + if(IsNPC() && temp_level_diff < -9) + { + temp_level_diff = -9; + } + + level_mod = temp_level_diff * temp_level_diff / 2; + if(temp_level_diff < 0) + { + level_mod = -level_mod; + } + + if(IsNPC() && (caster->GetLevel() - GetLevel()) < -20) + { + level_mod = 1000; + } + + //Even more level stuff this time dealing with damage spells + if(IsNPC() && IsDamageSpell(spell_id) && GetLevel() >= 17) + { + int level_diff; + if(GetLevel() >= RuleI(Casting,ResistFalloff)) + { + level_diff = (RuleI(Casting,ResistFalloff)-1) - caster->GetLevel(); + if(level_diff < 0) + { + level_diff = 0; + } + } + else + { + level_diff = GetLevel() - caster->GetLevel(); + } + level_mod += (2 * level_diff); } - level_mod += (2 * level_diff); } if (CharismaCheck) @@ -4461,6 +4426,43 @@ float Mob::ResistSpell(uint8 resist_type, uint16 spell_id, Mob *caster, bool use } } +int Mob::ResistPhysical(int level_diff, uint8 caster_level) +{ + /* Physical resists use the standard level mod calculation in + conjunction with a resist fall off formula that greatly prevents you + from landing abilities on mobs that are higher level than you. + After level 12, every 4 levels gained the max level you can hit + your target without a sharp resist penalty is raised by 1. + Extensive parsing confirms this, along with baseline phyiscal resist rates used. + */ + + + if (level_diff == 0) + return level_diff; + + int level_mod = 0; + + if (level_diff > 0) { + + int ResistFallOff = 0; + + if (caster_level <= 12) + ResistFallOff = 3; + else + ResistFallOff = caster_level/4; + + if (level_diff > ResistFallOff || level_diff >= 15) + level_mod = ((level_diff * 10) + level_diff)*2; + else + level_mod = level_diff * level_diff / 2; + } + + else + level_mod = -(level_diff * level_diff / 2); + + return level_mod; +} + int16 Mob::CalcResistChanceBonus() { int resistchance = spellbonuses.ResistSpellChance + itembonuses.ResistSpellChance; diff --git a/zone/zonedb.cpp b/zone/zonedb.cpp index 6c3b8ce6e..568780e7d 100644 --- a/zone/zonedb.cpp +++ b/zone/zonedb.cpp @@ -1047,6 +1047,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { "npc_types.FR," "npc_types.PR," "npc_types.Corrup," + "npc_types.PhR," "npc_types.mindmg," "npc_types.maxdmg," "npc_types.attack_count," @@ -1143,6 +1144,7 @@ const NPCType* ZoneDatabase::GetNPCType (uint32 id) { tmpNPCType->FR = atoi(row[r++]); tmpNPCType->PR = atoi(row[r++]); tmpNPCType->Corrup = atoi(row[r++]); + tmpNPCType->PhR = atoi(row[r++]); tmpNPCType->min_dmg = atoi(row[r++]); tmpNPCType->max_dmg = atoi(row[r++]); tmpNPCType->attack_count = atoi(row[r++]); diff --git a/zone/zonedump.h b/zone/zonedump.h index 726041d5b..2c036be61 100644 --- a/zone/zonedump.h +++ b/zone/zonedump.h @@ -75,6 +75,7 @@ struct NPCType int16 PR; int16 DR; int16 Corrup; + int16 PhR; uint8 haircolor; uint8 beardcolor; uint8 eyecolor1; // the eyecolors always seem to be the same, maybe left and right eye? From d1ecb3265298bcede93719fcdb0117ffe9b31fcd Mon Sep 17 00:00:00 2001 From: KimLS Date: Fri, 4 Apr 2014 12:27:18 -0700 Subject: [PATCH 03/14] Fix for crash in EntityList::MobInZone(Mob *who) when a dangling pointer is passed to the function. Which used to work without crashing but was changed at some point which can be triggered by quests in some situations. --- zone/entity.cpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/zone/entity.cpp b/zone/entity.cpp index f5533cb84..ed5ee33f0 100644 --- a/zone/entity.cpp +++ b/zone/entity.cpp @@ -3415,9 +3415,14 @@ void EntityList::ReloadAllClientsTaskState(int TaskID) bool EntityList::IsMobInZone(Mob *who) { - auto it = mob_list.find(who->GetID()); - if (it != mob_list.end()) - return who == it->second; + //We don't use mob_list.find(who) because this code needs to be able to handle dangling pointers for the quest code. + auto it = mob_list.begin(); + while(it != mob_list.end()) { + if(it->second == who) { + return true; + } + ++it; + } return false; } From 1d6e9473873663d1edf0d687ab20fcf46f60288d Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Fri, 4 Apr 2014 21:32:56 -0400 Subject: [PATCH 04/14] Change log correction. --- changelog.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/changelog.txt b/changelog.txt index d9a3c2106..7b4a8c72a 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4,7 +4,7 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) Kayen: Implemented 'Physical Resists' (Resist Type 9) to be consistent with live based on extensive parsing. SQL will add new field to npc_types 'PhR' and fill in database with values consistent with observations. -Optional SQL: utils/sql/git/optional/2014_04_04_PhysicalResists.sql +Required SQL: utils/sql/git/optional/2014_04_04_PhysicalResists.sql == 04/03/2014 == Kayen: Implemented live like spell projectiles (ie. Mage Bolts). From 2c69dd7c9336fe026d48d24b12e9eaf18a18888a Mon Sep 17 00:00:00 2001 From: KayenEQ Date: Fri, 4 Apr 2014 22:03:32 -0400 Subject: [PATCH 05/14] Implemented proper functionality of SE_Screech If you have a buff with SE_Screech with value of 1 it will block any other buff with SE_Screen that has a value of -1, giving you an immunity message. Example: 1383 Screech and 2785 Screech Immunity --- common/spdat.h | 2 +- zone/StringIDs.h | 1 + zone/bonuses.cpp | 6 ++++++ zone/common.h | 1 + zone/spells.cpp | 10 +++++++++- 5 files changed, 18 insertions(+), 2 deletions(-) diff --git a/common/spdat.h b/common/spdat.h index fcbb02aaf..c7452426d 100644 --- a/common/spdat.h +++ b/common/spdat.h @@ -270,7 +270,7 @@ typedef enum { #define SE_HealRate 120 // implemented - reduces healing by a % #define SE_ReverseDS 121 // implemented //#define SE_ReduceSkill 122 // not used -#define SE_Screech 123 // implemented? Spell Blocker(can only have one buff with this effect at one time) +#define SE_Screech 123 // implemented Spell Blocker(If have buff with value +1 will block any effect with -1) #define SE_ImprovedDamage 124 // implemented #define SE_ImprovedHeal 125 // implemented #define SE_SpellResistReduction 126 // implemented diff --git a/zone/StringIDs.h b/zone/StringIDs.h index 95227ae07..05bbffdec 100644 --- a/zone/StringIDs.h +++ b/zone/StringIDs.h @@ -334,6 +334,7 @@ #define ALREADY_CASTING 12442 //You are already casting a spell! #define SENSE_CORPSE_NOT_NAME 12446 //You don't sense any corpses of that name. #define SENSE_CORPSE_NONE 12447 //You don't sense any corpses. +#define SCREECH_BUFF_BLOCK 12448 //Your immunity buff protected you from the spell %1! #define NOT_HOLDING_ITEM 12452 //You are not holding an item! #define SENSE_UNDEAD 12471 //You sense undead in this direction. #define SENSE_ANIMAL 12472 //You sense an animal in this direction. diff --git a/zone/bonuses.cpp b/zone/bonuses.cpp index b6f341f56..56f8b9f5f 100644 --- a/zone/bonuses.cpp +++ b/zone/bonuses.cpp @@ -2588,6 +2588,12 @@ void Mob::ApplySpellsBonuses(uint16 spell_id, uint8 casterlevel, StatBonuses* ne case SE_NegateIfCombat: newbon->NegateIfCombat = true; + break; + + case SE_Screech: + newbon->Screech = effect_value; + break; + } } } diff --git a/zone/common.h b/zone/common.h index ee87bd164..d720f3d43 100644 --- a/zone/common.h +++ b/zone/common.h @@ -346,6 +346,7 @@ struct StatBonuses { uint16 AbsorbMagicAtt[2]; // 0 = magic rune value 1 = buff slot uint16 MeleeRune[2]; // 0 = rune value 1 = buff slot bool NegateIfCombat; // Bool Drop buff if cast or melee + int8 Screech; // -1 = Will be blocked if another Screech is +(1) // AAs int8 Packrat; //weight reduction for items, 1 point = 10% diff --git a/zone/spells.cpp b/zone/spells.cpp index e00ece3c0..43a0d2b19 100644 --- a/zone/spells.cpp +++ b/zone/spells.cpp @@ -2613,6 +2613,14 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, { effect1 = sp1.effectid[i]; effect2 = sp2.effectid[i]; + + if (spellbonuses.Screech == 1) { + if (effect2 == SE_Screech && sp2.base[i] == -1) { + Message_StringID(MT_SpellFailure, SCREECH_BUFF_BLOCK, sp2.name); + return -1; + } + } + if(effect2 == SE_StackingCommand_Overwrite) { overwrite_effect = sp2.base[i]; @@ -2657,7 +2665,7 @@ int Mob::CheckStackConflict(uint16 spellid1, int caster_level1, uint16 spellid2, mlog(SPELLS__STACKING, "%s (%d) blocks effect %d on slot %d below %d, but we do not have that effect on that slot. Ignored.", sp1.name, spellid1, blocked_effect, blocked_slot, blocked_below_value); } - } + } } } else { mlog(SPELLS__STACKING, "%s (%d) and %s (%d) appear to be in the same line, skipping Stacking Overwrite/Blocking checks", From fb3c6365e19ce541ae004a02041dcdfc410b652f Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 5 Apr 2014 01:58:16 -0500 Subject: [PATCH 06/14] Test --- changelog.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/changelog.txt b/changelog.txt index 6931fac1d..4bd4d8eaa 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,8 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/05/2014 == +Akkadius: BOOGABOOGABOOGA + == 04/01/2014 == demonstar55: Implemented ability for a merchant to open and close shop. Lua quest functions: e.self:MerchantOpenShop() and e.self:MerchantCloseShop() From 7cfc5b085e9efe72cd64eaf4e3c3a2a53a869f76 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 5 Apr 2014 03:38:58 -0500 Subject: [PATCH 07/14] (Performance Adjustment) Removed AsyncLoadVariables from InterserverTimer.Check() in both zone and world. By watching the MySQL general.log file on mass zone idle activity, you can see that the query 'SELECT varname, value, unix_timestamp() FROM variables where unix_timestamp(ts) >= timestamp' is called every 10 seconds. This function is loading variables that are initially loaded on World and Zone bootup. When running a large amount of zone servers, the amount of MySQL chatter that is produced is enormous and unnecessary. For example, if I ran 400 zone servers, I would see 3,456,000 unnecessary queries from all idle or active zone processes in a 24 hour interval. --- changelog.txt | 5 ++++- world/net.cpp | 2 +- zone/net.cpp | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/changelog.txt b/changelog.txt index 4bd4d8eaa..87229d5dc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,7 +1,10 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- == 04/05/2014 == -Akkadius: BOOGABOOGABOOGA +Akkadius: (Performance Adjustment) Removed AsyncLoadVariables from InterserverTimer.Check() in both zone and world. By watching the MySQL general.log file on mass zone idle activity, you can + see that the query 'SELECT varname, value, unix_timestamp() FROM variables where unix_timestamp(ts) >= timestamp' is called every 10 seconds. This function is loading + variables that are initially loaded on World and Zone bootup. When running a large amount of zone servers, the amount of MySQL chatter that is produced is enormous and + unnecessary. For example, if I ran 400 zone servers, I would see 3,456,000 unnecessary queries from all idle or active zone processes in a 24 hour interval. == 04/01/2014 == demonstar55: Implemented ability for a merchant to open and close shop. diff --git a/world/net.cpp b/world/net.cpp index 057367b00..3c5df74a2 100644 --- a/world/net.cpp +++ b/world/net.cpp @@ -461,7 +461,7 @@ int main(int argc, char** argv) { if (InterserverTimer.Check()) { InterserverTimer.Start(); database.ping(); - AsyncLoadVariables(dbasync, &database); + // AsyncLoadVariables(dbasync, &database); ReconnectCounter++; if (ReconnectCounter >= 12) { // only create thread to reconnect every 10 minutes. previously we were creating a new thread every 10 seconds ReconnectCounter = 0; diff --git a/zone/net.cpp b/zone/net.cpp index d870ee550..15e108e42 100644 --- a/zone/net.cpp +++ b/zone/net.cpp @@ -447,7 +447,7 @@ int main(int argc, char** argv) { if (InterserverTimer.Check()) { InterserverTimer.Start(); database.ping(); - AsyncLoadVariables(dbasync, &database); + // AsyncLoadVariables(dbasync, &database); entity_list.UpdateWho(); if (worldserver.TryReconnect() && (!worldserver.Connected())) worldserver.AsyncConnect(); From 35fad4d5a71435525a31172d9b29f0f8f89ed519 Mon Sep 17 00:00:00 2001 From: SecretsOTheP Date: Sat, 5 Apr 2014 17:18:19 -0400 Subject: [PATCH 08/14] Added a rule to enable multiple procs from the same weapon's other slots if a proc is deemed to trigger, Defaults to true. --- changelog.txt | 7 ++++++- common/ruletypes.h | 1 + utils/sql/git/optional/2014_04_05_ProcRules.sql | 1 + zone/attack.cpp | 4 ++++ 4 files changed, 12 insertions(+), 1 deletion(-) create mode 100644 utils/sql/git/optional/2014_04_05_ProcRules.sql diff --git a/changelog.txt b/changelog.txt index b16c283ad..67f2311e4 100644 --- a/changelog.txt +++ b/changelog.txt @@ -5,7 +5,12 @@ Akkadius: (Performance Adjustment) Removed AsyncLoadVariables from InterserverTi see that the query 'SELECT varname, value, unix_timestamp() FROM variables where unix_timestamp(ts) >= timestamp' is called every 10 seconds. This function is loading variables that are initially loaded on World and Zone bootup. When running a large amount of zone servers, the amount of MySQL chatter that is produced is enormous and unnecessary. For example, if I ran 400 zone servers, I would see 3,456,000 unnecessary queries from all idle or active zone processes in a 24 hour interval. - +Secrets: Added a rule to enable multiple procs from the same weapon's other slots if a proc is deemed to trigger, Defaults to true. + If Combat:OneProcPerWeapon is not enabled, we reset the try for that weapon regardless of if we procced or not. + This is for some servers that may want to have as many procs triggering from weapons as possible in a single round. + +Optional SQL: utils/sql/git/optional/2014_04_05_ProcRules.sql + == 04/04/2014 == Kayen: Implemented 'Physical Resists' (Resist Type 9) to be consistent with live based on extensive parsing. SQL will add new field to npc_types 'PhR' and fill in database with values consistent with observations. diff --git a/common/ruletypes.h b/common/ruletypes.h index 39886c9f5..dec5a6a8f 100644 --- a/common/ruletypes.h +++ b/common/ruletypes.h @@ -396,6 +396,7 @@ RULE_BOOL ( Combat, UseArcheryBonusRoll, false) //Make the 51+ archery bonus req RULE_INT ( Combat, ArcheryBonusChance, 50) RULE_INT ( Combat, BerserkerFrenzyStart, 35) RULE_INT ( Combat, BerserkerFrenzyEnd, 45) +RULE_BOOL ( Combat, OneProcPerWeapon, true) //If enabled, One proc per weapon per round RULE_CATEGORY_END() RULE_CATEGORY( NPC ) diff --git a/utils/sql/git/optional/2014_04_05_ProcRules.sql b/utils/sql/git/optional/2014_04_05_ProcRules.sql new file mode 100644 index 000000000..4566f45fd --- /dev/null +++ b/utils/sql/git/optional/2014_04_05_ProcRules.sql @@ -0,0 +1 @@ +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Combat:OneProcPerWeapon', 'true', 'If OneProcPerWeapon is not enabled, we reset the proc try for that weapon regardless of if we procced or not.'); diff --git a/zone/attack.cpp b/zone/attack.cpp index 91ce521d5..2d26f1c2b 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -4085,6 +4085,10 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on } } } + //If OneProcPerWeapon is not enabled, we reset the try for that weapon regardless of if we procced or not. + //This is for some servers that may want to have as many procs triggering from weapons as possible in a single round. + if(!RuleB(Combat, OneProcPerWeapon)) + proced = false; if (!proced && inst) { for (int r = 0; r < MAX_AUGMENT_SLOTS; r++) { From 07625336fd1c7cece494ae762678596a0f21c36a Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sat, 5 Apr 2014 18:47:14 -0400 Subject: [PATCH 09/14] Allow multiple aug procs if Combat:OneProcPerWeapon is false --- zone/attack.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/zone/attack.cpp b/zone/attack.cpp index 2d26f1c2b..3964fd934 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -4113,7 +4113,8 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on } } else { ExecWeaponProc(aug_i, aug->Proc.Effect, on); - break; + if (!RuleB(Combat, OneProcPerWeapon)) + break; } } } From 8db606008955efc839267a36d7b55da69776068a Mon Sep 17 00:00:00 2001 From: KimLS Date: Sat, 5 Apr 2014 16:07:13 -0700 Subject: [PATCH 10/14] Renamed errant sql file. --- ...014_04_04_PhysicalResist.txt => 2014_04_04_PhysicalResist.sql} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename utils/sql/git/required/{2014_04_04_PhysicalResist.txt => 2014_04_04_PhysicalResist.sql} (100%) diff --git a/utils/sql/git/required/2014_04_04_PhysicalResist.txt b/utils/sql/git/required/2014_04_04_PhysicalResist.sql similarity index 100% rename from utils/sql/git/required/2014_04_04_PhysicalResist.txt rename to utils/sql/git/required/2014_04_04_PhysicalResist.sql From 11d5e4b6ca970f595af3bc4f4070b91a96348557 Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 5 Apr 2014 18:09:12 -0500 Subject: [PATCH 11/14] Fix for the Fix for the Fix: Rule Combat:OneProcPerWeapon was created so that you can revert to the original proc functionality for custom servers that have balanced their content around having more than 1 aug proc on weapons. By having this rule set to 'false' you revert this functionality. This rule is set to 'true' by default as the original functionality from Live was intended to be --- changelog.txt | 20 +++----------------- zone/attack.cpp | 2 +- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/changelog.txt b/changelog.txt index 67f2311e4..3b4bd984d 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,27 +1,13 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- == 04/05/2014 == +Akkadius: Fix for the Fix for the Fix: Rule Combat:OneProcPerWeapon was created so that you can revert to the original proc functionality + for custom servers that have balanced their content around having more than 1 aug proc on weapons. By having this rule set to 'false' you revert this functionality. + This rule is set to 'true' by default as the original functionality from Live was intended to be Akkadius: (Performance Adjustment) Removed AsyncLoadVariables from InterserverTimer.Check() in both zone and world. By watching the MySQL general.log file on mass zone idle activity, you can see that the query 'SELECT varname, value, unix_timestamp() FROM variables where unix_timestamp(ts) >= timestamp' is called every 10 seconds. This function is loading variables that are initially loaded on World and Zone bootup. When running a large amount of zone servers, the amount of MySQL chatter that is produced is enormous and unnecessary. For example, if I ran 400 zone servers, I would see 3,456,000 unnecessary queries from all idle or active zone processes in a 24 hour interval. -Secrets: Added a rule to enable multiple procs from the same weapon's other slots if a proc is deemed to trigger, Defaults to true. - If Combat:OneProcPerWeapon is not enabled, we reset the try for that weapon regardless of if we procced or not. - This is for some servers that may want to have as many procs triggering from weapons as possible in a single round. - -Optional SQL: utils/sql/git/optional/2014_04_05_ProcRules.sql - -== 04/04/2014 == -Kayen: Implemented 'Physical Resists' (Resist Type 9) to be consistent with live based on extensive parsing. - SQL will add new field to npc_types 'PhR' and fill in database with values consistent with observations. - -Required SQL: utils/sql/git/optional/2014_04_04_PhysicalResists.sql - -== 04/03/2014 == -Kayen: Implemented live like spell projectiles (ie. Mage Bolts). - -Optional SQL: utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql -Note: The rules in this SQL are for setting the item id for the graphic used by the projectile on different clients. == 04/01/2014 == demonstar55: Implemented ability for a merchant to open and close shop. diff --git a/zone/attack.cpp b/zone/attack.cpp index 3964fd934..324f5942d 100644 --- a/zone/attack.cpp +++ b/zone/attack.cpp @@ -4113,7 +4113,7 @@ void Mob::TryWeaponProc(const ItemInst *inst, const Item_Struct *weapon, Mob *on } } else { ExecWeaponProc(aug_i, aug->Proc.Effect, on); - if (!RuleB(Combat, OneProcPerWeapon)) + if (RuleB(Combat, OneProcPerWeapon)) break; } } From 8ad1c1d8a99b858f04eeabb59b09edb6ae94aaba Mon Sep 17 00:00:00 2001 From: Akkadius Date: Sat, 5 Apr 2014 18:12:58 -0500 Subject: [PATCH 12/14] changelog.txt --- changelog.txt | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/changelog.txt b/changelog.txt index 3b4bd984d..4a7b1a43f 100644 --- a/changelog.txt +++ b/changelog.txt @@ -8,7 +8,24 @@ Akkadius: (Performance Adjustment) Removed AsyncLoadVariables from InterserverTi see that the query 'SELECT varname, value, unix_timestamp() FROM variables where unix_timestamp(ts) >= timestamp' is called every 10 seconds. This function is loading variables that are initially loaded on World and Zone bootup. When running a large amount of zone servers, the amount of MySQL chatter that is produced is enormous and unnecessary. For example, if I ran 400 zone servers, I would see 3,456,000 unnecessary queries from all idle or active zone processes in a 24 hour interval. +Secrets: Added a rule to enable multiple procs from the same weapon's other slots if a proc is deemed to trigger, Defaults to true. + If Combat:OneProcPerWeapon is not enabled, we reset the try for that weapon regardless of if we procced or not. + This is for some servers that may want to have as many procs triggering from weapons as possible in a single round. + +Optional SQL: utils/sql/git/optional/2014_04_05_ProcRules.sql + +== 04/04/2014 == +Kayen: Implemented 'Physical Resists' (Resist Type 9) to be consistent with live based on extensive parsing. + SQL will add new field to npc_types 'PhR' and fill in database with values consistent with observations. +Required SQL: utils/sql/git/optional/2014_04_04_PhysicalResists.sql + +== 04/03/2014 == +Kayen: Implemented live like spell projectiles (ie. Mage Bolts). + +Optional SQL: utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql +Note: The rules in this SQL are for setting the item id for the graphic used by the projectile on different clients. + == 04/01/2014 == demonstar55: Implemented ability for a merchant to open and close shop. Lua quest functions: e.self:MerchantOpenShop() and e.self:MerchantCloseShop() From 7e75f7559a516c91f5149d1040edd86f3791e28b Mon Sep 17 00:00:00 2001 From: "Michael Cook (mackal)" Date: Sun, 6 Apr 2014 03:03:18 -0400 Subject: [PATCH 13/14] Fix issue with optional SQL --- utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql b/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql index 70d8594b6..3601a5fef 100644 --- a/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql +++ b/utils/sql/git/optional/2014_04_03_SpellProjectileRules.sql @@ -1,3 +1,3 @@ INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_Titanium', '1113', 'Item id for Titanium clients for Fire spell projectile.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'FRProjectileItem_SOF', '80684', 'Item id for Titanium clients for Fire spell projectile.'); -INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'FRProjectileItem_NPC', '80684', 'Item id for Titanium clients for Fire spell projectile.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_SOF', '80684', 'Item id for Titanium clients for Fire spell projectile.'); +INSERT INTO `rule_values` (`ruleset_id`, `rule_name`, `rule_value`, `notes`) VALUES (1, 'Spells:FRProjectileItem_NPC', '80684', 'Item id for Titanium clients for Fire spell projectile.'); From 8e55b6618e4f160023cb2f489a32a0fc05840031 Mon Sep 17 00:00:00 2001 From: Uleat Date: Sun, 6 Apr 2014 05:39:37 -0400 Subject: [PATCH 14/14] Dual Wield changes - see posting linked in changelog.txt --- changelog.txt | 6 ++++++ zone/mob.cpp | 38 +++++++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/changelog.txt b/changelog.txt index 4a7b1a43f..dafc65268 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,5 +1,11 @@ EQEMu Changelog (Started on Sept 24, 2003 15:50) ------------------------------------------------------- +== 04/06/2014 == +Uleat: Changed Mob::CanThisClassDualWield() behavior. This should let non-monk/beastlord dual-wielding classes attack with either fist as long as the other hand is occupied. +Notes: + See this thread for more information and to provide feedback: http://www.eqemulator.org/forums/showthread.php?p=229328#post229328 + + == 04/05/2014 == Akkadius: Fix for the Fix for the Fix: Rule Combat:OneProcPerWeapon was created so that you can revert to the original proc functionality for custom servers that have balanced their content around having more than 1 aug proc on weapons. By having this rule set to 'false' you revert this functionality. diff --git a/zone/mob.cpp b/zone/mob.cpp index d3199d0e6..87486714c 100644 --- a/zone/mob.cpp +++ b/zone/mob.cpp @@ -2088,27 +2088,35 @@ void Mob::SetAttackTimer() { } -bool Mob::CanThisClassDualWield(void) const -{ - if (!IsClient()) { +bool Mob::CanThisClassDualWield(void) const { + if(!IsClient()) { return(GetSkill(SkillDualWield) > 0); - } else { - const ItemInst* inst = CastToClient()->GetInv().GetItem(SLOT_PRIMARY); + } + else if(CastToClient()->HasSkill(SkillDualWield)) { + const ItemInst* pinst = CastToClient()->GetInv().GetItem(SLOT_PRIMARY); + const ItemInst* sinst = CastToClient()->GetInv().GetItem(SLOT_SECONDARY); + // 2HS, 2HB, or 2HP - if (inst && inst->IsType(ItemClassCommon)) { - const Item_Struct* item = inst->GetItem(); - if ((item->ItemType == ItemType2HBlunt) || (item->ItemType == ItemType2HSlash) || (item->ItemType == ItemType2HPiercing)) + if(pinst && pinst->IsWeapon()) { + const Item_Struct* item = pinst->GetItem(); + + if((item->ItemType == ItemType2HBlunt) || (item->ItemType == ItemType2HSlash) || (item->ItemType == ItemType2HPiercing)) return false; - } else { - //No weapon in hand... using hand-to-hand... - //only monks and beastlords? can dual wield their fists. - if(class_ != MONK && class_ != MONKGM && class_ != BEASTLORD && class_ != BEASTLORDGM) { - return false; - } } - return (CastToClient()->HasSkill(SkillDualWield)); // No skill = no chance + // OffHand Weapon + if(sinst && !sinst->IsWeapon()) + return false; + + // Dual-Wielding Empty Fists + if(!pinst && !sinst) + if(class_ != MONK && class_ != MONKGM && class_ != BEASTLORD && class_ != BEASTLORDGM) + return false; + + return true; } + + return false; } bool Mob::CanThisClassDoubleAttack(void) const