// namespace Supercell.Laser.Logic.Battle.Objects // { // using Supercell.Laser.Logic.Battle.Level; // using Supercell.Laser.Logic.Data; // using Supercell.Laser.Titan.DataStream; // using Supercell.Laser.Titan.Debug; // using Supercell.Laser.Titan.Math; // public class Projectile : GameObject // { // public ProjectileData ProjectileData => DataTables.Get(DataType.Projectile).GetDataByGlobalId(DataId); // private List AlreadyDamagedObjectsGlobalIds; // private GameObject Source; // private int Angle; // private int Damage; // private int CastingTime; // private int SpawnX; // private int SpawnY; // private int TicksActive; // private bool ShouldDestructImmediately; // private bool IsUltiWeapon; // private LogicVector2 TargetPosition; // private int m_destroyedTicks; // private CharacterData SummonedCharacter; // public int MaxRange; // public Projectile(int classId, int instanceId) : base(classId, instanceId) // { // TicksActive = -1; // FullTravelTicks = -1; // Z = 500; // TargetPosition = new LogicVector2(); // AlreadyDamagedObjectsGlobalIds = new List(); // } // private int m_totalDelta; // public override void Tick() // { // if (IsDestroyed()) // { // if (m_destroyedTicks < 1) // { // TargetReached(); // } // m_destroyedTicks++; // return; // } // if (!ProjectileData.Indirect) // { // if (m_totalDelta > CastingTime * 180) return; // } // int deltaX = (int)(((float)LogicMath.Cos(Angle) / 20000) * ProjectileData.Speed); // int deltaY = (int)(((float)LogicMath.Sin(Angle) / 20000) * ProjectileData.Speed); // m_totalDelta += ProjectileData.Speed / 20; // Position.X += deltaX; // Position.Y += deltaY; // TileMap tileMap = GameObjectManager.GetBattle().GetTileMap(); // Tile tile = tileMap.GetTile(TileMap.LogicToTile(Position.X), TileMap.LogicToTile(Position.Y), true); // if (tile == null) // { // ShouldDestructImmediately = true; // return; // } // if (!ProjectileData.Indirect) // { // if (!tile.IsDestructed() && tile.Data.BlocksProjectiles && !(tile.Data.IsDestructibleNormalWeapon || (tile.Data.IsDestructible && ProjectileData.PiercesEnvironment))) // { // ShouldDestructImmediately = true; // } // else if (tile.Data.IsDestructibleNormalWeapon) // { // tile.Destruct(); // } // else if (tile.Data.IsDestructible && ProjectileData.PiercesEnvironment) // { // tile.Destruct(); // } // } // if (Position.X <= 250 || Position.Y <= 250) ShouldDestructImmediately = true; // if (!ProjectileData.Indirect) // { // HandleCollisions(); // } // if (ProjectileData.Indirect) // { // if (TicksActive < (FullTravelTicks / 2)) // { // Z += (ProjectileData.Gravity / 20) * (FullTravelTicks - TicksActive); // } // else // { // int tmp = FullTravelTicks - TicksActive; // int deltaZ = (ProjectileData.Gravity / 20) * (TicksActive - tmp); // if (deltaZ > 0) Z -= deltaZ; // } // if (TicksActive >= FullTravelTicks - 1) ShouldDestructImmediately = true; // } // if (!GameObjectManager.GetBattle().IsInPlayArea(Position.X, Position.Y)) // { // ShouldDestructImmediately = true; // SetForcedInvisible(); // } // TicksActive++; // } // private int FullTravelTicks; // private bool IsPiperMainAttack() // { // Character sourceCharacter = Source as Character; // if (sourceCharacter == null || sourceCharacter.CharacterData == null) // { // return false; // } // return !IsUltiWeapon && // sourceCharacter.CharacterData.Name == "Sniper" && // (ProjectileData.Name == "SniperProjectile" || ProjectileData.Name == "Piper002Projectile"); // } // private int GetScaledDamage() // { // if (!IsPiperMainAttack()) // { // return Damage; // } // int traveledDistance = Position.GetDistance(new LogicVector2(SpawnX, SpawnY)); // int maxTravelDistance = LogicMath.Max(1, CastingTime * 180); // float travelRatio = LogicMath.Min(1000, (traveledDistance * 1000) / maxTravelDistance) / 1000f; // float damageMultiplier = 0.30f + (0.70f * travelRatio); // return LogicMath.Max(1, (int)(Damage * damageMultiplier)); // } // public void SetTargetPosition(int x, int y) // { // TargetPosition.Set(x, y); // } // public void SetSummonedCharacter(CharacterData data) // { // SummonedCharacter = data; // } // private void HandleCollisions() // { // foreach (GameObject gameObject in GameObjectManager.GetGameObjects()) // { // if (gameObject == null) continue; // if (gameObject.GetObjectType() != 1) continue; // if (!gameObject.IsAlive()) continue; // if (AlreadyDamagedObjectsGlobalIds.Contains(gameObject.GetGlobalID())) continue; // int teamIndex = gameObject.GetIndex() / 16; // if (teamIndex == this.GetIndex() / 16 && ProjectileData.HealOwnPercent == 0) continue; // if (teamIndex != this.GetIndex() / 16 && ProjectileData.HealOwnPercent != 0) continue; // int radius1 = gameObject.GetRadius(); // int radius2 = this.GetRadius(); // if (Position.GetDistance(gameObject.GetPosition()) <= radius1 + radius2) // { // // Collision! // if (!ProjectileData.PiercesCharacters) ShouldDestructImmediately = true; // AlreadyDamagedObjectsGlobalIds.Add(gameObject.GetGlobalID()); // Character character = (Character)gameObject; // int appliedDamage = GetScaledDamage(); // if (ProjectileData.Name == "RocketGirlProjectile") continue; // if (ProjectileData.HealOwnPercent == 0) character.CauseDamage((Character)Source, appliedDamage); // else character.CauseDamage((Character)Source, -appliedDamage); // if (ProjectileData.PoisonType != 0) // { // character.AddPoison((Character)Source, ((int)(((float)ProjectileData.PoisonDamagePercent / 100) * (float)appliedDamage)), 4, ProjectileData.PoisonType, false); // } // if (ProjectileData.Name == "ShotgunGirlUltiProjectile") // { // character.ApplyKnockback(Source.GetX(), Source.GetY(), 200); // character.ApplySlow(12); // } // if (!ProjectileData.PiercesCharacters) return; // } // } // } // private void TargetReached() // { // if (ProjectileData.SpawnAreaEffectObject != null) // { // CreateAreaEffect(ProjectileData.SpawnAreaEffectObject); // } // if (ProjectileData.SpawnAreaEffectObject2 != null) // { // CreateAreaEffect(ProjectileData.SpawnAreaEffectObject2); // } // if (SummonedCharacter != null) // { // Character character = new Character(16, SummonedCharacter.GetInstanceId()); // character.SetPosition(GetX(), GetY(), 0); // character.SetIndex(Source.GetIndex()); // GameObjectManager.AddGameObject(character); // } // } // public override void OnDestruct() // { // ; // } // private void CreateAreaEffect(string name) // { // AreaEffectData data = DataTables.Get(DataType.AreaEffect).GetData(name); // AreaEffect effect = new AreaEffect(17, data.GetInstanceId()); // effect.SetPosition(GetX(), GetY(), 0); // effect.SetIndex(GetIndex()); // effect.SetDamage(Damage); // effect.SetSource((Character)Source); // GameObjectManager.AddGameObject(effect); // } // private bool IsDestroyed() // { // return ((m_totalDelta > CastingTime * 180 && !ProjectileData.Indirect) || ShouldDestructImmediately); // } // public override bool ShouldDestruct() // { // return ((m_totalDelta > CastingTime * 180 && !ProjectileData.Indirect) || ShouldDestructImmediately) && m_destroyedTicks > 2; // } // public override void Encode(BitStream bitStream, bool isOwnObject, int visionTeam) // { // base.Encode(bitStream, isOwnObject, visionTeam); // int effect = 0; // if (m_totalDelta > CastingTime * 180 && !ProjectileData.Indirect) effect = 1; // if (ShouldDestructImmediately) effect = 3; // bitStream.WritePositiveIntMax7(effect); // next effect // bitStream.WriteBoolean(ProjectileData.IsBouncing); // if (ProjectileData.IsBouncing) // { // bitStream.WritePositiveIntMax1023(0); // } // if (ProjectileData.TriggerWithDelayMs != 0 || ProjectileData.PreExplosionTimeMs != 0) // bitStream.WritePositiveVIntMax65535(0); // if (ProjectileData.PreExplosionTimeMs != 0) // { // bitStream.WritePositiveVIntMax65535(0); // bitStream.WritePositiveVIntMax65535(0); // } // bitStream.WritePositiveIntMax1023(0); // Total path // if (ProjectileData.Rendering != "DoNotRotateClip") // bitStream.WritePositiveIntMax511(Angle); // bitStream.WriteBoolean(false); // } // public void ShootProjectile(int angle, GameObject owner, int damage, int castingTime, bool isUlti) // { // TicksActive = 0; // Angle = angle; // Damage = damage; // CastingTime = castingTime; // Source = owner; // SetIndex(owner.GetIndex()); // Position.X = owner.GetX(); // Position.Y = owner.GetY(); // SpawnX = Position.X; // SpawnY = Position.Y; // IsUltiWeapon = isUlti; // // Вычисляем FullTravelTicks сразу при спавне, пока позиция совпадает со стартовой // if (ProjectileData.Indirect) // { // int distance = LogicMath.Max(1, new LogicVector2(SpawnX, SpawnY).GetDistance(TargetPosition)); // int speedPerTick = LogicMath.Max(1, ProjectileData.Speed / 20); // FullTravelTicks = distance / speedPerTick; // FullTravelTicks = LogicMath.Min(FullTravelTicks, MaxRange); // } // } // public override int GetObjectType() // { // return 2; // } // } // } namespace Supercell.Laser.Logic.Battle.Objects { using System.Collections.Generic; using Supercell.Laser.Logic.Battle.Component; using Supercell.Laser.Logic.Battle.Level; using Supercell.Laser.Logic.Data; using Supercell.Laser.Titan.DataStream; using Supercell.Laser.Titan.Debug; using Supercell.Laser.Titan.Math; public class Projectile : GameObject { public ProjectileData ProjectileData => DataTables.Get(DataType.Projectile).GetDataByGlobalId(DataId); private List AlreadyDamagedObjectsGlobalIds; private GameObject Source; private int Angle; private int Damage; private int CastingTime; private int SpawnX; private int SpawnY; private int TicksActive; private bool ShouldDestructImmediately; private bool IsUltiWeapon; private LogicVector2 TargetPosition; private int m_destroyedTicks; private CharacterData SummonedCharacter; public int MaxRange; private int m_totalDelta; private int FullTravelTicks; private int m_bounceCount; private bool m_destroyedByEnvironmentOrRange; // Флаг возвращения (для Карла) private bool m_isReturning; public Projectile(int classId, int instanceId) : base(classId, instanceId) { TicksActive = -1; FullTravelTicks = -1; Z = 500; TargetPosition = new LogicVector2(); AlreadyDamagedObjectsGlobalIds = new List(); } public override void Tick() { if (IsDestroyed()) { if (m_destroyedTicks < 1) { TargetReached(); } m_destroyedTicks++; return; } // ЛОГИКА ВОЗВРАТА БУМЕРАНГА if (ProjectileData.IsBoomerang && m_isReturning) { if (Source == null || !Source.IsAlive()) { ShouldDestructImmediately = true; return; } // Пересчитываем угол прямо на владельца Angle = LogicMath.GetAngle(Source.GetX() - Position.X, Source.GetY() - Position.Y); // Летим обратно (чуть быстрее, как в оригинале) int speed = ProjectileData.Speed + 300; int deltaXRet = (int)(((float)LogicMath.Cos(Angle) / 20000) * speed); int deltaYRet = (int)(((float)LogicMath.Sin(Angle) / 20000) * speed); Position.X += deltaXRet; Position.Y += deltaYRet; // Если долетел до владельца if (Position.GetDistance(Source.GetPosition()) <= Source.GetRadius() + GetRadius() + 100) { // Выставляем флаг на уничтожение. // В следующем тике вызовется IsDestroyed -> TargetReached -> Выдастся патрон ShouldDestructImmediately = true; return; } HandleCollisions(); TicksActive++; return; // На обратном пути игнорируем стены } // ОБЫЧНЫЙ ПОЛЕТ ТУДА if (!ProjectileData.Indirect) { // Если достиг максимальной дальности if (m_totalDelta > CastingTime * 180) { if (ProjectileData.IsBoomerang) { TurnAroundBoomerang(); return; } else { m_destroyedByEnvironmentOrRange = true; ShouldDestructImmediately = true; return; } } } int deltaX = (int)(((float)LogicMath.Cos(Angle) / 20000) * ProjectileData.Speed); int deltaY = (int)(((float)LogicMath.Sin(Angle) / 20000) * ProjectileData.Speed); int previousX = Position.X; int previousY = Position.Y; m_totalDelta += ProjectileData.Speed / 20; Position.X += deltaX; Position.Y += deltaY; TileMap tileMap = GameObjectManager.GetBattle().GetTileMap(); Tile tile = tileMap.GetTile(TileMap.LogicToTile(Position.X), TileMap.LogicToTile(Position.Y), true); if (tile == null) { if (ProjectileData.IsBoomerang) TurnAroundBoomerang(); else if (ProjectileData.IsBouncing) BounceProjectile(null, previousX, previousY, false); else { m_destroyedByEnvironmentOrRange = true; ShouldDestructImmediately = true; } return; } if (!ProjectileData.Indirect) { // Столкновение со стеной if (!tile.IsDestructed() && tile.Data.BlocksProjectiles && !(tile.Data.IsDestructibleNormalWeapon || (tile.Data.IsDestructible && ProjectileData.PiercesEnvironment))) { if (ProjectileData.IsBoomerang) TurnAroundBoomerang(); else if (ProjectileData.IsBouncing) BounceProjectile(tile, previousX, previousY, true); else { m_destroyedByEnvironmentOrRange = true; ShouldDestructImmediately = true; } } else if (tile.Data.IsDestructibleNormalWeapon || (tile.Data.IsDestructible && ProjectileData.PiercesEnvironment)) { tile.Destruct(); } } // Вылет за границы карты if (Position.X <= 250 || Position.Y <= 250) { if (ProjectileData.IsBoomerang) TurnAroundBoomerang(); else if (ProjectileData.IsBouncing) BounceProjectile(null, previousX, previousY, false); else { m_destroyedByEnvironmentOrRange = true; ShouldDestructImmediately = true; } } if (!ProjectileData.Indirect) { HandleCollisions(); } if (ProjectileData.Indirect) { if (TicksActive < (FullTravelTicks / 2)) { Z += (ProjectileData.Gravity / 20) * (FullTravelTicks - TicksActive); } else { int tmp = FullTravelTicks - TicksActive; int deltaZ = (ProjectileData.Gravity / 20) * (TicksActive - tmp); if (deltaZ > 0) Z -= deltaZ; } if (TicksActive >= FullTravelTicks - 1) { m_destroyedByEnvironmentOrRange = true; ShouldDestructImmediately = true; } } if (!GameObjectManager.GetBattle().IsInPlayArea(Position.X, Position.Y)) { if (ProjectileData.IsBoomerang) TurnAroundBoomerang(); else { if (ProjectileData.IsBouncing) { BounceProjectile(null, previousX, previousY, false); } else { m_destroyedByEnvironmentOrRange = true; ShouldDestructImmediately = true; SetForcedInvisible(); } } } foreach (GameObject gameObject in GameObjectManager.GetGameObjects()) { if (gameObject.GetObjectType() != 3) continue; AreaEffect areaEffect = (AreaEffect)gameObject; if (areaEffect.BlocksProjectiles(Position.X, Position.Y, GetRadius())) { ShouldDestructImmediately = true; break; } } TicksActive++; } // РАЗВОРОТ БУМЕРАНГА private void TurnAroundBoomerang() { if (!m_isReturning) { m_isReturning = true; // Очищаем цели, чтобы кирка нанесла урон еще раз на обратном пути AlreadyDamagedObjectsGlobalIds.Clear(); } } private bool IsPiperMainAttack() { Character sourceCharacter = Source as Character; if (sourceCharacter == null || sourceCharacter.CharacterData == null) { return false; } return !IsUltiWeapon && sourceCharacter.CharacterData.Name == "Sniper" && (ProjectileData.Name == "SniperProjectile" || ProjectileData.Name == "Piper002Projectile"); } private bool ShouldSuppressBrokenBounceVfx() { string projectileName = ProjectileData.Name; if (string.IsNullOrEmpty(projectileName)) { return false; } return projectileName == "TrickshotDudeProjectile" || projectileName == "TrickshotDudeUltiProjectile" || projectileName == "TrickshotDudeGadgetProjectile" || projectileName.StartsWith("Rico00"); } private int GetScaledDamage() { if (!IsPiperMainAttack()) { return Damage; } int traveledDistance = Position.GetDistance(new LogicVector2(SpawnX, SpawnY)); int maxTravelDistance = LogicMath.Max(1, CastingTime * 180); float travelRatio = LogicMath.Min(1000, (traveledDistance * 1000) / maxTravelDistance) / 1000f; float damageMultiplier = 0.30f + (0.70f * travelRatio); return LogicMath.Max(1, (int)(Damage * damageMultiplier)); } private int GetEncodedTotalPath() { if (ProjectileData.Indirect) { return LogicMath.Min(1023, LogicMath.Max(0, FullTravelTicks)); } return LogicMath.Min(1023, LogicMath.Max(0, CastingTime)); } private int GetProjectileStartZ(GameObject owner) { Character sourceCharacter = owner as Character; if (sourceCharacter?.CharacterData != null && sourceCharacter.CharacterData.ProjectileStartZ > 0) { return sourceCharacter.CharacterData.ProjectileStartZ; } return ProjectileData.Indirect ? 500 : 0; } private void GrappleEnemy(Character sourceChar, Character target) { if (sourceChar == null || target == null || !target.TryBeginGroupedControl(sourceChar)) { return; } int dx = target.GetX() - sourceChar.GetX(); int dy = target.GetY() - sourceChar.GetY(); int distance = LogicMath.Max(1, new LogicVector2(dx, dy).GetLength()); int finalDistance = sourceChar.GetRadius() + target.GetRadius() + 120; int targetX = sourceChar.GetX() + dx * finalDistance / distance; int targetY = sourceChar.GetY() + dy * finalDistance / distance; if (GameObjectManager.GetBattle().IsInPlayArea(targetX, targetY)) { target.SetPosition(targetX, targetY, target.GetZ()); } target.ApplySlow(10); } public void SetTargetPosition(int x, int y) { TargetPosition.Set(x, y); } public void SetSummonedCharacter(CharacterData data) { SummonedCharacter = data; } public void SetSharedDamagedObjects(List damagedObjects) { if (damagedObjects != null) { AlreadyDamagedObjectsGlobalIds = damagedObjects; } } private void HandleCollisions() { foreach (GameObject gameObject in GameObjectManager.GetGameObjects()) { if (gameObject == null || gameObject.GetObjectType() != 1 || !gameObject.IsAlive()) continue; if (AlreadyDamagedObjectsGlobalIds.Contains(gameObject.GetGlobalID())) continue; int teamIndex = gameObject.GetIndex() / 16; if (teamIndex == this.GetIndex() / 16 && ProjectileData.HealOwnPercent == 0 && ProjectileData.Name != "IceDudeProjectile") continue; if (teamIndex != this.GetIndex() / 16 && ProjectileData.HealOwnPercent != 0) continue; int radius1 = gameObject.GetRadius(); int radius2 = this.GetRadius(); if (Position.GetDistance(gameObject.GetPosition()) <= radius1 + radius2) { // Бумеранг пролетает сквозь врагов (как пронзающий снаряд) if (!ProjectileData.PiercesCharacters && !ProjectileData.IsBoomerang) ShouldDestructImmediately = true; AlreadyDamagedObjectsGlobalIds.Add(gameObject.GetGlobalID()); Character target = (Character)gameObject; Character sourceChar = (Character)Source; int appliedDamage = GetScaledDamage(); if (ProjectileData.Name == "RocketGirlProjectile") continue; if (ProjectileData.HealOwnPercent == 0) { target.CauseDamage(sourceChar, appliedDamage); } else { int healAmount = (appliedDamage * ProjectileData.HealOwnPercent) / 100; if (healAmount == 0) healAmount = appliedDamage; target.CauseDamage(sourceChar, -healAmount); } if (ProjectileData.PoisonType != 0) { target.AddPoison(sourceChar, (int)(((float)ProjectileData.PoisonDamagePercent / 100) * (float)appliedDamage), 4, ProjectileData.PoisonType, false); } if (ProjectileData.GrapplesEnemy) { GrappleEnemy(sourceChar, target); } if (ProjectileData.Name == "ShotgunGirlUltiProjectile" && target.TryBeginGroupedControl(sourceChar)) { target.ApplyKnockback(Source.GetX(), Source.GetY(), 200); target.ApplySlow(12); } if (ProjectileData.Name == "BlowerUltiProjectile" && target.TryBeginGroupedControl(sourceChar)) { target.ApplyKnockback(Source.GetX(), Source.GetY(), 200); } if (!ProjectileData.PiercesCharacters && !ProjectileData.IsBoomerang) return; } } } private void SpawnChainProjectile() { if (ProjectileData.ExecuteChainSpecialCase <= 0 || string.IsNullOrEmpty(ProjectileData.ChainBullet) || Source == null) { return; } if ((ProjectileData.ExecuteChainSpecialCase == 2 || ProjectileData.ExecuteChainSpecialCase == 3) && !m_destroyedByEnvironmentOrRange) { return; } ProjectileData chainProjectileData = DataTables.Get(DataType.Projectile).GetData(ProjectileData.ChainBullet); if (chainProjectileData == null) { return; } Projectile projectile = new Projectile(6, chainProjectileData.GetInstanceId()); int chainTravelDistance = LogicMath.Max(1, ProjectileData.ChainTravelDistance); int travelDistance; if (chainProjectileData.Indirect) { int speedPerTick = LogicMath.Max(1, chainProjectileData.Speed / 20); travelDistance = speedPerTick * chainTravelDistance; } else { travelDistance = chainTravelDistance * 180; } int targetX = GetX() + (LogicMath.Cos(Angle) * travelDistance) / 20000; int targetY = GetY() + (LogicMath.Sin(Angle) * travelDistance) / 20000; projectile.SetTargetPosition(targetX, targetY); projectile.ShootProjectile(Angle, Source, Damage, chainTravelDistance, IsUltiWeapon); projectile.SetPosition(GetX(), GetY(), Z); projectile.MaxRange = chainTravelDistance; GameObjectManager.AddGameObject(projectile); } private void BounceProjectile(Tile tile, int previousX, int previousY, bool addBounceDistance) { Position.X = previousX; Position.Y = previousY; if (tile == null) { Angle = LogicMath.NormalizeAngle360(Angle + 180); } else { int nextX = Position.X + (int)(((float)LogicMath.Cos(Angle) / 20000) * ProjectileData.Speed); int nextY = Position.Y + (int)(((float)LogicMath.Sin(Angle) / 20000) * ProjectileData.Speed); TileMap tileMap = GameObjectManager.GetBattle().GetTileMap(); Tile xTile = tileMap.GetTile(nextX, Position.Y); Tile yTile = tileMap.GetTile(Position.X, nextY); bool blockX = xTile == null || (!xTile.IsDestructed() && xTile.Data.BlocksProjectiles); bool blockY = yTile == null || (!yTile.IsDestructed() && yTile.Data.BlocksProjectiles); if (blockX && blockY) { Angle = LogicMath.NormalizeAngle360(Angle + 180); } else if (blockX) { Angle = LogicMath.NormalizeAngle360(180 - Angle); } else if (blockY) { Angle = LogicMath.NormalizeAngle360(360 - Angle); } else { Angle = LogicMath.NormalizeAngle360(Angle + 180); } } m_bounceCount++; if (m_bounceCount >= 6) { ShouldDestructImmediately = true; } if (addBounceDistance && ProjectileData.DistanceAddFromBounce > 0) { int bounceDistanceAdd = ProjectileData.DistanceAddFromBounce; if (ProjectileData.BouncePercent > 0) { bounceDistanceAdd = LogicMath.Max(1, (bounceDistanceAdd * ProjectileData.BouncePercent) / 100); } CastingTime += LogicMath.Max(1, bounceDistanceAdd / 180); } } private void TargetReached() { // Выдаем патрон, когда снаряд уничтожается if (ProjectileData.IsBoomerang && Source != null && Source.IsAlive()) { Character sourceChar = Source as Character; if (sourceChar != null && sourceChar.GetWeaponSkill() != null) { sourceChar.GetWeaponSkill().RestoreCharge(); } } SpawnChainProjectile(); if (ProjectileData.SpawnAreaEffectObject != null) { CreateAreaEffect(ProjectileData.SpawnAreaEffectObject); } if (ProjectileData.SpawnAreaEffectObject2 != null) { CreateAreaEffect(ProjectileData.SpawnAreaEffectObject2); } if (SummonedCharacter != null) { Character character = new Character(16, SummonedCharacter.GetInstanceId()); character.SetPosition(GetX(), GetY(), 0); character.SetIndex(Source.GetIndex()); GameObjectManager.AddGameObject(character); } } public override void OnDestruct() { } private void CreateAreaEffect(string name) { AreaEffectData data = DataTables.Get(DataType.AreaEffect).GetData(name); if (data == null) return; AreaEffect effect = new AreaEffect(17, data.GetInstanceId()); effect.SetPosition(GetX(), GetY(), 0); effect.SetIndex(GetIndex()); effect.SetDamage(Damage); effect.SetSource((Character)Source); GameObjectManager.AddGameObject(effect); } private bool IsDestroyed() { // Бумеранг не уничтожается просто потому что пролетел макс. дистанцию (уничтожается только вручную ShouldDestructImmediately=true) if (ProjectileData.IsBoomerang) return ShouldDestructImmediately; return ((m_totalDelta > CastingTime * 180 && !ProjectileData.Indirect) || ShouldDestructImmediately); } public override bool ShouldDestruct() { if (ProjectileData.IsBoomerang) return ShouldDestructImmediately && m_destroyedTicks > 2; return ((m_totalDelta > CastingTime * 180 && !ProjectileData.Indirect) || ShouldDestructImmediately) && m_destroyedTicks > 2; } public override int GetRadius() { return ProjectileData.Radius > 0 ? ProjectileData.Radius : base.GetRadius(); } public override void Encode(BitStream bitStream, bool isOwnObject, int visionTeam) { base.Encode(bitStream, isOwnObject, visionTeam); int effect = 0; if (m_totalDelta > CastingTime * 180 && !ProjectileData.Indirect && !ProjectileData.IsBoomerang) effect = 1; if (ShouldDestructImmediately) effect = 3; bool encodeBounceVfx = ProjectileData.IsBouncing && !ShouldSuppressBrokenBounceVfx(); bitStream.WritePositiveIntMax7(effect); // next effect bitStream.WriteBoolean(encodeBounceVfx); if (encodeBounceVfx) { bitStream.WritePositiveIntMax1023(0); } if (ProjectileData.TriggerWithDelayMs != 0 || ProjectileData.PreExplosionTimeMs != 0) bitStream.WritePositiveVIntMax65535(0); if (ProjectileData.PreExplosionTimeMs != 0) { bitStream.WritePositiveVIntMax65535(0); bitStream.WritePositiveVIntMax65535(0); } bitStream.WritePositiveIntMax1023(GetEncodedTotalPath()); // Total path if (ProjectileData.Rendering != "DoNotRotateClip") bitStream.WritePositiveIntMax511(Angle); bitStream.WriteBoolean(false); } public void ShootProjectile(int angle, GameObject owner, int damage, int castingTime, bool isUlti) { TicksActive = 0; Angle = angle; Damage = damage; CastingTime = castingTime; m_destroyedByEnvironmentOrRange = false; Source = owner; SetIndex(owner.GetIndex()); Position.X = owner.GetX(); Position.Y = owner.GetY(); Z = GetProjectileStartZ(owner); SpawnX = Position.X; SpawnY = Position.Y; IsUltiWeapon = isUlti; m_isReturning = false; // При спавне сбрасываем if (ProjectileData.Indirect) { int distance = LogicMath.Max(1, new LogicVector2(SpawnX, SpawnY).GetDistance(TargetPosition)); int speedPerTick = LogicMath.Max(1, ProjectileData.Speed / 20); FullTravelTicks = distance / speedPerTick; FullTravelTicks = LogicMath.Min(FullTravelTicks, MaxRange); } } public override int GetObjectType() { return 2; } } }