Capture changes - Engis now capture from outside. Added classic (legacy) capturable traits.

This commit is contained in:
Curtis Shmyr
2013-06-11 20:44:06 -06:00
parent d898899de7
commit 6a1b37b5b7
12 changed files with 391 additions and 109 deletions

View File

@@ -1,6 +1,6 @@
#region Copyright & License Information
/*
* Copyright 2007-2012 The OpenRA Developers (see AUTHORS)
* Copyright 2007-2013 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. For more information,
@@ -9,47 +9,75 @@
#endregion
using System.Linq;
using OpenRA.Mods.RA.Move;
using OpenRA.Traits;
using OpenRA.Effects;
using OpenRA.Mods.RA.Move;
using OpenRA.Mods.RA.Buildings;
namespace OpenRA.Mods.RA.Activities
{
class CaptureActor : Activity
{
Actor target;
Target target;
public CaptureActor(Actor target) { this.target = target; }
public CaptureActor(Target target) { this.target = target; }
public override Activity Tick(Actor self)
{
if (IsCanceled)
return NextActivity;
if (target == null || !target.IsInWorld || target.IsDead())
return NextActivity;
if (target.Owner == self.Owner)
if (!target.IsValid)
return NextActivity;
var capturesInfo = self.Info.Traits.Get<CapturesInfo>();
var health = target.Trait<Health>();
int damage = health.MaxHP / 4;
var capturable = target.Actor.Trait<Capturable>();
// Need to be next to building, TODO: stop capture when going away
var mobile = self.Trait<Mobile>();
var nearest = target.OccupiesSpace.NearestCellTo(mobile.toCell);
if ((nearest - mobile.toCell).LengthSquared > 2)
return Util.SequenceActivities(new MoveAdjacentTo(Target.FromActor(target)), this);
if (!capturesInfo.Sabotage || (capturesInfo.Sabotage && health.DamageState == DamageState.Heavy))
if (IsCanceled || !self.IsInWorld || self.IsDead())
{
if (!target.Trait<Capturable>().BeginCapture(target, self))
return NextActivity;
if (capturable.CaptureInProgress)
capturable.EndCapture();
return NextActivity;
}
var mobile = self.Trait<Mobile>();
var nearest = target.Actor.OccupiesSpace.NearestCellTo(mobile.toCell);
if ((nearest - mobile.toCell).LengthSquared > 2)
return Util.SequenceActivities(new MoveAdjacentTo(target), this);
if (!capturable.CaptureInProgress)
capturable.BeginCapture(self);
else
target.InflictDamage(self, damage, null);
{
if (capturable.Captor != self) return NextActivity;
if (capturesInfo != null && capturesInfo.WastedAfterwards)
self.World.AddFrameEndTask(w => self.Destroy());
if (capturable.CaptureProgressTime % 25 == 0)
{
self.World.Add(new FlashTarget(target.Actor)); // TODO: building should flash captor's color
self.World.Add(new FlashTarget(self));
}
if (capturable.CaptureProgressTime == capturable.Info.CaptureCompleteTime * 25)
{
var capturesInfo = self.Info.Traits.Get<CapturesInfo>();
self.World.AddFrameEndTask(w =>
{
var oldOwner = target.Actor.Owner;
target.Actor.ChangeOwner(self.Owner);
foreach (var t in target.Actor.TraitsImplementing<INotifyCapture>())
t.OnCapture(target.Actor, self, oldOwner, self.Owner);
foreach (var t in self.World.ActorsWithTrait<INotifyOtherCaptured>())
t.Trait.OnActorCaptured(t.Actor, target.Actor, self, oldOwner, self.Owner);
capturable.EndCapture();
if (capturesInfo != null && capturesInfo.ConsumeActor)
self.Destroy();
});
}
}
return this;
}
}

View File

@@ -0,0 +1,70 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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. For more information,
* see COPYING.
*/
#endregion
using System.Linq;
using OpenRA.Traits;
using OpenRA.Mods.RA.Move;
using OpenRA.Mods.RA.Buildings;
namespace OpenRA.Mods.RA.Activities
{
class LegacyCaptureActor : Activity
{
Target target;
public LegacyCaptureActor(Target target) { this.target = target; }
public override Activity Tick(Actor self)
{
if (IsCanceled)
return NextActivity;
if (!target.IsValid)
return NextActivity;
var b = target.Actor.TraitOrDefault<Building>();
if (b != null && b.Locked)
return NextActivity;
var capturesInfo = self.Info.Traits.Get<LegacyCapturesInfo>();
var capturableInfo = target.Actor.Info.Traits.Get<LegacyCapturableInfo>();
var health = target.Actor.Trait<Health>();
var lowEnoughHealth = health.HP <= capturableInfo.CaptureThreshold * health.MaxHP;
self.World.AddFrameEndTask(w =>
{
if (!capturesInfo.Sabotage || lowEnoughHealth || target.Actor.Owner.NonCombatant)
{
var oldOwner = target.Actor.Owner;
target.Actor.ChangeOwner(self.Owner);
foreach (var t in self.TraitsImplementing<INotifyCapture>())
t.OnCapture(target.Actor, self, oldOwner, self.Owner);
foreach (var t in self.World.ActorsWithTrait<INotifyOtherCaptured>())
t.Trait.OnActorCaptured(t.Actor, target.Actor, self, oldOwner, self.Owner);
if (b != null && b.Locked)
b.Unlock();
}
else
{
int damage = (int)(health.MaxHP * capturesInfo.SabotageHPRemoval);
target.Actor.InflictDamage(self, damage, null);
}
self.Destroy();
});
return this;
}
}
}

