using VeloBrawl.General.NetIsland.LaserBattle.Objects; using VeloBrawl.General.NetIsland.LaserBattle.Significant; using VeloBrawl.Supercell.Titan.CommonUtils.Utils; using VeloBrawl.Titan.DataStream; using VeloBrawl.Titan.Mathematical; using VeloBrawl.Titan.Mathematical.Data; using VeloBrawl.Tools.LaserCsv; using VeloBrawl.Tools.LaserCsv.Manufacturer.Laser; namespace VeloBrawl.General.NetIsland.LaserBattle.Objects.Laser; public class LogicProjectileServer : LogicGameObjectServer { private readonly LogicBattleModeServer _battle; private readonly LogicProjectileData _projectileData; private readonly LogicSkillData _skillData; private readonly LogicCharacterServer _owner; private int _startX, _startY; private int _targetX, _targetY; private int _deployedTick; private int _endingTick; private int _lengthDelta; private readonly int _damage; private bool _dead; private int _angle; private readonly HashSet _hitObjects = []; public LogicProjectileServer( LogicBattleModeServer battle, LogicCharacterServer owner, LogicProjectileData projectileData, LogicSkillData skillData, int startX, int startY, int targetX, int targetY, int damage) : base(battle, CsvHelperTable.Projectiles.GetId(), projectileData.GetInstanceId(), 0, owner.GetIndex()) { _battle = battle; _owner = owner; _projectileData = projectileData; _skillData = skillData; _damage = damage; _dead = false; _startX = startX; _startY = startY; _targetX = targetX; _targetY = targetY; _deployedTick = battle.GetTicksGone(); _angle = LogicMath.GetAngle(targetX - startX, targetY - startY); var speed = projectileData.GetSpeed(); var dist = LogicMath.Sqrt((targetX - startX) * (targetX - startX) + (targetY - startY) * (targetY - startY)); if (speed > 0 && dist > 0) { var travelTicks = dist * 20 / speed; if (travelTicks < 1) travelTicks = 1; _endingTick = _deployedTick + travelTicks; _lengthDelta = 1000 / travelTicks; } else { _endingTick = _deployedTick + 30; _lengthDelta = 33; } SetPosition(startX, startY, 300); } public override void Tick() { if (_dead) return; var tick = _battle.GetTicksGone(); var elapsed = tick - _deployedTick; var progress = LogicMath.Clamp(elapsed * _lengthDelta, 0, 1000); var nx = _startX + (_targetX - _startX) * progress / 1000; var ny = _startY + (_targetY - _startY) * progress / 1000; SetPosition(nx, ny, GetZ()); if (!_projectileData.GetPiercesEnvironment()) { var tileX = nx / 300; var tileY = ny / 300; var tile = _battle.GetTileMap().LogicTileMap.GetTile(tileX, tileY, true); if (tile != null && !tile.IsDestroyed() && tile.TileData.GetBlocksMovement()) { if (tile.IsDestroyableWithWeaponType()) tile.DestroyTile(); Destroy(); return; } } else { var tileX = nx / 300; var tileY = ny / 300; var tile = _battle.GetTileMap().LogicTileMap.GetTile(tileX, tileY, true); if (tile != null && !tile.IsDestroyed() && tile.IsDestroyableWithWeaponType()) tile.DestroyTile(); } CheckHits(); if (!_dead && tick >= _endingTick) Destroy(); } private void Destroy() { _dead = true; GetLogicGameObjectManager().RemoveGameObjectReferences(this); } private void CheckHits() { var radius = _projectileData.GetRadius() > 0 ? _projectileData.GetRadius() : 100; foreach (var obj in GetLogicGameObjectManager().GetNumGameObjects()) { if (obj is not LogicCharacterServer target) continue; if (target.GetObjectGlobalId() == _owner.GetObjectGlobalId()) continue; if (target.GetNowHitPoints() <= 0) continue; if (IsSameTeam(target)) continue; if (_hitObjects.Contains(target.GetObjectGlobalId()) && !_projectileData.GetPiercesCharacters()) continue; var dx = GetX() - target.GetX(); var dy = GetY() - target.GetY(); var dist2 = dx * dx + dy * dy; var hitR = radius + target.GetRadius(); if (dist2 > hitR * hitR) continue; _hitObjects.Add(target.GetObjectGlobalId()); ApplyDamage(target); if (!_projectileData.GetPiercesCharacters()) { Destroy(); return; } } } private bool IsSameTeam(LogicCharacterServer target) { var ownerTeam = _owner.GetIndex() / 16; if (target.GetPlayer() != null) return target.GetPlayer()!.GetTeamIndex() == ownerTeam; if (target.NpcTeamIndex >= 0) return target.NpcTeamIndex == ownerTeam; return false; } private void ApplyDamage(LogicCharacterServer target) { var dmg = _damage; var buff = _owner.GetBuff((int)LogicBuffServer.BuffType.Damage); if (buff != null) dmg = dmg * (100 + buff.Modifier) / 100; target.TakeDamage(dmg, _owner); if (_owner.GetPlayer() != null && !_projectileData.GetBlockUltiCharge()) _owner.GetPlayer()!.ChargeUlti(false, dmg, 100); } public override bool ShouldDestruct() => _dead; public override void Encode(BitStream bitStream, bool isOwnObject, int visionIndex, int visionTeam) { base.Encode(bitStream, isOwnObject, visionIndex, visionTeam); bitStream.WritePositiveIntMax511(LogicMath.NormalizeAngle360(_angle)); var elapsed = LogicMath.Clamp(_battle.GetTicksGone() - _deployedTick, 0, _endingTick - _deployedTick); var total = _endingTick - _deployedTick; var v1 = total > 0 ? LogicMath.Clamp(elapsed * 255 / total, 0, 255) : 255; bitStream.WritePositiveVIntMax255OftenZero(v1); bitStream.WriteBoolean(false); } public override int GetType() => ObjectTypeHelperTable.Projectile.GetObjectType(); }