mirror of
https://github.com/EQEmu/Server.git
synced 2025-12-13 14:41:28 +00:00
-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
This commit is contained in:
parent
bb541eeb60
commit
2cdd50b9e9
@ -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()
|
||||
|
||||
@ -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 )
|
||||
|
||||
@ -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.');
|
||||
@ -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();
|
||||
|
||||
@ -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())
|
||||
|
||||
51
zone/mob.cpp
51
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;
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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
|
||||
//
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user