View File

@@ -8,6 +8,7 @@
*/
#endregion
using System;
using System.Linq;
using OpenRA.Effects;
using OpenRA.FileFormats;
@@ -19,79 +20,83 @@ namespace OpenRA.Mods.RA
[Desc("This actor can be captured by a unit with Captures: trait.")]
public class CapturableInfo : ITraitInfo
{
[Desc("Type of actor (the Captures: trait defines what Types it can capture).")]
public readonly string Type = "building";
public readonly bool AllowAllies = false;
public readonly bool AllowNeutral = true;
public readonly bool AllowEnemies = true;
[Desc("Seconds it takes to change the owner.", "It stays neutral during this period. You might want to add a CapturableBar: trait, too.")]
public readonly int CaptureCompleteTime = 10;
[Desc("Seconds it takes to change the owner.", "You might want to add a CapturableBar: trait, too.")]
public readonly int CaptureCompleteTime = 15;
public object Create(ActorInitializer init) { return new Capturable(this); }
public object Create(ActorInitializer init) { return new Capturable(init.self, this); }
}
public class Capturable : ITick
{
[Sync] public Actor Captor = null;
[Sync] public int CaptureProgressTime = 0;
public bool CaptureInProgress { get { return Captor != null; } }
[Sync] public Actor Captor;
private Actor self;
public CapturableInfo Info;
public bool CaptureInProgress { get { return Captor != null; } }
public Capturable(CapturableInfo info)
public Capturable(Actor self, CapturableInfo info)
{
this.Info = info;
this.self = self;
Info = info;
}
public bool BeginCapture(Actor self, Actor captor)
public bool CanBeTargetedBy(Actor captor)
{
if (!CaptureInProgress && !self.Trait<Building>().Lock())
var c = captor.TraitOrDefault<Captures>();
if (c == null)
return false;
if (CaptureInProgress && Captor.Owner.Stances[captor.Owner] == Stance.Ally)
var playerRelationship = self.Owner.Stances[captor.Owner];
if (playerRelationship == Stance.Ally && !Info.AllowAllies)
return false;
CaptureProgressTime = 0;
if (playerRelationship == Stance.Enemy && !Info.AllowEnemies)
return false;
this.Captor = captor;
if (playerRelationship == Stance.Neutral && !Info.AllowNeutral)
return false;
if (self.Owner != self.World.WorldActor.Owner)
self.ChangeOwner(self.World.WorldActor.Owner);
if (!c.Info.CaptureTypes.Contains(Info.Type))
return false;
if (CaptureInProgress)
return false;
return true;
}
public void Tick(Actor self)
public void BeginCapture(Actor captor)
{
if (!CaptureInProgress) return;
var building = self.TraitOrDefault<Building>();
if (building != null)
building.Lock();
if (CaptureProgressTime < Info.CaptureCompleteTime * 25)
CaptureProgressTime++;
else
{
self.World.AddFrameEndTask(w =>
{
self.ChangeOwner(Captor.Owner);
ChangeCargoOwner(self, Captor.Owner);
foreach (var t in self.TraitsImplementing<INotifyCapture>())
t.OnCapture(self, Captor, self.Owner, Captor.Owner);
foreach (var t in Captor.World.ActorsWithTrait<INotifyOtherCaptured>())
t.Trait.OnActorCaptured(t.Actor, self, Captor, self.Owner, Captor.Owner);
Captor = null;
self.Trait<Building>().Unlock();
});
}
Captor = captor;
}
public static void ChangeCargoOwner(Actor self, Player captor)
public void EndCapture()
{
var cargo = self.TraitOrDefault<Cargo>();
if (cargo == null)
return;
var building = self.TraitOrDefault<Building>();
if (building != null)
building.Unlock();
foreach (var c in cargo.Passengers)
c.Owner = captor;
Captor = null;
}
public void Tick(Actor self)
{
if (Captor != null && (!Captor.IsInWorld || Captor.IsDead()))
EndCapture();
if (!CaptureInProgress)
CaptureProgressTime = 0;
else
CaptureProgressTime++;
}
}
}

