diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs index 1926c2d204..e6ea859e95 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs @@ -235,7 +235,8 @@ namespace OpenRA.Mods.Common.Traits if (!Ready) return; - var power = Instances.FirstOrDefault(i => !InstanceDisabled(i)); + var power = Instances.Where(i => !InstanceDisabled(i)) + .MinByOrDefault(a => (a.Self.CenterPosition - a.Self.World.Map.CenterOfCell(order.TargetLocation)).HorizontalLengthSquared); if (power == null) return; diff --git a/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj b/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj index 14bab04b9a..1dfe59b25f 100644 --- a/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj +++ b/OpenRA.Mods.TS/OpenRA.Mods.TS.csproj @@ -74,6 +74,7 @@ + diff --git a/OpenRA.Mods.TS/Traits/SupportPowers/AttackOrderPower.cs b/OpenRA.Mods.TS/Traits/SupportPowers/AttackOrderPower.cs new file mode 100644 index 0000000000..76af6fe64a --- /dev/null +++ b/OpenRA.Mods.TS/Traits/SupportPowers/AttackOrderPower.cs @@ -0,0 +1,148 @@ +#region Copyright & License Information +/* + * Copyright 2007-2016 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.Graphics; +using OpenRA.Mods.Common.Graphics; +using OpenRA.Mods.Common.Traits; +using OpenRA.Traits; + +namespace OpenRA.Mods.TS.Traits +{ + class AttackOrderPowerInfo : SupportPowerInfo, Requires + { + public override object Create(ActorInitializer init) { return new AttackOrderPower(init.Self, this); } + } + + class AttackOrderPower : SupportPower, INotifyCreated, INotifyBurstComplete + { + readonly AttackOrderPowerInfo info; + AttackBase attack; + + public AttackOrderPower(Actor self, AttackOrderPowerInfo info) + : base(self, info) + { + this.info = info; + } + + public override void SelectTarget(Actor self, string order, SupportPowerManager manager) + { + Game.Sound.PlayToPlayer(manager.Self.Owner, Info.SelectTargetSound); + self.World.OrderGenerator = new SelectAttackPowerTarget(self, order, manager, info.Cursor, MouseButton.Left, attack); + } + + public override void Activate(Actor self, Order order, SupportPowerManager manager) + { + base.Activate(self, order, manager); + attack.AttackTarget(Target.FromCell(self.World, order.TargetLocation), false, false, true); + } + + void INotifyCreated.Created(Actor self) + { + attack = self.Trait(); + } + + void INotifyBurstComplete.FiredBurst(Actor self, Target target, Armament a) + { + self.World.IssueOrder(new Order("Stop", self, false)); + } + } + + public class SelectAttackPowerTarget : IOrderGenerator + { + readonly SupportPowerManager manager; + readonly SupportPowerInstance instance; + readonly string order; + readonly string cursor; + readonly string cursorBlocked; + readonly MouseButton expectedButton; + readonly AttackBase attack; + + public SelectAttackPowerTarget(Actor self, string order, SupportPowerManager manager, string cursor, MouseButton button, AttackBase attack) + { + // Clear selection if using Left-Click Orders + if (Game.Settings.Game.UseClassicMouseStyle) + manager.Self.World.Selection.Clear(); + + instance = manager.GetPowersForActor(self).FirstOrDefault(); + this.manager = manager; + this.order = order; + this.cursor = cursor; + expectedButton = button; + this.attack = attack; + cursorBlocked = cursor + "-blocked"; + } + + Actor GetFiringActor(World world, CPos cell) + { + var pos = world.Map.CenterOfCell(cell); + var range = attack.GetMaximumRange().LengthSquared; + + return instance.Instances.Where(i => !i.Self.IsDisabled()).MinByOrDefault(a => (a.Self.CenterPosition - pos).HorizontalLengthSquared).Self; + } + + bool IsValidTarget(World world, CPos cell) + { + var pos = world.Map.CenterOfCell(cell); + var range = attack.GetMaximumRange().LengthSquared; + + return world.Map.Contains(cell) && instance.Instances.Any(a => !a.Self.IsDisabled() && (a.Self.CenterPosition - pos).HorizontalLengthSquared < range); + } + + IEnumerable IOrderGenerator.Order(World world, CPos cell, int2 worldPixel, MouseInput mi) + { + world.CancelInputMode(); + if (mi.Button == expectedButton && IsValidTarget(world, cell)) + yield return new Order(order, manager.Self, false) + { + TargetActor = GetFiringActor(world, cell), + TargetLocation = cell, + SuppressVisualFeedback = true + }; + } + + void IOrderGenerator.Tick(World world) + { + // Cancel the OG if we can't use the power + if (!manager.Powers.ContainsKey(order)) + world.CancelInputMode(); + } + + IEnumerable IOrderGenerator.Render(WorldRenderer wr, World world) { yield break; } + + IEnumerable IOrderGenerator.RenderAfterWorld(WorldRenderer wr, World world) + { + foreach (var a in instance.Instances.Where(i => !i.Self.IsDisabled())) + { + yield return new RangeCircleRenderable( + a.Self.CenterPosition, + attack.GetMinimumRange(), + 0, + Color.Red, + Color.FromArgb(96, Color.Black)); + + yield return new RangeCircleRenderable( + a.Self.CenterPosition, + attack.GetMaximumRange(), + 0, + Color.Red, + Color.FromArgb(96, Color.Black)); + } + } + + string IOrderGenerator.GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi) + { + return IsValidTarget(world, cell) ? cursor : cursorBlocked; + } + } +} diff --git a/mods/ts/cursors.yaml b/mods/ts/cursors.yaml index 31c884bf40..3bd577ab17 100644 --- a/mods/ts/cursors.yaml +++ b/mods/ts/cursors.yaml @@ -152,6 +152,12 @@ Cursors: nuke: Start: 319 Length: 9 + emp: + Start: 357 + Length: 19 + emp-blocked: + Start: 377 + Length: 1 sell: Start: 129 Length: 10 diff --git a/mods/ts/rules/shared-support.yaml b/mods/ts/rules/shared-support.yaml index a11b01cb61..a15036d5aa 100644 --- a/mods/ts/rules/shared-support.yaml +++ b/mods/ts/rules/shared-support.yaml @@ -3,8 +3,8 @@ NAPULS: Valued: Cost: 1000 Tooltip: - Name: EMP Cannon - Description: Disables vehicles.\nRequires power to operate.\n Strong vs Vehicles\n Weak vs Infantry, Aircraft + Name: E.M. Pulse Cannon + Description: Disables mechanical units in an area.\nRequires power to operate. Buildable: Queue: Defense BuildPaletteOrder: 90 @@ -22,12 +22,15 @@ NAPULS: Type: Heavy RevealsShroud: Range: 8c0 + -AutoTarget: Turreted: TurnSpeed: 10 InitialFacing: 224 AttackTurreted: Armament: Weapon: EMPulseCannon + LocalOffset: 150,0,1250 + LocalYaw: 0,100 WithSpriteTurret: Sequence: turret Power: @@ -39,3 +42,31 @@ NAPULS: FactionImages: gdi: napuls.gdi nod: napuls.nod + ProvidesPrerequisite@gdi: + Prerequisite: emp.gdi + Factions: gdi + ResetOnOwnerChange: true + ProvidesPrerequisite@nod: + Prerequisite: emp.nod + Factions: nod + ResetOnOwnerChange: true + AttackOrderPower@nod: + Cursor: emp + Icon: emp + ChargeTime: 135 + Description: E.M. Pulse + LongDesc: Fires a pulse blast which disables\nall mechanical units in the area. + EndChargeSound: speech-nod|00-i158.aud + SelectTargetSound: speech-nod|00-i042.aud + Prerequisites: emp.nod + OrderName: Nodemp + AttackOrderPower@gdi: + Cursor: emp + Icon: emp + ChargeTime: 135 + Description: E.M. Pulse + LongDesc: Fires a pulse blast which disables\nall mechanical units in the area. + EndChargeSound: speech-gdi|00-i158.aud + SelectTargetSound: speech-gdi|00-i042.aud + Prerequisites: emp.gdi + OrderName: GDIemp diff --git a/mods/ts/sequences/misc.yaml b/mods/ts/sequences/misc.yaml index 9323b27762..324d14058c 100644 --- a/mods/ts/sequences/misc.yaml +++ b/mods/ts/sequences/misc.yaml @@ -368,6 +368,7 @@ icon: clustermissile: mltiicon ioncannon: ioncicon hunterseeker: detnicon + emp: pulsicon clustermissile: up: null # TODO diff --git a/mods/ts/weapons/superweapons.yaml b/mods/ts/weapons/superweapons.yaml index cd12202d69..cbbbb7edb0 100644 --- a/mods/ts/weapons/superweapons.yaml +++ b/mods/ts/weapons/superweapons.yaml @@ -79,7 +79,7 @@ IonCannon: EMPulseCannon: ReloadDelay: 100 - Range: 10c0 + Range: 40c0 Report: plsecan2.aud Projectile: Bullet Speed: 425 @@ -90,7 +90,7 @@ EMPulseCannon: Warhead@1Eff: CreateEffect Explosions: pulse_explosion Warhead@emp: GrantUpgrade - Range: 3c0 + Range: 4c0 Duration: 250 Upgrades: empdisable, notmobile