From c9dfb215aee5a2e63dc02d4247669fb502a9b6a1 Mon Sep 17 00:00:00 2001 From: dnqbob Date: Tue, 25 Oct 2022 20:03:00 +0800 Subject: [PATCH] Auto carry action can be controlled by condition --- OpenRA.Mods.Common/Traits/AutoCarryable.cs | 64 +++++++++--- OpenRA.Mods.Common/Traits/AutoCarryall.cs | 107 ++++++++++++++++----- OpenRA.Mods.Common/Traits/Carryable.cs | 10 +- OpenRA.Mods.Common/Traits/Carryall.cs | 19 ++-- 4 files changed, 144 insertions(+), 56 deletions(-) diff --git a/OpenRA.Mods.Common/Traits/AutoCarryable.cs b/OpenRA.Mods.Common/Traits/AutoCarryable.cs index 7dc96881f0..0786d857b6 100644 --- a/OpenRA.Mods.Common/Traits/AutoCarryable.cs +++ b/OpenRA.Mods.Common/Traits/AutoCarryable.cs @@ -25,6 +25,10 @@ namespace OpenRA.Mods.Common.Traits public class AutoCarryable : Carryable, ICallForTransport { readonly AutoCarryableInfo info; + bool autoReserved = false; + + public CPos? Destination { get; private set; } + public bool WantsTransport => Destination != null && !IsTraitDisabled; public AutoCarryable(AutoCarryableInfo info) : base(info) @@ -44,14 +48,14 @@ namespace OpenRA.Mods.Common.Traits return; Destination = null; + autoReserved = false; // TODO: We could implement something like a carrier.Trait().CancelTransportNotify(self) and call it here } void RequestTransport(Actor self, CPos destination) { - var delta = self.World.Map.CenterOfCell(destination) - self.CenterPosition; - if (delta.HorizontalLengthSquared < info.MinDistance.LengthSquared) + if (!IsValidAutoCarryDistance(self, destination)) { Destination = null; return; @@ -63,13 +67,13 @@ namespace OpenRA.Mods.Common.Traits return; // Inform all idle carriers - var carriers = self.World.ActorsWithTrait() - .Where(c => c.Trait.State == Carryall.CarryallState.Idle && !c.Trait.IsTraitDisabled && !c.Actor.IsDead && c.Actor.Owner == self.Owner && c.Actor.IsInWorld) + var carriers = self.World.ActorsWithTrait() + .Where(c => c.Trait.State == Carryall.CarryallState.Idle && !c.Trait.IsTraitDisabled && c.Trait.EnableAutoCarry && !c.Actor.IsDead && c.Actor.Owner == self.Owner && c.Actor.IsInWorld) .OrderBy(p => (self.Location - p.Actor.Location).LengthSquared); // Enumerate idle carriers to find the first that is able to transport us foreach (var carrier in carriers) - if (carrier.Trait.RequestTransportNotify(carrier.Actor, self, destination)) + if (carrier.Trait.RequestTransportNotify(carrier.Actor, self)) return; } @@ -84,38 +88,66 @@ namespace OpenRA.Mods.Common.Traits base.Detached(self); } - public override bool Reserve(Actor self, Actor carrier) + public bool AutoReserve(Actor self, Actor carrier) { if (Reserved || !WantsTransport) return false; - var delta = self.World.Map.CenterOfCell(Destination.Value) - self.CenterPosition; - if (delta.HorizontalLengthSquared < info.MinDistance.LengthSquared) + if (!IsValidAutoCarryDistance(self, Destination.Value)) { // Cancel pickup MovementCancelled(); return false; } - return base.Reserve(self, carrier); + if (Reserve(self, carrier)) + { + autoReserved = true; + return true; + } + + return false; } // Prepare for transport pickup public override LockResponse LockForPickup(Actor self, Actor carrier) { - if ((state == State.Locked && Carrier != carrier) || !WantsTransport) + if (state == State.Locked && Carrier != carrier) return LockResponse.Failed; - // Last chance to change our mind... - var delta = self.World.Map.CenterOfCell(Destination.Value) - self.CenterPosition; - if (delta.HorizontalLengthSquared < info.MinDistance.LengthSquared) + // When "autoReserved" is true, the carrying operation is given by auto command + // we still need to check the validity of "Destination" to ensure an effective trip. + if (autoReserved) { - // Cancel pickup - MovementCancelled(); - return LockResponse.Failed; + if (!WantsTransport) + { + // Cancel pickup + MovementCancelled(); + return LockResponse.Failed; + } + + if (!IsValidAutoCarryDistance(self, Destination.Value)) + { + // Cancel pickup + MovementCancelled(); + return LockResponse.Failed; + } + + // Reset "autoReserved" as we finished the check + autoReserved = false; } return base.LockForPickup(self, carrier); } + + bool IsValidAutoCarryDistance(Actor self, CPos destination) + { + if (Mobile == null) + return false; + + // TODO: change the check here to pathfinding distance in the future + return (self.World.Map.CenterOfCell(destination) - self.CenterPosition).HorizontalLengthSquared >= info.MinDistance.LengthSquared + || !Mobile.PathFinder.PathExistsForLocomotor(Mobile.Locomotor, self.Location, destination); + } } } diff --git a/OpenRA.Mods.Common/Traits/AutoCarryall.cs b/OpenRA.Mods.Common/Traits/AutoCarryall.cs index bd6b2117ef..31ea1ba384 100644 --- a/OpenRA.Mods.Common/Traits/AutoCarryall.cs +++ b/OpenRA.Mods.Common/Traits/AutoCarryall.cs @@ -9,39 +9,69 @@ */ #endregion +using System.Collections.Generic; using System.Linq; using OpenRA.Activities; using OpenRA.Mods.Common.Activities; +using OpenRA.Support; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits { - [Desc("Automatically transports harvesters with the Carryable trait between resource fields and refineries.")] + [Desc("Automatically transports harvesters with the AutoCarryable and CarryableHarvester between resource fields and refineries.")] public class AutoCarryallInfo : CarryallInfo { + [ConsumedConditionReference] + [Desc("Boolean expression defining the condition under which the auto carry behavior is enabled. Enabled by default.")] + public readonly BooleanExpression AutoCarryCondition = null; + public override object Create(ActorInitializer init) { return new AutoCarryall(init.Self, this); } } - public class AutoCarryall : Carryall, INotifyBecomingIdle + public class AutoCarryall : Carryall, INotifyBecomingIdle, IObservesVariables { - bool busy; + readonly AutoCarryallInfo info; + + public bool EnableAutoCarry { get; private set; } public AutoCarryall(Actor self, AutoCarryallInfo info) - : base(self, info) { } + : base(self, info) + { + this.info = info; + EnableAutoCarry = true; + } + + static bool Busy(Actor self) => self.CurrentActivity != null && self.CurrentActivity is not FlyIdle; void INotifyBecomingIdle.OnBecomingIdle(Actor self) { - busy = false; + if (!EnableAutoCarry || IsTraitDisabled) + return; + FindCarryableForTransport(self); } - // A carryable notifying us that he'd like to be carried - public override bool RequestTransportNotify(Actor self, Actor carryable, CPos destination) + public override IEnumerable GetVariableObservers() { - if (busy || IsTraitDisabled) + foreach (var observer in base.GetVariableObservers()) + yield return observer; + + if (info.AutoCarryCondition != null) + yield return new VariableObserver(AutoCarryConditionsChanged, info.AutoCarryCondition.Variables); + } + + void AutoCarryConditionsChanged(Actor self, IReadOnlyDictionary conditions) + { + EnableAutoCarry = info.AutoCarryCondition.Evaluate(conditions); + } + + // A carryable notifying us that he'd like to be carried + public bool RequestTransportNotify(Actor self, Actor carryable) + { + if (Busy(self) || IsTraitDisabled || !EnableAutoCarry) return false; - if (ReserveCarryable(self, carryable)) + if (AutoReserveCarryable(self, carryable)) { self.QueueActivity(false, new FerryUnit(self, carryable)); return true; @@ -50,10 +80,28 @@ namespace OpenRA.Mods.Common.Traits return false; } + bool AutoReserveCarryable(Actor self, Actor carryable) + { + if (State == CarryallState.Reserved) + UnreserveCarryable(self); + + if (State != CarryallState.Idle) + return false; + + var act = carryable.TraitOrDefault(); + + if (act == null || !act.AutoReserve(carryable, self)) + return false; + + Carryable = carryable; + State = CarryallState.Reserved; + return true; + } + static bool IsBestAutoCarryallForCargo(Actor self, Actor candidateCargo) { // Find carriers - var carriers = self.World.ActorsHavingTrait(c => !c.busy) + var carriers = self.World.ActorsHavingTrait(c => !Busy(self) && c.EnableAutoCarry) .Where(a => a.Owner == self.Owner && a.IsInWorld); return carriers.ClosestTo(candidateCargo) == self; @@ -65,7 +113,7 @@ namespace OpenRA.Mods.Common.Traits return; // Get all carryables who want transport - var carryables = self.World.ActorsWithTrait().Where(p => + var carryables = self.World.ActorsWithTrait().Where(p => { var actor = p.Actor; if (actor == null) @@ -93,9 +141,8 @@ namespace OpenRA.Mods.Common.Traits foreach (var p in carryables) { // Check if its actually me who's the best candidate - if (IsBestAutoCarryallForCargo(self, p.Actor) && ReserveCarryable(self, p.Actor)) + if (IsBestAutoCarryallForCargo(self, p.Actor) && AutoReserveCarryable(self, p.Actor)) { - busy = true; self.QueueActivity(false, new FerryUnit(self, p.Actor)); break; } @@ -105,32 +152,48 @@ namespace OpenRA.Mods.Common.Traits sealed class FerryUnit : Activity { readonly Actor cargo; - readonly Carryable carryable; - readonly Carryall carryall; + readonly AutoCarryable carryable; + readonly AutoCarryall carryall; public FerryUnit(Actor self, Actor cargo) { this.cargo = cargo; - carryable = cargo.Trait(); - carryall = self.Trait(); + carryable = cargo.Trait(); + carryall = self.Trait(); } protected override void OnFirstRun(Actor self) { - if (!cargo.IsDead && !carryall.IsTraitDisabled) + if (!carryall.IsTraitDisabled && carryall.Carryable != null && !carryall.Carryable.IsDead) QueueChild(new PickupUnit(self, cargo, 0, carryall.Info.TargetLineColor)); } + public override IEnumerable TargetLineNodes(Actor self) + { + if (ChildActivity != null) + { + // Draw a line to destination if haven't pick up the cargo + if (ChildActivity is PickupUnit) + { + yield return new TargetLineNode(Target.FromActor(cargo), carryall.Info.TargetLineColor); + if (carryable.Destination != null) + yield return new TargetLineNode(Target.FromCell(self.World, carryable.Destination.Value), carryall.Info.TargetLineColor); + } + else + foreach (var n in ChildActivity.TargetLineNodes(self)) + yield return n; + } + } + public override bool Tick(Actor self) { // Cargo may have become invalid or PickupUnit cancelled. - if (carryall.IsTraitDisabled || carryall.Carryable == null || carryall.Carryable.IsDead) + if (IsCanceling || carryall.IsTraitDisabled || carryall.Carryable == null || carryall.Carryable.IsDead) return true; var dropRange = carryall.Info.DropRange; - var destination = carryable.Destination; - if (destination != null) - self.QueueActivity(true, new DeliverUnit(self, Target.FromCell(self.World, destination.Value), dropRange, carryall.Info.TargetLineColor)); + if (carryable.Destination != null) + QueueChild(new DeliverUnit(self, Target.FromCell(self.World, carryable.Destination.Value), dropRange, carryall.Info.TargetLineColor)); return true; } diff --git a/OpenRA.Mods.Common/Traits/Carryable.cs b/OpenRA.Mods.Common/Traits/Carryable.cs index 7a27bac45a..697272579d 100644 --- a/OpenRA.Mods.Common/Traits/Carryable.cs +++ b/OpenRA.Mods.Common/Traits/Carryable.cs @@ -48,14 +48,12 @@ namespace OpenRA.Mods.Common.Traits int carriedToken = Actor.InvalidConditionToken; int lockedToken = Actor.InvalidConditionToken; - Mobile mobile; IDelayCarryallPickup[] delayPickups; public Actor Carrier { get; private set; } public bool Reserved => state != State.Free; - public CPos? Destination { get; protected set; } - public bool WantsTransport => Destination != null && !IsTraitDisabled; + protected Mobile Mobile { get; private set; } protected enum State { Free, Reserved, Locked } protected State state = State.Free; protected bool attached; @@ -65,7 +63,7 @@ namespace OpenRA.Mods.Common.Traits protected override void Created(Actor self) { - mobile = self.TraitOrDefault(); + Mobile = self.TraitOrDefault(); delayPickups = self.TraitsImplementing().ToArray(); base.Created(self); @@ -129,7 +127,7 @@ namespace OpenRA.Mods.Common.Traits if (delayPickups.Any(d => d.IsTraitEnabled() && !d.TryLockForPickup(self, carrier))) return LockResponse.Pending; - if (mobile != null && !mobile.CanStayInCell(self.Location)) + if (Mobile != null && !Mobile.CanStayInCell(self.Location)) return LockResponse.Pending; if (state != State.Locked) @@ -142,7 +140,7 @@ namespace OpenRA.Mods.Common.Traits } // Make sure we are not moving and at our normal position with respect to the cell grid - if (mobile != null && mobile.IsMovingBetweenCells) + if (Mobile != null && Mobile.IsMovingBetweenCells) return LockResponse.Pending; return LockResponse.Success; diff --git a/OpenRA.Mods.Common/Traits/Carryall.cs b/OpenRA.Mods.Common/Traits/Carryall.cs index 76c285f6db..ed2134d76a 100644 --- a/OpenRA.Mods.Common/Traits/Carryall.cs +++ b/OpenRA.Mods.Common/Traits/Carryall.cs @@ -94,7 +94,7 @@ namespace OpenRA.Mods.Common.Traits Carrying } - readonly AircraftInfo aircraftInfo; + protected readonly AircraftInfo AircraftInfo; readonly Aircraft aircraft; readonly BodyOrientation body; readonly IFacing facing; @@ -102,8 +102,8 @@ namespace OpenRA.Mods.Common.Traits // The actor we are currently carrying. [Sync] - public Actor Carryable { get; private set; } - public CarryallState State { get; private set; } + public Actor Carryable { get; protected set; } + public CarryallState State { get; protected set; } WAngle cachedFacing; IActorPreview[] carryablePreview; @@ -120,7 +120,7 @@ namespace OpenRA.Mods.Common.Traits Carryable = null; State = CarryallState.Idle; - aircraftInfo = self.Info.TraitInfoOrDefault(); + AircraftInfo = self.Info.TraitInfoOrDefault(); aircraft = self.Trait(); body = self.Trait(); facing = self.Trait(); @@ -183,11 +183,6 @@ namespace OpenRA.Mods.Common.Traits UnreserveCarryable(self); } - public virtual bool RequestTransportNotify(Actor self, Actor carryable, CPos destination) - { - return false; - } - public virtual WVec OffsetForCarryable(Actor self, Actor carryable) { return Info.LocalOffset - carryable.Info.TraitInfo().LocalOffset; @@ -239,7 +234,7 @@ namespace OpenRA.Mods.Common.Traits CarryableOffset = WVec.Zero; } - public virtual bool ReserveCarryable(Actor self, Actor carryable) + public bool ReserveCarryable(Actor self, Actor carryable) { if (State == CarryallState.Reserved) UnreserveCarryable(self); @@ -327,7 +322,7 @@ namespace OpenRA.Mods.Common.Traits yield return new CarryallPickupOrderTargeter(Info); yield return new DeployOrderTargeter("Unload", 10, () => CanUnload() ? Info.UnloadCursor : Info.UnloadBlockedCursor); - yield return new CarryallDeliverUnitTargeter(aircraftInfo, Info); + yield return new CarryallDeliverUnitTargeter(AircraftInfo, Info); } } @@ -354,7 +349,7 @@ namespace OpenRA.Mods.Common.Traits if (order.OrderString == "DeliverUnit") { var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); - if (!aircraftInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) + if (!AircraftInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) return; self.QueueActivity(order.Queued, new DeliverUnit(self, order.Target, Info.DropRange, Info.TargetLineColor));