View File

@@ -16,14 +16,18 @@ using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Buildings;
using OpenRA.Mods.RA.Orders;
using OpenRA.Traits;
using OpenRA.FileFormats;
namespace OpenRA.Mods.RA
{
[Desc("This actor can capture other actors which have the Capturable: trait.")]
class CapturesInfo : ITraitInfo
{
public string[] CaptureTypes = {"building"};
public bool WastedAfterwards = true;
public bool Sabotage = false;
[Desc("Types of actors that it can capture, as long as the type also exists in the Capturable Type: trait.")]
public readonly string[] CaptureTypes = { "building" };
[Desc("Destroy the unit after capturing.")]
public readonly bool ConsumeActor = false;
public object Create(ActorInitializer init) { return new Captures(init.self, this); }
}
@@ -42,11 +46,11 @@ namespace OpenRA.Mods.RA
{
get
{
yield return new CaptureOrderTargeter(Info.CaptureTypes, target => CanCapture(target));
yield return new CaptureOrderTargeter(CanCapture);
}
}
public Order IssueOrder( Actor self, IOrderTargeter order, Target target, bool queued )
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
{
if (order.OrderID == "CaptureActor")
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
@@ -56,8 +60,7 @@ namespace OpenRA.Mods.RA
public string VoicePhraseForOrder(Actor self, Order order)
{
return (order.OrderString == "CaptureActor"
&& CanCapture(order.TargetActor)) ? "Attack" : null;
return (order.OrderString == "CaptureActor" && CanCapture(order.TargetActor)) ? "Attack" : null;
}
public void ResolveOrder(Actor self, Order order)
@@ -70,27 +73,25 @@ namespace OpenRA.Mods.RA
self.SetTargetLine(Target.FromOrder(order), Color.Red);
self.CancelActivity();
self.QueueActivity(new CaptureActor(order.TargetActor));
self.QueueActivity(new CaptureActor(Target.FromOrder(order)));
}
}
bool CanCapture(Actor target)
{
var c = target.TraitOrDefault<Capturable>();
return c != null && (!c.CaptureInProgress || c.Captor.Owner.Stances[self.Owner] != Stance.Ally);
return c != null && c.CanBeTargetedBy(self);
}
}
class CaptureOrderTargeter : UnitOrderTargeter
{
readonly string[] captureTypes;
readonly Func<Actor, bool> useEnterCursor;
readonly Func<Actor, bool> useCaptureCursor;
public CaptureOrderTargeter(string[] captureTypes, Func<Actor, bool> useEnterCursor)
public CaptureOrderTargeter(Func<Actor, bool> useCaptureCursor)
: base("CaptureActor", 6, "enter", true, true)
{
this.captureTypes = captureTypes;
this.useEnterCursor = useEnterCursor;
this.useCaptureCursor = useCaptureCursor;
}
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
@@ -98,26 +99,12 @@ namespace OpenRA.Mods.RA
if (!base.CanTargetActor(self, target, modifiers, ref cursor))
return false;
var ci = target.Info.Traits.GetOrDefault<CapturableInfo>();
if (ci == null)
return false;
var canTargetActor = useCaptureCursor(target);
cursor = canTargetActor ? "ability" : "move-blocked";
var playerRelationship = self.Owner.Stances[target.Owner];
if (playerRelationship == Stance.Ally && !ci.AllowAllies)
return false;
if (playerRelationship == Stance.Enemy && !ci.AllowEnemies)
return false;
if (playerRelationship == Stance.Neutral && !ci.AllowNeutral)
return false;
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
var Info = self.Info.Traits.Get<CapturesInfo>();
if (captureTypes.Contains(ci.Type))
if (canTargetActor)
{
cursor = (Info.WastedAfterwards) ? (useEnterCursor(target) ? "enter" : "enter-blocked") : "attack";
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
return true;
}

View File

@@ -29,7 +29,7 @@ namespace OpenRA.Mods.RA
public object Create( ActorInitializer init ) { return new Cargo( init, this ); }
}
public class Cargo : IPips, IIssueOrder, IResolveOrder, IOrderVoice, INotifyKilled
public class Cargo : IPips, IIssueOrder, IResolveOrder, IOrderVoice, INotifyKilled, INotifyCapture
{
readonly Actor self;
readonly CargoInfo info;
@@ -180,6 +180,18 @@ namespace OpenRA.Mods.RA
c.Destroy();
cargo.Clear();
}
public void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner)
{
if (cargo == null)
return;
self.World.AddFrameEndTask(w =>
{
foreach (var p in Passengers)
p.Owner = newOwner;
});
}
}
public interface INotifyPassengerEntered { void PassengerEntered(Actor self, Actor passenger); }

