Extend Enter activity class for (re)usability in enter/exit/dock logic

This commit is contained in:
atlimit8
2014-09-25 11:55:47 -05:00
parent 83acbe0266
commit af5b18a080
7 changed files with 250 additions and 24 deletions

View File

@@ -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<IMove>();
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<Target> update, Func<Actor, bool> primaryFilter, Func<Actor, bool>[] 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<IMove>();
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;
}
}
}

View File

@@ -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)));
}

View File

@@ -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

View File

@@ -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

View File

@@ -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)));
}
}
}

View File

@@ -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)));
}
}

View File

@@ -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