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;