From ecd8dee57538ba59e02bcf42200f85e9b0407ccf Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 5 Jun 2019 20:00:27 +0000 Subject: [PATCH] Add TransformsInto* traits to trigger construction yard undeploy. --- OpenRA.Game/Activities/Activity.cs | 6 +- OpenRA.Mods.Common/Activities/Transform.cs | 36 ++++ .../Buildings/TransformsIntoAircraft.cs | 184 +++++++++++++++++ .../Buildings/TransformsIntoEntersTunnels.cs | 93 +++++++++ .../Traits/Buildings/TransformsIntoMobile.cs | 192 ++++++++++++++++++ .../Buildings/TransformsIntoPassenger.cs | 142 +++++++++++++ .../Buildings/TransformsIntoRepairable.cs | 110 ++++++++++ OpenRA.Mods.Common/Traits/EntersTunnels.cs | 18 +- OpenRA.Mods.Common/Traits/Transforms.cs | 22 +- mods/cnc/rules/structures.yaml | 14 +- mods/ra/rules/structures.yaml | 14 +- mods/ts/rules/shared-structures.yaml | 20 +- 12 files changed, 826 insertions(+), 25 deletions(-) create mode 100644 OpenRA.Mods.Common/Traits/Buildings/TransformsIntoAircraft.cs create mode 100644 OpenRA.Mods.Common/Traits/Buildings/TransformsIntoEntersTunnels.cs create mode 100644 OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs create mode 100644 OpenRA.Mods.Common/Traits/Buildings/TransformsIntoPassenger.cs create mode 100644 OpenRA.Mods.Common/Traits/Buildings/TransformsIntoRepairable.cs diff --git a/OpenRA.Game/Activities/Activity.cs b/OpenRA.Game/Activities/Activity.cs index cf610a18f0..a0de19e1f3 100644 --- a/OpenRA.Game/Activities/Activity.cs +++ b/OpenRA.Game/Activities/Activity.cs @@ -27,7 +27,7 @@ namespace OpenRA.Activities * Queue a new instance instead. * - Avoid calling actor.CancelActivity(). It is almost always a bug. Call activity.Cancel() instead. */ - public abstract class Activity + public abstract class Activity : IActivityInterface { public ActivityState State { get; private set; } @@ -172,9 +172,9 @@ namespace OpenRA.Activities } } - public IEnumerable ActivitiesImplementing() where T : IActivityInterface + public IEnumerable ActivitiesImplementing(bool includeChildren = true) where T : IActivityInterface { - if (childActivity != null) + if (includeChildren && childActivity != null) foreach (var a in childActivity.ActivitiesImplementing()) yield return a; diff --git a/OpenRA.Mods.Common/Activities/Transform.cs b/OpenRA.Mods.Common/Activities/Transform.cs index 15e5738c67..eb0096afc4 100644 --- a/OpenRA.Mods.Common/Activities/Transform.cs +++ b/OpenRA.Mods.Common/Activities/Transform.cs @@ -9,6 +9,7 @@ */ #endregion +using System; using OpenRA.Activities; using OpenRA.Mods.Common.Traits; using OpenRA.Mods.Common.Traits.Render; @@ -87,6 +88,10 @@ namespace OpenRA.Mods.Common.Activities void DoTransform(Actor self) { + // This activity may be buried as a child within one or more parents + // We need to consider the top-level activities when transferring orders to the new actor! + var currentActivity = self.CurrentActivity; + self.World.AddFrameEndTask(w => { if (self.IsDead || self.WillDispose) @@ -132,6 +137,14 @@ namespace OpenRA.Mods.Common.Activities foreach (var nt in self.TraitsImplementing()) nt.AfterTransform(a); + // Use self.CurrentActivity to capture the parent activity if Transform is a child + foreach (var transfer in currentActivity.ActivitiesImplementing(false)) + { + var order = transfer.IssueOrderForTransformedActor(a); + foreach (var t in a.TraitsImplementing()) + t.ResolveOrder(a, order); + } + self.ReplacedByActor = a; if (selected) @@ -142,4 +155,27 @@ namespace OpenRA.Mods.Common.Activities }); } } + + class IssueOrderAfterTransform : Activity + { + readonly string orderString; + readonly Target target; + + public IssueOrderAfterTransform(string orderString, Target target) + { + this.orderString = orderString; + this.target = target; + } + + public Order IssueOrderForTransformedActor(Actor newActor) + { + return new Order(orderString, newActor, target, true); + } + + public override Activity Tick(Actor self) + { + // Activity is a placeholder that should never run + return NextActivity; + } + } } diff --git a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoAircraft.cs b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoAircraft.cs new file mode 100644 index 0000000000..2f1529ca30 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoAircraft.cs @@ -0,0 +1,184 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Activities; +using OpenRA.Mods.Common.Activities; +using OpenRA.Mods.Common.Orders; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Add to a building to expose a move cursor that triggers Transforms and issues a move order to the transformed actor.")] + public class TransformsIntoAircraftInfo : ConditionalTraitInfo, Requires + { + [Desc("Can the actor be ordered to move in to shroud?")] + public readonly bool MoveIntoShroud = true; + + [ActorReference] + [FieldLoader.Require] + public readonly HashSet DockActors = new HashSet { }; + + [VoiceReference] + public readonly string Voice = "Action"; + + public override object Create(ActorInitializer init) { return new TransformsIntoAircraft(init, this); } + } + + public class TransformsIntoAircraft : ConditionalTrait, IIssueOrder, IResolveOrder, IOrderVoice + { + readonly Actor self; + Transforms[] transforms; + + public TransformsIntoAircraft(ActorInitializer init, TransformsIntoAircraftInfo info) + : base(info) + { + self = init.Self; + } + + protected override void Created(Actor self) + { + transforms = self.TraitsImplementing().ToArray(); + base.Created(self); + } + + IEnumerable IIssueOrder.Orders + { + get + { + if (!IsTraitDisabled) + { + yield return new EnterAlliedActorTargeter("Enter", 5, AircraftCanEnter, + target => Reservable.IsAvailableFor(target, self)); + yield return new AircraftMoveOrderTargeter(self, this); + } + } + } + + public bool AircraftCanEnter(Actor a) + { + return !self.AppearsHostileTo(a) && Info.DockActors.Contains(a.Info.Name); + } + + // Note: Returns a valid order even if the unit can't move to the target + Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) + { + if (order.OrderID == "Enter" || order.OrderID == "Move") + return new Order(order.OrderID, self, target, queued); + + return null; + } + + void IResolveOrder.ResolveOrder(Actor self, Order order) + { + if (IsTraitDisabled) + return; + + if (order.OrderString == "Move") + { + var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); + if (!Info.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) + return; + + var target = Target.FromCell(self.World, cell); + + self.SetTargetLine(target, Color.Green); + } + else if (order.OrderString == "Enter") + { + // Enter and Repair orders are only valid for own/allied actors, + // which are guaranteed to never be frozen. + if (order.Target.Type != TargetType.Actor) + return; + + var targetActor = order.Target.Actor; + + // We only want to set a target line if the order will (most likely) succeed + if (Reservable.IsAvailableFor(targetActor, self)) + self.SetTargetLine(Target.FromActor(targetActor), Color.Green); + } + + var currentTransform = self.CurrentActivity as Transform; + var transform = transforms.FirstOrDefault(t => !t.IsTraitDisabled && !t.IsTraitPaused); + if (transform == null && currentTransform == null) + return; + + var activity = currentTransform ?? transform.GetTransformActivity(self); + activity.Queue(self, new IssueOrderAfterTransform(order.OrderString, order.Target)); + + if (currentTransform == null) + self.QueueActivity(order.Queued, activity); + } + + string IOrderVoice.VoicePhraseForOrder(Actor self, Order order) + { + if (IsTraitDisabled) + return null; + + switch (order.OrderString) + { + case "Move": + if (!Info.MoveIntoShroud && order.Target.Type != TargetType.Invalid) + { + var cell = self.World.Map.CellContaining(order.Target.CenterPosition); + if (!self.Owner.Shroud.IsExplored(cell)) + return null; + } + + return Info.Voice; + case "Enter": + return Info.Voice; + default: return null; + } + } + + class AircraftMoveOrderTargeter : IOrderTargeter + { + readonly TransformsIntoAircraft aircraft; + + public bool TargetOverridesSelection(TargetModifiers modifiers) + { + return modifiers.HasModifier(TargetModifiers.ForceMove); + } + + public AircraftMoveOrderTargeter(Actor self, TransformsIntoAircraft aircraft) + { + this.aircraft = aircraft; + } + + public string OrderID { get { return "Move"; } } + public int OrderPriority { get { return 4; } } + public bool IsQueued { get; protected set; } + + public bool CanTarget(Actor self, Target target, List othersAtTarget, ref TargetModifiers modifiers, ref string cursor) + { + if (target.Type != TargetType.Terrain || (aircraft.Info.RequiresForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove))) + return false; + + var location = self.World.Map.CellContaining(target.CenterPosition); + var explored = self.Owner.Shroud.IsExplored(location); + cursor = self.World.Map.Contains(location) ? + (self.World.Map.GetTerrainInfo(location).CustomCursor ?? "move") : + "move-blocked"; + + IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); + + if (!explored && !aircraft.Info.MoveIntoShroud) + cursor = "move-blocked"; + + return true; + } + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoEntersTunnels.cs b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoEntersTunnels.cs new file mode 100644 index 0000000000..562fbab691 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoEntersTunnels.cs @@ -0,0 +1,93 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Activities; +using OpenRA.Mods.Common.Activities; +using OpenRA.Mods.Common.Orders; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Add to a building to expose a move cursor that triggers Transforms and issues an enter tunnel order to the transformed actor.")] + public class TransformsIntoEntersTunnelsInfo : ConditionalTraitInfo, Requires + { + public readonly string EnterCursor = "enter"; + public readonly string EnterBlockedCursor = "enter-blocked"; + + [VoiceReference] + public readonly string Voice = "Action"; + + public override object Create(ActorInitializer init) { return new TransformsIntoEntersTunnels(this); } + } + + public class TransformsIntoEntersTunnels : ConditionalTrait, IIssueOrder, IResolveOrder, IOrderVoice + { + Transforms[] transforms; + + public TransformsIntoEntersTunnels(TransformsIntoEntersTunnelsInfo info) + : base(info) { } + + protected override void Created(Actor self) + { + transforms = self.TraitsImplementing().ToArray(); + base.Created(self); + } + + IEnumerable IIssueOrder.Orders + { + get + { + if (!IsTraitDisabled) + yield return new EntersTunnels.EnterTunnelOrderTargeter(Info.EnterCursor, Info.EnterBlockedCursor); + } + } + + Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) + { + if (order.OrderID == "EnterTunnel") + return new Order(order.OrderID, self, target, queued) { SuppressVisualFeedback = true }; + + return null; + } + + void IResolveOrder.ResolveOrder(Actor self, Order order) + { + if (IsTraitDisabled || order.OrderString != "EnterTunnel" || order.Target.Type != TargetType.Actor) + return; + + var tunnel = order.Target.Actor.TraitOrDefault(); + if (tunnel == null || !tunnel.Exit.HasValue) + return; + + var currentTransform = self.CurrentActivity as Transform; + var transform = transforms.FirstOrDefault(t => !t.IsTraitDisabled && !t.IsTraitPaused); + if (transform == null && currentTransform == null) + return; + + self.SetTargetLine(order.Target, Color.Green); + + var activity = currentTransform ?? transform.GetTransformActivity(self); + activity.Queue(self, new IssueOrderAfterTransform(order.OrderString, order.Target)); + + if (currentTransform == null) + self.QueueActivity(order.Queued, activity); + } + + string IOrderVoice.VoicePhraseForOrder(Actor self, Order order) + { + return order.OrderString == "EnterTunnel" ? Info.Voice : null; + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs new file mode 100644 index 0000000000..3dbaaa6db7 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs @@ -0,0 +1,192 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Activities; +using OpenRA.Mods.Common.Activities; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Add to a building to expose a move cursor that triggers Transforms and issues a move order to the transformed actor.")] + public class TransformsIntoMobileInfo : ConditionalTraitInfo, Requires + { + [LocomotorReference] + [FieldLoader.Require] + [Desc("Locomotor used by the transformed actor. Must be defined on the World actor.")] + public readonly string Locomotor = null; + + public readonly string Cursor = "move"; + public readonly string BlockedCursor = "move-blocked"; + + [VoiceReference] + public readonly string Voice = "Action"; + + [Desc("Require the force-move modifier to display the move cursor.")] + public readonly bool RequiresForceMove = false; + + public override object Create(ActorInitializer init) { return new TransformsIntoMobile(init, this); } + + public LocomotorInfo LocomotorInfo { get; private set; } + + public override void RulesetLoaded(Ruleset rules, ActorInfo ai) + { + var locomotorInfos = rules.Actors["world"].TraitInfos(); + LocomotorInfo = locomotorInfos.FirstOrDefault(li => li.Name == Locomotor); + if (LocomotorInfo == null) + throw new YamlException("A locomotor named '{0}' doesn't exist.".F(Locomotor)); + else if (locomotorInfos.Count(li => li.Name == Locomotor) > 1) + throw new YamlException("There is more than one locomotor named '{0}'.".F(Locomotor)); + + base.RulesetLoaded(rules, ai); + } + } + + public class TransformsIntoMobile : ConditionalTrait, IIssueOrder, IResolveOrder, IOrderVoice + { + readonly Actor self; + Transforms[] transforms; + + public TransformsIntoMobile(ActorInitializer init, TransformsIntoMobileInfo info) + : base(info) + { + self = init.Self; + } + + protected override void Created(Actor self) + { + transforms = self.TraitsImplementing().ToArray(); + base.Created(self); + } + + IEnumerable IIssueOrder.Orders + { + get + { + if (!IsTraitDisabled) + yield return new MoveOrderTargeter(self, this); + } + } + + // Note: Returns a valid order even if the unit can't move to the target + Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) + { + if (order is MoveOrderTargeter) + return new Order("Move", self, target, queued); + + return null; + } + + void IResolveOrder.ResolveOrder(Actor self, Order order) + { + if (IsTraitDisabled) + return; + + if (order.OrderString == "Move") + { + var cell = self.World.Map.Clamp(this.self.World.Map.CellContaining(order.Target.CenterPosition)); + if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) + return; + + var currentTransform = self.CurrentActivity as Transform; + var transform = transforms.FirstOrDefault(t => !t.IsTraitDisabled && !t.IsTraitPaused); + if (transform == null && currentTransform == null) + return; + + self.SetTargetLine(Target.FromCell(self.World, cell), Color.Green); + + var activity = currentTransform ?? transform.GetTransformActivity(self); + activity.Queue(self, new IssueOrderAfterTransform("Move", order.Target)); + + if (currentTransform == null) + self.QueueActivity(order.Queued, activity); + } + + if (order.OrderString == "Stop") + self.CancelActivity(); + } + + string IOrderVoice.VoicePhraseForOrder(Actor self, Order order) + { + if (IsTraitDisabled) + return null; + + switch (order.OrderString) + { + case "Move": + if (!Info.LocomotorInfo.MoveIntoShroud && order.Target.Type != TargetType.Invalid) + { + var cell = self.World.Map.CellContaining(order.Target.CenterPosition); + if (!self.Owner.Shroud.IsExplored(cell)) + return null; + } + + return Info.Voice; + case "Stop": + return Info.Voice; + default: + return null; + } + } + + class MoveOrderTargeter : IOrderTargeter + { + readonly TransformsIntoMobile mobile; + readonly bool rejectMove; + public bool TargetOverridesSelection(TargetModifiers modifiers) + { + return modifiers.HasModifier(TargetModifiers.ForceMove); + } + + public MoveOrderTargeter(Actor self, TransformsIntoMobile mobile) + { + this.mobile = mobile; + rejectMove = !self.AcceptsOrder("Move"); + } + + public string OrderID { get { return "Move"; } } + public int OrderPriority { get { return 4; } } + public bool IsQueued { get; protected set; } + + public bool CanTarget(Actor self, Target target, List othersAtTarget, ref TargetModifiers modifiers, ref string cursor) + { + if (rejectMove || target.Type != TargetType.Terrain || (mobile.Info.RequiresForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove))) + return false; + + var location = self.World.Map.CellContaining(target.CenterPosition); + IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); + + var explored = self.Owner.Shroud.IsExplored(location); + cursor = self.World.Map.Contains(location) ? + (self.World.Map.GetTerrainInfo(location).CustomCursor ?? mobile.Info.Cursor) : mobile.Info.BlockedCursor; + + var locomotor = mobile.Info.LocomotorInfo; + if (!mobile.transforms.Any(t => !t.IsTraitDisabled && !t.IsTraitPaused) + || (!explored && !locomotor.MoveIntoShroud) + || (explored && !CanEnterCell(self.World, self, location))) + cursor = mobile.Info.BlockedCursor; + + return true; + } + + bool CanEnterCell(World world, Actor self, CPos cell) + { + if (mobile.Info.LocomotorInfo.MovementCostForCell(world, cell) == int.MaxValue) + return false; + + return mobile.Info.LocomotorInfo.CanMoveFreelyInto(world, self, cell, null, CellConditions.BlockedByMovers); + } + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoPassenger.cs b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoPassenger.cs new file mode 100644 index 0000000000..e3defb21c7 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoPassenger.cs @@ -0,0 +1,142 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Activities; +using OpenRA.Mods.Common.Activities; +using OpenRA.Mods.Common.Orders; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Add to a building to expose a move cursor that triggers Transforms and issues an EnterTransport order to the transformed actor.")] + public class TransformsIntoPassengerInfo : ConditionalTraitInfo, Requires + { + public readonly string CargoType = null; + public readonly int Weight = 1; + + [Desc("Use to set when to use alternate transports (Never, Force, Default, Always).", + "Force - use force move modifier (Alt) to enable.", + "Default - use force move modifier (Alt) to disable.")] + public readonly AlternateTransportsMode AlternateTransportsMode = AlternateTransportsMode.Force; + + [VoiceReference] + public readonly string Voice = "Action"; + + public override object Create(ActorInitializer init) { return new TransformsIntoPassenger(this); } + } + + public class TransformsIntoPassenger : ConditionalTrait, IIssueOrder, IResolveOrder, IOrderVoice + { + readonly IOrderTargeter[] orders; + Transforms[] transforms; + + public TransformsIntoPassenger(TransformsIntoPassengerInfo info) + : base(info) + { + Func canTarget = IsCorrectCargoType; + Func useEnterCursor = CanEnter; + orders = new EnterAlliedActorTargeter[] + { + new EnterTransportTargeter("EnterTransport", 5, canTarget, useEnterCursor, Info.AlternateTransportsMode), + new EnterTransportsTargeter("EnterTransports", 5, canTarget, useEnterCursor, Info.AlternateTransportsMode) + }; + } + + protected override void Created(Actor self) + { + transforms = self.TraitsImplementing().ToArray(); + base.Created(self); + } + + IEnumerable IIssueOrder.Orders + { + get + { + return !IsTraitDisabled ? orders : Enumerable.Empty(); + } + } + + Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) + { + if (order.OrderID == "EnterTransport" || order.OrderID == "EnterTransports") + return new Order(order.OrderID, self, target, queued); + + return null; + } + + bool IsCorrectCargoType(Actor target) + { + var ci = target.Info.TraitInfo(); + return ci.Types.Contains(Info.CargoType); + } + + bool CanEnter(Actor target) + { + if (!transforms.Any(t => !t.IsTraitDisabled && !t.IsTraitPaused)) + return false; + + var cargo = target.TraitOrDefault(); + return cargo != null && cargo.HasSpace(Info.Weight); + } + + void IResolveOrder.ResolveOrder(Actor self, Order order) + { + if (IsTraitDisabled) + return; + + if (order.OrderString != "EnterTransport" && order.OrderString != "EnterTransports") + return; + + // Enter orders are only valid for own/allied actors, + // which are guaranteed to never be frozen. + if (order.Target.Type != TargetType.Actor) + return; + + var targetActor = order.Target.Actor; + if (!CanEnter(targetActor)) + return; + + if (!IsCorrectCargoType(targetActor)) + return; + + var currentTransform = self.CurrentActivity as Transform; + var transform = transforms.FirstOrDefault(t => !t.IsTraitDisabled && !t.IsTraitPaused); + if (transform == null && currentTransform == null) + return; + + self.SetTargetLine(order.Target, Color.Green); + + var activity = currentTransform ?? transform.GetTransformActivity(self); + activity.Queue(self, new IssueOrderAfterTransform(order.OrderString, order.Target)); + + if (currentTransform == null) + self.QueueActivity(order.Queued, activity); + } + + string IOrderVoice.VoicePhraseForOrder(Actor self, Order order) + { + if (IsTraitDisabled) + return null; + + if (order.OrderString != "EnterTransport" && order.OrderString != "EnterTransports") + return null; + + if (order.Target.Type != TargetType.Actor || !CanEnter(order.Target.Actor)) + return null; + + return Info.Voice; + } + } +} diff --git a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoRepairable.cs b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoRepairable.cs new file mode 100644 index 0000000000..1294cce065 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoRepairable.cs @@ -0,0 +1,110 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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, either version 3 of + * the License, or (at your option) any later version. For more + * information, see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Linq; +using OpenRA.Activities; +using OpenRA.Mods.Common.Activities; +using OpenRA.Mods.Common.Orders; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Add to a building to expose a move cursor that triggers Transforms and issues a repair order to the transformed actor.")] + public class TransformsIntoRepairableInfo : ConditionalTraitInfo, Requires, Requires + { + [ActorReference] + [FieldLoader.Require] + public readonly HashSet RepairActors = new HashSet { }; + + [VoiceReference] + public readonly string Voice = "Action"; + + public override object Create(ActorInitializer init) { return new TransformsIntoRepairable(this); } + } + + public class TransformsIntoRepairable : ConditionalTrait, IIssueOrder, IResolveOrder, IOrderVoice + { + Transforms[] transforms; + IHealth health; + + public TransformsIntoRepairable(TransformsIntoRepairableInfo info) + : base(info) { } + + protected override void Created(Actor self) + { + transforms = self.TraitsImplementing().ToArray(); + health = self.Trait(); + base.Created(self); + } + + IEnumerable IIssueOrder.Orders + { + get + { + if (!IsTraitDisabled) + yield return new EnterAlliedActorTargeter("Repair", 5, CanRepairAt, _ => CanRepair()); + } + } + + bool CanRepair() + { + return health.DamageState > DamageState.Undamaged && transforms.Any(t => !t.IsTraitDisabled && !t.IsTraitPaused); + } + + bool CanRepairAt(Actor target) + { + return Info.RepairActors.Contains(target.Info.Name); + } + + Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) + { + if (order.OrderID == "Repair") + return new Order(order.OrderID, self, target, queued); + + return null; + } + + void IResolveOrder.ResolveOrder(Actor self, Order order) + { + if (IsTraitDisabled || order.OrderString != "Repair") + return; + + // Repair orders are only valid for own/allied actors, + // which are guaranteed to never be frozen. + if (order.Target.Type != TargetType.Actor) + return; + + if (!CanRepairAt(order.Target.Actor) || !CanRepair()) + return; + + var currentTransform = self.CurrentActivity as Transform; + var transform = transforms.FirstOrDefault(t => !t.IsTraitDisabled && !t.IsTraitPaused); + if (transform == null && currentTransform == null) + return; + + self.SetTargetLine(order.Target, Color.Green); + + var activity = currentTransform ?? transform.GetTransformActivity(self); + activity.Queue(self, new IssueOrderAfterTransform(order.OrderString, order.Target)); + + if (currentTransform == null) + self.QueueActivity(order.Queued, activity); + } + + string IOrderVoice.VoicePhraseForOrder(Actor self, Order order) + { + return order.OrderString == "Repair" && !IsTraitDisabled && CanRepair() ? Info.Voice : null; + } + } +} diff --git a/OpenRA.Mods.Common/Traits/EntersTunnels.cs b/OpenRA.Mods.Common/Traits/EntersTunnels.cs index c82a23e38e..e3cef10545 100644 --- a/OpenRA.Mods.Common/Traits/EntersTunnels.cs +++ b/OpenRA.Mods.Common/Traits/EntersTunnels.cs @@ -44,7 +44,7 @@ namespace OpenRA.Mods.Common.Traits { get { - yield return new EnterTunnelOrderTargeter(info); + yield return new EnterTunnelOrderTargeter(info.EnterCursor, info.EnterBlockedCursor); } } @@ -78,14 +78,16 @@ namespace OpenRA.Mods.Common.Traits self.QueueActivity(move.MoveTo(tunnel.Exit.Value, tunnel.NearEnough)); } - class EnterTunnelOrderTargeter : UnitOrderTargeter + public class EnterTunnelOrderTargeter : UnitOrderTargeter { - readonly EntersTunnelsInfo info; + readonly string enterCursor; + readonly string enterBlockedCursor; - public EnterTunnelOrderTargeter(EntersTunnelsInfo info) - : base("EnterTunnel", 6, info.EnterCursor, true, true) + public EnterTunnelOrderTargeter(string enterCursor, string enterBlockedCursor) + : base("EnterTunnel", 6, enterCursor, true, true) { - this.info = info; + this.enterCursor = enterCursor; + this.enterBlockedCursor = enterBlockedCursor; } public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor) @@ -109,11 +111,11 @@ namespace OpenRA.Mods.Common.Traits if (!tunnel.Exit.HasValue) { - cursor = info.EnterBlockedCursor; + cursor = enterBlockedCursor; return false; } - cursor = info.EnterCursor; + cursor = enterCursor; return true; } diff --git a/OpenRA.Mods.Common/Traits/Transforms.cs b/OpenRA.Mods.Common/Traits/Transforms.cs index d3f080c2a3..1b5db57fc8 100644 --- a/OpenRA.Mods.Common/Traits/Transforms.cs +++ b/OpenRA.Mods.Common/Traits/Transforms.cs @@ -10,6 +10,7 @@ #endregion using System.Collections.Generic; +using OpenRA.Activities; using OpenRA.Mods.Common.Activities; using OpenRA.Mods.Common.Orders; using OpenRA.Traits; @@ -85,6 +86,18 @@ namespace OpenRA.Mods.Common.Traits return buildingInfo == null || self.World.CanPlaceBuilding(self.Location + Info.Offset, actorInfo, buildingInfo, self); } + public Activity GetTransformActivity(Actor self) + { + return new Transform(self, Info.IntoActor) + { + Offset = Info.Offset, + Facing = Info.Facing, + Sounds = Info.TransformSounds, + Notification = Info.TransformNotification, + Faction = faction + }; + } + public IEnumerable Orders { get @@ -127,14 +140,7 @@ namespace OpenRA.Mods.Common.Traits if (!queued) self.CancelActivity(); - self.QueueActivity(new Transform(self, Info.IntoActor) - { - Offset = Info.Offset, - Facing = Info.Facing, - Sounds = Info.TransformSounds, - Notification = Info.TransformNotification, - Faction = faction - }); + self.QueueActivity(GetTransformActivity(self)); } public void ResolveOrder(Actor self, Order order) diff --git a/mods/cnc/rules/structures.yaml b/mods/cnc/rules/structures.yaml index b98a723579..aae2502db5 100644 --- a/mods/cnc/rules/structures.yaml +++ b/mods/cnc/rules/structures.yaml @@ -20,11 +20,21 @@ FACT: Production: Produces: Building.GDI, Building.Nod, Defence.GDI, Defence.Nod Transforms: - RequiresCondition: factundeploy && !build-incomplete - PauseOnCondition: being-demolished + RequiresCondition: factundeploy + PauseOnCondition: being-demolished || build-incomplete IntoActor: mcv Offset: 1,1 Facing: 108 + TransformsIntoMobile: + RequiresCondition: factundeploy + Locomotor: heavywheeled + RequiresForceMove: true + TransformsIntoPassenger: + RequiresCondition: factundeploy + CargoType: Vehicle + TransformsIntoRepairable: + RequiresCondition: factundeploy + RepairActors: fix GrantConditionOnPrerequisite@GLOBALFACTUNDEPLOY: Condition: factundeploy Prerequisites: global-factundeploy diff --git a/mods/ra/rules/structures.yaml b/mods/ra/rules/structures.yaml index 009c9819b4..61766822d1 100644 --- a/mods/ra/rules/structures.yaml +++ b/mods/ra/rules/structures.yaml @@ -1137,11 +1137,21 @@ FACT: ActorTypes: e1,e1,e1,tecn,tecn,e6 BaseBuilding: Transforms: - RequiresCondition: factundeploy && !build-incomplete - PauseOnCondition: chrono-vortex || being-captured || being-demolished + RequiresCondition: factundeploy + PauseOnCondition: chrono-vortex || being-captured || being-demolished || build-incomplete IntoActor: mcv Offset: 1,1 Facing: 96 + TransformsIntoMobile: + RequiresCondition: factundeploy + Locomotor: heavywheeled + RequiresForceMove: true + TransformsIntoPassenger: + RequiresCondition: factundeploy + CargoType: Vehicle + TransformsIntoRepairable: + RequiresCondition: factundeploy + RepairActors: fix Sellable: RequiresCondition: !build-incomplete && !chrono-vortex && !being-captured && !being-demolished GrantConditionOnPrerequisite@GLOBALFACTUNDEPLOY: diff --git a/mods/ts/rules/shared-structures.yaml b/mods/ts/rules/shared-structures.yaml index f357e75801..b8e6bd19ad 100644 --- a/mods/ts/rules/shared-structures.yaml +++ b/mods/ts/rules/shared-structures.yaml @@ -28,12 +28,28 @@ GACNST: Value: 2500 BaseBuilding: Transforms: - RequiresCondition: factundeploy && !build-incomplete - PauseOnCondition: empdisable || being-demolished + RequiresCondition: factundeploy + PauseOnCondition: empdisable || being-demolished || build-incomplete IntoActor: mcv Offset: 1,1 Facing: 96 DeployCursor: undeploy + TransformsIntoMobile: + RequiresCondition: factundeploy + Locomotor: tracked + Voice: Move + RequiresForceMove: true + TransformsIntoRepairable: + RequiresCondition: factundeploy + RepairActors: gadept + Voice: Move + TransformsIntoEntersTunnels: + RequiresCondition: factundeploy + Voice: Move + TransformsIntoPassenger: + RequiresCondition: factundeploy + CargoType: Vehicle + Voice: Move GrantConditionOnPrerequisite@GLOBALFACTUNDEPLOY: Condition: factundeploy Prerequisites: global-factundeploy