diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index 19a1a42820..6bf6769b07 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -709,6 +709,7 @@ + diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs index 368074de39..49b8ad1d21 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/GrantUpgradePower.cs @@ -46,10 +46,10 @@ namespace OpenRA.Mods.Common.Traits this.info = info; } - public override IOrderGenerator OrderGenerator(string order, SupportPowerManager manager) + public override void SelectTarget(Actor self, string order, SupportPowerManager manager) { Game.Sound.PlayToPlayer(manager.Self.Owner, Info.SelectTargetSound); - return new SelectTarget(Self.World, order, manager, this); + self.World.OrderGenerator = new SelectUpgradeTarget(Self.World, order, manager, this); } public override void Activate(Actor self, Order order, SupportPowerManager manager) @@ -101,7 +101,7 @@ namespace OpenRA.Mods.Common.Traits }); } - class SelectTarget : IOrderGenerator + class SelectUpgradeTarget : IOrderGenerator { readonly GrantUpgradePower power; readonly int range; @@ -109,7 +109,7 @@ namespace OpenRA.Mods.Common.Traits readonly SupportPowerManager manager; readonly string order; - public SelectTarget(World world, string order, SupportPowerManager manager, GrantUpgradePower power) + public SelectUpgradeTarget(World world, string order, SupportPowerManager manager, GrantUpgradePower power) { // Clear selection if using Left-Click Orders if (Game.Settings.Game.UseClassicMouseStyle) diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/ProduceActorPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/ProduceActorPower.cs new file mode 100644 index 0000000000..bbe7737ea9 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/SupportPowers/ProduceActorPower.cs @@ -0,0 +1,75 @@ +#region Copyright & License Information +/* + * Copyright 2007-2015 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; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Produces an actor without using the standard production queue.")] + public class ProduceActorPowerInfo : SupportPowerInfo + { + [ActorReference, FieldLoader.Require] + [Desc("Actors to produce.")] + public readonly string[] Actors = null; + + [FieldLoader.Require] + [Desc("Production queue type to use")] + public readonly string Type = null; + + [Desc("Notification played when production is activated.", + "The filename of the audio is defined per faction in notifications.yaml.")] + public readonly string ReadyAudio = null; + + [Desc("Notification played when the exit is jammed.", + "The filename of the audio is defined per faction in notifications.yaml.")] + public readonly string BlockedAudio = null; + + public override object Create(ActorInitializer init) { return new ProduceActorPower(init, this); } + } + + public class ProduceActorPower : SupportPower + { + readonly string faction; + + public ProduceActorPower(ActorInitializer init, ProduceActorPowerInfo info) + : base(init.Self, info) + { + faction = init.Contains() ? init.Get() : init.Self.Owner.Faction.InternalName; + } + + public override void SelectTarget(Actor self, string order, SupportPowerManager manager) + { + self.World.IssueOrder(new Order(order, manager.Self, false)); + } + + public override void Activate(Actor self, Order order, SupportPowerManager manager) + { + base.Activate(self, order, manager); + + var info = Info as ProduceActorPowerInfo; + var sp = self.TraitsImplementing() + .FirstOrDefault(p => p.Info.Produces.Contains(info.Type)); + + // TODO: The power should not reset if the production fails. + // Fixing this will require a larger rework of the support power code + var activated = false; + + if (sp != null) + foreach (var name in info.Actors) + activated |= sp.Produce(self, self.World.Map.Rules.Actors[name], faction); + + if (activated) + Game.Sound.PlayNotification(self.World.Map.Rules, manager.Self.Owner, "Speech", info.ReadyAudio, self.Owner.Faction.InternalName); + else + Game.Sound.PlayNotification(self.World.Map.Rules, manager.Self.Owner, "Speech", info.BlockedAudio, self.Owner.Faction.InternalName); + } + } +} diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs index d05c1959d8..2fa461bb18 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs @@ -81,6 +81,12 @@ namespace OpenRA.Mods.Common.Traits Game.Sound.PlayToPlayer(self.Owner, Info.EndChargeSound); } + public virtual void SelectTarget(Actor self, string order, SupportPowerManager manager) + { + Game.Sound.PlayToPlayer(manager.Self.Owner, Info.SelectTargetSound); + self.World.OrderGenerator = new SelectGenericPowerTarget(order, manager, info.Cursor, MouseButton.Left); + } + public virtual void Activate(Actor self, Order order, SupportPowerManager manager) { if (Info.DisplayRadarPing && manager.RadarPings != null) @@ -92,11 +98,5 @@ namespace OpenRA.Mods.Common.Traits Info.RadarPingDuration); } } - - public virtual IOrderGenerator OrderGenerator(string order, SupportPowerManager manager) - { - Game.Sound.PlayToPlayer(manager.Self.Owner, Info.SelectTargetSound); - return new SelectGenericPowerTarget(order, manager, info.Cursor, MouseButton.Left); - } } } diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs index 908ae81489..eeb32378f8 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs @@ -222,7 +222,11 @@ namespace OpenRA.Mods.Common.Traits if (!Ready) return; - manager.Self.World.OrderGenerator = Instances.First().OrderGenerator(key, manager); + var power = Instances.FirstOrDefault(); + if (power == null) + return; + + power.SelectTarget(power.Self, key, manager); } public void Activate(Order order) diff --git a/OpenRA.Mods.RA/Traits/SupportPowers/ChronoshiftPower.cs b/OpenRA.Mods.RA/Traits/SupportPowers/ChronoshiftPower.cs index a9ebfd1328..de66933d82 100644 --- a/OpenRA.Mods.RA/Traits/SupportPowers/ChronoshiftPower.cs +++ b/OpenRA.Mods.RA/Traits/SupportPowers/ChronoshiftPower.cs @@ -35,10 +35,10 @@ namespace OpenRA.Mods.RA.Traits { public ChronoshiftPower(Actor self, ChronoshiftPowerInfo info) : base(self, info) { } - public override IOrderGenerator OrderGenerator(string order, SupportPowerManager manager) + public override void SelectTarget(Actor self, string order, SupportPowerManager manager) { Game.Sound.PlayToPlayer(manager.Self.Owner, Info.SelectTargetSound); - return new SelectTarget(Self.World, order, manager, this); + self.World.OrderGenerator = new SelectChronoshiftTarget(Self.World, order, manager, this); } public override void Activate(Actor self, Order order, SupportPowerManager manager) @@ -94,7 +94,7 @@ namespace OpenRA.Mods.RA.Traits return true; } - class SelectTarget : IOrderGenerator + class SelectChronoshiftTarget : IOrderGenerator { readonly ChronoshiftPower power; readonly int range; @@ -102,7 +102,7 @@ namespace OpenRA.Mods.RA.Traits readonly SupportPowerManager manager; readonly string order; - public SelectTarget(World world, string order, SupportPowerManager manager, ChronoshiftPower power) + public SelectChronoshiftTarget(World world, string order, SupportPowerManager manager, ChronoshiftPower power) { // Clear selection if using Left-Click Orders if (Game.Settings.Game.UseClassicMouseStyle) diff --git a/mods/d2k/rules/infantry.yaml b/mods/d2k/rules/infantry.yaml index 3f8812bc8f..a8fa0c52b1 100644 --- a/mods/d2k/rules/infantry.yaml +++ b/mods/d2k/rules/infantry.yaml @@ -113,7 +113,7 @@ fremen: Buildable: Queue: Infantry BuildPaletteOrder: 100 - Prerequisites: ~barracks.atreides, palace, ~techlevel.high + Prerequisites: ~disabled Mobile: Speed: 43 Health: @@ -207,7 +207,7 @@ saboteur: Buildable: Queue: Infantry BuildPaletteOrder: 100 - Prerequisites: ~barracks.ordos, palace, ~techlevel.high + Prerequisites: ~disabled Valued: Cost: 800 Tooltip: diff --git a/mods/d2k/rules/structures.yaml b/mods/d2k/rules/structures.yaml index 34a3020956..5fc064a4be 100644 --- a/mods/d2k/rules/structures.yaml +++ b/mods/d2k/rules/structures.yaml @@ -782,6 +782,12 @@ palace: ProvidesPrerequisite@nuke: Prerequisite: palace.nuke Factions: harkonnen + ProvidesPrerequisite@fremen: + Prerequisite: palace.fremen + Factions: atreides + ProvidesPrerequisite@saboteur: + Prerequisite: palace.saboteur + Factions: ordos NukePower: Cursor: nuke Icon: deathhand @@ -800,6 +806,39 @@ palace: DisplayRadarPing: True CameraActor: camera ActivationSequence: + ProduceActorPower@fremen: + Description: Recruit Fremen + LongDesc: Elite sniper infantry unit \n Strong vs Infantry\n Weak vs Vehicles\n Special Ability: Invisibility + Icon: fremen + Prerequisites: ~techlevel.superweapons, ~palace.fremen + Actors: fremen, fremen + Type: Palace + ChargeTime: 60 + ReadyAudio: Reinforce + BlockedAudio: NoRoom + OrderName: ProduceActorPower.Fremen + ProduceActorPower@saboteur: + Description: Recruit Saboteur + LongDesc: Sneaky infantry, armed with explosives \n Strong vs Buildings\n Weak vs Everything\n Special Ability: destroy buildings + Icon: saboteur + Prerequisites: ~techlevel.superweapons, ~palace.saboteur + Actors: saboteur + Type: Palace + ChargeTime: 40 + ReadyAudio: Reinforce + BlockedAudio: NoRoom + OrderName: ProduceActorPower.Saboteur + Exit@1: + SpawnOffset: -704,768,0 + ExitCell: -1,2 + Exit@2: + SpawnOffset: -704,768,0 + ExitCell: -1,3 + Exit@3: + SpawnOffset: -704,768,0 + ExitCell: 0,3 + Production: + Produces: Palace CanPowerDown: DisabledOverlay: RequiresPower: diff --git a/mods/d2k/sequences/misc.yaml b/mods/d2k/sequences/misc.yaml index bb02238c20..8bbdd66857 100644 --- a/mods/d2k/sequences/misc.yaml +++ b/mods/d2k/sequences/misc.yaml @@ -324,6 +324,12 @@ icon: ornistrike: DATA.R8 Start: 4031 Offset: -30,-24 + fremen: DATA.R8 + Start: 4032 + Offset: -30,-24 + saboteur: DATA.R8 + Start: 4034 + Offset: -30,-24 deathhand: DATA.R8 Start: 4035 Offset: -30,-24 diff --git a/mods/ts/rules/aircraft.yaml b/mods/ts/rules/aircraft.yaml index d32dd5380c..bfe1196264 100644 --- a/mods/ts/rules/aircraft.yaml +++ b/mods/ts/rules/aircraft.yaml @@ -287,3 +287,44 @@ APACHE: RenderSprites: Hovers: +HUNTER: + Inherits@1: ^GainsExperience + Inherits@2: ^ExistsInWorld + Valued: + Cost: 1000 + Tooltip: + Name: Hunter-Seeker Droid + Health: + HP: 500 + Armor: + Type: Light + DemoTruck: + Explodes: + Weapon: SuicideBomb + EmptyWeapon: SuicideBomb + Aircraft: + RearmBuildings: + RepairBuildings: + ROT: 16 + Speed: 355 + CruiseAltitude: 256 + CanHover: True + Targetable: + TargetTypes: Ground, Vehicle + HiddenUnderFog: + Type: CenterPosition + BodyOrientation: + UseClassicPerspectiveFudge: False + RenderSprites: + Image: GGHUNT + WithFacingSpriteBody: + Hovers: + QuantizeFacingsFromSequence: + AutoSelectionSize: + DrawLineToTarget: + AppearsOnRadar: + UseLocation: yes + Selectable: + SelectionDecorations: + Palette: pips + ActorLostNotification: diff --git a/mods/ts/rules/gdi-structures.yaml b/mods/ts/rules/gdi-structures.yaml index 879a3964c5..c559a543e3 100644 --- a/mods/ts/rules/gdi-structures.yaml +++ b/mods/ts/rules/gdi-structures.yaml @@ -375,6 +375,18 @@ GAPLUG: InsufficientPowerSound: DisplayRadarPing: True CameraActor: camera + ProduceActorPower: + UpgradeTypes: plug.hunterseeker + UpgradeMinEnabledLevel: 1 + Description: Hunter Seeker + LongDesc: Releases a drone that will acquire and destroy an enemy target. + Icon: hunterseeker + Actors: hunter + Type: HunterSeeker + ChargeTime: 720 + Production: + Produces: HunterSeeker + Exit@1: SupportPowerChargeBar: Power: Amount: -150 @@ -382,22 +394,36 @@ GAPLUG: UpgradeTypes: plug.ioncannon UpgradeMinEnabledLevel: 1 Amount: -100 + Power@hunterseeker: + UpgradeTypes: plug.hunterseeker + UpgradeMinEnabledLevel: 1 + Amount: -50 Pluggable@pluga: Offset: 0,2 Upgrades: plug.ioncannon: plug.ioncannon, plug.ioncannona + plug.hunterseeker: plug.hunterseeker, plug.hunterseekera WithIdleOverlay@ioncannona: UpgradeTypes: plug.ioncannona UpgradeMinEnabledLevel: 1 Sequence: idle-ioncannona + WithIdleOverlay@hunterseekera: + UpgradeTypes: plug.hunterseekera + UpgradeMinEnabledLevel: 1 + Sequence: idle-hunterseekera Pluggable@plugb: Offset: 1,2 Upgrades: plug.ioncannon: plug.ioncannon, plug.ioncannonb + plug.hunterseeker: plug.hunterseeker, plug.hunterseekerb WithIdleOverlay@ioncannonb: UpgradeTypes: plug.ioncannonb UpgradeMinEnabledLevel: 1 Sequence: idle-ioncannonb + WithIdleOverlay@hunterseekerb: + UpgradeTypes: plug.hunterseekerb + UpgradeMinEnabledLevel: 1 + Sequence: idle-hunterseekerb ProvidesPrerequisite@buildingname: SelectionDecorations: VisualBounds: 115,104,0,-24 diff --git a/mods/ts/rules/gdi-support.yaml b/mods/ts/rules/gdi-support.yaml index 5d1f9b1fe0..9bebf43ebc 100644 --- a/mods/ts/rules/gdi-support.yaml +++ b/mods/ts/rules/gdi-support.yaml @@ -189,6 +189,22 @@ GAPOWRUP: Power: Amount: 50 +GAPLUG2: + Inherits: ^BuildingPlug + Valued: + Cost: 1000 + Tooltip: + Name: Seeker Control + Description: Enables use of the hunter-seeker droid. + Buildable: + Queue: Defense + BuildPaletteOrder: 1000 + Prerequisites: gaplug, gatech, gaweap, ~structures.gdi + Plug: + Type: plug.hunterseeker + Power: + Amount: -50 + GAPLUG3: Inherits: ^BuildingPlug Valued: diff --git a/mods/ts/rules/nod-structures.yaml b/mods/ts/rules/nod-structures.yaml index 7437004358..5d23c5cdba 100644 --- a/mods/ts/rules/nod-structures.yaml +++ b/mods/ts/rules/nod-structures.yaml @@ -311,6 +311,16 @@ NATMPL: Amount: -200 WithIdleOverlay@LIGHTS: Sequence: idle-lights + ProduceActorPower: + Description: Hunter Seeker + LongDesc: Releases a drone that will acquire and destroy an enemy target. + Icon: hunterseeker + Actors: hunter + Type: HunterSeeker + ChargeTime: 720 + Production: + Produces: HunterSeeker + Exit@1: NASTLH: Inherits: ^Building diff --git a/mods/ts/rules/shared-vehicles.yaml b/mods/ts/rules/shared-vehicles.yaml index 01256b59f0..5b9d61178a 100644 --- a/mods/ts/rules/shared-vehicles.yaml +++ b/mods/ts/rules/shared-vehicles.yaml @@ -148,27 +148,3 @@ LPST: UpgradeMinEnabledLevel: 1 Range: 18 RenderDetectionCircle: - -GGHUNT: - Inherits: ^Vehicle - Valued: - Cost: 1000 - Tooltip: - Name: Hunter-Seeker Droid - Mobile: - ROT: 16 - Speed: 355 - Health: - HP: 500 - Armor: - Type: Light - RevealsShroud: - Range: 7c0 - WithFacingSpriteBody: - DemoTruck: - Voice: Attack - Explodes: - Weapon: SuicideBomb - EmptyWeapon: SuicideBomb - AutoSelectionSize: - diff --git a/mods/ts/sequences/misc.yaml b/mods/ts/sequences/misc.yaml index 503d966cbe..b9877ce5c7 100644 --- a/mods/ts/sequences/misc.yaml +++ b/mods/ts/sequences/misc.yaml @@ -334,6 +334,7 @@ largecraters: icon: clustermissile: mltiicon ioncannon: ioncicon + hunterseeker: detnicon clustermissile: up: null # TODO diff --git a/mods/ts/sequences/structures.yaml b/mods/ts/sequences/structures.yaml index 7f9e449028..4f6af930dd 100644 --- a/mods/ts/sequences/structures.yaml +++ b/mods/ts/sequences/structures.yaml @@ -1234,6 +1234,15 @@ gaplug: Length: 15 Reverses: true Tick: 120 + idle-hunterseekera: gaplug_e + Length: 15 + Tick: 120 + Reverses: true + Offset: -12, -42 + idle-hunterseekerb: gaplug_e + Length: 15 + Reverses: true + Tick: 120 make: gtplugmk Length: 17 ShadowStart: 17 @@ -1241,5 +1250,8 @@ gaplug: Offset: 0, 0 UseTilesetCode: false +gaplug2: + icon: rad2icon + gaplug3: icon: rad3icon \ No newline at end of file