diff --git a/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs b/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs index 7a61cfbaea..644d4abfd8 100644 --- a/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs +++ b/OpenRA.Mods.Common/Activities/FindAndDeliverResources.cs @@ -116,7 +116,7 @@ namespace OpenRA.Mods.Common.Activities // of the refinery entrance. if (LastSearchFailed) { - var lastproc = harv.DockClientManager.LastReservedHost; + var lastproc = harv.DockClientManager?.LastReservedHost; if (lastproc != null) { var deliveryLoc = self.World.Map.CellContaining(lastproc.DockPosition); @@ -171,7 +171,7 @@ namespace OpenRA.Mods.Common.Activities else { searchRadius = harvInfo.SearchFromProcRadius; - var dock = harv.DockClientManager.LastReservedHost; + var dock = harv.DockClientManager?.LastReservedHost; if (dock != null) { dockPos = dock.DockPosition; @@ -247,7 +247,7 @@ namespace OpenRA.Mods.Common.Activities else { var manager = harv.DockClientManager; - if (manager.ReservedHostActor != null) + if (manager?.ReservedHostActor != null) yield return new TargetLineNode(Target.FromActor(manager.ReservedHostActor), manager.DockLineColor); } } diff --git a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoDockClientManager.cs b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoDockClientManager.cs new file mode 100644 index 0000000000..f4d2388394 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoDockClientManager.cs @@ -0,0 +1,165 @@ +#region Copyright & License Information +/* + * Copyright (c) The OpenRA Developers and Contributors + * 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.Collections.Generic; +using System.Linq; +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 dock order to the transformed actor.")] + public class TransformsIntoDockClientInfo : ConditionalTraitInfo, Requires, IDockClientManagerInfo + { + [CursorReference] + [Desc("Cursor to display when able to dock at target actor.")] + public readonly string EnterCursor = "enter"; + + [CursorReference] + [Desc("Cursor to display when unable to dock at target actor.")] + public readonly string EnterBlockedCursor = "enter-blocked"; + + [VoiceReference] + [Desc("Voice.")] + public readonly string Voice = "Action"; + + [Desc("Color to use for the target line of docking orders.")] + public readonly Color DockLineColor = Color.Green; + + [Desc("Require the force-move modifier to display the dock cursor.")] + public readonly bool RequiresForceMove = false; + + public override object Create(ActorInitializer init) { return new TransformsIntoDockClient(init.Self, this); } + } + + public class TransformsIntoDockClient : ConditionalTrait, IResolveOrder, IOrderVoice, IIssueOrder + { + readonly Actor self; + protected IDockClient[] dockClients; + + readonly Transforms[] transforms; + + public TransformsIntoDockClient(Actor self, TransformsIntoDockClientInfo info) + : base(info) + { + this.self = self; + transforms = self.TraitsImplementing().ToArray(); + } + + protected override void Created(Actor self) + { + base.Created(self); + dockClients = self.TraitsImplementing().ToArray(); + } + + IEnumerable IIssueOrder.Orders + { + get + { + yield return new EnterAlliedActorTargeter( + "ForceDock", + 6, + Info.EnterCursor, + Info.EnterBlockedCursor, + ForceDockingPossible, + target => CanDockAt(target, true)); + yield return new EnterAlliedActorTargeter( + "Dock", + 5, + Info.EnterCursor, + Info.EnterBlockedCursor, + DockingPossible, + target => CanDockAt(target, false)); + } + } + + void IResolveOrder.ResolveOrder(Actor self, Order order) + { + if (order.OrderString == "Dock" || order.OrderString == "ForceDock") + { + // Deliver orders are only valid for own/allied actors, + // which are guaranteed to never be frozen. + // TODO: support frozen actors. + if (order.Target.Type != TargetType.Actor) + return; + + if (IsTraitDisabled) + return; + + var currentTransform = self.CurrentActivity as Transform; + var transform = transforms.FirstOrDefault(t => !t.IsTraitDisabled && !t.IsTraitPaused); + if (transform == null && currentTransform == null) + return; + + // Manually manage the inner activity queue. + var activity = currentTransform ?? transform.GetTransformActivity(); + if (!order.Queued) + activity.NextActivity?.Cancel(self); + + activity.Queue(new IssueOrderAfterTransform(order.OrderString, order.Target, Info.DockLineColor)); + + if (currentTransform == null) + self.QueueActivity(order.Queued, activity); + + self.ShowTargetLines(); + } + } + + string IOrderVoice.VoicePhraseForOrder(Actor self, Order order) + { + if (order.OrderString == "Dock" && CanDockAt(order.Target.Actor, false)) + return Info.Voice; + else if (order.OrderString == "ForceDock" && CanDockAt(order.Target.Actor, true)) + return Info.Voice; + + return null; + } + + Order IIssueOrder.IssueOrder(Actor self, IOrderTargeter order, in Target target, bool queued) + { + if (order.OrderID == "Dock" || order.OrderID == "ForceDock") + return new Order(order.OrderID, self, target, queued); + + return null; + } + + /// Clone of . + public bool DockingPossible(Actor target, TargetModifiers modifiers) + { + var forceEnter = modifiers.HasModifier(TargetModifiers.ForceMove); + if (Info.RequiresForceMove && !forceEnter) + return false; + + return !IsTraitDisabled && target.TraitsImplementing().Any(host => dockClients.Any(client => client.IsDockingPossible(host.GetDockType))); + } + + /// Clone of . + public bool ForceDockingPossible(Actor target, TargetModifiers modifiers) + { + var forceEnter = modifiers.HasModifier(TargetModifiers.ForceMove); + if (Info.RequiresForceMove && !forceEnter) + return false; + + return !IsTraitDisabled && target.TraitsImplementing().Any(host => dockClients.Any(client => client.IsDockingPossible(host.GetDockType, forceEnter))); + } + + /// Clone of . + public bool CanDockAt(Actor target, bool forceEnter = false) + { + if (!(self.CurrentActivity is Transform || transforms.Any(t => !t.IsTraitDisabled && !t.IsTraitPaused))) + return false; + + return !IsTraitDisabled && target.TraitsImplementing().Any(host => dockClients.Any(client => client.CanDockAt(target, host, forceEnter, true))); + } + } +} diff --git a/OpenRA.Mods.Common/Traits/DockClientBase.cs b/OpenRA.Mods.Common/Traits/DockClientBase.cs index 4b974fa257..3472056e4a 100644 --- a/OpenRA.Mods.Common/Traits/DockClientBase.cs +++ b/OpenRA.Mods.Common/Traits/DockClientBase.cs @@ -14,7 +14,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - public abstract class DockClientBaseInfo : ConditionalTraitInfo, IDockClientInfo, Requires { } + public abstract class DockClientBaseInfo : ConditionalTraitInfo, IDockClientInfo, Requires { } public abstract class DockClientBase : ConditionalTrait, IDockClient, INotifyCreated where InfoType : DockClientBaseInfo { @@ -27,7 +27,7 @@ namespace OpenRA.Mods.Common.Traits : base(info) { this.self = self; - DockClientManager = self.Trait(); + DockClientManager = self.TraitOrDefault(); } protected virtual bool CanDock() diff --git a/OpenRA.Mods.Common/Traits/DockClientManager.cs b/OpenRA.Mods.Common/Traits/DockClientManager.cs index 43678d7b8d..f471183ffd 100644 --- a/OpenRA.Mods.Common/Traits/DockClientManager.cs +++ b/OpenRA.Mods.Common/Traits/DockClientManager.cs @@ -19,7 +19,7 @@ using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { [Desc("Manages DockClients on the actor.")] - public class DockClientManagerInfo : ConditionalTraitInfo + public class DockClientManagerInfo : ConditionalTraitInfo, IDockClientManagerInfo { [Desc("How long (in ticks) to wait until (re-)checking for a nearby available DockHost.")] public readonly int SearchForDockDelay = 125; diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs index df9fd2ea34..0ebfcabde1 100644 --- a/OpenRA.Mods.Common/TraitsInterfaces.cs +++ b/OpenRA.Mods.Common/TraitsInterfaces.cs @@ -222,6 +222,8 @@ namespace OpenRA.Mods.Common.Traits public interface IDockClient { BitSet GetDockType { get; } + + /// When null, the client should act as if it can dock but never do. DockClientManager DockClientManager { get; } void OnDockStarted(Actor self, Actor hostActor, IDockHost host); bool OnDockTick(Actor self, Actor hostActor, IDockHost dock); @@ -283,6 +285,8 @@ namespace OpenRA.Mods.Common.Traits int DragLength { get; } } + public interface IDockClientManagerInfo : ITraitInfoInterface { } + [RequireExplicitImplementation] public interface INotifyLoadCargo {