diff --git a/OpenRA.Mods.Common/Activities/CaptureActor.cs b/OpenRA.Mods.Common/Activities/CaptureActor.cs
index 3f1cc78d4a..90c4fc2427 100644
--- a/OpenRA.Mods.Common/Activities/CaptureActor.cs
+++ b/OpenRA.Mods.Common/Activities/CaptureActor.cs
@@ -38,6 +38,13 @@ namespace OpenRA.Mods.Common.Activities
return !actor.IsDead && !targetManager.BeingCaptured && targetManager.CanBeTargetedBy(actor, self, manager);
}
+ protected override bool TryStartEnter(Actor self)
+ {
+ // CanEnter is only called when the actor is ready to start entering the target.
+ // We can (ab)use this as a notification that the capture is starting.
+ return manager.StartCapture(self, actor, targetManager);
+ }
+
protected override void OnInside(Actor self)
{
if (!CanReserve(self))
@@ -94,6 +101,23 @@ namespace OpenRA.Mods.Common.Activities
});
}
+ protected override void OnLastRun(Actor self)
+ {
+ CancelCapture(self);
+ base.OnLastRun(self);
+ }
+
+ protected override void OnActorDispose(Actor self)
+ {
+ CancelCapture(self);
+ base.OnActorDispose(self);
+ }
+
+ void CancelCapture(Actor self)
+ {
+ manager.CancelCapture(self, actor, targetManager);
+ }
+
public override Activity Tick(Actor self)
{
if (!targetManager.CanBeTargetedBy(actor, self, manager))
diff --git a/OpenRA.Mods.Common/Activities/Enter.cs b/OpenRA.Mods.Common/Activities/Enter.cs
index d9c3f51824..1fe061d0ca 100644
--- a/OpenRA.Mods.Common/Activities/Enter.cs
+++ b/OpenRA.Mods.Common/Activities/Enter.cs
@@ -22,7 +22,7 @@ namespace OpenRA.Mods.Common.Activities
public abstract class Enter : Activity
{
public enum ReserveStatus { None, TooFar, Pending, Ready }
- enum EnterState { ApproachingOrEntering, Inside, Exiting, Done }
+ enum EnterState { ApproachingOrEntering, WaitingToEnter, Inside, Exiting, Done }
readonly IMove move;
readonly int maxTries = 0;
@@ -58,6 +58,12 @@ namespace OpenRA.Mods.Common.Activities
protected virtual void Unreserve(Actor self, bool abort) { }
protected virtual void OnInside(Actor self) { }
+ ///
+ /// Called when the actor is ready to transition from approaching to entering the target.
+ /// Return true to start entering, or false to wait in the WaitingToEnter state.
+ ///
+ protected virtual bool TryStartEnter(Actor self) { return true; }
+
protected bool TryGetAlternateTargetInCircle(
Actor self, WDist radius, Action update, Func primaryFilter, Func[] preferenceFilters = null)
{
@@ -187,6 +193,10 @@ namespace OpenRA.Mods.Common.Activities
break; // Reserved target -> start entering target
}
+ // Can we enter yet?
+ if (!TryStartEnter(self))
+ return EnterState.WaitingToEnter;
+
// Entering
isEnteringOrInside = true;
savedPos = self.CenterPosition; // Save position of self, before entering, for returning on exit
diff --git a/OpenRA.Mods.Common/Traits/CaptureManager.cs b/OpenRA.Mods.Common/Traits/CaptureManager.cs
index d0aa5ddf4a..486432cc33 100644
--- a/OpenRA.Mods.Common/Traits/CaptureManager.cs
+++ b/OpenRA.Mods.Common/Traits/CaptureManager.cs
@@ -20,10 +20,24 @@ namespace OpenRA.Mods.Common.Traits
public sealed class CaptureType { CaptureType() { } }
[Desc("Manages Captures and Capturable traits on an actor.")]
- public class CaptureManagerInfo : TraitInfo { }
+ public class CaptureManagerInfo : ITraitInfo
+ {
+ [GrantedConditionReference]
+ [Desc("Condition granted when capturing an actor.")]
+ public readonly string CapturingCondition = null;
+
+ [GrantedConditionReference]
+ [Desc("Condition granted when being captured by another actor.")]
+ public readonly string BeingCapturedCondition = null;
+
+ public virtual object Create(ActorInitializer init) { return new CaptureManager(this); }
+ }
public class CaptureManager : INotifyCreated, INotifyCapture
{
+ readonly CaptureManagerInfo info;
+ ConditionManager conditionManager;
+
BitSet allyCapturableTypes;
BitSet neutralCapturableTypes;
BitSet enemyCapturableTypes;
@@ -32,10 +46,24 @@ namespace OpenRA.Mods.Common.Traits
IEnumerable enabledCapturable;
IEnumerable enabledCaptures;
+ // Related to a specific capture in process
+ Actor currentTarget;
+ CaptureManager currentTargetManager;
+ int currentTargetDelay;
+ int capturingToken = ConditionManager.InvalidConditionToken;
+ int beingCapturedToken = ConditionManager.InvalidConditionToken;
+
public bool BeingCaptured { get; private set; }
+ public CaptureManager(CaptureManagerInfo info)
+ {
+ this.info = info;
+ }
+
void INotifyCreated.Created(Actor self)
{
+ conditionManager = self.TraitOrDefault();
+
enabledCapturable = self.TraitsImplementing()
.ToArray()
.Where(Exts.IsTraitEnabled);
@@ -121,5 +149,57 @@ namespace OpenRA.Mods.Common.Traits
BeingCaptured = true;
self.World.AddFrameEndTask(w => BeingCaptured = false);
}
+
+ ///
+ /// Called by CaptureActor when the activity is ready to enter and capture the target.
+ /// This method grants the capturing conditions on the captor and target and returns
+ /// true if the captor is able to start entering or false if it needs to wait.
+ ///
+ public bool StartCapture(Actor self, Actor target, CaptureManager targetManager)
+ {
+ if (target != currentTarget)
+ {
+ if (currentTarget != null)
+ CancelCapture(self, currentTarget, currentTargetManager);
+
+ currentTarget = target;
+ currentTargetManager = targetManager;
+ currentTargetDelay = 0;
+ }
+ else
+ currentTargetDelay += 1;
+
+ if (conditionManager != null && !string.IsNullOrEmpty(info.CapturingCondition) &&
+ capturingToken == ConditionManager.InvalidConditionToken)
+ capturingToken = conditionManager.GrantCondition(self, info.CapturingCondition);
+
+ if (targetManager.conditionManager != null && !string.IsNullOrEmpty(targetManager.info.BeingCapturedCondition) &&
+ targetManager.beingCapturedToken == ConditionManager.InvalidConditionToken)
+ targetManager.beingCapturedToken = targetManager.conditionManager.GrantCondition(target, targetManager.info.BeingCapturedCondition);
+
+ foreach (var c in enabledCaptures.OrderBy(c => c.Info.CaptureDelay))
+ if (targetManager.CanBeTargetedBy(target, self, c) && c.Info.CaptureDelay <= currentTargetDelay)
+ return true;
+
+ return false;
+ }
+
+ ///
+ /// Called by CaptureActor when the activity finishes or is cancelled
+ /// This method revokes the capturing conditions on the captor and target
+ /// and resets any capturing progress.
+ ///
+ public void CancelCapture(Actor self, Actor target, CaptureManager targetManager)
+ {
+ if (capturingToken != ConditionManager.InvalidConditionToken)
+ capturingToken = conditionManager.RevokeCondition(self, capturingToken);
+
+ if (targetManager.beingCapturedToken != ConditionManager.InvalidConditionToken)
+ targetManager.beingCapturedToken = targetManager.conditionManager.RevokeCondition(self, targetManager.beingCapturedToken);
+
+ currentTarget = null;
+ currentTargetManager = null;
+ currentTargetDelay = 0;
+ }
}
}
diff --git a/OpenRA.Mods.Common/Traits/Captures.cs b/OpenRA.Mods.Common/Traits/Captures.cs
index c8bc5c786a..d8e6697456 100644
--- a/OpenRA.Mods.Common/Traits/Captures.cs
+++ b/OpenRA.Mods.Common/Traits/Captures.cs
@@ -32,6 +32,9 @@ namespace OpenRA.Mods.Common.Traits
[Desc("Sabotage damage expressed as a percentage of maximum target health.")]
public readonly int SabotageHPRemoval = 50;
+ [Desc("Delay (in ticks) that to wait next to the target before initiating the capture.")]
+ public readonly int CaptureDelay = 0;
+
[Desc("Experience granted to the capturing player.")]
public readonly int PlayerExperience = 0;