diff --git a/zone/special_attacks.cpp b/zone/special_attacks.cpp index be3408c81..6c49948d5 100644 --- a/zone/special_attacks.cpp +++ b/zone/special_attacks.cpp @@ -929,11 +929,31 @@ bool Mob::TryProjectileAttack(Mob *other, const EQ::ItemData *item, EQ::skills:: if (slot < 0) return false; - float speed_mod = speed; - + float distance_mod = 0.0f; float distance = other->CalculateDistance(GetX(), GetY(), GetZ()); - float hit = - 1200.0f + (10 * distance / speed_mod); // Calcuation: 60 = Animation Lag, 1.8 = Speed modifier for speed of (4) + + /* + New Distance Mod constant (7/25/21 update), modifier is needed to adjust slower speeds to have correct impact times at short distances. + We use archery 4.0 speed as a baseline for the forumla. At speed 1.5 at 50 pct distance mod is needed, where as speed 4.0 there is no modifer. + Therefore, we derive out our modifer as follows. distance_mod = (speed - 4) * ((50 - 0)/(1.5-4)). The ratio there is -20.0f. distance_mod = (speed - 4) * -20.0f + For distances >125 we use different modifier, this was all meticulously tested by eye to get the best possible outcome for projectile impact times. Not perfect though. + */ + + if (distance <= 125.0f) { + if (speed != 4.0f) { //Standard functions will always be 4.0f for archery. + distance_mod = (speed - 4.0f) * -20.0f; + distance += distance * distance_mod / 100.0f; + } + } + else if (distance > 125.0f && distance <= 200.0f) + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + + else if (distance > 200.0f) { + distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing. + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + } + + float hit = 1200.0f + (10 * distance / speed); ProjectileAtk[slot].increment = 1; ProjectileAtk[slot].hit_increment = static_cast(hit); // This projected hit time if target does NOT MOVE @@ -951,14 +971,12 @@ bool Mob::TryProjectileAttack(Mob *other, const EQ::ItemData *item, EQ::skills:: ProjectileAtk[slot].ammo_slot = 0; ProjectileAtk[slot].skill = skillInUse; - ProjectileAtk[slot].speed_mod = speed_mod; + ProjectileAtk[slot].speed_mod = speed; SetProjectileAttack(true); if (item) SendItemAnimation(other, item, skillInUse, speed); - // else if (IsNPC()) - // ProjectileAnimation(other, 0,false,speed,0,0,0,CastToNPC()->GetAmmoIDfile(),skillInUse); return true; } @@ -978,23 +996,40 @@ void Mob::ProjectileAttack() disable = false; Mob *target = entity_list.GetMobID(ProjectileAtk[i].target_id); - if (target && target->IsMoving()) { // Only recalculate hit increment if target moving - // Due to frequency that we need to check increment the targets position variables may not be - // updated even if moving. Do a simple check before calculating distance. + if (target && target->IsMoving()) { + /* + Only recalculate hit increment if target is moving. + Due to frequency that we need to check increment the targets position variables may not be + updated even if moving. Do a simple check before calculating distance. + */ if (ProjectileAtk[i].tlast_x != target->GetX() || ProjectileAtk[i].tlast_y != target->GetY()) { + ProjectileAtk[i].tlast_x = target->GetX(); ProjectileAtk[i].tlast_y = target->GetY(); - float distance = target->CalculateDistance( - ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z); - float hit = 1200.0f + (10 * distance / ProjectileAtk[i].speed_mod); // Calcuation: 60 = - // Animation Lag, 1.8 = - // Speed modifier for speed - // of (4) + + //Recalculate from the original location the projectile was fired in relation to the current targets location. + float distance = target->CalculateDistance(ProjectileAtk[i].origin_x, ProjectileAtk[i].origin_y, ProjectileAtk[i].origin_z); + float distance_mod = 0.0f; + + if (distance <= 125.0f) { + distance_mod = (ProjectileAtk[i].speed_mod - 4.0f) * -20.0f; + distance += distance * distance_mod / 100.0f; + } + else if (distance > 125.0f && distance <= 200.0f) + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + + else if (distance > 200.0f) { + distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing. + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + } + + float hit = 1200.0f + (10 * distance / ProjectileAtk[i].speed_mod); + ProjectileAtk[i].hit_increment = static_cast(hit); } } - // We hit I guess? + // Check if we hit. if (ProjectileAtk[i].hit_increment <= ProjectileAtk[i].increment) { if (target) { if (IsNPC()) { @@ -1496,7 +1531,7 @@ void Mob::ProjectileAnimation(Mob* to, int item_id, bool IsArrow, float speed, f speed = 4.0; } if(!angle) { - angle = CalculateHeadingToTarget(to->GetX(), to->GetY()) * 2; + angle = CalculateHeadingToTarget(to->GetX(), to->GetY()); } if(!tilt) { tilt = 125; diff --git a/zone/spell_effects.cpp b/zone/spell_effects.cpp index e5e447b5b..d20df5ace 100644 --- a/zone/spell_effects.cpp +++ b/zone/spell_effects.cpp @@ -6995,7 +6995,7 @@ bool Mob::PassCastRestriction(bool UseCastRestriction, int16 value, bool IsDama return false; } -bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ +bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed) { /*For mage 'Bolt' line and other various spells. -This is mostly accurate for how the modern clients handle this effect. @@ -7020,7 +7020,7 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ //Make sure there is an avialable bolt to be cast. for (int i = 0; i < MAX_SPELL_PROJECTILE; i++) { - if (ProjectileAtk[i].target_id == 0){ + if (ProjectileAtk[i].target_id == 0) { slot = i; break; } @@ -7029,11 +7029,35 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ if (slot < 0) return false; + float arc = 0.0f; + float distance_mod = 0.0f; + if (CheckLosFN(spell_target)) { - float speed_mod = speed; //Constant for adjusting speeds to match calculated impact time. float distance = spell_target->CalculateDistance(GetX(), GetY(), GetZ()); - float hit = 1200.0f + (10 * distance / speed_mod); + + /* + New Distance Mod constant (7/25/21 update), modifier is needed to adjust slower speeds to have correct impact times at short distances. + We use archery 4.0 speed as a baseline for the forumla. At speed 1.5 at 50 pct distance mod is needed, where as speed 4.0 there is no modifer. + Therefore, we derive out our modifer as follows. distance_mod = (speed - 4) * ((50 - 0)/(1.5-4)). The ratio there is -20.0f. distance_mod = (speed - 4) * -20.0f + For distances >125 we use different modifier, this was all meticulously tested by eye to get the best possible outcome for projectile impact times. Not perfect though. + */ + + if (distance <= 125.0f) { + distance_mod = (speed - 4.0f) * -20.0f; + distance += distance * distance_mod / 100.0f; + } + else if (distance > 125.0f && distance <= 200.0f) + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + + else if (distance > 200.0f) { + arc = 50.0f - ((distance - 200.0f) * 0.266f); //Arc angle gets drastically larger if >200 distance, lets lower it down gradually for better effect. + arc = std::max(arc, 20.0f); //No lower than 20 arc + distance = distance * 1.30f; //Add 30% to base distance if over 200 range to tighten up hit timing. + distance = 3.14f * (distance / 2.0f); //Get distance of arc to better reflect projectile path length + } + + float hit = 1200.0f + (10 * distance / speed); ProjectileAtk[slot].increment = 1; ProjectileAtk[slot].hit_increment = static_cast(hit); //This projected hit time if target does NOT MOVE @@ -7043,34 +7067,33 @@ bool Mob::TrySpellProjectile(Mob* spell_target, uint16 spell_id, float speed){ ProjectileAtk[slot].origin_y = GetY(); ProjectileAtk[slot].origin_z = GetZ(); ProjectileAtk[slot].skill = EQ::skills::SkillConjuration; - ProjectileAtk[slot].speed_mod = speed_mod; + ProjectileAtk[slot].speed_mod = speed; SetProjectileAttack(true); } //This will use the correct graphic as defined in the player_1 field of spells_new table. Found in UF+ spell files. if (RuleB(Spells, UseLiveSpellProjectileGFX)) { - ProjectileAnimation(spell_target,0, false, speed,0,0,0, spells[spell_id].player_1); + ProjectileAnimation(spell_target, 0, false, speed, 0.0f, 0.0f, arc, spells[spell_id].player_1); } - //This allows limited support for server using older spell files that do not contain data for bolt graphics. else { //Only use fire graphic for fire spells. if (spells[spell_id].resisttype == RESIST_FIRE) { - if (IsClient()){ + if (IsClient()) { if (CastToClient()->ClientVersionBit() <= 4) //Titanium needs alternate graphic. - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_Titanium)), false, speed); + ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_Titanium)), false, speed, 0.0f, 0.0f, arc); else - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_SOF)), false, speed); - } + ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_SOF)), false, speed, 0.0f, 0.0f, arc); + } else - ProjectileAnimation(spell_target,(RuleI(Spells, FRProjectileItem_NPC)), false, speed); + ProjectileAnimation(spell_target, (RuleI(Spells, FRProjectileItem_NPC)), false, speed, 0.0f, 0.0f, arc); } //Default to an arrow if not using a mage bolt (Use up to date spell file and enable above rules for best results) else - ProjectileAnimation(spell_target,0, 1, speed); + ProjectileAnimation(spell_target, 0, 1, speed, 0.0f, 0.0f, arc); } if (spells[spell_id].CastingAnim == 64)