diff --git a/OpenRA.Mods.RA/AttackBomber.cs b/OpenRA.Mods.RA/AttackBomber.cs index 622b8840d0..f39df8820c 100644 --- a/OpenRA.Mods.RA/AttackBomber.cs +++ b/OpenRA.Mods.RA/AttackBomber.cs @@ -23,48 +23,34 @@ namespace OpenRA.Mods.RA [Desc("Armament name")] public readonly string Guns = "secondary"; public readonly int FacingTolerance = 2; - public readonly WRange VisionRange = WRange.FromCells(10); public override object Create(ActorInitializer init) { return new AttackBomber(init.self, this); } } - class AttackBomber : AttackBase, ISync, INotifyKilled + class AttackBomber : AttackBase, ISync, INotifyRemovedFromWorld { AttackBomberInfo info; - Actor camera; [Sync] Target target; + [Sync] bool inAttackRange; + + public event Action OnRemovedFromWorld = self => { }; + public event Action OnEnteredAttackRange = self => { }; + public event Action OnExitedAttackRange = self => { }; public AttackBomber(Actor self, AttackBomberInfo info) : base(self, info) { this.info = info; - this.camera = null; } public override void Tick(Actor self) { base.Tick(self); - var facing = self.TraitOrDefault(); var cp = self.CenterPosition; var bombTarget = Target.FromPos(cp - new WVec(0, 0, cp.Z)); - - // Provide vision - if (this.camera == null && - target.IsInRange(self.CenterPosition, this.info.VisionRange)) - { - this.camera = self.World.CreateActor("camera", new TypeDictionary - { - new LocationInit(target.CenterPosition.ToCPos()), - new OwnerInit(self.Owner), - }); - } - else if (this.camera != null && - !target.IsInRange(self.CenterPosition, this.info.VisionRange)) - { - self.World.Remove(this.camera); - this.camera = null; - } + var wasInAttackRange = inAttackRange; + inAttackRange = false; // Bombs drop anywhere in range foreach (var a in Armaments.Where(a => a.Info.Name == info.Bombs)) @@ -72,39 +58,43 @@ namespace OpenRA.Mods.RA if (!target.IsInRange(self.CenterPosition, a.Weapon.Range)) continue; - a.CheckFire(self, this, facing, bombTarget); + inAttackRange = true; + a.CheckFire(self, this, facing.Value, bombTarget); } // Guns only fire when approaching the target - var facingToTarget = Util.GetFacing(target.CenterPosition - self.CenterPosition, facing.Facing); - if (Math.Abs(facingToTarget - facing.Facing) % 256 > info.FacingTolerance) - return; - - foreach (var a in Armaments.Where(a => a.Info.Name == info.Guns)) + var f = facing.Value.Facing; + var facingToTarget = Util.GetFacing(target.CenterPosition - self.CenterPosition, f); + if (Math.Abs(facingToTarget - f) % 256 <= info.FacingTolerance) { - if (!target.IsInRange(self.CenterPosition, a.Weapon.Range)) - continue; + foreach (var a in Armaments.Where(a => a.Info.Name == info.Guns)) + { + if (!target.IsInRange(self.CenterPosition, a.Weapon.Range)) + continue; - var t = Target.FromPos(cp - new WVec(0, a.Weapon.Range.Range / 2, cp.Z).Rotate(WRot.FromFacing(facing.Facing))); - a.CheckFire(self, this, facing, t); + var t = Target.FromPos(cp - new WVec(0, a.Weapon.Range.Range / 2, cp.Z).Rotate(WRot.FromFacing(f))); + inAttackRange = true; + a.CheckFire(self, this, facing.Value, t); + } } + + if (inAttackRange && !wasInAttackRange) + OnEnteredAttackRange(self); + + if (!inAttackRange && wasInAttackRange) + OnExitedAttackRange(self); } public void SetTarget(WPos pos) { target = Target.FromPos(pos); } - public void Killed(Actor self, AttackInfo e) + public void RemovedFromWorld(Actor self) { - if (this.camera != null) - { - self.World.Remove(this.camera); - this.camera = null; - } + OnRemovedFromWorld(self); } public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove) { - // TODO: Player controlled units want this too! - throw new NotImplementedException("CarpetBomb requires a scripted target"); + throw new NotImplementedException("AttackBomber requires a scripted target"); } } } diff --git a/OpenRA.Mods.RA/SupportPowers/AirstrikePower.cs b/OpenRA.Mods.RA/SupportPowers/AirstrikePower.cs index 35a484a28f..bb6204144c 100755 --- a/OpenRA.Mods.RA/SupportPowers/AirstrikePower.cs +++ b/OpenRA.Mods.RA/SupportPowers/AirstrikePower.cs @@ -1,6 +1,6 @@ #region Copyright & License Information /* - * Copyright 2007-2013 The OpenRA Developers (see AUTHORS) + * Copyright 2007-2014 The OpenRA Developers (see AUTHORS) * This file is part of OpenRA, which is free software. It is made * available to you under the terms of the GNU General Public License * as published by the Free Software Foundation. For more information, @@ -9,6 +9,8 @@ #endregion using System; +using System.Collections.Generic; +using System.Linq; using OpenRA.FileFormats; using OpenRA.Mods.RA.Activities; using OpenRA.Mods.RA.Air; @@ -27,8 +29,18 @@ namespace OpenRA.Mods.RA public readonly WRange Cordon = new WRange(5120); [ActorReference] - public readonly string FlareType = null; - public readonly int FlareTime = 3000; // 2 minutes + [Desc("Actor to spawn when the aircraft first enter the map")] + public readonly string FlareActor = null; + + [Desc("Amount of time to keep the flare alive after the aircraft have finished attacking")] + public readonly int FlareRemoveDelay = 25; + + [ActorReference] + [Desc("Actor to spawn when the aircraft start attacking")] + public readonly string CameraActor = null; + + [Desc("Amount of time to keep the camera alive after the aircraft have finished attacking")] + public readonly int CameraRemoveDelay = 25; public override object Create(ActorInitializer init) { return new AirstrikePower(init.self, this); } } @@ -51,24 +63,65 @@ namespace OpenRA.Mods.RA var startEdge = target - (self.World.DistanceToMapEdge(target, -delta) + info.Cordon).Range * delta / 1024; var finishEdge = target + (self.World.DistanceToMapEdge(target, delta) + info.Cordon).Range * delta / 1024; + Actor flare = null; + Actor camera = null; + Dictionary aircraftInRange = new Dictionary(); + + Action onEnterRange = a => + { + // Spawn a camera when the first plane enters the target area + if (info.CameraActor != null && !aircraftInRange.Any(kv => kv.Value)) + { + self.World.AddFrameEndTask(w => + { + camera = w.CreateActor(info.CameraActor, new TypeDictionary + { + new LocationInit(order.TargetLocation), + new OwnerInit(self.Owner), + }); + }); + } + + aircraftInRange[a] = true; + }; + + Action onExitRange = a => + { + aircraftInRange[a] = false; + + // Remove the camera when the final plane leaves the target area + if (!aircraftInRange.Any(kv => kv.Value)) + { + if (camera != null) + { + camera.QueueActivity(new Wait(info.CameraRemoveDelay)); + camera.QueueActivity(new RemoveSelf()); + } + + if (flare != null) + { + flare.QueueActivity(new Wait(info.FlareRemoveDelay)); + flare.QueueActivity(new RemoveSelf()); + } + + camera = flare = null; + } + }; + self.World.AddFrameEndTask(w => { - var notification = self.Owner.IsAlliedWith(self.World.RenderPlayer) ? Info.LaunchSound : Info.IncomingSound; - Sound.Play(notification); - - Actor flare = null; - if (info.FlareType != null) + if (info.FlareActor != null) { - flare = w.CreateActor(info.FlareType, new TypeDictionary + flare = w.CreateActor(info.FlareActor, new TypeDictionary { new LocationInit(order.TargetLocation), new OwnerInit(self.Owner), }); - - flare.QueueActivity(new Wait(info.FlareTime)); - flare.QueueActivity(new RemoveSelf()); } + var notification = self.Owner.IsAlliedWith(self.World.RenderPlayer) ? Info.LaunchSound : Info.IncomingSound; + Sound.Play(notification); + for (var i = -info.SquadSize / 2; i <= info.SquadSize / 2; i++) { // Even-sized squads skip the lead plane @@ -77,8 +130,8 @@ namespace OpenRA.Mods.RA // Includes the 90 degree rotation between body and world coordinates var so = info.SquadOffset; - var spawnOffset = new WVec(i*so.Y, -Math.Abs(i)*so.X, 0).Rotate(attackRotation); - var targetOffset = new WVec(i*so.Y, 0, 0).Rotate(attackRotation); + var spawnOffset = new WVec(i * so.Y, -Math.Abs(i) * so.X, 0).Rotate(attackRotation); + var targetOffset = new WVec(i * so.Y, 0, 0).Rotate(attackRotation); var a = w.CreateActor(info.UnitType, new TypeDictionary { @@ -87,13 +140,15 @@ namespace OpenRA.Mods.RA new FacingInit(attackFacing), }); - a.Trait().SetTarget(target + targetOffset); - - if (flare != null) - a.QueueActivity(new CallFunc(() => flare.Destroy())); + var attack = a.Trait(); + attack.SetTarget(target + targetOffset); + attack.OnEnteredAttackRange += onEnterRange; + attack.OnExitedAttackRange += onExitRange; + attack.OnRemovedFromWorld += onExitRange; a.QueueActivity(new Fly(a, Target.FromPos(finishEdge + spawnOffset))); a.QueueActivity(new RemoveSelf()); + aircraftInRange.Add(a, false); } }); } diff --git a/mods/cnc/rules/structures.yaml b/mods/cnc/rules/structures.yaml index d0f4c254e7..8961845dc1 100644 --- a/mods/cnc/rules/structures.yaml +++ b/mods/cnc/rules/structures.yaml @@ -385,6 +385,7 @@ HQ: UnitType: a10 DisplayBeacon: True DisplayRadarPing: True + CameraActor: camera SupportPowerChargeBar: FIX: diff --git a/mods/d2k/rules/atreides.yaml b/mods/d2k/rules/atreides.yaml index 6c98002bc2..c601c4d67d 100644 --- a/mods/d2k/rules/atreides.yaml +++ b/mods/d2k/rules/atreides.yaml @@ -59,7 +59,6 @@ PALACEA: LongDesc: Ornithopter drops a load of parachuted\nbombs on your target UnitType: orni.bomber SelectTargetSound: - FlareType: CanPowerDown: DisabledOverlay: RequiresPower: diff --git a/mods/d2k/rules/ordos.yaml b/mods/d2k/rules/ordos.yaml index c414e0b203..53437f9f25 100644 --- a/mods/d2k/rules/ordos.yaml +++ b/mods/d2k/rules/ordos.yaml @@ -88,7 +88,6 @@ PALACEO: LongDesc: Ornithopter drops a load of parachuted\nbombs on your target UnitType: orni.bomber SelectTargetSound: - FlareType: CanPowerDown: DisabledOverlay: RequiresPower: diff --git a/mods/ra/rules/system-actors.yaml b/mods/ra/rules/system-actors.yaml index df5a2be455..1fabf9381a 100644 --- a/mods/ra/rules/system-actors.yaml +++ b/mods/ra/rules/system-actors.yaml @@ -152,7 +152,7 @@ powerproxy.parabombs: AllowMultiple: yes UnitType: badr.bomber SelectTargetSound: slcttgt1.aud - FlareType: flare + FlareActor: flare powerproxy.sonarpulse: SonarPulsePower: