diff --git a/Intersect.Client/Entities/Projectiles/Projectile.cs b/Intersect.Client/Entities/Projectiles/Projectile.cs index 48207fab4f..ebf0a610c5 100644 --- a/Intersect.Client/Entities/Projectiles/Projectile.cs +++ b/Intersect.Client/Entities/Projectiles/Projectile.cs @@ -13,48 +13,46 @@ namespace Intersect.Client.Entities.Projectiles public partial class Projectile : Entity { - private bool mDisposing; + private bool _isDisposing; - private bool mLoaded; + private bool _isLoaded; - private object mLock = new object(); + private readonly object _lock = new object(); - private ProjectileBase mMyBase; + private ProjectileBase _myBase; - private Guid mOwner; + private Guid _owner; - private int mQuantity; + private int _quantity; - private int mSpawnCount; + private int _spawnCount; - private int mSpawnedAmount; + private int _spawnedAmount; - private long mSpawnTime; + private long _spawnTime; - private int mTotalSpawns; + private int _totalSpawns; - public Guid ProjectileId; + private Guid _projectileId; // Individual Spawns - public ProjectileSpawns[] Spawns; + private ProjectileSpawns[] _spawns; - public Guid TargetId; + private Guid _targetId; - public int mLastTargetX = -1; + private int _lastTargetX = -1; - public int mLastTargetY = -1; + private int _lastTargetY = -1; - public Guid mLastTargetMapId = Guid.Empty; - - public Dictionary mCurrentCoords = new Dictionary(); + private Guid _lastTargetMapId = Guid.Empty; /// - /// The constructor for the inherated projectile class + /// The constructor for the inherated projectile class /// public Projectile(Guid id, ProjectileEntityPacket packet) : base(id, packet, EntityType.Projectile) { - Vital[(int) Enums.Vital.Health] = 1; - MaxVital[(int) Enums.Vital.Health] = 1; + Vital[(int)Enums.Vital.Health] = 1; + MaxVital[(int)Enums.Vital.Health] = 1; HideName = true; Passable = true; IsMoving = true; @@ -62,63 +60,67 @@ public Projectile(Guid id, ProjectileEntityPacket packet) : base(id, packet, Ent public override void Load(EntityPacket packet) { - if (mLoaded) + if (_isLoaded) { return; } base.Load(packet); - var pkt = (ProjectileEntityPacket) packet; - ProjectileId = pkt.ProjectileId; + var pkt = (ProjectileEntityPacket)packet; + _projectileId = pkt.ProjectileId; Dir = (Direction)pkt.ProjectileDirection; - TargetId = pkt.TargetId; - mOwner = pkt.OwnerId; - mMyBase = ProjectileBase.Get(ProjectileId); - if (mMyBase != null) + _targetId = pkt.TargetId; + _owner = pkt.OwnerId; + _myBase = ProjectileBase.Get(_projectileId); + if (_myBase != null) { for (var x = 0; x < ProjectileBase.SPAWN_LOCATIONS_WIDTH; x++) { - for (var y = 0; y < ProjectileBase.SPAWN_LOCATIONS_WIDTH; y++) + for (var y = 0; y < ProjectileBase.SPAWN_LOCATIONS_HEIGHT; y++) { for (var d = 0; d < ProjectileBase.MAX_PROJECTILE_DIRECTIONS; d++) { - if (mMyBase.SpawnLocations[x, y].Directions[d] == true) + if (_myBase.SpawnLocations[x, y].Directions[d] == true) { - mTotalSpawns++; + _totalSpawns++; } } } } - mTotalSpawns *= mMyBase.Quantity; + _totalSpawns *= _myBase.Quantity; } - Spawns = new ProjectileSpawns[mTotalSpawns]; - mLoaded = true; + _spawns = new ProjectileSpawns[_totalSpawns]; + _isLoaded = true; } + /// + /// Disposes of the resources used by this instance, preventing any further use. + /// public override void Dispose() { - if (!mDisposing) + if (!_isDisposing) { - lock (mLock) + lock (_lock) { - mDisposing = true; - if (mSpawnedAmount == 0) + _isDisposing = true; + + // Perform a final update if no projectiles have been spawned. + if (_spawnedAmount == 0) { Update(); } - if (Spawns != null) + if (_spawns != null) { - foreach (var s in Spawns) + foreach (var s in _spawns) { - if (s != null && s.Anim != null) - { - s.Anim.DisposeNextDraw(); - } + s?.Anim?.DisposeNextDraw(); } } + + GC.SuppressFinalize(this); } } } @@ -132,15 +134,19 @@ public override bool CanBeAttacked } } - //Find out which animation data to load depending on what spawn wave we are on during projection. + /// + /// Determines the appropriate animation data index for the current spawn wave of the projectile. + /// This method iterates through the available animations defined in the projectile's base configuration, + /// selecting the one whose spawn range encompasses the current quantity of spawns. + /// + /// The index of the animation data to use for the current spawn wave. private int FindSpawnAnimationData() { var start = 0; - var end = 1; - for (var i = 0; i < mMyBase.Animations.Count; i++) + for (var i = 0; i < _myBase.Animations.Count; i++) { - end = mMyBase.Animations[i].SpawnRange; - if (mQuantity >= start && mQuantity < end) + var end = _myBase.Animations[i].SpawnRange; + if (_quantity >= start && _quantity < end) { return i; } @@ -148,215 +154,201 @@ private int FindSpawnAnimationData() start = end; } - //If reaches maximum and the developer(s) have fucked up the animation ranges on each spawn of projectiles somewhere, just assign it to the last animation state. - return mMyBase.Animations.Count - 1; + // If no suitable animation is found (e.g., developer(s) fucked up the animation ranges), default to the last animation. + // This serves as a fallback to prevent crashes or undefined behavior in case of misconfiguration. + return _myBase.Animations.Count - 1; } + /// + /// Adds projectile spawns based on predefined spawn locations and directions. + /// private void AddProjectileSpawns() { var spawn = FindSpawnAnimationData(); - var animBase = AnimationBase.Get(mMyBase.Animations[spawn].AnimationId); + var animBase = AnimationBase.Get(_myBase.Animations[spawn].AnimationId); for (var x = 0; x < ProjectileBase.SPAWN_LOCATIONS_WIDTH; x++) { - for (var y = 0; y < ProjectileBase.SPAWN_LOCATIONS_WIDTH; y++) + for (var y = 0; y < ProjectileBase.SPAWN_LOCATIONS_HEIGHT; y++) { for (var d = 0; d < ProjectileBase.MAX_PROJECTILE_DIRECTIONS; d++) { - if (mMyBase.SpawnLocations[x, y].Directions[d] == true) + // Check if the current direction is enabled for spawning at this location. + if (_myBase.SpawnLocations[x, y].Directions[d] == true) { + // Calculate the spawn position and direction for the new projectile var s = new ProjectileSpawns( FindProjectileRotationDir(Dir, (Direction)d), - (byte)(X + FindProjectileRotationX(Dir, x - 2, y - 2)), - (byte)(Y + FindProjectileRotationY(Dir, x - 2, y - 2)), Z, MapId, animBase, - mMyBase.Animations[spawn].AutoRotate, mMyBase, this + (byte)(X + FindProjectileRotation(Dir, x - 2, y - 2, true)), + (byte)(Y + FindProjectileRotation(Dir, x - 2, y - 2, false)), Z, MapId, animBase, + _myBase.Animations[spawn].AutoRotate, _myBase, this ); - Spawns[mSpawnedAmount] = s; - mCurrentCoords.Add(s, (s.SpawnX, s.SpawnY)); - if (Collided(mSpawnedAmount)) + _spawns[_spawnedAmount] = s; + if (Collided(_spawnedAmount)) { - TryRemoveSpawn(mSpawnedAmount); - mSpawnCount--; + TryRemoveSpawn(_spawnedAmount); + _spawnCount--; } - mSpawnedAmount++; - mSpawnCount++; + // Add the new spawn to the array and increment counters + _spawnedAmount++; + _spawnCount++; } } } } - mQuantity++; - mSpawnTime = Timing.Global.Milliseconds + mMyBase.Delay; + // Increment the quantity of projectiles spawned and update the spawn time based on the delay + _quantity++; + _spawnTime = Timing.Global.Milliseconds + _myBase.Delay; } - private static int FindProjectileRotationX(Direction direction, int x, int y) + /// + /// Finds the projectile rotation value based on the direction, position and the axis. + /// + /// The direction of the projectile. + /// The x-coordinate value. + /// The y-coordinate value. + /// True for horizontal (X-axis), false for vertical (Y-axis) calculation. + /// The rotation value for the specified axis based on the direction. + private static int FindProjectileRotation(Direction direction, int x, int y, bool isXAxis) { - switch (direction) + if (isXAxis) { - case Direction.Up: - return x; - case Direction.Down: - return -x; - case Direction.Left: - case Direction.UpLeft: - case Direction.DownLeft: - return y; - case Direction.Right: - case Direction.UpRight: - case Direction.DownRight: - return -y; - default: - return x; + return direction switch + { + Direction.Up => x, + Direction.Down => -x, + Direction.Left or Direction.UpLeft or Direction.DownLeft => y, + Direction.Right or Direction.UpRight or Direction.DownRight => -y, + _ => x, + }; } - } - - private static int FindProjectileRotationY(Direction direction, int x, int y) - { - switch (direction) + else { - case Direction.Up: - return y; - case Direction.Down: - return -y; - case Direction.Left: - case Direction.UpLeft: - case Direction.DownLeft: - return -x; - case Direction.Right: - case Direction.UpRight: - case Direction.DownRight: - return x; - default: - return y; + return direction switch + { + Direction.Up => y, + Direction.Down => -y, + Direction.Left or Direction.UpLeft or Direction.DownLeft => -x, + Direction.Right or Direction.UpRight or Direction.DownRight => x, + _ => y, + }; } } private static Direction FindProjectileRotationDir(Direction entityDir, Direction projectionDir) => (Direction)ProjectileBase.ProjectileRotationDir[(int)entityDir * ProjectileBase.MAX_PROJECTILE_DIRECTIONS + (int)projectionDir]; - private static float GetRangeX(Direction direction, float range) + /// + /// Calculates the projectile range based on the given direction, range, and axis. + /// + /// The direction of the projectile. + /// The range of the projectile. + /// True for horizontal (X-axis), false for vertical (Y-axis) calculation. + /// The calculated range value. + private static float GetRange(Direction direction, float range, bool isXAxis) { - switch (direction) + if (isXAxis) { - case Direction.Left: - case Direction.UpLeft: - case Direction.DownLeft: - return -range; - case Direction.Right: - case Direction.UpRight: - case Direction.DownRight: - return range; - case Direction.Up: - case Direction.Down: - default: - return 0; + return direction switch + { + Direction.Left or Direction.UpLeft or Direction.DownLeft => -range, + Direction.Right or Direction.UpRight or Direction.DownRight => range, + _ => 0, + }; } - } - - private static float GetRangeY(Direction direction, float range) - { - switch (direction) + else { - case Direction.Up: - case Direction.UpLeft: - case Direction.UpRight: - return -range; - case Direction.Down: - case Direction.DownLeft: - case Direction.DownRight: - return range; - case Direction.Left: - case Direction.Right: - default: - return 0; + return direction switch + { + Direction.Up or Direction.UpLeft or Direction.UpRight => -range, + Direction.Down or Direction.DownLeft or Direction.DownRight => range, + _ => 0, + }; } } /// - /// Gets the displacement of the projectile during projection + /// Gets the displacement of the projectile during projection /// /// The displacement from the co-ordinates if placed on a Options.TileHeight grid. private float GetDisplacement(long spawnTime) { var elapsedTime = Timing.Global.Milliseconds - spawnTime; - var displacementPercent = elapsedTime / (float) mMyBase.Speed; + var displacementPercent = elapsedTime / (float)_myBase.Speed; + var calculatedDisplacement = displacementPercent * Options.TileHeight * _myBase.Range; - return displacementPercent * Options.TileHeight * mMyBase.Range; + // Ensure displacement does not exceed the maximum range of the projectile + var maxDisplacement = Options.TileHeight * _myBase.Range; + return Math.Min(calculatedDisplacement, maxDisplacement); } /// - /// Overwrite updating the offsets for projectile movement. + /// Overwrite updating the offsets for projectile movement. /// public override bool Update() { - if (mMyBase == null) + if (_myBase == null) { return false; } - lock (mLock) + lock (_lock) { - var tmpI = -1; var map = MapId; var y = Y; - if (!mDisposing && mQuantity < mMyBase.Quantity && mSpawnTime < Timing.Global.Milliseconds) + if (!_isDisposing && _quantity < _myBase.Quantity && _spawnTime < Timing.Global.Milliseconds) { AddProjectileSpawns(); } if (IsMoving) { - for (var s = 0; s < mSpawnedAmount; s++) + for (var s = 0; s < _spawnedAmount; s++) { - if (Spawns[s] != null && Maps.MapInstance.Get(Spawns[s].SpawnMapId) != null) + var spawn = _spawns[s]; + + if (spawn != null && Maps.MapInstance.Get(spawn.SpawnMapId) != null) { - if (TargetId != Guid.Empty && TargetId != mOwner && Globals.Entities.ContainsKey(TargetId) && (mMyBase.HomingBehavior || mMyBase.DirectShotBehavior)) + if (_targetId != Guid.Empty && _targetId != _owner && Globals.Entities.ContainsKey(_targetId) && (_myBase.HomingBehavior || _myBase.DirectShotBehavior)) { - var target = Globals.Entities[TargetId]; - mLastTargetX = target.X; - mLastTargetY = target.Y; - mLastTargetMapId = target.MapId; + var target = Globals.Entities[_targetId]; + _lastTargetX = target.X; + _lastTargetY = target.Y; + _lastTargetMapId = target.MapId; - Spawns[s].OffsetX = GetProjectileLerping(Spawns[s], true); - Spawns[s].OffsetY = GetProjectileLerping(Spawns[s], false); - SetProjectileRotation(Spawns[s]); + spawn.OffsetX = GetProjectileLerping(spawn, true); + spawn.OffsetY = GetProjectileLerping(spawn, false); + SetProjectileRotation(spawn); - if (mMyBase.DirectShotBehavior) + if (_myBase.DirectShotBehavior) { - TargetId = Guid.Empty; + _targetId = Guid.Empty; } } - else if(mLastTargetX != -1 && mLastTargetY != -1) + else if (_lastTargetX != -1 && _lastTargetY != -1) { - Spawns[s].OffsetX = GetProjectileLerping(Spawns[s], true); - Spawns[s].OffsetY = GetProjectileLerping(Spawns[s], false); - SetProjectileRotation(Spawns[s]); + spawn.OffsetX = GetProjectileLerping(spawn, true); + spawn.OffsetY = GetProjectileLerping(spawn, false); + SetProjectileRotation(spawn); } else { - Spawns[s].OffsetX = GetRangeX(Spawns[s].Dir, GetDisplacement(Spawns[s].SpawnTime)); - Spawns[s].OffsetY = GetRangeY(Spawns[s].Dir, GetDisplacement(Spawns[s].SpawnTime)); - Spawns[s].Anim.SetRotation(false); + spawn.OffsetX = GetRange(spawn.Dir, GetDisplacement(spawn.SpawnTime), true); + spawn.OffsetY = GetRange(spawn.Dir, GetDisplacement(spawn.SpawnTime), false); + spawn.Anim.SetRotation(false); } - Spawns[s] - .Anim.SetPosition( - Maps.MapInstance.Get(Spawns[s].SpawnMapId).GetX() + - Spawns[s].SpawnX * Options.TileWidth + - Spawns[s].OffsetX + - Options.TileWidth / 2, - Maps.MapInstance.Get(Spawns[s].SpawnMapId).GetY() + - Spawns[s].SpawnY * Options.TileHeight + - Spawns[s].OffsetY + - Options.TileHeight / 2, X, Y, MapId, - Spawns[s].AutoRotate ? Spawns[s].Dir : Direction.Up, - Spawns[s].Z - ); - - Spawns[s].Anim.Update(); + var spawnMapId = Maps.MapInstance.Get(spawn.SpawnMapId); + var spawnX = spawnMapId.GetX() + spawn.SpawnX * Options.TileWidth + spawn.OffsetX + Options.TileWidth / 2; + var spawnY = spawnMapId.GetY() + spawn.SpawnY * Options.TileHeight + spawn.OffsetY + Options.TileHeight / 2; + var spawnDirection = spawn.AutoRotate ? spawn.Dir : Direction.Up; + + spawn.Anim.SetPosition(spawnX, spawnY, X, Y, MapId, spawnDirection, spawn.Z); + spawn.Anim.Update(); } } } @@ -368,311 +360,370 @@ public override bool Update() } /// - /// Returns the distance of the projectile from the target in tiles in the X axis. + /// Calculates the offset for the projectile spawn position based on the target map and spawn location. /// - /// The spawn of the projectile. - /// The distance in tiles from the target in the X axis. - private float GetProjectileX(ProjectileSpawns spawn) + /// The projectile spawn information. + /// True for horizontal (X-axis), false for vertical (Y-axis) calculation. + /// The calculated offset value. + private float GetProjectileOffset(ProjectileSpawns spawn, bool isXAxis) { - if (mLastTargetMapId != Guid.Empty && mLastTargetMapId != spawn.SpawnMapId) + if (_lastTargetMapId == Guid.Empty || _lastTargetMapId == spawn.SpawnMapId || !Maps.MapInstance.TryGet(spawn.SpawnMapId, out var map)) + { + return isXAxis ? _lastTargetX - spawn.SpawnX : _lastTargetY - spawn.SpawnY; + } + + for (var y = map.GridY - 1; y <= map.GridY + 1; y++) { - var map = Maps.MapInstance.Get(spawn.SpawnMapId); - for (var y = map.GridY - 1; y <= map.GridY + 1; y++) + for (var x = map.GridX - 1; x <= map.GridX + 1; x++) { - for (var x = map.GridX - 1; x <= map.GridX + 1; x++) + if (x < 0 || x >= Globals.MapGrid.GetLength(0) || y < 0 || y >= Globals.MapGrid.GetLength(1)) + { + continue; + } + + if (Globals.MapGrid[x, y] != _lastTargetMapId || Globals.MapGrid[x, y] == Guid.Empty) + { + continue; + } + + if (isXAxis) // Horizontal (X) calculation { - if (x < 0 || x >= Globals.MapGrid.GetLength(0) || y < 0 || y >= Globals.MapGrid.GetLength(1)) + var leftSide = x == map.GridX - 1; + var rightSide = x == map.GridX + 1; + + if (leftSide) { - continue; + return _lastTargetX - Options.MapWidth - spawn.SpawnX; } - if (Globals.MapGrid[x, y] != Guid.Empty && Globals.MapGrid[x, y] == mLastTargetMapId) + if (rightSide) { - var leftSide = x == map.GridX - 1; - var rightSide = x == map.GridX + 1; - - if (leftSide) - { - return mLastTargetX - Options.MapWidth - spawn.SpawnX; - } - - if (rightSide) - { - return mLastTargetX + Options.MapWidth - spawn.SpawnX; - } + return _lastTargetX + Options.MapWidth - spawn.SpawnX; } } - } - } - - return mLastTargetX - spawn.SpawnX; - } - - /// - /// Returns the distance of the projectile from the target in tiles in the Y axis. - /// - /// The spawn of the projectile. - /// The distance in tiles from the target in the Y axis. - private float GetProjectileY(ProjectileSpawns spawn) - { - if (mLastTargetMapId != Guid.Empty && mLastTargetMapId != spawn.SpawnMapId) - { - var map = Maps.MapInstance.Get(spawn.SpawnMapId); - for (var y = map.GridY - 1; y <= map.GridY + 1; y++) - { - for (var x = map.GridX - 1; x <= map.GridX + 1; x++) + else // Vertical (Y) calculation { - if (x < 0 || x >= Globals.MapGrid.GetLength(0) || y < 0 || y >= Globals.MapGrid.GetLength(1)) + var topSide = y == map.GridY + 1; + var bottomSide = y == map.GridY - 1; + + if (topSide) { - continue; + return _lastTargetY + Options.MapHeight - spawn.SpawnY; } - if (Globals.MapGrid[x, y] != Guid.Empty && Globals.MapGrid[x, y] == mLastTargetMapId) + if (bottomSide) { - var upSide = y == map.GridY + 1; - var downSide = y == map.GridY - 1; - - if (upSide) - { - return mLastTargetY + Options.MapHeight - spawn.SpawnY; - } - - if (downSide) - { - return mLastTargetY - Options.MapHeight - spawn.SpawnY; - } + return _lastTargetY - Options.MapHeight - spawn.SpawnY; } } } } - return mLastTargetY - spawn.SpawnY; + return isXAxis ? _lastTargetX - spawn.SpawnX : _lastTargetY - spawn.SpawnY; } + /// + /// Calculates the interpolated (lerped) position value for a projectile along the X or Y axis. + /// + /// The spawn information of the projectile, including its initial position and spawn time. + /// True for horizontal (X-axis), false for vertical (Y-axis) calculation. + /// The interpolated position value for the projectile on the specified axis, taking into account the projectile's travel direction, speed, and elapsed time since spawning. + /// + /// This method determines the projectile's current position by interpolating between its initial offset and its desired position at the current time. + /// It calculates the direction and magnitude of the projectile's movement, normalizes this to obtain a unit vector in the direction of movement, + /// and then applies linear interpolation based on the elapsed time since the projectile was spawned. + /// The interpolation factor is clamped to ensure that the projectile does not overshoot its target position. + /// private float GetProjectileLerping(ProjectileSpawns spawn, bool isXAxis) { - var directionX = GetProjectileX(spawn); - var directionY = GetProjectileY(spawn); - var valueToLerp = isXAxis ? directionX : directionY; + var (directionX, directionY) = (GetProjectileOffset(spawn, true), GetProjectileOffset(spawn, false)); + var distance = MathF.Sqrt(directionX * directionX + directionY * directionY); + if (distance == 0) return 0; - var length = (float)Math.Sqrt(directionX * directionX + directionY * directionY); - valueToLerp /= length; - - var lerpFactor = 0.1f; + var valueToLerp = (isXAxis ? directionX : directionY) / distance; var offset = isXAxis ? spawn.OffsetX : spawn.OffsetY; - var desiredValue = GetDisplacement(spawn.SpawnTime) * valueToLerp; + var desiredValue = GetDisplacement(spawn.SpawnTime + Options.Instance.Processing.ProjectileUpdateInterval) * valueToLerp; + var totalDuration = (float)_myBase.Range * (_myBase.Speed / Options.TileHeight); + var elapsedTime = Timing.Global.Milliseconds - spawn.SpawnTime; + var lerpFactor = Utilities.MathHelper.Clamp(elapsedTime / totalDuration, 0f, 1f); - return offset + (desiredValue - offset) * lerpFactor; + // Dynamically calculated lerp factor + return (1 - lerpFactor) * offset + lerpFactor * desiredValue; } + /// + /// Sets the rotation of a projectile based on its spawn information. + /// + /// The spawn information of the projectile, including its current animation state and position. + /// + /// This method calculates the angle between the projectile's current position and its target, + /// converting the angle from radians to degrees and adjusting it by 90 degrees to align with the game's coordinate system. + /// The calculated angle is then applied to the projectile's animation state to visually orient the projectile towards its target. + /// private void SetProjectileRotation(ProjectileSpawns spawn) { - var directionX = GetProjectileX(spawn); - var directionY = GetProjectileY(spawn); + var (directionX, directionY) = (GetProjectileOffset(spawn, true), GetProjectileOffset(spawn, false)); var angle = (float)(Math.Atan2(directionY, directionX) * (180.0 / Math.PI) + 90); spawn.Anim.SetRotation(angle); } + /// + /// Checks for collision of the projectile with other entities or map boundaries. + /// public void CheckForCollision() { - if (mSpawnCount != 0 || mQuantity < mMyBase.Quantity) + if (_spawnCount == 0 && _quantity > _myBase.Quantity) { - for (var i = 0; i < mSpawnedAmount; i++) + Globals.Entities[Id].Dispose(); + return; + } + + for (var i = 0; i < _spawnedAmount && i < _spawns.Length; i++) + { + var projectileSpawn = _spawns[i]; + if (projectileSpawn == null || Timing.Global.Milliseconds <= projectileSpawn.TransmissionTimer) { - if (Spawns[i] != null && Timing.Global.Milliseconds > Spawns[i].TransmittionTimer) - { - var spawnMap = Maps.MapInstance.Get(Spawns[i].MapId); - if (spawnMap != null) - { - var newx = Spawns[i].X + (int)GetRangeX(Spawns[i].Dir, 1); - var newy = Spawns[i].Y + (int)GetRangeY(Spawns[i].Dir, 1); + continue; + } - if (mMyBase.HomingBehavior || mMyBase.DirectShotBehavior) - { - if (TargetId != Guid.Empty && TargetId != mOwner || mLastTargetX != -1 && mLastTargetY != -1 && mLastTargetMapId != Guid.Empty) - { - float deltaX = GetProjectileX(Spawns[i]); - float deltaY = GetProjectileY(Spawns[i]); - float distance = MathF.Sqrt(deltaX * deltaX + deltaY * deltaY); - float xFactor = deltaX / distance; - float yFactor = deltaY / distance; - - mCurrentCoords[Spawns[i]] = (mCurrentCoords[Spawns[i]].Item1 + xFactor, mCurrentCoords[Spawns[i]].Item2 + yFactor); - newx = (int)Math.Round(mCurrentCoords[Spawns[i]].Item1); - newy = (int)Math.Round(mCurrentCoords[Spawns[i]].Item2); - } - } + if (!Maps.MapInstance.TryGet(projectileSpawn.MapId, out var spawnMap)) + { + continue; + } - var newMapId = Spawns[i].MapId; - var killSpawn = false; + float newx = projectileSpawn.X; + float newy = projectileSpawn.Y; - Spawns[i].Distance++; + if (_myBase.HomingBehavior || _myBase.DirectShotBehavior) + { + if (_targetId != Guid.Empty && _targetId != _owner || _lastTargetX != -1 && _lastTargetY != -1 && _lastTargetMapId != Guid.Empty) + { + var (deltaX, deltaY) = (GetProjectileOffset(projectileSpawn, true), GetProjectileOffset(projectileSpawn, false)); + float distance = MathF.Sqrt(deltaX * deltaX + deltaY * deltaY); + float xFactor = deltaX / distance; + float yFactor = deltaY / distance; + newx += xFactor; + newy += yFactor; + } + } + else + { + newx += GetRange(projectileSpawn.Dir, 1, true); + newy += GetRange(projectileSpawn.Dir, 1, false); + } - if (newx < 0) - { - if (Maps.MapInstance.Get(spawnMap.Left) != null) - { - newMapId = spawnMap.Left; - newx = Options.MapWidth - 1; - mCurrentCoords[Spawns[i]] = (newx, mCurrentCoords[Spawns[i]].Item2); - } - else - { - killSpawn = true; - } - } + AdjustPositionOnMapBoundaries(ref newx, ref newy, ref spawnMap); - if (newx > Options.MapWidth - 1) - { - if (Maps.MapInstance.Get(spawnMap.Right) != null) - { - newMapId = spawnMap.Right; - newx = 0; - mCurrentCoords[Spawns[i]] = (newx, mCurrentCoords[Spawns[i]].Item2); - } - else - { - killSpawn = true; - } - } + projectileSpawn.X = (int)newx; + projectileSpawn.Y = (int)newy; + projectileSpawn.MapId = spawnMap.Id; + projectileSpawn.Distance++; + projectileSpawn.TransmissionTimer = Timing.Global.MillisecondsOffset + (long)(_myBase.Speed / (float)_myBase.Range); - if (newy < 0) - { - if (Maps.MapInstance.Get(spawnMap.Up) != null) - { - newMapId = spawnMap.Up; - newy = Options.MapHeight - 1; - mCurrentCoords[Spawns[i]] = (mCurrentCoords[Spawns[i]].Item1, newy); - } - else - { - killSpawn = true; - } - } + var killSpawn = Collided(i) || projectileSpawn.Distance >= _myBase.Range || + newx < 0 || newx >= Options.MapWidth || newy < 0 || + newy >= Options.MapHeight; - if (newy > Options.MapHeight - 1) - { - if (Maps.MapInstance.Get(spawnMap.Down) != null) - { - newMapId = spawnMap.Down; - newy = 0; - mCurrentCoords[Spawns[i]] = (mCurrentCoords[Spawns[i]].Item1, newy); - } - else - { - killSpawn = true; - } - } + // Check for map boundaries and remove the spawn if it goes out of bounds. + if (killSpawn) + { + TryRemoveSpawn(i); + _spawnCount--; - if (killSpawn) - { - TryRemoveSpawn(i); - mSpawnCount--; + continue; + } - continue; - } + // Check for Z-Dimension + if (!killSpawn && spawnMap.Attributes != null && + projectileSpawn.X >= 0 && projectileSpawn.Y >= 0 && projectileSpawn.X < spawnMap.Attributes.GetLength(0) && + projectileSpawn.Y < spawnMap.Attributes.GetLength(1)) + { + var attribute = spawnMap.Attributes[projectileSpawn.X, projectileSpawn.Y]; - Spawns[i].X = newx; - Spawns[i].Y = newy; - Spawns[i].MapId = newMapId; - var newMap = Maps.MapInstance.Get(newMapId); + if (attribute != null && attribute.Type == MapAttribute.ZDimension) + { + var zDimensionAttribute = (MapZDimensionAttribute)attribute; - //Check for Z-Dimension - if (newMap.Attributes[Spawns[i].X, Spawns[i].Y] != null) - { - if (newMap.Attributes[Spawns[i].X, Spawns[i].Y].Type == MapAttribute.ZDimension) - { - if (((MapZDimensionAttribute) newMap.Attributes[Spawns[i].X, Spawns[i].Y]) - .GatewayTo > - 0) - { - Spawns[i].Z = - ((MapZDimensionAttribute) newMap.Attributes[Spawns[i].X, Spawns[i].Y]) - .GatewayTo - - 1; - } - } - } + // If the Z dimension attribute specifies a blocked level that matches the projectile's current Z level, + // mark the projectile for destruction. + if (zDimensionAttribute.BlockedLevel > 0 && projectileSpawn.Z == zDimensionAttribute.BlockedLevel - 1) + { + killSpawn = true; + } - if (killSpawn == false) - { - killSpawn = Collided(i); - } + // If the Z dimension attribute specifies a gateway to another level, + // adjust the projectile's Z level to the specified gateway level. + if (zDimensionAttribute.GatewayTo > 0) + { + projectileSpawn.Z = zDimensionAttribute.GatewayTo - 1; + } + } + } - Spawns[i].TransmittionTimer = Timing.Global.Milliseconds + - (long) (mMyBase.Speed / (float) mMyBase.Range); + if (killSpawn) + { + TryRemoveSpawn(i); + _spawnCount--; + } + } + } - if (Spawns[i].Distance >= mMyBase.Range) - { - killSpawn = true; - } + /// + /// Adjusts the position of a projectile on the map boundaries, potentially changing its map instance. + /// + /// The new x-coordinate of the projectile. + /// The new y-coordinate of the projectile. + /// The map instance where the projectile is spawned. This may be updated if the projectile crosses map boundaries. + /// + /// This method checks if the projectile crosses the boundaries of its current map. If it does, it attempts to move the projectile + /// to the adjacent map in the direction it crossed the boundary. This includes handling corner cases where the projectile crosses + /// at the corners of the map, potentially moving it diagonally to a new map. The new position of the projectile is adjusted to + /// visually reflect its entry point into the adjacent map. + /// + private static void AdjustPositionOnMapBoundaries(ref float newx, ref float newy, ref Maps.MapInstance spawnMap) + { + int MapWidth = Options.MapWidth; + int MapHeight = Options.MapHeight; - if (killSpawn) - { - TryRemoveSpawn(i); - mSpawnCount--; - } - } - } + // Determine if the projectile crosses any of the map boundaries. + bool crossesLeftBoundary = MathF.Floor(newx) < 0; + bool crossesRightBoundary = MathF.Ceiling(newx) > MapWidth - 1; + bool crossesTopBoundary = MathF.Floor(newy) < 0; + bool crossesBottomBoundary = MathF.Ceiling(newy) > MapHeight - 1; + + // Handle corner cases: crossing boundaries at the corners of the map. + if (crossesLeftBoundary && crossesTopBoundary) + { + // Move to the map diagonally up-left if possible. + if (Maps.MapInstance.TryGet(spawnMap.Up, out var upMap) && + Maps.MapInstance.TryGet(upMap.Left, out var upLeftMap)) + { + spawnMap = upLeftMap; + newx = MapWidth - 1; + newy = MapHeight - 1; } } - else + else if (crossesRightBoundary && crossesTopBoundary) { - Globals.Entities[Id].Dispose(); + // Move to the map diagonally up-right if possible. + if (Maps.MapInstance.TryGet(spawnMap.Up, out var upMap) && + Maps.MapInstance.TryGet(upMap.Right, out var upRightMap)) + { + spawnMap = upRightMap; + newx = 0; + newy = MapHeight - 1; + } + } + else if (crossesLeftBoundary && crossesBottomBoundary) + { + // Move to the map diagonally down-left if possible. + if (Maps.MapInstance.TryGet(spawnMap.Down, out var downMap) && + Maps.MapInstance.TryGet(downMap.Left, out var downLeftMap)) + { + spawnMap = downLeftMap; + newx = MapWidth - 1; + newy = 0; + } + } + else if (crossesRightBoundary && crossesBottomBoundary) + { + // Move to the map diagonally down-right if possible. + if (Maps.MapInstance.TryGet(spawnMap.Down, out var downMap) && + Maps.MapInstance.TryGet(downMap.Right, out var downRightMap)) + { + spawnMap = downRightMap; + newx = 0; + newy = 0; + } + } + // Handle cases where the projectile crosses one boundary. + else if (crossesLeftBoundary) + { + // Move to the map on the left if possible. + if (Maps.MapInstance.TryGet(spawnMap.Left, out var leftMap)) + { + spawnMap = leftMap; + newx = MapWidth - 1; + } + } + else if (crossesRightBoundary) + { + // Move to the map on the right if possible. + if (Maps.MapInstance.TryGet(spawnMap.Right, out var rightMap)) + { + spawnMap = rightMap; + newx = 0; + } + } + else if (crossesTopBoundary) + { + // Move to the map above if possible. + if (Maps.MapInstance.TryGet(spawnMap.Up, out var upMap)) + { + spawnMap = upMap; + newy = MapHeight - 1; + } + } + else if (crossesBottomBoundary) + { + // Move to the map below if possible. + if (Maps.MapInstance.TryGet(spawnMap.Down, out var downMap)) + { + spawnMap = downMap; + newy = 0; + } } } + /// + /// Determines if a projectile spawn has collided with an entity, resource, or map block. + /// + /// The index of the projectile spawn in the _spawns array. + /// True if the projectile spawn has collided and should be destroyed; otherwise, false. private bool Collided(int i) { var killSpawn = false; IEntity? blockedBy = default; - var spawn = Spawns[i]; + var spawn = _spawns[i]; + + // Check if the tile at the projectile's location is blocked. var tileBlocked = Globals.Me.IsTileBlocked( - new Point(spawn.X, spawn.Y), - Z, - Spawns[i].MapId, - ref blockedBy, - spawn.ProjectileBase.IgnoreActiveResources, - spawn.ProjectileBase.IgnoreExhaustedResources, - true, - true + delta: new Point(spawn.X, spawn.Y), + z: Z, + mapId: spawn.MapId, + blockedBy: ref blockedBy, + ignoreAliveResources: spawn.ProjectileBase.IgnoreActiveResources, + ignoreDeadResources: spawn.ProjectileBase.IgnoreExhaustedResources, + ignoreNpcAvoids: true, + projectileTrigger: true ); switch (tileBlocked) { - case -1: + case -1: // No collision detected return killSpawn; - case -6 when - blockedBy != default && - blockedBy.Id != mOwner && - Globals.Entities.ContainsKey(blockedBy.Id): - { - if (blockedBy is Resource) + case -6: // Collision with an entity other than the owner + if (blockedBy != default && blockedBy.Id != _owner && Globals.Entities.ContainsKey(blockedBy.Id)) { - killSpawn = true; + if (blockedBy is Resource) + { + killSpawn = true; + } } - break; - } - case -2: - { - if (!Spawns[i].ProjectileBase.IgnoreMapBlocks) + case -2: // Collision with a map block + if (!spawn.ProjectileBase.IgnoreMapBlocks) { killSpawn = true; } - break; - } - case -3: - { - if (!Spawns[i].ProjectileBase.IgnoreZDimension) + case -3: // Collision with a Z-dimension block + if (!spawn.ProjectileBase.IgnoreZDimension) { killSpawn = true; } - break; - } - case -5: + case -5: // Collision with an unspecified block type or out of map bounds killSpawn = true; break; } @@ -681,7 +732,7 @@ private bool Collided(int i) } /// - /// Rendering all of the individual projectiles from a singular spawn to a map. + /// Rendering all of the individual projectiles from a singular spawn to a map. /// public override void Draw() { @@ -693,7 +744,7 @@ public override void Draw() public void SpawnDead(int spawnIndex) { - if (spawnIndex < mSpawnedAmount && Spawns[spawnIndex] != null) + if (spawnIndex < _spawnedAmount && _spawns[spawnIndex] != null) { TryRemoveSpawn(spawnIndex); } @@ -701,11 +752,10 @@ public void SpawnDead(int spawnIndex) private void TryRemoveSpawn(int spawnIndex) { - if (spawnIndex < mSpawnedAmount && Spawns[spawnIndex] != null) + if (spawnIndex < _spawnedAmount && _spawns[spawnIndex] != null) { - if (mCurrentCoords.ContainsKey(Spawns[spawnIndex])) mCurrentCoords.Remove(Spawns[spawnIndex]); - Spawns[spawnIndex].Dispose(); - Spawns[spawnIndex] = null; + _spawns[spawnIndex].Dispose(); + _spawns[spawnIndex] = null; } } diff --git a/Intersect.Client/Entities/Projectiles/ProjectileSpawns.cs b/Intersect.Client/Entities/Projectiles/ProjectileSpawns.cs index 9d9cf0709d..221fa1f4a6 100644 --- a/Intersect.Client/Entities/Projectiles/ProjectileSpawns.cs +++ b/Intersect.Client/Entities/Projectiles/ProjectileSpawns.cs @@ -1,5 +1,4 @@ -using System; -using Intersect.Enums; +using Intersect.Enums; using Intersect.GameObjects; using Intersect.Utilities; @@ -34,7 +33,7 @@ public partial class ProjectileSpawns public int SpawnY; - public long TransmittionTimer = Timing.Global.Milliseconds; + public long TransmissionTimer = Timing.Global.Milliseconds; public int X; @@ -65,8 +64,7 @@ Entity parent Anim = new Animation(animBase, true, autoRotate, Z, parent); AutoRotate = autoRotate; ProjectileBase = projectileBase; - TransmittionTimer = Timing.Global.Milliseconds + - (long) ((float) ProjectileBase.Speed / (float) ProjectileBase.Range); + TransmissionTimer = Timing.Global.Milliseconds + (ProjectileBase.Speed / ProjectileBase.Range); } public void Dispose() diff --git a/Intersect.Server.Core/Entities/Projectile.cs b/Intersect.Server.Core/Entities/Projectile.cs index c7bb4b3e6d..70f0b79422 100644 --- a/Intersect.Server.Core/Entities/Projectile.cs +++ b/Intersect.Server.Core/Entities/Projectile.cs @@ -20,15 +20,15 @@ public partial class Projectile : Entity public ItemBase Item; - private int mQuantity; + private int _quantity; - private int mSpawnCount; + private int _spawnCount; - private int mSpawnedAmount; + private int _spawnedAmount; - private long mSpawnTime; + private long _spawnTime; - private int mTotalSpawns; + private int _totalSpawns; public Entity Owner; @@ -37,13 +37,13 @@ public partial class Projectile : Entity public SpellBase Spell; - public Entity Target; + public new Entity Target; - private int mLastTargetX = -1; + private int _lastTargetX = -1; - private int mLastTargetY = -1; + private int _lastTargetY = -1; - private Guid mLastTargetMapId = Guid.Empty; + private Guid _lastTargetMapId = Guid.Empty; public Projectile( Entity owner, @@ -83,35 +83,46 @@ Entity target { if (Base.SpawnLocations[x, y].Directions[d] == true) { - mTotalSpawns++; + _totalSpawns++; } } } } - mTotalSpawns *= Base.Quantity; - Spawns = new ProjectileSpawn[mTotalSpawns]; + _totalSpawns *= Base.Quantity; + Spawns = new ProjectileSpawn[_totalSpawns]; } - private void AddProjectileSpawns(List> spawnDeaths) + /// + /// Adds projectile spawns based on predefined spawn locations and directions. + /// + private void AddProjectileSpawns() { + // Iterate over all possible spawn locations within the defined width and height for (byte x = 0; x < ProjectileBase.SPAWN_LOCATIONS_WIDTH; x++) { for (byte y = 0; y < ProjectileBase.SPAWN_LOCATIONS_HEIGHT; y++) { + // Iterate over all possible directions a projectile can be spawned in for (byte d = 0; d < ProjectileBase.MAX_PROJECTILE_DIRECTIONS; d++) { - if (Base.SpawnLocations[x, y].Directions[d] == true && mSpawnedAmount < Spawns.Length) + // Check if the current direction is enabled for spawning at this location + // and if the maximum number of spawned projectiles has not been reached + if (Base.SpawnLocations[x, y].Directions[d] && _spawnedAmount < Spawns.Length) { + // Calculate the spawn position and direction for the new projectile var s = new ProjectileSpawn( FindProjectileRotationDir(Dir, (Direction)d), - (byte) (X + FindProjectileRotationX(Dir, x - 2, y - 2)), - (byte) (Y + FindProjectileRotationY(Dir, x - 2, y - 2)), (byte) Z, MapId, MapInstanceId, Base, this + (byte)(X + FindProjectileRotation(Dir, x - 2, y - 2, true)), + (byte)(Y + FindProjectileRotation(Dir, x - 2, y - 2, false)), + (byte)Z, MapId, MapInstanceId, Base, this ); - Spawns[mSpawnedAmount] = s; - mSpawnedAmount++; - mSpawnCount++; + // Add the new spawn to the array and increment counters + Spawns[_spawnedAmount] = s; + _spawnedAmount++; + _spawnCount++; + if (CheckForCollision(s)) { s.Dead = true; @@ -121,49 +132,42 @@ private void AddProjectileSpawns(List> spawnDeaths) } } - mQuantity++; - mSpawnTime = Timing.Global.Milliseconds + Base.Delay; + // Increment the quantity of projectiles spawned and update the spawn time based on the delay + _quantity++; + _spawnTime = Timing.Global.Milliseconds + Base.Delay; } - private static int FindProjectileRotationX(Direction direction, int x, int y) + /// + /// Finds the projectile rotation value based on the direction, position and the axis. + /// + /// The direction of the projectile. + /// The x-coordinate value. + /// The y-coordinate value. + /// True for horizontal (X-axis), false for vertical (Y-axis) calculation. + /// The rotation value for the specified axis based on the direction. + private static int FindProjectileRotation(Direction direction, int x, int y, bool isXAxis) { - switch (direction) + if (isXAxis) { - case Direction.Up: - return x; - case Direction.Down: - return -x; - case Direction.Left: - case Direction.UpLeft: - case Direction.DownLeft: - return y; - case Direction.Right: - case Direction.UpRight: - case Direction.DownRight: - return -y; - default: - return x; + return direction switch + { + Direction.Up => x, + Direction.Down => -x, + Direction.Left or Direction.UpLeft or Direction.DownLeft => y, + Direction.Right or Direction.UpRight or Direction.DownRight => -y, + _ => x, + }; } - } - - private static int FindProjectileRotationY(Direction direction, int x, int y) - { - switch (direction) + else { - case Direction.Up: - return y; - case Direction.Down: - return -y; - case Direction.Left: - case Direction.UpLeft: - case Direction.DownLeft: - return -x; - case Direction.Right: - case Direction.UpRight: - case Direction.DownRight: - return x; - default: - return y; + return direction switch + { + Direction.Up => y, + Direction.Down => -y, + Direction.Left or Direction.UpLeft or Direction.DownLeft => -x, + Direction.Right or Direction.UpRight or Direction.DownRight => x, + _ => y, + }; } } @@ -172,49 +176,40 @@ private static Direction FindProjectileRotationDir(Direction entityDir, Directio public void Update(List projDeaths, List> spawnDeaths) { - if (mQuantity < Base.Quantity && Timing.Global.Milliseconds > mSpawnTime) + if (_quantity < Base.Quantity && Timing.Global.Milliseconds > _spawnTime) { - AddProjectileSpawns(spawnDeaths); + AddProjectileSpawns(); } ProcessFragments(projDeaths, spawnDeaths); } - private static float GetRangeX(Direction direction, float range) + /// + /// Calculates the projectile range based on the given direction, range, and axis. + /// + /// The direction of the projectile. + /// The range of the projectile. + /// True for horizontal (X-axis), false for vertical (Y-axis) calculation. + /// The calculated range value. + private static float GetRange(Direction direction, float range, bool isXAxis) { - switch (direction) + if (isXAxis) { - case Direction.Left: - case Direction.UpLeft: - case Direction.DownLeft: - return -range; - case Direction.Right: - case Direction.UpRight: - case Direction.DownRight: - return range; - case Direction.Up: - case Direction.Down: - default: - return 0; + return direction switch + { + Direction.Left or Direction.UpLeft or Direction.DownLeft => -range, + Direction.Right or Direction.UpRight or Direction.DownRight => range, + _ => 0, + }; } - } - - private static float GetRangeY(Direction direction, float range) - { - switch (direction) + else { - case Direction.Up: - case Direction.UpLeft: - case Direction.UpRight: - return -range; - case Direction.Down: - case Direction.DownLeft: - case Direction.DownRight: - return range; - case Direction.Left: - case Direction.Right: - default: - return 0; + return direction switch + { + Direction.Up or Direction.UpLeft or Direction.UpRight => -range, + Direction.Down or Direction.DownLeft or Direction.DownRight => range, + _ => 0, + }; } } @@ -225,14 +220,14 @@ public void ProcessFragments(List projDeaths, List return; } - if (mSpawnCount != 0 || mQuantity < Base.Quantity) + if (_spawnCount != 0 || _quantity < Base.Quantity) { - for (var i = 0; i < mSpawnedAmount; i++) + for (var i = 0; i < _spawnedAmount; i++) { var spawn = Spawns[i]; if (spawn != null) { - while (Timing.Global.Milliseconds > spawn.TransmittionTimer && Spawns[i] != null) + while (Timing.Global.Milliseconds > spawn.TransmissionTimer && Spawns[i] != null) { var x = spawn.X; var y = spawn.Y; @@ -247,11 +242,11 @@ public void ProcessFragments(List projDeaths, List } } - if (killSpawn || spawn.Dead) + if (killSpawn || spawn.Dead || CheckForCollision(spawn)) { spawnDeaths.Add(new KeyValuePair(Id, i)); Spawns[i] = null; - mSpawnCount--; + _spawnCount--; } } } @@ -267,9 +262,14 @@ public void ProcessFragments(List projDeaths, List } } + /// + /// Checks for collision between the projectile with other entities and attributes on the map. + /// + /// The projectile spawn information. + /// True if the projectile collides with an entity or attributes, false otherwise. public bool CheckForCollision(ProjectileSpawn spawn) { - if(spawn == null) + if (spawn == null) { return false; } @@ -278,24 +278,51 @@ public bool CheckForCollision(ProjectileSpawn spawn) //Check Map Entities For Hits var map = MapController.Get(spawn.MapId); - if (Math.Round(spawn.X) < 0 || Math.Round(spawn.X) >= Options.Instance.MapOpts.MapWidth || - Math.Round(spawn.Y) < 0 || Math.Round(spawn.Y) >= Options.Instance.MapOpts.MapHeight) + if (map == null) + { + return false; + } + + var roundedX = MathF.Round(spawn.X); + var roundedY = MathF.Round(spawn.Y); + + //Checking if the coordinates are within the map boundaries + if (roundedX < 0 || roundedX >= Options.MapWidth || + roundedY < 0 || roundedY >= Options.MapHeight) { return false; } - var attribute = map.Attributes[(int)Math.Round(spawn.X), (int)Math.Round(spawn.Y)]; + + GameObjects.Maps.MapAttribute attribute; + + // Before accessing map attributes, check if the coordinates are within the bounds of the map's attribute array. + if (roundedX >= 0 && roundedX < map.Attributes.GetLength(0) && + roundedY >= 0 && roundedY < map.Attributes.GetLength(1)) + { + attribute = map.Attributes[(int)roundedX, (int)roundedY]; + } + else + { + attribute = null; + } if (!killSpawn && attribute != null) { - //Check for Z-Dimension - if (!spawn.ProjectileBase.IgnoreZDimension) + // Check for Z-Dimension + if (!spawn.ProjectileBase.IgnoreZDimension && attribute is MapZDimensionAttribute zDimAttr && zDimAttr != null) { - if (attribute.Type == MapAttribute.ZDimension) + // If the Z dimension attribute specifies a blocked level that matches the projectile's current Z level, + // mark the projectile for destruction. + if (zDimAttr.BlockedLevel > 0 && spawn.Z == zDimAttr.BlockedLevel - 1) { - if (((MapZDimensionAttribute) attribute).GatewayTo > 0) - { - spawn.Z = (byte) (((MapZDimensionAttribute) attribute).GatewayTo - 1); - } + killSpawn = true; + } + + // If the Z dimension attribute specifies a gateway to another level, + // adjust the projectile's Z level to the specified gateway level. + if (zDimAttr.GatewayTo > 0) + { + spawn.Z = (byte)(zDimAttr.GatewayTo - 1); } } @@ -305,9 +332,9 @@ public bool CheckForCollision(ProjectileSpawn spawn) !spawn.Parent.HasGrappled && (spawn.X != Owner.X || spawn.Y != Owner.Y)) { - if (!spawn.ProjectileBase.HomingBehavior && !spawn.ProjectileBase.DirectShotBehavior && - (spawn.Dir <= Direction.Right || spawn.Dir != Direction.None && Options.Instance.MapOpts.EnableDiagonalMovement) - ) + // Grapple hooks are only allowed in the default projectile behavior + if (!spawn.ProjectileBase.HomingBehavior && !spawn.ProjectileBase.DirectShotBehavior && + (spawn.Dir <= Direction.Right || (spawn.Dir != Direction.None && Options.Instance.MapOpts.EnableDiagonalMovement))) { spawn.Parent.HasGrappled = true; @@ -326,7 +353,7 @@ public bool CheckForCollision(ProjectileSpawn spawn) } if (!spawn.ProjectileBase.IgnoreMapBlocks && - (attribute.Type == MapAttribute.Blocked || attribute.Type == MapAttribute.Animation && ((MapAnimationAttribute)attribute).IsBlock)) + ((attribute.Type == MapAttribute.Blocked) || (attribute.Type == MapAttribute.Animation && ((MapAnimationAttribute)attribute).IsBlock))) { killSpawn = true; } @@ -337,222 +364,263 @@ public bool CheckForCollision(ProjectileSpawn spawn) var entities = mapInstance.GetEntities(); for (var z = 0; z < entities.Count; z++) { - if (entities[z] != null && entities[z] != spawn.Parent.Owner && entities[z].Z == spawn.Z && - (entities[z].X == Math.Round(spawn.X)) && - (entities[z].Y == Math.Round(spawn.Y)) && + var entity = entities[z]; + if (entity != null && entity != spawn.Parent.Owner && entity.Z == spawn.Z && + entity.X == roundedX && entity.Y == roundedY && (spawn.X != Owner.X || spawn.Y != Owner.Y)) { - killSpawn = spawn.HitEntity(entities[z]); + killSpawn = spawn.HitEntity(entity); if (killSpawn && !spawn.ProjectileBase.PierceTarget) { return killSpawn; } } - else + else if (z == entities.Count - 1 && spawn.Distance >= Base.Range) { - if (z == entities.Count - 1) - { - if (spawn.Distance >= Base.Range) - { - killSpawn = true; - } - } + killSpawn = true; } } } - return killSpawn; + return killSpawn || spawn?.Distance >= Base.Range; } - private float GetProjectileX(ProjectileSpawn spawn) + /// + /// Calculates the offset for the projectile spawn position based on the target map and spawn location. + /// + /// The projectile spawn information. + /// True for horizontal (X-axis), false for vertical (Y-axis) calculation. + /// The calculated offset value. + private float GetProjectileOffset(ProjectileSpawn spawn, bool isXAxis) { - if (mLastTargetMapId != Guid.Empty && mLastTargetMapId != spawn.MapId) + if (_lastTargetMapId == Guid.Empty || _lastTargetMapId == spawn.MapId || !MapController.TryGet(spawn.MapId, out var map)) { - var map = MapController.Get(spawn.MapId); - var grid = DbInterface.GetGrid(map.MapGrid); + return isXAxis ? _lastTargetX - spawn.X : _lastTargetY - spawn.Y; + } + + var grid = DbInterface.GetGrid(map.MapGrid); - //loop through surrounding maps - for (var y = map.MapGridY - 1; y <= map.MapGridY + 1; y++) + for (var y = map.MapGridY - 1; y <= map.MapGridY + 1; y++) + { + for (var x = map.MapGridX - 1; x <= map.MapGridX + 1; x++) { - for (var x = map.MapGridX - 1; x <= map.MapGridX + 1; x++) + if (x < 0 || x >= grid.MapIdGrid.GetLength(0) || y < 0 || y >= grid.MapIdGrid.GetLength(1)) + { + continue; + } + + if (grid.MapIdGrid[x, y] != _lastTargetMapId || grid.MapIdGrid[x, y] == Guid.Empty) + { + continue; + } + + if (isXAxis) { - if (x < 0 || x >= grid.MapIdGrid.GetLength(0) || y < 0 || y >= grid.MapIdGrid.GetLength(1)) + var leftSide = x == map.MapGridX - 1; + var rightSide = x == map.MapGridX + 1; + + if (leftSide) { - continue; + return _lastTargetX - Options.MapWidth - spawn.X; } - if (grid.MapIdGrid[x, y] != Guid.Empty && grid.MapIdGrid[x, y] == mLastTargetMapId) + if (rightSide) { - var leftSide = x == map.MapGridX - 1; - var rightSide = x == map.MapGridX + 1; - - if (leftSide) - { - return mLastTargetX - Options.MapWidth - spawn.X; - } - - if (rightSide) - { - return mLastTargetX + Options.MapWidth - spawn.X; - } + return _lastTargetX + Options.MapWidth - spawn.X; } } - } - } - - return mLastTargetX - spawn.X; - } - - private float GetProjectileY(ProjectileSpawn spawn) - { - if (mLastTargetMapId != Guid.Empty && mLastTargetMapId != spawn.MapId) - { - var map = MapController.Get(spawn.MapId); - var grid = DbInterface.GetGrid(map.MapGrid); - - //loop through surrounding maps - for (var y = map.MapGridY - 1; y <= map.MapGridY + 1; y++) - { - for (var x = map.MapGridX - 1; x <= map.MapGridX + 1; x++) + else { - if (x < 0 || x >= grid.MapIdGrid.GetLength(0) || y < 0 || y >= grid.MapIdGrid.GetLength(1)) + var topSide = y == map.MapGridY - 1; + var bottomSide = y == map.MapGridY + 1; + + if (topSide) { - continue; + return _lastTargetY - Options.MapHeight - spawn.Y; } - if (grid.MapIdGrid[x, y] != Guid.Empty && grid.MapIdGrid[x, y] == mLastTargetMapId) + if (bottomSide) { - var topSide = y == map.MapGridY - 1; - var bottomSide = y == map.MapGridY + 1; - - if (topSide) - { - return mLastTargetY - Options.MapHeight - spawn.Y; - } - - if (bottomSide) - { - return mLastTargetY + Options.MapHeight - spawn.Y; - } + return _lastTargetY + Options.MapHeight - spawn.Y; } } } } - return mLastTargetY - spawn.Y; + return isXAxis ? _lastTargetX - spawn.X : _lastTargetY - spawn.Y; } + /// + /// Moves the projectile fragment based on the specified spawn information. This method is responsible for + /// updating the position of the projectile, handling homing and direct shot behaviors, and checking for out-of-bounds conditions. + /// + /// The projectile spawn information, including current position, direction, and other relevant details. + /// Indicates whether the fragment should be moved. This can be used to pause movement if needed. Default is true. + /// True if the projectile goes out of bounds after moving, false otherwise. public bool MoveFragment(ProjectileSpawn spawn, bool move = true) { - float newx = spawn.X; - float newy = spawn.Y; - var newMapId = spawn.MapId; - if (move) { + // Increase the distance traveled by the projectile and update its transmission timer. spawn.Distance++; - spawn.TransmittionTimer += (long)(Base.Speed / (float)Base.Range); - + spawn.TransmissionTimer += (long)(Base.Speed / (float)Base.Range); + if (Target != default && Target.Id != Owner.Id && (Base.HomingBehavior || Base.DirectShotBehavior)) { - //homing logic - mLastTargetX = Target.X; - mLastTargetY = Target.Y; - mLastTargetMapId = Target.MapId; - var directionX = GetProjectileX(spawn); - var directionY = GetProjectileY(spawn); - var length = Math.Sqrt(directionX * directionX + directionY * directionY); - - newx += (float)(directionX / length); - newy += (float)(directionY / length); - + // Homing or direct shot logic: Adjusts the projectile's trajectory towards the target. + _lastTargetX = Target.X; + _lastTargetY = Target.Y; + _lastTargetMapId = Target.MapId; + var (directionX, directionY) = (GetProjectileOffset(spawn, true), GetProjectileOffset(spawn, false)); + var distance = MathF.Sqrt(directionX * directionX + directionY * directionY); + + // Normalize the direction and update the projectile's position. + spawn.X += directionX / distance; + spawn.Y += directionY / distance; + + // For direct shots, reset the target after moving towards it once. if (Base.DirectShotBehavior) { Target = default; } } - else if (mLastTargetX != -1 && mLastTargetY != -1) + else if (_lastTargetX != -1 && _lastTargetY != -1) { - //last target location logic - var directionX = GetProjectileX(spawn); - var directionY = GetProjectileY(spawn); - var length = Math.Sqrt(directionX * directionX + directionY * directionY); + // Last known target location logic: Moves the projectile towards the last known position of the target. + var (directionX, directionY) = (GetProjectileOffset(spawn, true), GetProjectileOffset(spawn, false)); + var distance = MathF.Sqrt(directionX * directionX + directionY * directionY); - newx += (float)(directionX / length); - newy += (float)(directionY / length); + // Normalize the direction and update the projectile's position. + spawn.X += directionX / distance; + spawn.Y += directionY / distance; } else { - // Default logic - newx = spawn.X + GetRangeX(spawn.Dir, 1); - newy = spawn.Y + GetRangeY(spawn.Dir, 1); + // Default movement logic: Moves the projectile in its current direction. + spawn.X += GetRange(spawn.Dir, 1, true); + spawn.Y += GetRange(spawn.Dir, 1, false); } } - var killSpawn = false; + // Adjust the projectile's position if it crosses map boundaries and potentially transitions it to a neighboring map. + AdjustPositionOnMapBoundaries(ref spawn.X, ref spawn.Y, ref spawn); + + // Check for map boundaries and remove the spawn if the projectile has gone out of bounds after moving. + return spawn.X < 0 || spawn.X >= Options.MapWidth || spawn.Y < 0 || spawn.Y >= Options.MapHeight; + } + + /// + /// Adjusts the position of a projectile when it crosses the boundaries of the current map. + /// This method checks if the projectile crosses the map boundaries and, if so, attempts to + /// transition the projectile to the adjacent map in the direction it is moving. The new position + /// on the adjacent map is calculated to maintain the continuity of the projectile's path. + /// + /// The new x-coordinate of the projectile, which may be adjusted if crossing map boundaries. + /// The new y-coordinate of the projectile, which may be adjusted if crossing map boundaries. + /// The projectile spawn information, including the current map ID, which may be updated to a new map. + private static void AdjustPositionOnMapBoundaries(ref float newx, ref float newy, ref ProjectileSpawn spawn) + { + // Retrieve the current map based on the projectile's spawn information. var map = MapController.Get(spawn.MapId); + int MapWidth = Options.MapWidth; + int MapHeight = Options.MapHeight; + + // Determine if the projectile crosses any of the map's boundaries. + bool crossesLeftBoundary = MathF.Floor(newx) < 0; + bool crossesRightBoundary = MathF.Ceiling(newx) > MapWidth - 1; + bool crossesTopBoundary = MathF.Floor(newy) < 0; + bool crossesBottomBoundary = MathF.Ceiling(newy) > MapHeight - 1; - if (Math.Floor(newx) < 0) + // Check for corner cases where the projectile crosses two boundaries. + if (crossesLeftBoundary && crossesTopBoundary) { - var leftMap = MapController.Get(map.Left); - if (leftMap != null) + // Projectile crosses the top-left corner of the map. + if (MapController.TryGet(map.Up, out var upMap) && + MapController.TryGet(upMap.Left, out var upLeftMap)) { - newMapId = leftMap.Id; - newx = Options.MapWidth - 1; + // Transition to the map diagonally up-left and adjust position to bottom-right corner. + spawn.MapId = upLeftMap.Id; + newx = MapWidth - 1; + newy = MapHeight - 1; } - else + } + else if (crossesRightBoundary && crossesTopBoundary) + { + // Projectile crosses the top-right corner of the map. + if (MapController.TryGet(map.Up, out var upMap) && + MapController.TryGet(upMap.Right, out var upRightMap)) { - killSpawn = true; + // Transition to the map diagonally up-right and adjust position to bottom-left corner. + spawn.MapId = upRightMap.Id; + newx = 0; + newy = MapHeight - 1; } } - - if (Math.Ceiling(newx) > Options.MapWidth) + else if (crossesLeftBoundary && crossesBottomBoundary) { - var rightMap = MapController.Get(map.Right); - if (rightMap != null) + // Projectile crosses the bottom-left corner of the map. + if (MapController.TryGet(map.Down, out var downMap) && + MapController.TryGet(downMap.Left, out var downLeftMap)) { - newMapId = rightMap.Id; - newx = 0; + // Transition to the map diagonally down-left and adjust position to top-right corner. + spawn.MapId = downLeftMap.Id; + newx = MapWidth - 1; + newy = 0; } - else + } + else if (crossesRightBoundary && crossesBottomBoundary) + { + // Projectile crosses the bottom-right corner of the map. + if (MapController.TryGet(map.Down, out var downMap) && + MapController.TryGet(downMap.Right, out var downRightMap)) { - killSpawn = true; + // Transition to the map diagonally down-right and adjust position to top-left corner. + spawn.MapId = downRightMap.Id; + newx = 0; + newy = 0; } } - - if (Math.Floor(newy) < 0) + // Check for single boundary crossings and adjust position accordingly. + else if (crossesLeftBoundary) { - var upMap = MapController.Get(map.Up); - if (upMap != null) + // Projectile crosses the left boundary of the map. + if (MapController.TryGet(map.Left, out var leftMap)) { - newMapId = upMap.Id; - newy = Options.MapHeight - 1; + // Transition to the map on the left and adjust position to the right edge. + spawn.MapId = leftMap.Id; + newx = MapWidth - 1; } - else + } + else if (crossesRightBoundary) + { + // Projectile crosses the right boundary of the map. + if (MapController.TryGet(map.Right, out var rightMap)) { - killSpawn = true; + // Transition to the map on the right and adjust position to the left edge. + spawn.MapId = rightMap.Id; + newx = 0; } } - - if (Math.Ceiling(newy) > Options.MapHeight) + else if (crossesTopBoundary) { - var downMap = MapController.Get(map.Down); - if (downMap != null) + // Projectile crosses the top boundary of the map. + if (MapController.TryGet(map.Up, out var upMap)) { - newMapId = downMap.Id; - newy = 0; + // Transition to the map above and adjust position to the bottom edge. + spawn.MapId = upMap.Id; + newy = MapHeight - 1; } - else + } + else if (crossesBottomBoundary) + { + // Projectile crosses the bottom boundary of the map. + if (MapController.TryGet(map.Down, out var downMap)) { - killSpawn = true; + // Transition to the map below and adjust position to the top edge. + spawn.MapId = downMap.Id; + newy = 0; } } - - spawn.X = newx; - spawn.Y = newy; - spawn.MapId = newMapId; - - return killSpawn; } public override void Die(bool dropItems = true, Entity killer = null) @@ -570,16 +638,12 @@ public override void Die(bool dropItems = true, Entity killer = null) public override EntityPacket EntityPacket(EntityPacket packet = null, Player forPlayer = null) { - if (packet == null) - { - packet = new ProjectileEntityPacket(); - } - + packet ??= new ProjectileEntityPacket(); packet = base.EntityPacket(packet, forPlayer); - var pkt = (ProjectileEntityPacket) packet; + var pkt = (ProjectileEntityPacket)packet; pkt.ProjectileId = Base.Id; - pkt.ProjectileDirection = (byte) Dir; + pkt.ProjectileDirection = (byte)Dir; pkt.TargetId = Owner.Target != default && Owner.Target.Id != Guid.Empty ? Owner.Target.Id : Guid.Empty; pkt.OwnerId = Owner?.Id ?? Guid.Empty; diff --git a/Intersect.Server.Core/Entities/ProjectileSpawn.cs b/Intersect.Server.Core/Entities/ProjectileSpawn.cs index 3e3e0bdf16..9cb1ab249e 100644 --- a/Intersect.Server.Core/Entities/ProjectileSpawn.cs +++ b/Intersect.Server.Core/Entities/ProjectileSpawn.cs @@ -1,5 +1,3 @@ -using System; -using System.Collections.Generic; using Intersect.Enums; using Intersect.GameObjects; using Intersect.Server.Entities.Combat; @@ -22,7 +20,7 @@ public partial class ProjectileSpawn public ProjectileBase ProjectileBase; - public long TransmittionTimer = Timing.Global.Milliseconds; + public long TransmissionTimer = Timing.Global.Milliseconds; public float X; @@ -34,7 +32,7 @@ public partial class ProjectileSpawn public Guid MapInstanceId; - private List mEntitiesCollided = new List(); + private List _entitiesCollided = new List(); public ProjectileSpawn( Direction dir, @@ -55,8 +53,8 @@ Projectile parent Dir = dir; ProjectileBase = projectileBase; Parent = parent; - TransmittionTimer = Timing.Global.Milliseconds + - (long) ((float) ProjectileBase.Speed / (float) ProjectileBase.Range); + TransmissionTimer = Timing.Global.Milliseconds + + (long)(ProjectileBase.Speed / (float)ProjectileBase.Range); } public bool IsAtLocation(Guid mapId, int x, int y, int z) @@ -76,13 +74,13 @@ public bool HitEntity(Entity targetEntity) if (targetEntity != null && targetEntity != Parent.Owner) { // Have we collided with this entity before? If so, cancel out. - if (mEntitiesCollided.Contains(targetEntity.Id)) + if (_entitiesCollided.Contains(targetEntity.Id)) { if (!Parent.Base.PierceTarget) { - if(targetPlayer != null) + if (targetPlayer != null) { - if(targetPlayer.Map.ZoneType == Enums.MapZone.Safe || + if (targetPlayer.Map.ZoneType == Enums.MapZone.Safe || Parent.Owner is Player plyr && plyr.InParty(targetPlayer)) { return false; @@ -96,7 +94,7 @@ public bool HitEntity(Entity targetEntity) return false; } } - mEntitiesCollided.Add(targetEntity.Id); + _entitiesCollided.Add(targetEntity.Id); if (targetPlayer != null) { @@ -123,9 +121,9 @@ public bool HitEntity(Entity targetEntity) } else if (targetEntity is Resource targetResource) { - if(targetResource.IsDead()) + if (targetResource.IsDead()) { - if(!ProjectileBase.IgnoreExhaustedResources) + if (!ProjectileBase.IgnoreExhaustedResources) { return true; } @@ -144,13 +142,12 @@ public bool HitEntity(Entity targetEntity) } else //Any other Parent.Target { - var ownerNpc = Parent.Owner as Npc; - if (ownerNpc == null || + if (Parent.Owner is not Npc ownerNpc || ownerNpc.CanNpcCombat(targetEntity, Parent.Spell != null && Parent.Spell.Combat.Friendly)) { Parent.Owner.TryAttack(targetEntity, Parent.Base, Parent.Spell, Parent.Item, Dir); - if (Dir <= Direction.Right && ShouldHook(targetEntity) && !Parent.HasGrappled) + if (Dir <= Direction.Right && ShouldHook(targetEntity) && !Parent.HasGrappled) { HookEntity(); } @@ -173,25 +170,18 @@ public bool HitEntity(Entity targetEntity) /// public bool ShouldHook(Entity en) { - if(en == null) + if (en == null) { return false; } - switch(en) + return en switch { - case Player _: - return ProjectileBase.GrappleHookOptions.Contains(Enums.GrappleOption.Player); - - case Npc _: - return ProjectileBase.GrappleHookOptions.Contains(Enums.GrappleOption.NPC); - - case Resource _: - return ProjectileBase.GrappleHookOptions.Contains(Enums.GrappleOption.Resource); - - default: - throw new ArgumentException($"Unsupported entity type {en.GetType().FullName}", nameof(en)); - } + Player => ProjectileBase.GrappleHookOptions.Contains(Enums.GrappleOption.Player), + Npc => ProjectileBase.GrappleHookOptions.Contains(Enums.GrappleOption.NPC), + Resource => ProjectileBase.GrappleHookOptions.Contains(Enums.GrappleOption.Resource), + _ => throw new ArgumentException($"Unsupported entity type {en.GetType().FullName}", nameof(en)), + }; } ///