diff --git a/Source/Framework/Constants/Spells/SpellConst.cs b/Source/Framework/Constants/Spells/SpellConst.cs index fd8e51f93..df4ab400f 100644 --- a/Source/Framework/Constants/Spells/SpellConst.cs +++ b/Source/Framework/Constants/Spells/SpellConst.cs @@ -30,6 +30,8 @@ namespace Framework.Constants public const int EffectFirstFound = 254; public const int EffectAll = 255; + + public const float TrajectoryMissileSize = 3.0f; } @@ -2416,7 +2418,8 @@ namespace Framework.Constants Channel, Nearby, Cone, - Area + Area, + Traj } public enum SpellTargetReferenceTypes diff --git a/Source/Framework/Dynamic/EventSystem.cs b/Source/Framework/Dynamic/EventSystem.cs index d1ddec8cc..41f7677d9 100644 --- a/Source/Framework/Dynamic/EventSystem.cs +++ b/Source/Framework/Dynamic/EventSystem.cs @@ -96,6 +96,20 @@ namespace Framework.Dynamic m_events.Add(e_time, Event); } + public void ModifyEventTime(BasicEvent Event, ulong newTime) + { + foreach (var pair in m_events) + { + if (pair.Value != Event) + continue; + + Event.m_execTime = newTime; + m_events.Remove(pair); + m_events.Add(newTime, Event); + break; + } + } + public ulong CalculateTime(ulong t_offset) { return (m_time + t_offset); diff --git a/Source/Game/Conditions/ConditionManager.cs b/Source/Game/Conditions/ConditionManager.cs index 6defd27b2..ae6d8bd46 100644 --- a/Source/Game/Conditions/ConditionManager.cs +++ b/Source/Game/Conditions/ConditionManager.cs @@ -961,6 +961,7 @@ namespace Game case SpellTargetSelectionCategories.Nearby: case SpellTargetSelectionCategories.Cone: case SpellTargetSelectionCategories.Area: + case SpellTargetSelectionCategories.Traj: continue; default: break; @@ -971,6 +972,7 @@ namespace Game case SpellTargetSelectionCategories.Nearby: case SpellTargetSelectionCategories.Cone: case SpellTargetSelectionCategories.Area: + case SpellTargetSelectionCategories.Traj: continue; default: break; diff --git a/Source/Game/Entities/Object/Position.cs b/Source/Game/Entities/Object/Position.cs index 07a97e918..e3c85938d 100644 --- a/Source/Game/Entities/Object/Position.cs +++ b/Source/Game/Entities/Object/Position.cs @@ -288,11 +288,12 @@ namespace Game.Entities return ((angle >= lborder) && (angle <= rborder)); } - public bool HasInLine(Position pos, float width) + public bool HasInLine(Position pos, float objSize, float width) { if (!HasInArc(MathFunctions.PI, pos)) return false; + width += objSize; float angle = GetRelativeAngle(pos); return Math.Abs(Math.Sin(angle)) * GetExactDist2d(pos.GetPositionX(), pos.GetPositionY()) < width; } diff --git a/Source/Game/Handlers/SpellHandler.cs b/Source/Game/Handlers/SpellHandler.cs index 77f74e22a..6f351fd04 100644 --- a/Source/Game/Handlers/SpellHandler.cs +++ b/Source/Game/Handlers/SpellHandler.cs @@ -569,6 +569,9 @@ namespace Game pos.Relocate(packet.CollisionPos); spell.m_targets.ModDst(pos); + // we changed dest, recalculate flight time + spell.RecalculateDelayMomentForDst(); + NotifyMissileTrajectoryCollision data = new NotifyMissileTrajectoryCollision(); data.Caster = packet.Target; data.CastID = packet.CastID; diff --git a/Source/Game/Spells/Spell.cs b/Source/Game/Spells/Spell.cs index 38d21c5d3..8b84a63d0 100644 --- a/Source/Game/Spells/Spell.cs +++ b/Source/Game/Spells/Spell.cs @@ -279,25 +279,39 @@ namespace Game.Spells } } } + ulong dstDelay = CalculateDelayMomentForDst(); + if (dstDelay != 0) + m_delayMoment = dstDelay; + } + ulong CalculateDelayMomentForDst() + { if (m_targets.HasDst()) { if (m_targets.HasTraj()) { float speed = m_targets.GetSpeedXY(); if (speed > 0.0f) - m_delayMoment = (ulong)Math.Floor(m_targets.GetDist2d() / speed * 1000.0f); + return (ulong)Math.Floor(m_targets.GetDist2d() / speed * 1000.0f); } else if (m_spellInfo.Speed > 0.0f) { // We should not subtract caster size from dist calculation (fixes execution time desync with animation on client, eg. Malleable Goo cast by PP) float dist = m_caster.GetExactDist(m_targets.GetDstPos()); if (!m_spellInfo.HasAttribute(SpellAttr9.SpecialDelayCalculation)) - m_delayMoment = (ulong)Math.Floor(dist / m_spellInfo.Speed * 1000.0f); + return (ulong)Math.Floor(dist / m_spellInfo.Speed * 1000.0f); else - m_delayMoment = (ulong)(m_spellInfo.Speed * 1000.0f); + return (ulong)(m_spellInfo.Speed * 1000.0f); } } + + return 0; + } + + public void RecalculateDelayMomentForDst() + { + m_delayMoment = CalculateDelayMomentForDst(); + m_caster.m_Events.ModifyEventTime(_spellEvent, GetDelayStart() + m_delayMoment); } void SelectEffectImplicitTargets(uint effIndex, SpellImplicitTargetInfo targetType, uint processedEffectMask) @@ -356,6 +370,12 @@ namespace Game.Spells case SpellTargetSelectionCategories.Area: SelectImplicitAreaTargets(effIndex, targetType, effectMask); break; + case SpellTargetSelectionCategories.Traj: + // just in case there is no dest, explanation in SelectImplicitDestDestTargets + CheckDst(); + + SelectImplicitTrajTargets(effIndex, targetType); + break; case SpellTargetSelectionCategories.Default: switch (targetType.GetObjectType()) { @@ -901,9 +921,6 @@ namespace Game.Spells case Targets.DestDynobjNone: case Targets.DestDest: return; - case Targets.DestTraj: - SelectImplicitTrajTargets(effIndex); - return; default: SpellEffectInfo effect = GetEffect(effIndex); if (effect != null) @@ -1037,7 +1054,7 @@ namespace Game.Spells return 0.0f; } - void SelectImplicitTrajTargets(uint effIndex) + void SelectImplicitTrajTargets(uint effIndex, SpellImplicitTargetInfo targetType) { if (!m_targets.HasTraj()) return; @@ -1046,12 +1063,15 @@ namespace Game.Spells if (dist2d == 0) return; - float srcToDestDelta = m_targets.GetDstPos().posZ - m_targets.GetSrcPos().posZ; + Position srcPos = m_targets.GetSrcPos(); + srcPos.SetOrientation(m_caster.GetOrientation()); + float srcToDestDelta = m_targets.GetDstPos().posZ - srcPos.posZ; + SpellEffectInfo effect = m_spellInfo.GetEffect(effIndex); List targets = new List(); - var spellTraj = new WorldObjectSpellTrajTargetCheck(dist2d, m_targets.GetSrcPos(), m_caster, m_spellInfo); + var spellTraj = new WorldObjectSpellTrajTargetCheck(dist2d, srcPos, m_caster, m_spellInfo, targetType.GetCheckType(), effect.ImplicitTargetConditions); var searcher = new WorldObjectListSearcher(m_caster, targets, spellTraj); - SearchTargets(searcher, GridMapTypeMask.All, m_caster, m_targets.GetSrcPos(), dist2d); + SearchTargets(searcher, GridMapTypeMask.All, m_caster, srcPos, dist2d); if (targets.Empty()) return; @@ -1061,15 +1081,16 @@ namespace Game.Spells float a = (srcToDestDelta - dist2d * b) / (dist2d * dist2d); if (a > -0.0001f) a = 0; - Log.outError(LogFilter.Spells, "Spell.SelectTrajTargets: a {0} b {1}", a, b); + // We should check if triggered spell has greater range (which is true in many cases, and initial spell has too short max range) + // limit max range to 300 yards, sometimes triggered spells can have 50000yds float bestDist = m_spellInfo.GetMaxRange(false); + SpellInfo triggerSpellInfo = Global.SpellMgr.GetSpellInfo(effect.TriggerSpell); + if (triggerSpellInfo != null) + bestDist = Math.Min(Math.Max(bestDist, triggerSpellInfo.GetMaxRange(false)), Math.Min(dist2d, 300.0f)); foreach (var obj in targets) { - if (!m_caster.HasInLine(obj, 5.0f)) - continue; - if (m_spellInfo.CheckTarget(m_caster, obj, true) != SpellCastResult.SpellCastOk) continue; @@ -1087,133 +1108,34 @@ namespace Game.Spells } } - float size = Math.Max(obj.GetObjectSize() * 0.7f, 1.0f); // 1/sqrt(3) - // @todo all calculation should be based on src instead of m_caster - float objDist2d = (float)(m_targets.GetSrcPos().GetExactDist2d(obj) * Math.Cos(m_targets.GetSrcPos().GetRelativeAngle(obj))); - float dz = obj.GetPositionZ() - m_targets.GetSrcPos().posZ; + float size = Math.Max(obj.GetObjectSize(), 1.0f); + float objDist2d = srcPos.GetExactDist2d(obj); + float dz = obj.GetPositionZ() - srcPos.posZ; - Log.outError(LogFilter.Spells, "Spell.SelectTrajTargets: check {0}, dist between {1} {2}, height between {3} {4}.", obj.GetEntry(), objDist2d - size, objDist2d + size, dz - size, dz + size); - - float dist = objDist2d - size; - float height = dist * (a * dist + b); - Log.outError(LogFilter.Spells, "Spell.SelectTrajTargets: dist {0}, height {1}.", dist, height); - if (dist < bestDist && height < dz + size && height > dz - size) - { - bestDist = dist > 0 ? dist : 0; - break; - } - - if (a == 0) - { - height = dz - size; - dist = height / b; - - if (dist > bestDist) - continue; - if (dist < objDist2d + size && dist > objDist2d - size) - { - bestDist = dist; - break; - } - - height = dz + size; - dist = height / b; - - if (dist > bestDist) - continue; - if (dist < objDist2d + size && dist > objDist2d - size) - { - bestDist = dist; - break; - } + float horizontalDistToTraj = (float)Math.Abs(objDist2d * Math.Sin(srcPos.GetRelativeAngle(obj))); + float sizeFactor = (float)Math.Cos((horizontalDistToTraj / size) * (Math.PI / 2.0f)); + float distToHitPoint = (float)Math.Max(objDist2d * Math.Cos(srcPos.GetRelativeAngle(obj)) - size * sizeFactor, 0.0f); + float height = distToHitPoint * (a * distToHitPoint + b); + if (Math.Abs(dz - height) > size + b / 2.0f + SpellConst.TrajectoryMissileSize) continue; - } - height = dz - size; - float sqrt1 = b * b + 4 * a * height; - if (sqrt1 > 0) + if (distToHitPoint < bestDist) { - sqrt1 = (float)Math.Sqrt(sqrt1); - dist = (sqrt1 - b) / (2 * a); + bestDist = distToHitPoint; + break; - if (dist > bestDist) - continue; - if (dist < objDist2d + size && dist > objDist2d - size) - { - bestDist = dist; - break; - } - } - - height = dz + size; - float sqrt2 = b * b + 4 * a * height; - if (sqrt2 > 0) - { - sqrt2 = (float)Math.Sqrt(sqrt2); - dist = (sqrt2 - b) / (2 * a); - - if (dist > bestDist) - continue; - if (dist < objDist2d + size && dist > objDist2d - size) - { - bestDist = dist; - break; - } - - dist = (-sqrt2 - b) / (2 * a); - - if (dist > bestDist) - continue; - if (dist < objDist2d + size && dist > objDist2d - size) - { - bestDist = dist; - break; - } - } - - if (sqrt1 > 0) - { - dist = (-sqrt1 - b) / (2 * a); - - if (dist > bestDist) - continue; - if (dist < objDist2d + size && dist > objDist2d - size) - { - bestDist = dist; - break; - } } } - if (m_targets.GetSrcPos().GetExactDist2d(m_targets.GetDstPos()) > bestDist) + if (dist2d > bestDist) { float x = (float)(m_targets.GetSrcPos().posX + Math.Cos(m_caster.GetOrientation()) * bestDist); float y = (float)(m_targets.GetSrcPos().posY + Math.Sin(m_caster.GetOrientation()) * bestDist); float z = m_targets.GetSrcPos().posZ + bestDist * (a * bestDist + b); - var obj = targets[0]; - if (obj != null) - { - float distSq = obj.GetExactDistSq(x, y, z); - float sizeSq = obj.GetObjectSize(); - sizeSq *= sizeSq; - if (distSq > sizeSq) - { - float factor = 1 - (float)Math.Sqrt(sizeSq / distSq); - x += factor * (obj.GetPositionX() - x); - y += factor * (obj.GetPositionY() - y); - z += factor * (obj.GetPositionZ() - z); - distSq = obj.GetExactDistSq(x, y, z); - } - } - - Position trajDst = new Position(); - trajDst.Relocate(x, y, z, m_caster.GetOrientation()); - SpellDestination dest = m_targets.GetDst(); - dest.Relocate(trajDst); - - CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, new SpellImplicitTargetInfo(Targets.DestTraj)); + SpellDestination dest = new SpellDestination(x, y, z, m_caster.GetOrientation()); + CallScriptDestinationTargetSelectHandlers(ref dest, effIndex, targetType); m_targets.ModDst(dest); } @@ -2425,8 +2347,8 @@ namespace Game.Spells } // create and add update event for this spell - SpellEvent Event = new SpellEvent(this); - m_caster.m_Events.AddEvent(Event, m_caster.m_Events.CalculateTime(1)); + _spellEvent = new SpellEvent(this); + m_caster.m_Events.AddEvent(_spellEvent, m_caster.m_Events.CalculateTime(1)); //Prevent casting at cast another spell (ServerSide check) if (!Convert.ToBoolean(_triggeredCastFlags & TriggerCastFlags.IgnoreCastInProgress) && m_caster.IsNonMeleeSpellCast(false, true, true) && !m_castId.IsEmpty()) @@ -7360,6 +7282,7 @@ namespace Game.Spells SpellState m_spellState; int m_timer; + SpellEvent _spellEvent; TriggerCastFlags _triggeredCastFlags; // if need this can be replaced by Aura copy @@ -7749,7 +7672,7 @@ namespace Game.Spells } else if (_spellInfo.HasAttribute(SpellCustomAttributes.ConeLine)) { - if (!_caster.HasInLine(target, _caster.GetObjectSize() + target.GetObjectSize())) + if (!_caster.HasInLine(target, target.GetObjectSize(), _caster.GetObjectSize())) return false; } else @@ -7766,16 +7689,27 @@ namespace Game.Spells float _coneAngle; } - public class WorldObjectSpellTrajTargetCheck : WorldObjectSpellAreaTargetCheck + public class WorldObjectSpellTrajTargetCheck : WorldObjectSpellTargetCheck { - public WorldObjectSpellTrajTargetCheck(float range, Position position, Unit caster, SpellInfo spellInfo) - : base(range, position, caster, caster, spellInfo, SpellTargetCheckTypes.Default, null) { } + float _range; + Position _position; + + public WorldObjectSpellTrajTargetCheck(float range, Position position, Unit caster, SpellInfo spellInfo, SpellTargetCheckTypes selectionType, List condList) + : base(caster, caster, spellInfo, selectionType, condList) + { + _range = range; + _position = position; + } public override bool Invoke(WorldObject target) { // return all targets on missile trajectory (0 - size of a missile) - if (!_caster.HasInLine(target, target.GetObjectSize())) + if (!_caster.HasInLine(target, target.GetObjectSize(), SpellConst.TrajectoryMissileSize)) return false; + + if (target.GetExactDist2d(_position) > _range) + return false; + return base.Invoke(target); } } diff --git a/Source/Game/Spells/SpellInfo.cs b/Source/Game/Spells/SpellInfo.cs index e95186cdb..195abad09 100644 --- a/Source/Game/Spells/SpellInfo.cs +++ b/Source/Game/Spells/SpellInfo.cs @@ -4792,7 +4792,7 @@ namespace Game.Spells new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Random), // 86 TARGET_DEST_DEST_RANDOM new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 87 TARGET_DEST_DEST new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 88 TARGET_DEST_DYNOBJ_NONE - new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 89 TARGET_DEST_TRAJ + new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Traj , SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 89 TARGET_DEST_TRAJ new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Target, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 90 TARGET_UNIT_TARGET_MINIPET new StaticData(SpellTargetObjectTypes.Dest, SpellTargetReferenceTypes.Dest, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.Random), // 91 TARGET_DEST_DEST_RADIUS new StaticData(SpellTargetObjectTypes.Unit, SpellTargetReferenceTypes.Caster, SpellTargetSelectionCategories.Default, SpellTargetCheckTypes.Default, SpellTargetDirectionTypes.None), // 92 TARGET_UNIT_SUMMONER diff --git a/Source/Game/Spells/SpellManager.cs b/Source/Game/Spells/SpellManager.cs index 3a998e715..0532e1f7c 100644 --- a/Source/Game/Spells/SpellManager.cs +++ b/Source/Game/Spells/SpellManager.cs @@ -2261,6 +2261,21 @@ namespace Game.Entities if (effect == null) continue; + if (effect.IsEffect() && (effect.TargetA.GetTarget() == Targets.DestTraj || effect.TargetB.GetTarget() == Targets.DestTraj)) + { + // Get triggered spell if any + SpellInfo spellInfoTrigger = GetSpellInfo(effect.TriggerSpell); + if (spellInfoTrigger != null) + { + float maxRangeMain = spellInfo.GetMaxRange(); + float maxRangeTrigger = spellInfoTrigger.GetMaxRange(); + + // check if triggered spell has enough max range to cover trajectory + if (maxRangeTrigger < maxRangeMain) + spellInfoTrigger.RangeEntry = spellInfo.RangeEntry; + } + } + switch (effect.Effect) { case SpellEffectName.Charge: