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 //