View File

@@ -0,0 +1,64 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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. For more information,
* see COPYING.
*/
#endregion
using System.Linq;
using OpenRA.Traits;
using OpenRA.FileFormats;
namespace OpenRA.Mods.RA
{
[Desc("This actor can be captured by a unit with LegacyCaptures: trait.")]
class LegacyCapturableInfo : ITraitInfo
{
[Desc("Type of actor (the LegacyCaptures: trait defines what Types it can capture).")]
public readonly string Type = "building";
public readonly bool AllowAllies = false;
public readonly bool AllowNeutral = true;
public readonly bool AllowEnemies = true;
[Desc("Health percentage the target must be at (or below) before it can be captured.")]
public readonly double CaptureThreshold = 0.5;
public object Create(ActorInitializer init) { return new LegacyCapturable(init.self, this); }
}
class LegacyCapturable
{
[Sync] Actor self;
public LegacyCapturableInfo Info;
public LegacyCapturable(Actor self, LegacyCapturableInfo info)
{
this.self = self;
Info = info;
}
public bool CanBeTargetedBy(Actor captor)
{
var c = captor.TraitOrDefault<LegacyCaptures>();
if (c == null)
return false;
var playerRelationship = self.Owner.Stances[captor.Owner];
if (playerRelationship == Stance.Ally && !Info.AllowAllies)
return false;
if (playerRelationship == Stance.Enemy && !Info.AllowEnemies)
return false;
if (playerRelationship == Stance.Neutral && !Info.AllowNeutral)
return false;
if (!c.Info.CaptureTypes.Contains(Info.Type))
return false;
return true;
}
}
}

View File

@@ -0,0 +1,118 @@
#region Copyright & License Information
/*
* Copyright 2007-2013 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. For more information,
* see COPYING.
*/
#endregion
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using OpenRA.Mods.RA.Activities;
using OpenRA.Mods.RA.Buildings;
using OpenRA.Mods.RA.Orders;
using OpenRA.Traits;
using OpenRA.FileFormats;
namespace OpenRA.Mods.RA
{
[Desc("This actor can capture other actors which have the LegacyCapturable: trait.")]
class LegacyCapturesInfo : ITraitInfo
{
[Desc("Types of actors that it can capture, as long as the type also exists in the LegacyCapturable Type: trait.")]
public readonly string[] CaptureTypes = { "building" };
[Desc("Unit will do damage to the actor instead of capturing it. Unit is destroyed when sabotaging.")]
public readonly bool Sabotage = true;
[Desc("Only used if Sabotage=true. Sabotage damage expressed as a percentage of enemy health removed.")]
public readonly double SabotageHPRemoval = 0.5;
public object Create(ActorInitializer init) { return new LegacyCaptures(init.self, this); }
}
class LegacyCaptures : IIssueOrder, IResolveOrder, IOrderVoice
{
public readonly LegacyCapturesInfo Info;
readonly Actor self;
public LegacyCaptures(Actor self, LegacyCapturesInfo info)
{
this.self = self;
Info = info;
}
public IEnumerable<IOrderTargeter> Orders
{
get
{
yield return new LegacyCaptureOrderTargeter(CanCapture);
}
}
public Order IssueOrder(Actor self, IOrderTargeter order, Target target, bool queued)
{
if (order.OrderID == "LegacyCaptureActor")
return new Order(order.OrderID, self, queued) { TargetActor = target.Actor };
return null;
}
public string VoicePhraseForOrder(Actor self, Order order)
{
return (order.OrderString == "LegacyCaptureActor") ? "Attack" : null;
}
public void ResolveOrder(Actor self, Order order)
{
if (order.OrderString == "LegacyCaptureActor")
{
self.SetTargetLine(Target.FromOrder(order), Color.Red);
self.CancelActivity();
self.QueueActivity(new Enter(order.TargetActor, new LegacyCaptureActor(Target.FromOrder(order))));
}
}
bool CanCapture(Actor target)
{
var c = target.TraitOrDefault<LegacyCapturable>();
return c != null && c.CanBeTargetedBy(self);
}
class LegacyCaptureOrderTargeter : UnitOrderTargeter
{
readonly Func<Actor, bool> useCaptureCursor;
public LegacyCaptureOrderTargeter(Func<Actor, bool> useCaptureCursor)
: base("LegacyCaptureActor", 6, "enter", true, true)
{
this.useCaptureCursor = useCaptureCursor;
}
public override bool CanTargetActor(Actor self, Actor target, TargetModifiers modifiers, ref string cursor)
{
if (!base.CanTargetActor(self, target, modifiers, ref cursor)) return false;
var canTargetActor = useCaptureCursor(target);
if (canTargetActor)
{
var c = target.Trait<LegacyCapturable>();
var health = target.Trait<Health>();
var lowEnoughHealth = health.HP <= c.Info.CaptureThreshold * health.MaxHP;
cursor = lowEnoughHealth ? "enter" : "capture";
IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue);
return true;
}
cursor = "enter-blocked";
return false;
}
}
}
}

