Files
OpenRA/OpenRA.Mods.Common/Traits/Captures.cs
Paul Chote a53ef6e503 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.
2018-10-07 18:46:21 +02:00

161 lines
4.9 KiB
C#

#region Copyright & License Information
/*
* Copyright 2007-2018 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.Drawing;
using System.Linq;
using OpenRA.Mods.Common.Activities;
using OpenRA.Mods.Common.Orders;
using OpenRA.Primitives;
using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("This actor can capture other actors which have the Capturable: trait.")]
public class CapturesInfo : ConditionalTraitInfo, Requires<CaptureManagerInfo>
{
[Desc("Types of actors that it can capture, as long as the type also exists in the Capturable Type: trait.")]
public readonly BitSet<CaptureType> CaptureTypes = new BitSet<CaptureType>("building");
[Desc("Targets with health above this percentage will be sabotaged instead of captured.",
"Set to 0 to disable sabotaging.")]
public readonly int SabotageThreshold = 50;
[Desc("Sabotage damage expressed as a percentage of maximum target health.")]
public readonly int SabotageHPRemoval = 50;
[Desc("Experience granted to the capturing player.")]
public readonly int PlayerExperience = 0;
[Desc("Stance that the structure's previous owner needs to have for the capturing player to receive Experience.")]
public readonly Stance PlayerExperienceStances = Stance.Enemy;
public readonly string SabotageCursor = "capture";
public readonly string EnterCursor = "enter";
public readonly string EnterBlockedCursor = "enter-blocked";
[VoiceReference] public readonly string Voice = "Action";
public override object Create(ActorInitializer init) { return new Captures(init.Self, this); }
}
public class Captures : ConditionalTrait<CapturesInfo>, IIssueOrder, IResolveOrder, IOrderVoice
{
readonly CaptureManager captureManager;
public Captures(Actor self, CapturesInfo info)
: base(info)
{
captureManager = self.Trait<CaptureManager>();
}
public IEnumerable<IOrderTargeter> Orders
{
get
{
if (IsTraitDisabled)
yield break;
yield return new CaptureOrderTargeter(this);
}
}
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
{
if (order.OrderID != "CaptureActor")
return null;
return new Order(order.OrderID, self, target, queued);
}
public string VoicePhraseForOrder(Actor self, Order order)
{
return order.OrderString == "CaptureActor" ? Info.Voice : null;
}
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString != "CaptureActor" || IsTraitDisabled)
return;
var target = self.ResolveFrozenActorOrder(order, Color.Red);
if (target.Type != TargetType.Actor)
return;
if (!order.Queued)
self.CancelActivity();
self.SetTargetLine(target, Color.Red);
self.QueueActivity(new CaptureActor(self, target.Actor));
}
protected override void TraitEnabled(Actor self) { captureManager.RefreshCaptures(self); }
protected override void TraitDisabled(Actor self) { captureManager.RefreshCaptures(self); }
class CaptureOrderTargeter : UnitOrderTargeter
{
readonly Captures captures;
public CaptureOrderTargeter(Captures captures)
: base("CaptureActor", 6, captures.Info.EnterCursor, true, true)
{
this.captures = captures;
}
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
{
var captureManager = target.TraitOrDefault<CaptureManager>();
if (captureManager == null || !captureManager.CanBeTargetedBy(target, self, captures))
{
cursor = captures.Info.EnterBlockedCursor;
return false;
}
cursor = captures.Info.EnterCursor;
if (captures.Info.SabotageThreshold > 0 && !target.Owner.NonCombatant)
{
var health = target.Trait<IHealth>();
// Sabotage instead of capture
if ((long)health.HP * 100 > captures.Info.SabotageThreshold * (long)health.MaxHP)
cursor = captures.Info.SabotageCursor;
}
return true;
}
public override bool CanTargetFrozenActor(Actor self, FrozenActor target, TargetModifiers modifiers, ref string cursor)
{
// Actors with FrozenUnderFog should not disable the Capturable trait.
var c = target.Info.TraitInfoOrDefault<CapturableInfo>();
if (c == null || !c.CanBeTargetedBy(self, target.Owner))
{
cursor = captures.Info.EnterCursor;
return false;
}
cursor = captures.Info.EnterCursor;
if (captures.Info.SabotageThreshold > 0 && !target.Owner.NonCombatant)
{
var healthInfo = target.Info.TraitInfoOrDefault<IHealthInfo>();
// Sabotage instead of capture
if ((long)target.HP * 100 > captures.Info.SabotageThreshold * (long)healthInfo.MaxHP)
cursor = captures.Info.SabotageCursor;
}
return true;
}
}
}
}