diff --git a/OpenRA.Mods.RA/Activities/CaptureActor.cs b/OpenRA.Mods.RA/Activities/CaptureActor.cs index 54be70ec7f..67666faa0f 100644 --- a/OpenRA.Mods.RA/Activities/CaptureActor.cs +++ b/OpenRA.Mods.RA/Activities/CaptureActor.cs @@ -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(); - var health = target.Trait(); - int damage = health.MaxHP / 4; + var capturable = target.Actor.Trait(); - // Need to be next to building, TODO: stop capture when going away - var mobile = self.Trait(); - 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().BeginCapture(target, self)) - return NextActivity; + if (capturable.CaptureInProgress) + capturable.EndCapture(); + + return NextActivity; } + + var mobile = self.Trait(); + 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(); + + self.World.AddFrameEndTask(w => + { + var oldOwner = target.Actor.Owner; + + target.Actor.ChangeOwner(self.Owner); + + foreach (var t in target.Actor.TraitsImplementing()) + t.OnCapture(target.Actor, self, oldOwner, self.Owner); + + foreach (var t in self.World.ActorsWithTrait()) + t.Trait.OnActorCaptured(t.Actor, target.Actor, self, oldOwner, self.Owner); + + capturable.EndCapture(); + + if (capturesInfo != null && capturesInfo.ConsumeActor) + self.Destroy(); + }); + } + } return this; } } diff --git a/OpenRA.Mods.RA/Activities/LegacyCaptureActor.cs b/OpenRA.Mods.RA/Activities/LegacyCaptureActor.cs new file mode 100644 index 0000000000..062de5daaf --- /dev/null +++ b/OpenRA.Mods.RA/Activities/LegacyCaptureActor.cs @@ -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(); + if (b != null && b.Locked) + return NextActivity; + + var capturesInfo = self.Info.Traits.Get(); + var capturableInfo = target.Actor.Info.Traits.Get(); + + var health = target.Actor.Trait(); + 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()) + t.OnCapture(target.Actor, self, oldOwner, self.Owner); + + foreach (var t in self.World.ActorsWithTrait()) + 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; + } + } +} diff --git a/OpenRA.Mods.RA/Capturable.cs b/OpenRA.Mods.RA/Capturable.cs index 7378066253..ffe33564e2 100644 --- a/OpenRA.Mods.RA/Capturable.cs +++ b/OpenRA.Mods.RA/Capturable.cs @@ -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().Lock()) + var c = captor.TraitOrDefault(); + 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(); + 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()) - t.OnCapture(self, Captor, self.Owner, Captor.Owner); - - foreach (var t in Captor.World.ActorsWithTrait()) - t.Trait.OnActorCaptured(t.Actor, self, Captor, self.Owner, Captor.Owner); - - Captor = null; - self.Trait().Unlock(); - }); - } + Captor = captor; } - public static void ChangeCargoOwner(Actor self, Player captor) + public void EndCapture() { - var cargo = self.TraitOrDefault(); - if (cargo == null) - return; + var building = self.TraitOrDefault(); + 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++; } } } diff --git a/OpenRA.Mods.RA/Captures.cs b/OpenRA.Mods.RA/Captures.cs index 1e7e97d530..093bfec04a 100644 --- a/OpenRA.Mods.RA/Captures.cs +++ b/OpenRA.Mods.RA/Captures.cs @@ -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(); - 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 useEnterCursor; + readonly Func useCaptureCursor; - public CaptureOrderTargeter(string[] captureTypes, Func useEnterCursor) + public CaptureOrderTargeter(Func 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(); - 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(); - if (captureTypes.Contains(ci.Type)) + if (canTargetActor) { - cursor = (Info.WastedAfterwards) ? (useEnterCursor(target) ? "enter" : "enter-blocked") : "attack"; + IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); return true; } diff --git a/OpenRA.Mods.RA/Cargo.cs b/OpenRA.Mods.RA/Cargo.cs index d51aed6f03..24f90e7f7a 100644 --- a/OpenRA.Mods.RA/Cargo.cs +++ b/OpenRA.Mods.RA/Cargo.cs @@ -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); } diff --git a/OpenRA.Mods.RA/LegacyCapturable.cs b/OpenRA.Mods.RA/LegacyCapturable.cs new file mode 100644 index 0000000000..f02e05959f --- /dev/null +++ b/OpenRA.Mods.RA/LegacyCapturable.cs @@ -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(); + 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; + } + } +} diff --git a/OpenRA.Mods.RA/LegacyCaptures.cs b/OpenRA.Mods.RA/LegacyCaptures.cs new file mode 100644 index 0000000000..4c61695556 --- /dev/null +++ b/OpenRA.Mods.RA/LegacyCaptures.cs @@ -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 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(); + return c != null && c.CanBeTargetedBy(self); + } + + class LegacyCaptureOrderTargeter : UnitOrderTargeter + { + readonly Func useCaptureCursor; + + public LegacyCaptureOrderTargeter(Func 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(); + var health = target.Trait(); + 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; + } + } + } +} diff --git a/OpenRA.Mods.RA/Missions/Allies02Script.cs b/OpenRA.Mods.RA/Missions/Allies02Script.cs index 3f7033f887..761eb0b94f 100644 --- a/OpenRA.Mods.RA/Missions/Allies02Script.cs +++ b/OpenRA.Mods.RA/Missions/Allies02Script.cs @@ -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()) diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj index 08a365901f..c6b3a49af7 100644 --- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj +++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj @@ -75,6 +75,7 @@ + @@ -171,6 +172,7 @@ + @@ -238,6 +240,7 @@ + diff --git a/mods/cnc/rules/defaults.yaml b/mods/cnc/rules/defaults.yaml index 1e664dc30a..e5a06b9e5c 100644 --- a/mods/cnc/rules/defaults.yaml +++ b/mods/cnc/rules/defaults.yaml @@ -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 diff --git a/mods/cnc/rules/infantry.yaml b/mods/cnc/rules/infantry.yaml index 4721bd11ee..2e7bcaa96f 100644 --- a/mods/cnc/rules/infantry.yaml +++ b/mods/cnc/rules/infantry.yaml @@ -167,7 +167,7 @@ E6: PipType: Yellow EngineerRepair: RepairsBridges: - Captures: + LegacyCaptures: CaptureTypes: building, husk -AutoTarget: AttackMove: diff --git a/mods/ra/rules/civilian.yaml b/mods/ra/rules/civilian.yaml index 08cddcdf9f..216b662439 100644 --- a/mods/ra/rules/civilian.yaml +++ b/mods/ra/rules/civilian.yaml @@ -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