diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index f843856bcc..a9d628a3bb 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -101,6 +101,7 @@ namespace OpenRA.Traits public interface INotifyCapture { void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner); } public interface INotifyHarvest { void Harvested(Actor self, ResourceType resource); } public interface INotifyInfiltrated { void Infiltrated(Actor self, Actor infiltrator); } + public interface IDisableMove { bool MoveDisabled(Actor self); } public interface IUpgradable { @@ -206,9 +207,12 @@ namespace OpenRA.Traits Activity MoveWithinRange(Target target, WRange minRange, WRange maxRange); Activity MoveFollow(Actor self, Target target, WRange minRange, WRange maxRange); Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any); + Activity MoveToTarget(Actor self, Target target); + Activity MoveIntoTarget(Actor self, Target target); Activity VisualMove(Actor self, WPos fromPos, WPos toPos); CPos NearestMoveableCell(CPos target); bool IsMoving { get; set; } + bool CanEnterTargetNow(Actor self, Target target); } public interface INotifyBlockingMove { void OnNotifyBlockingMove(Actor self, Actor blocking); } diff --git a/OpenRA.Mods.Cnc/Activities/HarvesterDockSequence.cs b/OpenRA.Mods.Cnc/Activities/HarvesterDockSequence.cs index f509ae8ed8..c6c491e430 100644 --- a/OpenRA.Mods.Cnc/Activities/HarvesterDockSequence.cs +++ b/OpenRA.Mods.Cnc/Activities/HarvesterDockSequence.cs @@ -47,10 +47,10 @@ namespace OpenRA.Mods.Cnc return this; case State.Turn: state = State.DragIn; - return Util.SequenceActivities(new Turn(112), this); + return Util.SequenceActivities(new Turn(self, 112), this); case State.DragIn: state = State.Dock; - return Util.SequenceActivities(new Drag(startDock, endDock, 12), this); + return Util.SequenceActivities(new Drag(self, startDock, endDock, 12), this); case State.Dock: ru.PlayCustomAnimation(self, "dock", () => { ru.PlayCustomAnimRepeating(self, "dock-loop"); @@ -73,7 +73,7 @@ namespace OpenRA.Mods.Cnc state = State.Wait; return this; case State.DragOut: - return Util.SequenceActivities(new Drag(endDock, startDock, 12), NextActivity); + return Util.SequenceActivities(new Drag(self, endDock, startDock, 12), NextActivity); } throw new InvalidOperationException("Invalid harvester dock state"); 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/Attack.cs b/OpenRA.Mods.RA/Activities/Attack.cs index ef7bfb7e84..1034122638 100755 --- a/OpenRA.Mods.RA/Activities/Attack.cs +++ b/OpenRA.Mods.RA/Activities/Attack.cs @@ -64,7 +64,7 @@ namespace OpenRA.Mods.RA.Activities var desiredFacing = Util.GetFacing(Target.CenterPosition - self.CenterPosition, 0); if (facing.Facing != desiredFacing) - return Util.SequenceActivities(new Turn(desiredFacing), this); + return Util.SequenceActivities(new Turn(self, desiredFacing), this); attack.DoAttack(self, Target); diff --git a/OpenRA.Mods.RA/Activities/Enter.cs b/OpenRA.Mods.RA/Activities/Enter.cs index ff926a8e3c..9a699b862b 100755 --- a/OpenRA.Mods.RA/Activities/Enter.cs +++ b/OpenRA.Mods.RA/Activities/Enter.cs @@ -8,41 +8,267 @@ */ #endregion +using System; +using System.Collections.Generic; using System.Linq; +using OpenRA.Mods.RA.Move; using OpenRA.Traits; namespace OpenRA.Mods.RA.Activities { public class Enter : Activity { - readonly Target target; - readonly Activity inner; + public enum ReserveStatus { None, TooFar, Pending, Ready } + enum State { ApproachingOrEntering, Inside, Exiting, Done } - public Enter(Actor target, Activity inner) + readonly Activity inside; + readonly IMove move; + readonly int maxTries = 0; + Target target; + State nextState = State.ApproachingOrEntering; // Hint/starting point for next state + bool isEnteringOrInside = false; // Used to know if exiting should be used + WPos savedPos; // Position just before entering + Activity inner; + bool firstApproach = true; + + protected Enter(Actor self, Actor target, int maxTries = 1) + : this(self, target, null) { + this.maxTries = maxTries; + } + + public Enter(Actor self, Actor target, Activity inside) + { + this.move = self.Trait(); this.target = Target.FromActor(target); - this.inner = inner; + this.inside = inside; + } + + // CanEnter(target) should to be true; othwise, Enter may abort. + // Tries counter starts at 1 (reset every tick) + protected virtual bool TryGetAlternateTarget(Actor self, int tries, ref Target target) { return false; } + protected virtual bool CanReserve(Actor self) { return true; } + protected virtual ReserveStatus Reserve(Actor self) + { + return !CanReserve(self) ? ReserveStatus.None : move.CanEnterTargetNow(self, target) ? ReserveStatus.Ready : ReserveStatus.TooFar; + } + + protected virtual void Unreserve(Actor self, bool abort) { } + protected virtual void OnInside(Actor self) { } + + protected bool TryGetAlternateTargetInCircle(Actor self, WRange radius, Action update, Func primaryFilter, Func[] preferenceFilters = null) + { + var radiusSquared = radius.Range * radius.Range; + var diff = new WVec(radius, radius, WRange.Zero); + var candidates = self.World.ActorMap.ActorsInBox(self.CenterPosition - diff, self.CenterPosition + diff) + .Where(primaryFilter).Select(a => new { Actor = a, Ls = (self.CenterPosition - a.CenterPosition).HorizontalLengthSquared }) + .Where(p => p.Ls <= radiusSquared).OrderBy(p => p.Ls).Select(p => p.Actor); + if (preferenceFilters != null) + foreach (var filter in preferenceFilters) + { + var preferredCandidate = candidates.FirstOrDefault(filter); + if (preferredCandidate == null) + continue; + target = Target.FromActor(preferredCandidate); + update(target); + return true; + } + + var candidate = candidates.FirstOrDefault(); + if (candidate == null) + return false; + target = Target.FromActor(candidate); + update(target); + return true; + } + + // Called when inner activity is this and returns inner activity for next tick. + protected virtual Activity InsideTick(Actor self) { return Util.RunActivity(self, inside); } + + // Abort entering and/or leave if necessary + protected virtual void AbortOrExit(Actor self) + { + if (nextState == State.Done) + return; + nextState = isEnteringOrInside ? State.Exiting : State.Done; + if (inner == this) + inner = null; + else if (inner != null) + inner.Cancel(self); + if (isEnteringOrInside) + Unreserve(self, true); + isEnteringOrInside = false; + } + + // Cancel inner activity and mark as done unless already leaving or done + protected void Done(Actor self) + { + if (nextState == State.Done) + return; + nextState = State.Done; + if (inner == this) + inner = null; + else if (inner != null) + inner.Cancel(self); + } + + public override void Cancel(Actor self) + { + AbortOrExit(self); + if (nextState < State.Exiting) + base.Cancel(self); + else + NextActivity = null; + } + + ReserveStatus TryReserveElseTryAlternateReserve(Actor self) + { + for (var tries = 0;;) + switch (Reserve(self)) + { + case ReserveStatus.None: + if (++tries > maxTries || !TryGetAlternateTarget(self, tries, ref target)) + return ReserveStatus.None; + continue; + case ReserveStatus.TooFar: + // Always goto to transport on first approach + if (firstApproach) + { + firstApproach = false; + return ReserveStatus.TooFar; + } + + if (++tries > maxTries) + return ReserveStatus.TooFar; + Target t = target; + if (!TryGetAlternateTarget(self, tries, ref t)) + return ReserveStatus.TooFar; + if ((target.CenterPosition - self.CenterPosition).HorizontalLengthSquared <= (t.CenterPosition - self.CenterPosition).HorizontalLengthSquared) + return ReserveStatus.TooFar; + target = t; + continue; + case ReserveStatus.Pending: + return ReserveStatus.Pending; + case ReserveStatus.Ready: + return ReserveStatus.Ready; + } + } + + State FindAndTransitionToNextState(Actor self) + { + switch (nextState) + { + case State.ApproachingOrEntering: + + // Reserve to enter or approach + isEnteringOrInside = false; + switch (TryReserveElseTryAlternateReserve(self)) + { + case ReserveStatus.None: + return State.Done; // No available target -> abort to next activity + case ReserveStatus.TooFar: + inner = move.MoveToTarget(self, Target.FromPos(target.CenterPosition)); // Approach + return State.ApproachingOrEntering; + case ReserveStatus.Pending: + return State.ApproachingOrEntering; // Retry next tick + case ReserveStatus.Ready: + break; // Reserved target -> start entering target + } + + // Entering + isEnteringOrInside = true; + savedPos = self.CenterPosition; // Save position of self, before entering, for returning on exit + inner = move.MoveIntoTarget(self, target); // Enter + + if (inner != null) + { + nextState = State.Inside; // Should be inside once inner activity is null + return State.ApproachingOrEntering; + } + + // Can enter but there is no activity for it, so go inside without one + goto case State.Inside; + + case State.Inside: + // Might as well teleport into target if there is no MoveIntoTarget activity + if (nextState == State.ApproachingOrEntering) + nextState = State.Inside; + + // Otherwise, try to recover from moving target + else if (target.CenterPosition != self.CenterPosition) + { + nextState = State.ApproachingOrEntering; + Unreserve(self, false); + if (Reserve(self) == ReserveStatus.Ready) + { + inner = move.MoveIntoTarget(self, target); // Enter + if (inner != null) + return State.ApproachingOrEntering; + + nextState = State.ApproachingOrEntering; + goto case State.ApproachingOrEntering; + } + + nextState = State.ApproachingOrEntering; + isEnteringOrInside = false; + inner = move.MoveIntoWorld(self, self.World.Map.CellContaining(savedPos)); + + return State.ApproachingOrEntering; + } + + OnInside(self); + + // Return if Abort(Actor) or Done(self) was called from OnInside. + if (nextState >= State.Exiting) + return State.Inside; + + inner = this; // Start inside activity + nextState = State.Exiting; // Exit once inner activity is null (unless Done(self) is called) + return State.Inside; + + // TODO: Handle target moved while inside or always call done for movable targets and use a separate exit activity + case State.Exiting: + inner = move.MoveIntoWorld(self, self.World.Map.CellContaining(savedPos)); + + // If not successfully exiting, retry on next tick + if (inner == null) + return State.Exiting; + isEnteringOrInside = false; + nextState = State.Done; + return State.Exiting; + + case State.Done: + return State.Done; + } + + return State.Done; // dummy to quiet dumb compiler + } + + Activity CanceledTick(Actor self) + { + if (inner == null) + return Util.RunActivity(self, NextActivity); + inner.Cancel(self); + inner.Queue(NextActivity); + return Util.RunActivity(self, inner); } public override Activity Tick(Actor self) { - if (IsCanceled || !target.IsValidFor(self)) - return NextActivity; + if (IsCanceled) + return CanceledTick(self); - if (target.Type != TargetType.Actor) - return NextActivity; + // Check target validity if not exiting or done + if (nextState != State.Done && (target.Type != TargetType.Actor || !target.IsValidFor(self))) + AbortOrExit(self); - if (!Util.AdjacentCells(self.World, target).Any(c => c == self.Location)) - return Util.SequenceActivities(new MoveAdjacentTo(self, target), this); + // If no current activity, tick next activity + if (inner == null && FindAndTransitionToNextState(self) == State.Done) + return CanceledTick(self); - // Move to the middle of the target, ignoring impassable tiles - var move = self.Trait(); - return Util.SequenceActivities( - move.VisualMove(self, self.CenterPosition, target.CenterPosition), - inner, - move.VisualMove(self, target.CenterPosition, self.CenterPosition), - NextActivity - ); + // Run inner activity/InsideTick + inner = inner == this ? InsideTick(self) : Util.RunActivity(self, inner); + return this; } } } 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/FindResources.cs b/OpenRA.Mods.RA/Activities/FindResources.cs index 41be46c913..1b119efb52 100755 --- a/OpenRA.Mods.RA/Activities/FindResources.cs +++ b/OpenRA.Mods.RA/Activities/FindResources.cs @@ -153,7 +153,7 @@ namespace OpenRA.Mods.RA.Activities var facing = self.Trait().Facing; var desired = Util.QuantizeFacing(facing, harvInfo.HarvestFacings) * (256 / harvInfo.HarvestFacings); if (desired != facing) - return Util.SequenceActivities(new Turn(desired), this); + return Util.SequenceActivities(new Turn(self, desired), this); } var resLayer = self.World.WorldActor.Trait(); diff --git a/OpenRA.Mods.RA/Activities/RAHarvesterDockSequence.cs b/OpenRA.Mods.RA/Activities/RAHarvesterDockSequence.cs index bad965a5e8..9655735332 100644 --- a/OpenRA.Mods.RA/Activities/RAHarvesterDockSequence.cs +++ b/OpenRA.Mods.RA/Activities/RAHarvesterDockSequence.cs @@ -43,7 +43,7 @@ namespace OpenRA.Mods.RA return this; case State.Turn: state = State.Dock; - return Util.SequenceActivities(new Turn(angle), this); + return Util.SequenceActivities(new Turn(self, angle), this); case State.Dock: ru.PlayCustomAnimation(self, "dock", () => { ru.PlayCustomAnimRepeating(self, "dock-loop"); diff --git a/OpenRA.Mods.RA/Activities/Turn.cs b/OpenRA.Mods.RA/Activities/Turn.cs index e48d687f76..ea428fc1f8 100755 --- a/OpenRA.Mods.RA/Activities/Turn.cs +++ b/OpenRA.Mods.RA/Activities/Turn.cs @@ -8,22 +8,30 @@ */ #endregion +using System.Collections.Generic; +using System.Linq; using OpenRA.Traits; namespace OpenRA.Mods.RA.Activities { public class Turn : Activity { - int desiredFacing; + readonly IEnumerable moveDisablers; + readonly int desiredFacing; - public Turn( int desiredFacing ) + public Turn(Actor self, int desiredFacing) { + moveDisablers = self.TraitsImplementing(); this.desiredFacing = desiredFacing; } public override Activity Tick( Actor self ) { - if (IsCanceled) return NextActivity; + if (IsCanceled) + return NextActivity; + if (moveDisablers.Any(d => d.MoveDisabled(self))) + return this; + var facing = self.Trait(); if( desiredFacing == facing.Facing ) 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/Air/Aircraft.cs b/OpenRA.Mods.RA/Air/Aircraft.cs index 7ac89f57d4..11508803b3 100644 --- a/OpenRA.Mods.RA/Air/Aircraft.cs +++ b/OpenRA.Mods.RA/Air/Aircraft.cs @@ -135,7 +135,7 @@ namespace OpenRA.Mods.RA.Air if (afld == null) return; - var res = afld.Trait(); + var res = afld.TraitOrDefault(); if (res != null) { @@ -204,6 +204,18 @@ namespace OpenRA.Mods.RA.Air public SubCell GetAvailableSubCell(CPos a, SubCell preferredSubCell = SubCell.Any, Actor ignoreActor = null, bool checkTransientActors = true) { return SubCell.Invalid; } // Does not use any subcell public bool CanEnterCell(CPos cell, Actor ignoreActor = null, bool checkTransientActors = true) { return true; } + public bool CanEnterTargetNow(Actor self, Target target) + { + if (target.Positions.Any(p => self.World.ActorMap.GetUnitsAt(self.World.Map.CellContaining(p)).Any(a => a != self && a != target.Actor))) + return false; + var res = target.Actor.TraitOrDefault(); + if (res == null) + return true; + UnReserve(); + Reservation = res.Reserve(target.Actor, self, this); + return true; + } + public int MovementSpeed { get diff --git a/OpenRA.Mods.RA/Air/HeliReturn.cs b/OpenRA.Mods.RA/Air/HeliReturn.cs index 8a066c463b..5fa2924f02 100755 --- a/OpenRA.Mods.RA/Air/HeliReturn.cs +++ b/OpenRA.Mods.RA/Air/HeliReturn.cs @@ -40,7 +40,7 @@ namespace OpenRA.Mods.RA.Air .ClosestTo(self); if (nearestHpad == null) - return Util.SequenceActivities(new Turn(initialFacing), new HeliLand(true), NextActivity); + return Util.SequenceActivities(new Turn(self, initialFacing), new HeliLand(true), NextActivity); else return Util.SequenceActivities(new HeliFly(self, Target.FromActor(nearestHpad))); } @@ -58,7 +58,7 @@ namespace OpenRA.Mods.RA.Air return Util.SequenceActivities( new HeliFly(self, Target.FromPos(dest.CenterPosition + offset)), - new Turn(initialFacing), + new Turn(self, initialFacing), new HeliLand(false), new ResupplyAircraft()); } diff --git a/OpenRA.Mods.RA/Air/Helicopter.cs b/OpenRA.Mods.RA/Air/Helicopter.cs index 52cfb744ef..bc2dc46471 100644 --- a/OpenRA.Mods.RA/Air/Helicopter.cs +++ b/OpenRA.Mods.RA/Air/Helicopter.cs @@ -8,6 +8,7 @@ */ #endregion +using System; using System.Collections.Generic; using System.Drawing; using System.Linq; @@ -70,7 +71,7 @@ namespace OpenRA.Mods.RA.Air if (Info.LandWhenIdle) { if (Info.TurnToLand) - self.QueueActivity(new Turn(Info.InitialFacing)); + self.QueueActivity(new Turn(self, Info.InitialFacing)); self.QueueActivity(new HeliLand(true)); } @@ -96,7 +97,7 @@ namespace OpenRA.Mods.RA.Air self.CancelActivity(); self.QueueActivity(new HeliFly(self, Target.FromPos(order.TargetActor.CenterPosition + offset))); - self.QueueActivity(new Turn(Info.InitialFacing)); + self.QueueActivity(new Turn(self, Info.InitialFacing)); self.QueueActivity(new HeliLand(false)); self.QueueActivity(new ResupplyAircraft()); self.QueueActivity(new TakeOff()); @@ -116,7 +117,7 @@ namespace OpenRA.Mods.RA.Air if (Info.LandWhenIdle) { if (Info.TurnToLand) - self.QueueActivity(new Turn(Info.InitialFacing)); + self.QueueActivity(new Turn(self, Info.InitialFacing)); self.QueueActivity(new HeliLand(true)); } @@ -153,6 +154,12 @@ namespace OpenRA.Mods.RA.Air return new HeliFly(self, Target.FromCell(self.World, cell, subCell)); } + public Activity MoveIntoTarget(Actor self, Target target) { return new HeliLand(false); } + public Activity MoveToTarget(Actor self, Target target) + { + return Util.SequenceActivities(new HeliFly(self, target), new Turn(self, Info.InitialFacing)); + } + public Activity VisualMove(Actor self, WPos fromPos, WPos toPos) { // TODO: Ignore repulsion when moving diff --git a/OpenRA.Mods.RA/Air/Plane.cs b/OpenRA.Mods.RA/Air/Plane.cs index e2292f5e27..ba159fc8c7 100644 --- a/OpenRA.Mods.RA/Air/Plane.cs +++ b/OpenRA.Mods.RA/Air/Plane.cs @@ -8,6 +8,7 @@ */ #endregion +using System; using System.Drawing; using OpenRA.Mods.RA.Activities; using OpenRA.Traits; @@ -129,5 +130,7 @@ namespace OpenRA.Mods.RA.Air public Activity MoveIntoWorld(Actor self, CPos cell, SubCell subCell = SubCell.Any) { return new Fly(self, Target.FromCell(self.World, cell)); } public Activity VisualMove(Actor self, WPos fromPos, WPos toPos) { return Util.SequenceActivities(new CallFunc(() => SetVisualPosition(self, fromPos)), new Fly(self, Target.FromPos(toPos))); } + public Activity MoveToTarget(Actor self, Target target) { return new Fly(self, target, WRange.FromCells(3), WRange.FromCells(5)); } + public Activity MoveIntoTarget(Actor self, Target target) { return new Land(target); } } } diff --git a/OpenRA.Mods.RA/C4Demolition.cs b/OpenRA.Mods.RA/C4Demolition.cs index 51188b5e86..2e220bd515 100644 --- a/OpenRA.Mods.RA/C4Demolition.cs +++ b/OpenRA.Mods.RA/C4Demolition.cs @@ -76,7 +76,7 @@ namespace OpenRA.Mods.RA self.CancelActivity(); self.SetTargetLine(target, Color.Red); - self.QueueActivity(new Enter(target.Actor, new Demolish( + self.QueueActivity(new Enter(self, target.Actor, new Demolish( target.Actor, info.C4Delay, info.Flashes, info.FlashesDelay, info.FlashInterval, info.FlashDuration))); } diff --git a/OpenRA.Mods.RA/Captures.cs b/OpenRA.Mods.RA/Captures.cs index abd92a7dbe..5b5b9d6e05 100644 --- a/OpenRA.Mods.RA/Captures.cs +++ b/OpenRA.Mods.RA/Captures.cs @@ -75,7 +75,7 @@ namespace OpenRA.Mods.RA self.CancelActivity(); self.SetTargetLine(target, Color.Red); - self.QueueActivity(new Enter(target.Actor, new CaptureActor(target))); + self.QueueActivity(new Enter(self, target.Actor, new CaptureActor(target))); } class CaptureOrderTargeter : UnitOrderTargeter diff --git a/OpenRA.Mods.RA/Cargo.cs b/OpenRA.Mods.RA/Cargo.cs index ce0299fd81..967c8350ca 100644 --- a/OpenRA.Mods.RA/Cargo.cs +++ b/OpenRA.Mods.RA/Cargo.cs @@ -29,13 +29,16 @@ namespace OpenRA.Mods.RA public object Create(ActorInitializer init) { return new Cargo(init, this); } } - public class Cargo : IPips, IIssueOrder, IResolveOrder, IOrderVoice, INotifyKilled, INotifyCapture, ITick, INotifySold + public class Cargo : IPips, IIssueOrder, IResolveOrder, IOrderVoice, INotifyKilled, INotifyCapture, ITick, INotifySold, IDisableMove { - 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,8 @@ namespace OpenRA.Mods.RA return self.HasVoice("Unload") ? "Unload" : "Move"; } - public bool HasSpace(int weight) { return totalWeight + weight <= Info.MaxWeight; } + public bool MoveDisabled(Actor self) { return reserves.Any(); } + 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 +203,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/EngineerRepair.cs b/OpenRA.Mods.RA/EngineerRepair.cs index 1f24b50c78..60c5cdde8b 100644 --- a/OpenRA.Mods.RA/EngineerRepair.cs +++ b/OpenRA.Mods.RA/EngineerRepair.cs @@ -78,7 +78,7 @@ namespace OpenRA.Mods.RA self.CancelActivity(); self.SetTargetLine(target, Color.Yellow); - self.QueueActivity(new Enter(target.Actor, new RepairBuilding(target.Actor))); + self.QueueActivity(new Enter(self, target.Actor, new RepairBuilding(target.Actor))); } class EngineerRepairOrderTargeter : UnitOrderTargeter diff --git a/OpenRA.Mods.RA/Husk.cs b/OpenRA.Mods.RA/Husk.cs index 4e43874f7e..aa57435a4d 100644 --- a/OpenRA.Mods.RA/Husk.cs +++ b/OpenRA.Mods.RA/Husk.cs @@ -49,7 +49,7 @@ namespace OpenRA.Mods.RA var finalPos = init.world.Map.CenterOfCell(TopLeft); var distance = (finalPos - CenterPosition).Length; if (speed > 0 && distance > 0) - self.QueueActivity(new Drag(CenterPosition, finalPos, distance / speed)); + self.QueueActivity(new Drag(init.self, CenterPosition, finalPos, distance / speed)); } public IEnumerable> OccupiedCells() { yield return Pair.New(TopLeft, SubCell.FullCell); } diff --git a/OpenRA.Mods.RA/Infiltration/Infiltrates.cs b/OpenRA.Mods.RA/Infiltration/Infiltrates.cs index ada7140a79..78d4b97ee1 100644 --- a/OpenRA.Mods.RA/Infiltration/Infiltrates.cs +++ b/OpenRA.Mods.RA/Infiltration/Infiltrates.cs @@ -96,7 +96,7 @@ namespace OpenRA.Mods.RA.Infiltration self.CancelActivity(); self.SetTargetLine(target, Color.Red); - self.QueueActivity(new Enter(target.Actor, new Infiltrate(target.Actor))); + self.QueueActivity(new Enter(self, target.Actor, new Infiltrate(target.Actor))); } } } diff --git a/OpenRA.Mods.RA/Move/Drag.cs b/OpenRA.Mods.RA/Move/Drag.cs index d3c6d50d2d..8d1bab4472 100755 --- a/OpenRA.Mods.RA/Move/Drag.cs +++ b/OpenRA.Mods.RA/Move/Drag.cs @@ -9,18 +9,25 @@ #endregion using System.Collections.Generic; +using System.Linq; using OpenRA.Traits; namespace OpenRA.Mods.RA.Move { public class Drag : Activity { + readonly IPositionable positionable; + readonly IMove movement; + readonly IEnumerable moveDisablers; WPos start, end; int length; int ticks = 0; - public Drag(WPos start, WPos end, int length) + public Drag(Actor self, WPos start, WPos end, int length) { + positionable = self.Trait(); + movement = self.TraitOrDefault(); + moveDisablers = self.TraitsImplementing(); this.start = start; this.end = end; this.length = length; @@ -28,8 +35,8 @@ namespace OpenRA.Mods.RA.Move public override Activity Tick(Actor self) { - var positionable = self.Trait(); - var movement = self.TraitOrDefault(); + if (moveDisablers.Any(d => d.MoveDisabled(self))) + return this; var pos = length > 1 ? WPos.Lerp(start, end, ticks, length - 1) diff --git a/OpenRA.Mods.RA/Move/Mobile.cs b/OpenRA.Mods.RA/Move/Mobile.cs index 149885ce6d..f4ae825988 100644 --- a/OpenRA.Mods.RA/Move/Mobile.cs +++ b/OpenRA.Mods.RA/Move/Mobile.cs @@ -660,22 +660,42 @@ namespace OpenRA.Mods.RA.Move SetPosition(self, cell, subCell); SetVisualPosition(self, pos); - // Animate transition - var to = self.World.Map.CenterOfSubCell(cell, subCell); - var speed = MovementSpeedForCell(self, cell); - var length = speed > 0 ? (to - pos).Length / speed : 0; + return VisualMove(self, pos, self.World.Map.CenterOfSubCell(cell, subCell), cell); + } - var facing = Util.GetFacing(to - pos, Facing); - return Util.SequenceActivities(new Turn(facing), new Drag(pos, to, length)); + public Activity MoveToTarget(Actor self, Target target) + { + if (target.Type == TargetType.Invalid) + return null; + + return new MoveAdjacentTo(self, target); + } + + public Activity MoveIntoTarget(Actor self, Target target) + { + if (target.Type == TargetType.Invalid) + return null; + + return VisualMove(self, self.CenterPosition, target.CenterPosition); + } + + public bool CanEnterTargetNow(Actor self, Target target) + { + return self.Location == self.World.Map.CellContaining(target.CenterPosition) || Util.AdjacentCells(self.World, target).Any(c => c == self.Location); } public Activity VisualMove(Actor self, WPos fromPos, WPos toPos) { - var speed = MovementSpeedForCell(self, self.Location); + return VisualMove(self, fromPos, toPos, self.Location); + } + + public Activity VisualMove(Actor self, WPos fromPos, WPos toPos, CPos cell) + { + var speed = MovementSpeedForCell(self, cell); var length = speed > 0 ? (toPos - fromPos).Length / speed : 0; var facing = Util.GetFacing(toPos - fromPos, Facing); - return Util.SequenceActivities(new Turn(facing), new Drag(fromPos, toPos, length)); + return Util.SequenceActivities(new Turn(self, facing), new Drag(self, fromPos, toPos, length)); } } } diff --git a/OpenRA.Mods.RA/Move/Move.cs b/OpenRA.Mods.RA/Move/Move.cs index 6df61ce348..1d792d800a 100644 --- a/OpenRA.Mods.RA/Move/Move.cs +++ b/OpenRA.Mods.RA/Move/Move.cs @@ -151,7 +151,7 @@ namespace OpenRA.Mods.RA.Move if (firstFacing != mobile.Facing) { path.Add(nextCell.Value.First); - return Util.SequenceActivities(new Turn(firstFacing), this); + return Util.SequenceActivities(new Turn(self, firstFacing), this); } else { 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; + } } } diff --git a/OpenRA.Mods.RA/RepairsBridges.cs b/OpenRA.Mods.RA/RepairsBridges.cs index f0e8c2302e..b3d8087042 100644 --- a/OpenRA.Mods.RA/RepairsBridges.cs +++ b/OpenRA.Mods.RA/RepairsBridges.cs @@ -59,7 +59,7 @@ namespace OpenRA.Mods.RA self.SetTargetLine(Target.FromOrder(self.World, order), Color.Yellow); self.CancelActivity(); - self.QueueActivity(new Enter(order.TargetActor, new RepairBridge(order.TargetActor))); + self.QueueActivity(new Enter(self, order.TargetActor, new RepairBridge(order.TargetActor))); } } diff --git a/OpenRA.Mods.RA/Scripting/Global/ReinforcementsGlobal.cs b/OpenRA.Mods.RA/Scripting/Global/ReinforcementsGlobal.cs index 6dc646a029..1f2994118e 100644 --- a/OpenRA.Mods.RA/Scripting/Global/ReinforcementsGlobal.cs +++ b/OpenRA.Mods.RA/Scripting/Global/ReinforcementsGlobal.cs @@ -177,7 +177,7 @@ namespace OpenRA.Mods.RA.Scripting var heli = transport.TraitOrDefault(); if (heli != null) { - transport.QueueActivity(new Turn(heli.Info.InitialFacing)); + transport.QueueActivity(new Turn(transport, heli.Info.InitialFacing)); transport.QueueActivity(new HeliLand(true)); transport.QueueActivity(new Wait(15)); } diff --git a/OpenRA.Mods.RA/SupplyTruck.cs b/OpenRA.Mods.RA/SupplyTruck.cs index fc828be078..61b392caf2 100644 --- a/OpenRA.Mods.RA/SupplyTruck.cs +++ b/OpenRA.Mods.RA/SupplyTruck.cs @@ -67,7 +67,7 @@ namespace OpenRA.Mods.RA self.CancelActivity(); self.SetTargetLine(target, Color.Yellow); - self.QueueActivity(new Enter(target.Actor, new DonateSupplies(target.Actor, info.Payload))); + self.QueueActivity(new Enter(self, target.Actor, new DonateSupplies(target.Actor, info.Payload))); } class SupplyTruckOrderTargeter : UnitOrderTargeter diff --git a/OpenRA.Mods.RA/Transforms.cs b/OpenRA.Mods.RA/Transforms.cs index aca9bd65f7..f28ddbd023 100644 --- a/OpenRA.Mods.RA/Transforms.cs +++ b/OpenRA.Mods.RA/Transforms.cs @@ -87,7 +87,7 @@ namespace OpenRA.Mods.RA self.CancelActivity(); if (self.HasTrait()) - self.QueueActivity(new Turn(info.Facing)); + self.QueueActivity(new Turn(self, info.Facing)); foreach (var nt in self.TraitsImplementing()) nt.BeforeTransform(self); diff --git a/OpenRA.Mods.TS/Activities/VoxelHarvesterDockSequence.cs b/OpenRA.Mods.TS/Activities/VoxelHarvesterDockSequence.cs index 142756c4d5..4c3a6a24d6 100644 --- a/OpenRA.Mods.TS/Activities/VoxelHarvesterDockSequence.cs +++ b/OpenRA.Mods.TS/Activities/VoxelHarvesterDockSequence.cs @@ -41,7 +41,7 @@ namespace OpenRA.Mods.TS { case State.Turn: state = State.Dock; - return Util.SequenceActivities(new Turn(160), this); + return Util.SequenceActivities(new Turn(self, 160), this); case State.Dock: if (proc.IsInWorld && !proc.IsDead()) foreach (var nd in proc.TraitsImplementing())