View File

@@ -322,7 +322,6 @@ namespace OpenRA.Mods.RA.Missions
foreach (var actor in world.Actors.Where(a => a.Owner == allies && a != allies.PlayerActor))
{
actor.ChangeOwner(allies2);
Capturable.ChangeCargoOwner(actor, allies2);
if (actor.Info.Name == "proc")
actor.QueueActivity(new Transform(actor, "proc") { SkipMakeAnims = true }); // for harv spawn
foreach (var c in actor.TraitsImplementing<INotifyCapture>())

View File

@@ -75,6 +75,7 @@
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Activities\LegacyCaptureActor.cs" />
<Compile Include="AI\AttackOrFleeFuzzy.cs" />
<Compile Include="AI\BaseBuilder.cs" />
<Compile Include="AI\HackyAI.cs" />
@@ -171,6 +172,7 @@
<Compile Include="C4Demolition.cs" />
<Compile Include="Capturable.cs" />
<Compile Include="CapturableBar.cs" />
<Compile Include="LegacyCapturable.cs" />
<Compile Include="Captures.cs" />
<Compile Include="Cargo.cs" />
<Compile Include="CarpetBomb.cs" />
@@ -238,6 +240,7 @@
<Compile Include="IronCurtainable.cs" />
<Compile Include="JamsMissiles.cs" />
<Compile Include="LeavesHusk.cs" />
<Compile Include="LegacyCaptures.cs" />
<Compile Include="LightPaletteRotator.cs" />
<Compile Include="LimitedAmmo.cs" />
<Compile Include="Lint\CheckActorReferences.cs" />

View File

@@ -267,8 +267,7 @@
AutoTargetIgnore:
ShakeOnDeath:
Sellable:
Capturable:
CapturableBar:
LegacyCapturable:
DebugMuzzlePositions:
Guardable:
Range: 3
@@ -287,8 +286,7 @@
RenderBuilding:
WithBuildingExplosion:
-RepairableBuilding:
-Capturable:
-CapturableBar:
-LegacyCapturable:
-Sellable:
Tooltip:
Name: Civilian Building
@@ -307,8 +305,7 @@
^TechBuilding:
Inherits: ^CivBuilding
Capturable:
CapturableBar:
LegacyCapturable:
RepairableBuilding:
RevealsShroud:
Range: 3

View File

@@ -167,7 +167,7 @@ E6:
PipType: Yellow
EngineerRepair:
RepairsBridges:
Captures:
LegacyCaptures:
CaptureTypes: building, husk
-AutoTarget:
AttackMove:

View File

@@ -91,9 +91,8 @@ V01:
TransformOnCapture:
IntoActor: v01.sniper
SkipMakeAnims: true
Capturable:
LegacyCapturable:
Type: civilianbuilding
CaptureCompleteTime: 0
EditorTilesetFilter:
ExcludeTilesets: DESERT
@@ -119,7 +118,7 @@ V01.SNIPER:
OnExit: v01
SkipMakeAnims: true
BecomeNeutral: true
-Capturable:
-LegacyCapturable:
EditorTilesetFilter:
ExcludeTilesets: DESERT