Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AutoCarryall can receive manual order and auto action can be turn on/off by condition #20398

Merged
merged 1 commit into from
Aug 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
64 changes: 48 additions & 16 deletions OpenRA.Mods.Common/Traits/AutoCarryable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ public class AutoCarryableInfo : CarryableInfo
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)
Expand All @@ -44,14 +48,14 @@ void MovementCancelled()
return;

Destination = null;
autoReserved = false;

// TODO: We could implement something like a carrier.Trait<Carryall>().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;
Expand All @@ -63,13 +67,13 @@ void RequestTransport(Actor self, CPos destination)
return;

// Inform all idle carriers
var carriers = self.World.ActorsWithTrait<Carryall>()
.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<AutoCarryall>()
.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;
}

Expand All @@ -84,38 +88,66 @@ public override void Detached(Actor self)
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);
}
}
}
105 changes: 84 additions & 21 deletions OpenRA.Mods.Common/Traits/AutoCarryall.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

public override IEnumerable<VariableObserver> GetVariableObservers()
{
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<string, int> conditions)
{
EnableAutoCarry = info.AutoCarryCondition.Evaluate(conditions);
}

// A carryable notifying us that he'd like to be carried
public override bool RequestTransportNotify(Actor self, Actor carryable, CPos destination)
public bool RequestTransportNotify(Actor self, Actor carryable)
{
if (busy || IsTraitDisabled)
if (Busy(self) || IsTraitDisabled || !EnableAutoCarry)
return false;

if (ReserveCarryable(self, carryable))
if (AutoReserveCarryable(self, carryable))
{
self.QueueActivity(false, new FerryUnit(self, carryable));
return true;
Expand All @@ -50,10 +80,28 @@ public override bool RequestTransportNotify(Actor self, Actor carryable, CPos de
return false;
}

bool AutoReserveCarryable(Actor self, Actor carryable)
{
if (State == CarryallState.Reserved)
UnreserveCarryable(self);

if (State != CarryallState.Idle)
return false;

var act = carryable.TraitOrDefault<AutoCarryable>();

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<AutoCarryall>(c => !c.busy)
var carriers = self.World.ActorsHavingTrait<AutoCarryall>(c => !Busy(self) && c.EnableAutoCarry)
.Where(a => a.Owner == self.Owner && a.IsInWorld);

return carriers.ClosestTo(candidateCargo) == self;
Expand All @@ -65,7 +113,7 @@ void FindCarryableForTransport(Actor self)
return;

// Get all carryables who want transport
var carryables = self.World.ActorsWithTrait<Carryable>().Where(p =>
var carryables = self.World.ActorsWithTrait<AutoCarryable>().Where(p =>
{
var actor = p.Actor;
if (actor == null)
Expand Down Expand Up @@ -93,9 +141,8 @@ void FindCarryableForTransport(Actor self)
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;
}
Expand All @@ -105,32 +152,48 @@ void FindCarryableForTransport(Actor self)
sealed class FerryUnit : Activity
{
readonly Actor cargo;
readonly Carryable carryable;
readonly Carryall carryall;
readonly AutoCarryable carryable;
readonly AutoCarryall carryall;

PunkPun marked this conversation as resolved.
Show resolved Hide resolved
public FerryUnit(Actor self, Actor cargo)
{
this.cargo = cargo;
carryable = cargo.Trait<Carryable>();
carryall = self.Trait<Carryall>();
carryable = cargo.Trait<AutoCarryable>();
carryall = self.Trait<AutoCarryall>();
}

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<TargetLineNode> 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;
}
Expand Down
10 changes: 4 additions & 6 deletions OpenRA.Mods.Common/Traits/Carryable.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,12 @@ public class Carryable : ConditionalTrait<CarryableInfo>
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;
Expand All @@ -65,7 +63,7 @@ public Carryable(CarryableInfo info)

protected override void Created(Actor self)
{
mobile = self.TraitOrDefault<Mobile>();
Mobile = self.TraitOrDefault<Mobile>();
delayPickups = self.TraitsImplementing<IDelayCarryallPickup>().ToArray();

base.Created(self);
Expand Down Expand Up @@ -129,7 +127,7 @@ public virtual LockResponse LockForPickup(Actor self, Actor carrier)
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)
Expand All @@ -142,7 +140,7 @@ public virtual LockResponse LockForPickup(Actor self, Actor carrier)
}

// 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;
Expand Down