diff --git a/OpenRA.Mods.Cnc/WithCargo.cs b/OpenRA.Mods.Cnc/WithCargo.cs index 99f86b5401..cdc489ca56 100644 --- a/OpenRA.Mods.Cnc/WithCargo.cs +++ b/OpenRA.Mods.Cnc/WithCargo.cs @@ -62,7 +62,7 @@ namespace OpenRA.Mods.Cnc cargoFacing.Facing = facing.Facing; var cargoPassenger = c.Trait(); - if (cargoInfo.DisplayTypes.Contains(cargoPassenger.info.CargoType)) + if (cargoInfo.DisplayTypes.Contains(cargoPassenger.Info.CargoType)) { var offset = pos - c.CenterPosition + body.LocalToWorld(cargoInfo.LocalOffset[i++ % cargoInfo.LocalOffset.Length].Rotate(bodyOrientation)); foreach (var cr in c.Render(wr)) diff --git a/OpenRA.Mods.RA/Activities/EnterTransport.cs b/OpenRA.Mods.RA/Activities/EnterTransport.cs index 55597d7c31..df8230cbce 100644 --- a/OpenRA.Mods.RA/Activities/EnterTransport.cs +++ b/OpenRA.Mods.RA/Activities/EnterTransport.cs @@ -8,39 +8,42 @@ */ #endregion +using System; using System.Linq; using OpenRA.Traits; namespace OpenRA.Mods.RA.Activities { - class EnterTransport : Activity + class EnterTransport : Enter { readonly Actor transport; - readonly Cargo cargo; + readonly Passenger passenger; + readonly int maxTries; + Cargo cargo; - public EnterTransport(Actor self, Actor transport) + public EnterTransport(Actor self, Actor transport, int maxTries = 0) + : base(self, transport, maxTries) { this.transport = transport; + this.maxTries = maxTries; cargo = transport.Trait(); + passenger = self.Trait(); } - public override Activity Tick(Actor self) + protected override void Unreserve(Actor self, bool abort) { passenger.Unreserve(self); } + protected override bool CanReserve(Actor self) { return cargo.Unloading || cargo.CanLoad(transport, self); } + protected override ReserveStatus Reserve(Actor self) { - if (IsCanceled) - return NextActivity; - - if (transport == null || !transport.IsInWorld) - return NextActivity; - - if (!cargo.CanLoad(transport, self)) - return NextActivity; - - // TODO: Queue a move order to the transport? need to be - // careful about units that can't path to the transport - var cells = Util.AdjacentCells(self.World, Target.FromActor(transport)); - if (!cells.Contains(self.Location)) - return NextActivity; + var status = base.Reserve(self); + if (status != ReserveStatus.Ready) + return status; + if (passenger.Reserve(self, cargo)) + return ReserveStatus.Ready; + return ReserveStatus.Pending; + } + protected override void OnInside(Actor self) + { self.World.AddFrameEndTask(w => { if (self.IsDead() || transport.IsDead() || !cargo.CanLoad(transport, self)) @@ -50,7 +53,19 @@ namespace OpenRA.Mods.RA.Activities w.Remove(self); }); - return this; + Done(self); + } + + protected override bool TryGetAlternateTarget(Actor self, int tries, ref Target target) + { + if (tries > maxTries) + return false; + var type = target.Actor.Info.Name; + return TryGetAlternateTargetInCircle( + self, passenger.Info.AlternateTransportScanRange, + t => cargo = t.Actor.Trait(), // update cargo + a => { var c = a.TraitOrDefault(); return c != null && (c.Unloading || c.CanLoad(a, self)); }, + new Func[] { a => a.Info.Name == type }); // Prefer transports of the same type } } } diff --git a/OpenRA.Mods.RA/Activities/UnloadCargo.cs b/OpenRA.Mods.RA/Activities/UnloadCargo.cs index 74a1f41031..b606c96e8e 100644 --- a/OpenRA.Mods.RA/Activities/UnloadCargo.cs +++ b/OpenRA.Mods.RA/Activities/UnloadCargo.cs @@ -54,6 +54,7 @@ namespace OpenRA.Mods.RA.Activities public override Activity Tick(Actor self) { + cargo.Unloading = false; if (IsCanceled || cargo.IsEmpty(self)) return NextActivity; @@ -93,6 +94,7 @@ namespace OpenRA.Mods.RA.Activities if (!unloadAll || cargo.IsEmpty(self)) return NextActivity; + cargo.Unloading = true; return this; } } diff --git a/OpenRA.Mods.RA/Cargo.cs b/OpenRA.Mods.RA/Cargo.cs index ce0299fd81..0a2db520a6 100644 --- a/OpenRA.Mods.RA/Cargo.cs +++ b/OpenRA.Mods.RA/Cargo.cs @@ -31,11 +31,14 @@ namespace OpenRA.Mods.RA public class Cargo : IPips, IIssueOrder, IResolveOrder, IOrderVoice, INotifyKilled, INotifyCapture, ITick, INotifySold { - readonly Actor self; public readonly CargoInfo Info; + readonly Actor self; + public bool Unloading { get; internal set; } int totalWeight = 0; + int reservedWeight = 0; List cargo = new List(); + HashSet reserves = new HashSet(); public IEnumerable Passengers { get { return cargo; } } CPos currentCell; @@ -45,6 +48,7 @@ namespace OpenRA.Mods.RA { self = init.self; Info = info; + Unloading = false; if (init.Contains()) { @@ -97,6 +101,7 @@ namespace OpenRA.Mods.RA if (!CanUnload()) return; + Unloading = true; self.CancelActivity(); self.QueueActivity(new UnloadCargo(self, true)); } @@ -115,7 +120,27 @@ namespace OpenRA.Mods.RA public bool CanLoad(Actor self, Actor a) { - return HasSpace(GetWeight(a)) && self.CenterPosition.Z == 0; + return (reserves.Contains(a) || HasSpace(GetWeight(a))) && self.CenterPosition.Z == 0; + } + + internal bool ReserveSpace(Actor a) + { + if (reserves.Contains(a)) + return true; + var w = GetWeight(a); + if (!HasSpace(w)) + return false; + reserves.Add(a); + reservedWeight += w; + return true; + } + + internal void UnreserveSpace(Actor a) + { + if (!reserves.Contains(a)) + return; + reservedWeight -= GetWeight(a); + reserves.Remove(a); } public string CursorForOrder(Actor self, Order order) @@ -130,7 +155,7 @@ namespace OpenRA.Mods.RA return self.HasVoice("Unload") ? "Unload" : "Move"; } - public bool HasSpace(int weight) { return totalWeight + weight <= Info.MaxWeight; } + public bool HasSpace(int weight) { return totalWeight + reservedWeight + weight <= Info.MaxWeight; } public bool IsEmpty(Actor self) { return cargo.Count == 0; } public Actor Peek(Actor self) { return cargo[0]; } @@ -177,7 +202,13 @@ namespace OpenRA.Mods.RA public void Load(Actor self, Actor a) { cargo.Add(a); - totalWeight += GetWeight(a); + var w = GetWeight(a); + totalWeight += w; + if (reserves.Contains(a)) + { + reservedWeight -= w; + reserves.Remove(a); + } foreach (var npe in self.TraitsImplementing()) npe.PassengerEntered(self, a); diff --git a/OpenRA.Mods.RA/Passenger.cs b/OpenRA.Mods.RA/Passenger.cs index d5e7f1fc37..2e65cb3d96 100644 --- a/OpenRA.Mods.RA/Passenger.cs +++ b/OpenRA.Mods.RA/Passenger.cs @@ -8,6 +8,7 @@ */ #endregion +using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -17,34 +18,113 @@ using OpenRA.Traits; namespace OpenRA.Mods.RA { + public enum AlternateTransportsMode { None, Force, Default, Always } + + public class EnterTransportTargeter : EnterAlliedActorTargeter + { + readonly AlternateTransportsMode mode; + + public EnterTransportTargeter(string order, int priority, + Func canTarget, Func useEnterCursor, + AlternateTransportsMode mode) + : base (order, priority, canTarget, useEnterCursor) { this.mode = mode; } + + public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor) + { + switch (mode) + { + case AlternateTransportsMode.None: + break; + case AlternateTransportsMode.Force: + if (modifiers.HasModifier(TargetModifiers.ForceMove)) + return false; + break; + case AlternateTransportsMode.Default: + if (!modifiers.HasModifier(TargetModifiers.ForceMove)) + return false; + break; + case AlternateTransportsMode.Always: + return false; + } + + return base.CanTargetActor(self, target, modifiers, ref cursor); + } + } + + public class EnterTransportsTargeter : EnterAlliedActorTargeter + { + readonly AlternateTransportsMode mode; + + public EnterTransportsTargeter(string order, int priority, + Func canTarget, Func useEnterCursor, + AlternateTransportsMode mode) + : base (order, priority, canTarget, useEnterCursor) { this.mode = mode; } + + public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor) + { + switch (mode) + { + case AlternateTransportsMode.None: + return false; + case AlternateTransportsMode.Force: + if (!modifiers.HasModifier(TargetModifiers.ForceMove)) + return false; + break; + case AlternateTransportsMode.Default: + if (modifiers.HasModifier(TargetModifiers.ForceMove)) + return false; + break; + case AlternateTransportsMode.Always: + break; + } + return base.CanTargetActor(self, target, modifiers, ref cursor); + } + } + [Desc("This actor can enter Cargo actors.")] public class PassengerInfo : ITraitInfo { public readonly string CargoType = null; public readonly PipType PipType = PipType.Green; - public int Weight = 1; + 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; + + [Desc("Number of retries using alternate transports.")] + public readonly int MaxAlternateTransportAttempts = 1; + + [Desc("Range from self for looking for an alternate transport (default: 5.5 cells).")] + public readonly WRange AlternateTransportScanRange = WRange.FromCells(11) / 2; public object Create(ActorInitializer init) { return new Passenger(this); } } - public class Passenger : IIssueOrder, IResolveOrder, IOrderVoice + public class Passenger : IIssueOrder, IResolveOrder, IOrderVoice, INotifyRemovedFromWorld { - public readonly PassengerInfo info; - public Passenger(PassengerInfo info) { this.info = info; } + public readonly PassengerInfo Info; + public Passenger(PassengerInfo info) { Info = info; } public Actor Transport; + public Cargo ReservedCargo { get; private set; } public IEnumerable Orders { get { - yield return new EnterAlliedActorTargeter("EnterTransport", 6, - target => IsCorrectCargoType(target), target => CanEnter(target)); + yield return new EnterTransportTargeter("EnterTransport", 6, + target => IsCorrectCargoType(target), target => CanEnter(target), + Info.AlternateTransportsMode); + yield return new EnterTransportsTargeter("EnterTransports", 6, + target => IsCorrectCargoType(target), target => CanEnter(target), + Info.AlternateTransportsMode); } } public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued) { - if (order.OrderID == "EnterTransport") + if (order.OrderID == "EnterTransport" || order.OrderID == "EnterTransports") return new Order(order.OrderID, self, queued) { TargetActor = target.Actor }; return null; @@ -53,25 +133,29 @@ namespace OpenRA.Mods.RA bool IsCorrectCargoType(Actor target) { var ci = target.Info.Traits.Get(); - return ci.Types.Contains(info.CargoType); + return ci.Types.Contains(Info.CargoType); + } + + bool CanEnter(Cargo cargo) + { + return cargo != null && cargo.HasSpace(Info.Weight); } bool CanEnter(Actor target) { - var cargo = target.TraitOrDefault(); - return cargo != null && cargo.HasSpace(info.Weight); + return CanEnter(target.TraitOrDefault()); } public string VoicePhraseForOrder(Actor self, Order order) { - if (order.OrderString != "EnterTransport" || + if ((order.OrderString != "EnterTransport" && order.OrderString != "EnterTransports") || !CanEnter(order.TargetActor)) return null; return "Move"; } public void ResolveOrder(Actor self, Order order) { - if (order.OrderString == "EnterTransport") + if (order.OrderString == "EnterTransport" || order.OrderString == "EnterTransports") { if (order.TargetActor == null) return; if (!CanEnter(order.TargetActor)) return; @@ -81,9 +165,26 @@ namespace OpenRA.Mods.RA self.SetTargetLine(target, Color.Green); self.CancelActivity(); - self.QueueActivity(new MoveAdjacentTo(self, target)); - self.QueueActivity(new EnterTransport(self, order.TargetActor)); + self.QueueActivity(new EnterTransport(self, order.TargetActor, order.OrderString == "EnterTransport" ? 0 : Info.MaxAlternateTransportAttempts)); } } + + public bool Reserve(Actor self, Cargo cargo) + { + Unreserve(self); + if (!cargo.ReserveSpace(self)) + return false; + ReservedCargo = cargo; + return true; + } + + public void RemovedFromWorld(Actor self) { Unreserve(self); } + public void Unreserve(Actor self) + { + if (ReservedCargo == null) + return; + ReservedCargo.UnreserveSpace(self); + ReservedCargo = null; + } } }