diff --git a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoDockClientManager.cs b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoDockClientManager.cs index 6e7600bff0..b1c784d9ce 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoDockClientManager.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoDockClientManager.cs @@ -12,7 +12,6 @@ using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Activities; -using OpenRA.Mods.Common.Orders; using OpenRA.Primitives; using OpenRA.Traits; @@ -66,20 +65,13 @@ namespace OpenRA.Mods.Common.Traits { get { - yield return new EnterAlliedActorTargeter( - "ForceDock", + yield return new DockActorTargeter( 6, Info.EnterCursor, Info.EnterBlockedCursor, - ForceDockingPossible, - target => CanDockAt(target, true)); - yield return new EnterAlliedActorTargeter( - "Dock", - 5, - Info.EnterCursor, - Info.EnterBlockedCursor, + () => Info.RequiresForceMove, DockingPossible, - target => CanDockAt(target, false)); + CanDockAt); } } @@ -133,30 +125,14 @@ namespace OpenRA.Mods.Common.Traits return null; } - /// Clone of . - public bool DockingPossible(Actor target, TargetModifiers modifiers) + /// Clone of . + public bool DockingPossible(Actor target, bool forceEnter) { - 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))); + return !IsTraitDisabled && target.TraitsImplementing().Any(host => dockClients.Any(client => client.IsDockingPossible(host.GetDockType))); } /// Clone of . - public bool CanDockAt(Actor target, bool forceEnter = false) + public bool CanDockAt(Actor target, bool forceEnter) { if (!(self.CurrentActivity is Transform || transforms.Any(t => !t.IsTraitDisabled && !t.IsTraitPaused))) return false; diff --git a/OpenRA.Mods.Common/Traits/DockClientManager.cs b/OpenRA.Mods.Common/Traits/DockClientManager.cs index 783704914d..ce2f14874a 100644 --- a/OpenRA.Mods.Common/Traits/DockClientManager.cs +++ b/OpenRA.Mods.Common/Traits/DockClientManager.cs @@ -9,11 +9,12 @@ */ #endregion +using System; using System.Collections.Generic; using System.Linq; using OpenRA.Mods.Common.Activities; -using OpenRA.Mods.Common.Orders; using OpenRA.Primitives; +using OpenRA.Support; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -27,6 +28,10 @@ namespace OpenRA.Mods.Common.Traits [Desc("The pathfinding cost penalty applied for each dock client waiting to unload at a DockHost.")] public readonly int OccupancyCostModifier = 12; + [ConsumedConditionReference] + [Desc("Boolean expression defining the condition under which the regular (non-force) enter cursor is disabled.")] + public readonly BooleanExpression RequireForceMoveCondition = null; + [CursorReference] [Desc("Cursor to display when able to dock at target actor.")] public readonly string EnterCursor = "enter"; @@ -51,6 +56,7 @@ namespace OpenRA.Mods.Common.Traits protected IDockClient[] dockClients; public Color DockLineColor => Info.DockLineColor; public int OccupancyCostModifier => Info.OccupancyCostModifier; + bool requireForceMove; public DockClientManager(Actor self, DockClientManagerInfo info) : base(info) @@ -149,26 +155,19 @@ namespace OpenRA.Mods.Common.Traits { get { - yield return new EnterAlliedActorTargeter( - "ForceDock", + yield return new DockActorTargeter( 6, Info.EnterCursor, Info.EnterBlockedCursor, + () => requireForceMove, DockingPossible, - target => CanDockAt(target, true, true)); - yield return new EnterAlliedActorTargeter( - "Dock", - 5, - Info.EnterCursor, - Info.EnterBlockedCursor, - (actor, modifiers) => DockingPossible(actor), - target => CanDockAt(target, false, true)); + (target, forceEnter) => CanDockAt(target, forceEnter, true)); } } void IResolveOrder.ResolveOrder(Actor self, Order order) { - if (order.OrderString == "Dock") + if (order.OrderString == "Dock" || order.OrderString == "ForceDock") { var target = order.Target; @@ -181,27 +180,9 @@ namespace OpenRA.Mods.Common.Traits if (IsTraitDisabled) return; - var dock = AvailableDockHosts(target.Actor, false, true).ClosestDock(self, this); - if (!dock.HasValue) - return; + var dock = AvailableDockHosts(target.Actor, default, order.OrderString == "ForceDock", true) + .ClosestDock(self, this); - self.QueueActivity(order.Queued, new MoveToDock(self, dock.Value.Actor, dock.Value.Trait)); - self.ShowTargetLines(); - } - else if (order.OrderString == "ForceDock") - { - var target = order.Target; - - // Deliver orders are only valid for own/allied actors, - // which are guaranteed to never be frozen. - // TODO: support frozen actors - if (target.Type != TargetType.Actor) - return; - - if (IsTraitDisabled) - return; - - var dock = AvailableDockHosts(target.Actor, true, true).ClosestDock(self, this); if (!dock.HasValue) return; @@ -231,6 +212,20 @@ namespace OpenRA.Mods.Common.Traits return null; } + public override IEnumerable GetVariableObservers() + { + foreach (var observer in base.GetVariableObservers()) + yield return observer; + + if (Info.RequireForceMoveCondition != null) + yield return new VariableObserver(RequireForceMoveConditionChanged, Info.RequireForceMoveCondition.Variables); + } + + void RequireForceMoveConditionChanged(Actor self, IReadOnlyDictionary conditions) + { + requireForceMove = Info.RequireForceMoveCondition.Evaluate(conditions); + } + /// Do we have an enabled client with matching . public bool DockingPossible(BitSet type, bool forceEnter = false) { @@ -238,18 +233,11 @@ namespace OpenRA.Mods.Common.Traits } /// Does this contain at least one enabled with maching . - public bool DockingPossible(Actor target) + public bool DockingPossible(Actor target, bool forceEnter = false) { - return !IsTraitDisabled && target.TraitsImplementing().Any( - host => dockClients.Any(client => client.IsDockingPossible(host.GetDockType))); - } - - /// Does this contain at least one enabled with maching . - public bool DockingPossible(Actor target, TargetModifiers modifiers) - { - var forceEnter = modifiers.HasModifier(TargetModifiers.ForceMove); - return !IsTraitDisabled && target.TraitsImplementing().Any( - host => dockClients.Any(client => client.IsDockingPossible(host.GetDockType, forceEnter))); + return !IsTraitDisabled && + target.TraitsImplementing() + .Any(host => dockClients.Any(client => client.IsDockingPossible(host.GetDockType, forceEnter))); } /// Can we dock to this . @@ -270,7 +258,7 @@ namespace OpenRA.Mods.Common.Traits /// If is not set, scans all clients. Does not check if is enabled. public TraitPair? ClosestDock(IDockHost ignore, BitSet type = default, bool forceEnter = false, bool ignoreOccupancy = false) { - var clients = type.IsEmpty ? dockClients : AvailableDockClients(type); + var clients = type.IsEmpty ? dockClients : AvailableDockClients(type, forceEnter); return self.World.ActorsWithTrait() .Where(host => host.Trait != ignore && @@ -279,19 +267,21 @@ namespace OpenRA.Mods.Common.Traits } /// Get viable 's on the . - /// Does not check if is enabled. - public IEnumerable> AvailableDockHosts(Actor target, bool forceEnter = false, bool ignoreOccupancy = false) + /// If is not set, checks all clients. Does not check if is enabled. + public IEnumerable> AvailableDockHosts(Actor target, BitSet type = default, + bool forceEnter = false, bool ignoreOccupancy = false) { + var clients = type.IsEmpty ? dockClients : AvailableDockClients(type, forceEnter); return target.TraitsImplementing() - .Where(host => dockClients.Any(client => client.CanDockAt(target, host, forceEnter, ignoreOccupancy))) + .Where(host => clients.Any(client => client.CanDockAt(target, host, forceEnter, ignoreOccupancy))) .Select(host => new TraitPair(target, host)); } /// Get clients of matching . /// Does not check if is enabled. - public IEnumerable AvailableDockClients(BitSet type) + public IEnumerable AvailableDockClients(BitSet type, bool forceEnter = false) { - return dockClients.Where(client => client.IsDockingPossible(type)); + return dockClients.Where(client => client.IsDockingPossible(type, forceEnter)); } void INotifyKilled.Killed(Actor self, AttackInfo e) { UnreserveHost(); } @@ -299,6 +289,54 @@ namespace OpenRA.Mods.Common.Traits void INotifyActorDisposing.Disposing(Actor self) { UnreserveHost(); } } + public class DockActorTargeter : IOrderTargeter + { + readonly string enterCursor; + readonly string enterBlockedCursor; + readonly Func requireForceMove; + readonly Func canTarget; + readonly Func useEnterCursor; + + public DockActorTargeter(int priority, string enterCursor, string enterBlockedCursor, + Func requireForceMove, Func canTarget, Func useEnterCursor) + { + OrderID = "Dock"; + OrderPriority = priority; + this.enterCursor = enterCursor; + this.enterBlockedCursor = enterBlockedCursor; + this.requireForceMove = requireForceMove; + this.canTarget = canTarget; + this.useEnterCursor = useEnterCursor; + } + + public string OrderID { get; private set; } + public int OrderPriority { get; } + public bool TargetOverridesSelection(Actor self, in Target target, List actorsAt, CPos xy, TargetModifiers modifiers) { return true; } + + public bool CanTarget(Actor self, in Target target, ref TargetModifiers modifiers, ref string cursor) + { + // TODO: support frozen actors + if (target.Type != TargetType.Actor) + return false; + + cursor = enterCursor; + IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); + var forceEnter = modifiers.HasModifier(TargetModifiers.ForceMove); + OrderID = forceEnter ? "ForceDock" : "Dock"; + + if (requireForceMove() && !forceEnter) + return false; + + if (!self.Owner.IsAlliedWith(target.Actor.Owner) || !canTarget(target.Actor, forceEnter)) + return false; + + cursor = useEnterCursor(target.Actor, forceEnter) ? enterCursor : enterBlockedCursor; + return true; + } + + public virtual bool IsQueued { get; protected set; } + } + public static class DockExts { public static TraitPair? ClosestDock(this IEnumerable> docks, Actor clientActor, DockClientManager client) @@ -318,7 +356,7 @@ namespace OpenRA.Mods.Common.Traits return 0; // Prefer docks with less occupancy (multiplier is to offset distance cost): - // TODO: add custom wieghts. E.g. owner vs allied. + // TODO: add custom weights. E.g. owner vs allied. return dock.Trait.ReservationCount * client.OccupancyCostModifier; });