301 lines
10 KiB
C#
301 lines
10 KiB
C#
#region Copyright & License Information
|
|
/*
|
|
* Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
|
|
* This file is part of OpenRA, which is free software. It is made
|
|
* available to you under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation, either version 3 of
|
|
* the License, or (at your option) any later version. For more
|
|
* information, see COPYING.
|
|
*/
|
|
#endregion
|
|
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using OpenRA.Primitives;
|
|
using OpenRA.Traits;
|
|
|
|
namespace OpenRA.Mods.Common.Traits
|
|
{
|
|
public sealed class CaptureType { CaptureType() { } }
|
|
|
|
[RequireExplicitImplementation]
|
|
public interface ICaptureProgressWatcher
|
|
{
|
|
void Update(Actor self, Actor captor, Actor target, int progress, int total);
|
|
}
|
|
|
|
[Desc("Manages Captures and Capturable traits on an actor.")]
|
|
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;
|
|
|
|
[Desc("Should units friendly to the capturing actor auto-target this actor while it is being captured?")]
|
|
public readonly bool PreventsAutoTarget = true;
|
|
|
|
public virtual object Create(ActorInitializer init) { return new CaptureManager(this); }
|
|
|
|
public bool CanBeTargetedBy(FrozenActor frozenActor, Actor captor, Captures captures)
|
|
{
|
|
if (captures.IsTraitDisabled)
|
|
return false;
|
|
|
|
// TODO: FrozenActors don't yet have a way of caching conditions, so we can't filter disabled traits
|
|
// This therefore assumes that all Capturable traits are enabled, which is probably wrong.
|
|
// Actors with FrozenUnderFog should therefore not disable the Capturable trait.
|
|
var stance = frozenActor.Owner.Stances[captor.Owner];
|
|
return frozenActor.Info.TraitInfos<CapturableInfo>()
|
|
.Any(c => c.ValidStances.HasStance(stance) &&
|
|
captures.Info.CaptureTypes.Overlaps(c.Types));
|
|
}
|
|
}
|
|
|
|
public class CaptureManager : INotifyCreated, INotifyCapture, ITick, IDisableEnemyAutoTarget
|
|
{
|
|
readonly CaptureManagerInfo info;
|
|
ConditionManager conditionManager;
|
|
IMove move;
|
|
ICaptureProgressWatcher[] progressWatchers;
|
|
|
|
BitSet<CaptureType> allyCapturableTypes;
|
|
BitSet<CaptureType> neutralCapturableTypes;
|
|
BitSet<CaptureType> enemyCapturableTypes;
|
|
BitSet<CaptureType> capturesTypes;
|
|
|
|
IEnumerable<Capturable> enabledCapturable;
|
|
IEnumerable<Captures> enabledCaptures;
|
|
|
|
// Related to a specific capture in process
|
|
Actor currentTarget;
|
|
CaptureManager currentTargetManager;
|
|
int currentTargetDelay;
|
|
int currentTargetTotal;
|
|
int capturingToken = ConditionManager.InvalidConditionToken;
|
|
int beingCapturedToken = ConditionManager.InvalidConditionToken;
|
|
bool enteringCurrentTarget;
|
|
|
|
HashSet<Actor> currentCaptors = new HashSet<Actor>();
|
|
|
|
public bool BeingCaptured { get; private set; }
|
|
|
|
public CaptureManager(CaptureManagerInfo info)
|
|
{
|
|
this.info = info;
|
|
}
|
|
|
|
void INotifyCreated.Created(Actor self)
|
|
{
|
|
conditionManager = self.TraitOrDefault<ConditionManager>();
|
|
move = self.TraitOrDefault<IMove>();
|
|
progressWatchers = self.TraitsImplementing<ICaptureProgressWatcher>().ToArray();
|
|
|
|
enabledCapturable = self.TraitsImplementing<Capturable>()
|
|
.ToArray()
|
|
.Where(Exts.IsTraitEnabled);
|
|
|
|
enabledCaptures = self.TraitsImplementing<Captures>()
|
|
.ToArray()
|
|
.Where(Exts.IsTraitEnabled);
|
|
|
|
RefreshCaptures(self);
|
|
RefreshCapturable(self);
|
|
}
|
|
|
|
public void RefreshCapturable(Actor self)
|
|
{
|
|
allyCapturableTypes = neutralCapturableTypes = enemyCapturableTypes = default(BitSet<CaptureType>);
|
|
foreach (var c in enabledCapturable)
|
|
{
|
|
if (c.Info.ValidStances.HasStance(Stance.Ally))
|
|
allyCapturableTypes = allyCapturableTypes.Union(c.Info.Types);
|
|
|
|
if (c.Info.ValidStances.HasStance(Stance.Neutral))
|
|
neutralCapturableTypes = neutralCapturableTypes.Union(c.Info.Types);
|
|
|
|
if (c.Info.ValidStances.HasStance(Stance.Enemy))
|
|
enemyCapturableTypes = enemyCapturableTypes.Union(c.Info.Types);
|
|
}
|
|
}
|
|
|
|
public void RefreshCaptures(Actor self)
|
|
{
|
|
capturesTypes = enabledCaptures.Aggregate(
|
|
default(BitSet<CaptureType>),
|
|
(a, b) => a.Union(b.Info.CaptureTypes));
|
|
}
|
|
|
|
public bool CanBeTargetedBy(Actor self, Actor captor, CaptureManager captorManager)
|
|
{
|
|
var stance = self.Owner.Stances[captor.Owner];
|
|
if (stance.HasStance(Stance.Enemy))
|
|
return captorManager.capturesTypes.Overlaps(enemyCapturableTypes);
|
|
|
|
if (stance.HasStance(Stance.Neutral))
|
|
return captorManager.capturesTypes.Overlaps(neutralCapturableTypes);
|
|
|
|
if (stance.HasStance(Stance.Ally))
|
|
return captorManager.capturesTypes.Overlaps(allyCapturableTypes);
|
|
|
|
return false;
|
|
}
|
|
|
|
public bool CanBeTargetedBy(Actor self, Actor captor, Captures captures)
|
|
{
|
|
if (captures.IsTraitDisabled)
|
|
return false;
|
|
|
|
var stance = self.Owner.Stances[captor.Owner];
|
|
if (stance.HasStance(Stance.Enemy))
|
|
return captures.Info.CaptureTypes.Overlaps(enemyCapturableTypes);
|
|
|
|
if (stance.HasStance(Stance.Neutral))
|
|
return captures.Info.CaptureTypes.Overlaps(neutralCapturableTypes);
|
|
|
|
if (stance.HasStance(Stance.Ally))
|
|
return captures.Info.CaptureTypes.Overlaps(allyCapturableTypes);
|
|
|
|
return false;
|
|
}
|
|
|
|
public Captures ValidCapturesWithLowestSabotageThreshold(Actor self, Actor captee, CaptureManager capteeManager)
|
|
{
|
|
if (captee.IsDead)
|
|
return null;
|
|
|
|
foreach (var c in enabledCaptures.OrderBy(c => c.Info.SabotageThreshold))
|
|
if (capteeManager.CanBeTargetedBy(captee, self, c))
|
|
return c;
|
|
|
|
return null;
|
|
}
|
|
|
|
void INotifyCapture.OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner, BitSet<CaptureType> captureTypes)
|
|
{
|
|
BeingCaptured = true;
|
|
self.World.AddFrameEndTask(w => BeingCaptured = false);
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public bool StartCapture(Actor self, Actor target, CaptureManager targetManager, out Captures captures)
|
|
{
|
|
captures = null;
|
|
|
|
// Prevent a capture being restarted after it has been canceled during disposal
|
|
if (self.WillDispose)
|
|
return false;
|
|
|
|
if (target != currentTarget)
|
|
{
|
|
if (currentTarget != null)
|
|
CancelCapture(self, currentTarget, currentTargetManager);
|
|
|
|
targetManager.currentCaptors.Add(self);
|
|
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);
|
|
|
|
captures = enabledCaptures
|
|
.OrderBy(c => c.Info.CaptureDelay)
|
|
.FirstOrDefault(c => targetManager.CanBeTargetedBy(target, self, c));
|
|
|
|
if (captures == null)
|
|
return false;
|
|
|
|
// HACK: Make sure the target is not moving and at its normal position with respect to the cell grid
|
|
var enterMobile = target.TraitOrDefault<Mobile>();
|
|
if (enterMobile != null && enterMobile.IsMovingBetweenCells)
|
|
return false;
|
|
|
|
if (progressWatchers.Any() || targetManager.progressWatchers.Any())
|
|
{
|
|
currentTargetTotal = captures.Info.CaptureDelay;
|
|
if (move != null && captures.Info.ConsumedByCapture)
|
|
{
|
|
var pos = target.GetTargetablePositions().PositionClosestTo(self.CenterPosition);
|
|
currentTargetTotal += move.EstimatedMoveDuration(self, self.CenterPosition, pos);
|
|
}
|
|
|
|
foreach (var w in progressWatchers)
|
|
w.Update(self, self, target, currentTargetDelay, currentTargetTotal);
|
|
|
|
foreach (var w in targetManager.progressWatchers)
|
|
w.Update(target, self, target, currentTargetDelay, currentTargetTotal);
|
|
}
|
|
|
|
enteringCurrentTarget = currentTargetDelay >= captures.Info.CaptureDelay;
|
|
return enteringCurrentTarget;
|
|
}
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public void CancelCapture(Actor self, Actor target, CaptureManager targetManager)
|
|
{
|
|
if (currentTarget == null)
|
|
return;
|
|
|
|
foreach (var w in progressWatchers)
|
|
w.Update(self, self, target, 0, 0);
|
|
|
|
foreach (var w in targetManager.progressWatchers)
|
|
w.Update(target, self, target, 0, 0);
|
|
|
|
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;
|
|
enteringCurrentTarget = false;
|
|
targetManager.currentCaptors.Remove(self);
|
|
}
|
|
|
|
void ITick.Tick(Actor self)
|
|
{
|
|
// TryCapture is not called once the captor starts entering the target
|
|
// so we continue ticking the progress watchers ourself
|
|
if (!enteringCurrentTarget)
|
|
return;
|
|
|
|
if (currentTargetDelay < currentTargetTotal)
|
|
currentTargetDelay++;
|
|
|
|
foreach (var w in progressWatchers)
|
|
w.Update(self, self, currentTarget, currentTargetDelay, currentTargetTotal);
|
|
|
|
foreach (var w in currentTargetManager.progressWatchers)
|
|
w.Update(currentTarget, self, currentTarget, currentTargetDelay, currentTargetTotal);
|
|
}
|
|
|
|
bool IDisableEnemyAutoTarget.DisableEnemyAutoTarget(Actor self, Actor attacker)
|
|
{
|
|
return info.PreventsAutoTarget && currentCaptors.Any(c => attacker.AppearsFriendlyTo(c));
|
|
}
|
|
}
|
|
}
|