Add CaptureManager trait to fix multiple-trait interactions.

This fixes the various edge cases that occur when multiple
Captures or Capturable traits are defined on an actor and
are toggled using conditions.

The Sabotage threshold field moves from Capturable to
Captures in order to simplify the plumbing. The previous
behaviour ingame can be restored by creating a new
capturable type for each threshold level, each with their
own Captures trait.
This commit is contained in:
Paul Chote
2018-09-29 16:28:22 +00:00
committed by abcdefg30
parent 588a5d784f
commit a53ef6e503
6 changed files with 237 additions and 86 deletions

View File

@@ -9,6 +9,7 @@
*/
#endregion
using System;
using System.Linq;
using OpenRA.Activities;
using OpenRA.Mods.Common.Traits;
@@ -20,28 +21,26 @@ namespace OpenRA.Mods.Common.Activities
{
readonly Actor actor;
readonly Building building;
readonly Capturable capturable;
readonly Captures[] captures;
readonly IHealth health;
readonly CaptureManager targetManager;
readonly CaptureManager manager;
public CaptureActor(Actor self, Actor target)
: base(self, target, EnterBehaviour.Dispose)
{
actor = target;
building = actor.TraitOrDefault<Building>();
captures = self.TraitsImplementing<Captures>().ToArray();
capturable = target.Trait<Capturable>();
health = actor.Trait<IHealth>();
manager = self.Trait<CaptureManager>();
targetManager = target.Trait<CaptureManager>();
}
protected override bool CanReserve(Actor self)
{
return !capturable.BeingCaptured && capturable.CanBeTargetedBy(self, actor.Owner);
return !actor.IsDead && !targetManager.BeingCaptured && targetManager.CanBeTargetedBy(actor, self, manager);
}
protected override void OnInside(Actor self)
{
if (actor.IsDead || capturable.BeingCaptured || capturable.IsTraitDisabled)
if (!CanReserve(self))
return;
if (building != null && !building.Lock())
@@ -52,39 +51,43 @@ namespace OpenRA.Mods.Common.Activities
if (building != null && building.Locked)
building.Unlock();
var activeCaptures = captures.FirstOrDefault(c => !c.IsTraitDisabled);
if (actor.IsDead || capturable.BeingCaptured || activeCaptures == null)
// Prioritize capturing over sabotaging
var captures = manager.ValidCapturesWithLowestSabotageThreshold(self, actor, targetManager);
if (captures == null)
return;
var capturesInfo = activeCaptures.Info;
// Cast to long to avoid overflow when multiplying by the health
var lowEnoughHealth = health.HP <= (int)(capturable.Info.CaptureThreshold * (long)health.MaxHP / 100);
if (!capturesInfo.Sabotage || lowEnoughHealth || actor.Owner.NonCombatant)
// Sabotage instead of capture
if (captures.Info.SabotageThreshold > 0 && !actor.Owner.NonCombatant)
{
var oldOwner = actor.Owner;
var health = actor.Trait<IHealth>();
actor.ChangeOwner(self.Owner);
foreach (var t in actor.TraitsImplementing<INotifyCapture>())
t.OnCapture(actor, self, oldOwner, self.Owner);
if (building != null && building.Locked)
building.Unlock();
if (self.Owner.Stances[oldOwner].HasStance(capturesInfo.PlayerExperienceStances))
// Cast to long to avoid overflow when multiplying by the health
if (100 * (long)health.HP > captures.Info.SabotageThreshold * (long)health.MaxHP)
{
var exp = self.Owner.PlayerActor.TraitOrDefault<PlayerExperience>();
if (exp != null)
exp.GiveExperience(capturesInfo.PlayerExperience);
var damage = (int)((long)health.MaxHP * captures.Info.SabotageHPRemoval / 100);
actor.InflictDamage(self, new Damage(damage));
self.Dispose();
return;
}
}
else
// Do the capture
var oldOwner = actor.Owner;
actor.ChangeOwner(self.Owner);
foreach (var t in actor.TraitsImplementing<INotifyCapture>())
t.OnCapture(actor, self, oldOwner, self.Owner);
if (building != null && building.Locked)
building.Unlock();
if (self.Owner.Stances[oldOwner].HasStance(captures.Info.PlayerExperienceStances))
{
// Cast to long to avoid overflow when multiplying by the health
var damage = (int)((long)health.MaxHP * capturesInfo.SabotageHPRemoval / 100);
actor.InflictDamage(self, new Damage(damage));
var exp = self.Owner.PlayerActor.TraitOrDefault<PlayerExperience>();
if (exp != null)
exp.GiveExperience(captures.Info.PlayerExperience);
}
self.Dispose();
@@ -93,7 +96,7 @@ namespace OpenRA.Mods.Common.Activities
public override Activity Tick(Actor self)
{
if (captures.All(c => c.IsTraitDisabled))
if (!targetManager.CanBeTargetedBy(actor, self, manager))
Cancel(self);
return base.Tick(self);