diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SpawnActorPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SpawnActorPower.cs index ebc86a7000..41d9654c91 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SpawnActorPower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SpawnActorPower.cs @@ -9,8 +9,12 @@ */ #endregion +using System.Collections.Generic; +using System.Linq; +using OpenRA.Graphics; using OpenRA.Mods.Common.Activities; using OpenRA.Mods.Common.Effects; +using OpenRA.Mods.Common.Orders; using OpenRA.Primitives; using OpenRA.Traits; @@ -27,6 +31,11 @@ namespace OpenRA.Mods.Common.Traits [Desc("Amount of time to keep the actor alive in ticks. Value < 0 means this actor will not remove itself.")] public readonly int LifeTime = 250; + [Desc("Only allow this to be spawned on this terrain.")] + public readonly string[] Terrain = null; + + public readonly bool AllowUnderShroud = true; + public readonly string DeploySound = null; public readonly string EffectImage = null; @@ -37,6 +46,9 @@ namespace OpenRA.Mods.Common.Traits [PaletteReference] public readonly string EffectPalette = null; + [Desc("Cursor to display when the location is unsuitable.")] + public readonly string BlockedCursor = "move-blocked"; + public override object Create(ActorInitializer init) { return new SpawnActorPower(init.Self, this); } } @@ -47,33 +59,108 @@ namespace OpenRA.Mods.Common.Traits public override void Activate(Actor self, Order order, SupportPowerManager manager) { + var info = Info as SpawnActorPowerInfo; + var position = order.Target.CenterPosition; + var cell = self.World.Map.CellContaining(position); + + if (!Validate(self.World, info, cell)) + return; + base.Activate(self, order, manager); - var info = Info as SpawnActorPowerInfo; - - if (info.Actor != null) + self.World.AddFrameEndTask(w => { - self.World.AddFrameEndTask(w => + PlayLaunchSounds(); + Game.Sound.Play(SoundType.World, info.DeploySound, position); + + if (!string.IsNullOrEmpty(info.EffectSequence) && !string.IsNullOrEmpty(info.EffectPalette)) + w.Add(new SpriteEffect(position, w, info.EffectImage, info.EffectSequence, info.EffectPalette)); + + var actor = w.CreateActor(info.Actor, new TypeDictionary { - PlayLaunchSounds(); - Game.Sound.Play(SoundType.World, info.DeploySound, order.Target.CenterPosition); - - if (!string.IsNullOrEmpty(info.EffectSequence) && !string.IsNullOrEmpty(info.EffectPalette)) - w.Add(new SpriteEffect(order.Target.CenterPosition, w, info.EffectImage, info.EffectSequence, info.EffectPalette)); - - var actor = w.CreateActor(info.Actor, new TypeDictionary - { - new LocationInit(self.World.Map.CellContaining(order.Target.CenterPosition)), - new OwnerInit(self.Owner), - }); - - if (info.LifeTime > -1) - { - actor.QueueActivity(new Wait(info.LifeTime)); - actor.QueueActivity(new RemoveSelf()); - } + new LocationInit(cell), + new OwnerInit(self.Owner), }); - } + + if (info.LifeTime > -1) + { + actor.QueueActivity(new Wait(info.LifeTime)); + actor.QueueActivity(new RemoveSelf()); + } + }); + } + + public override void SelectTarget(Actor self, string order, SupportPowerManager manager) + { + Game.Sound.PlayToPlayer(SoundType.UI, manager.Self.Owner, Info.SelectTargetSound); + Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", + Info.SelectTargetSpeechNotification, self.Owner.Faction.InternalName); + self.World.OrderGenerator = new SelectSpawnActorPowerTarget(order, manager, this, MouseButton.Left); + } + + public bool Validate(World world, SpawnActorPowerInfo info, CPos cell) + { + if (!world.Map.Contains(cell)) + return false; + + if (!info.AllowUnderShroud && world.ShroudObscures(cell)) + return false; + + if (info.Terrain != null && !info.Terrain.Contains(world.Map.GetTerrainInfo(cell).Type)) + return false; + + return true; + } + } + + public class SelectSpawnActorPowerTarget : OrderGenerator + { + readonly SpawnActorPower power; + readonly SpawnActorPowerInfo info; + readonly SupportPowerManager manager; + readonly string order; + readonly MouseButton expectedButton; + + public string OrderKey { get { return order; } } + + public SelectSpawnActorPowerTarget(string order, SupportPowerManager manager, SpawnActorPower power, MouseButton button) + { + // Clear selection if using Left-Click Orders + if (Game.Settings.Game.UseClassicMouseStyle) + manager.Self.World.Selection.Clear(); + + this.manager = manager; + this.power = power; + this.order = order; + expectedButton = button; + + info = (SpawnActorPowerInfo)power.Info; + } + + protected override IEnumerable OrderInner(World world, CPos cell, int2 worldPixel, MouseInput mi) + { + world.CancelInputMode(); + + if (!power.Validate(world, info, cell)) + yield break; + + if (mi.Button == expectedButton) + yield return new Order(order, manager.Self, Target.FromCell(world, cell), false) { SuppressVisualFeedback = true }; + } + + protected override void Tick(World world) + { + // Cancel the OG if we can't use the power + if (!manager.Powers.ContainsKey(order)) + world.CancelInputMode(); + } + + protected override IEnumerable Render(WorldRenderer wr, World world) { yield break; } + protected override IEnumerable RenderAboveShroud(WorldRenderer wr, World world) { yield break; } + protected override IEnumerable RenderAnnotations(WorldRenderer wr, World world) { yield break; } + protected override string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi) + { + return power.Validate(world, info, cell) ? info.Cursor : info.BlockedCursor; } } } diff --git a/mods/ra/rules/misc.yaml b/mods/ra/rules/misc.yaml index 51f0e07cc7..8def47ff66 100644 --- a/mods/ra/rules/misc.yaml +++ b/mods/ra/rules/misc.yaml @@ -370,6 +370,8 @@ powerproxy.sonarpulse: EndChargeSpeechNotification: SonarPulseReady SelectTargetSpeechNotification: SelectTarget Actor: sonar + Terrain: Water + AllowUnderShroud: false LifeTime: 250 DeploySound: sonpulse.aud EffectImage: moveflsh