diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index b4ca63d1aa..3bb01cbe2c 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -619,6 +619,8 @@
+
+
diff --git a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs
index 1de95bee5c..41faa7cf3f 100644
--- a/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs
+++ b/OpenRA.Mods.Common/Orders/PlaceBuildingOrderGenerator.cs
@@ -24,6 +24,7 @@ namespace OpenRA.Mods.Common.Orders
readonly string building;
readonly BuildingInfo buildingInfo;
readonly PlaceBuildingInfo placeBuildingInfo;
+ readonly BuildingInfluence buildingInfluence;
readonly string race;
readonly Sprite buildOk;
readonly Sprite buildBlocked;
@@ -52,6 +53,8 @@ namespace OpenRA.Mods.Common.Orders
buildOk = map.SequenceProvider.GetSequence("overlay", "build-valid-{0}".F(tileset)).GetSprite(0);
buildBlocked = map.SequenceProvider.GetSequence("overlay", "build-invalid").GetSprite(0);
+
+ buildingInfluence = producer.World.WorldActor.Trait();
}
public IEnumerable Order(World world, CPos xy, MouseInput mi)
@@ -73,16 +76,33 @@ namespace OpenRA.Mods.Common.Orders
if (mi.Button == MouseButton.Left)
{
+ var orderType = "PlaceBuilding";
var topLeft = xy - FootprintUtils.AdjustForBuildingSize(buildingInfo);
- if (!world.CanPlaceBuilding(building, buildingInfo, topLeft, null)
- || !buildingInfo.IsCloseEnoughToBase(world, producer.Owner, building, topLeft))
+
+ var plugInfo = world.Map.Rules.Actors[building].Traits.GetOrDefault();
+ if (plugInfo != null)
{
- Sound.PlayNotification(world.Map.Rules, producer.Owner, "Speech", "BuildingCannotPlaceAudio", producer.Owner.Country.Race);
- yield break;
+ orderType = "PlacePlug";
+ if (!AcceptsPlug(topLeft, plugInfo))
+ {
+ Sound.PlayNotification(world.Map.Rules, producer.Owner, "Speech", "BuildingCannotPlaceAudio", producer.Owner.Country.Race);
+ yield break;
+ }
+ }
+ else
+ {
+ if (!world.CanPlaceBuilding(building, buildingInfo, topLeft, null)
+ || !buildingInfo.IsCloseEnoughToBase(world, producer.Owner, building, topLeft))
+ {
+ Sound.PlayNotification(world.Map.Rules, producer.Owner, "Speech", "BuildingCannotPlaceAudio", producer.Owner.Country.Race);
+ yield break;
+ }
+
+ if (world.Map.Rules.Actors[building].Traits.Contains())
+ orderType = "LineBuild";
}
- var isLineBuild = world.Map.Rules.Actors[building].Traits.Contains();
- yield return new Order(isLineBuild ? "LineBuild" : "PlaceBuilding", producer.Owner.PlayerActor, false)
+ yield return new Order(orderType, producer.Owner.PlayerActor, false)
{
TargetLocation = topLeft,
TargetActor = producer,
@@ -101,6 +121,16 @@ namespace OpenRA.Mods.Common.Orders
p.Tick();
}
+ bool AcceptsPlug(CPos cell, PlugInfo plug)
+ {
+ var host = buildingInfluence.GetBuildingAt(cell);
+ if (host == null)
+ return false;
+
+ var location = host.Location;
+ return host.TraitsImplementing().Any(p => location + p.Info.Offset == cell && p.AcceptsPlug(host, plug.Type));
+ }
+
public IEnumerable Render(WorldRenderer wr, World world) { yield break; }
public IEnumerable RenderAfterWorld(WorldRenderer wr, World world)
{
@@ -116,10 +146,17 @@ namespace OpenRA.Mods.Common.Orders
var cells = new Dictionary();
- // Linebuild for walls.
- // Requires a 1x1 footprint
- if (rules.Actors[building].Traits.Contains())
+ var plugInfo = rules.Actors[building].Traits.GetOrDefault();
+ if (plugInfo != null)
{
+ if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1)
+ throw new InvalidOperationException("Plug requires a 1x1 sized Building");
+
+ cells.Add(topLeft, AcceptsPlug(topLeft, plugInfo));
+ }
+ else if (rules.Actors[building].Traits.Contains())
+ {
+ // Linebuild for walls.
if (buildingInfo.Dimensions.X != 1 || buildingInfo.Dimensions.Y != 1)
throw new InvalidOperationException("LineBuild requires a 1x1 sized Building");
diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs
index 3b3fdd383f..ae9ba6267a 100644
--- a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs
+++ b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs
@@ -18,7 +18,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
- public abstract class AttackBaseInfo : ITraitInfo
+ public abstract class AttackBaseInfo : UpgradableTraitInfo, ITraitInfo
{
[Desc("Armament names")]
public readonly string[] Armaments = { "primary", "secondary" };
@@ -35,11 +35,10 @@ namespace OpenRA.Mods.Common.Traits
public abstract object Create(ActorInitializer init);
}
- public abstract class AttackBase : IIssueOrder, IResolveOrder, IOrderVoice, ISync
+ public abstract class AttackBase : UpgradableTrait, IIssueOrder, IResolveOrder, IOrderVoice, ISync
{
[Sync] public bool IsAttacking { get; internal set; }
public IEnumerable Armaments { get { return getArmaments(); } }
- public readonly AttackBaseInfo Info;
protected Lazy facing;
protected Lazy building;
@@ -49,9 +48,9 @@ namespace OpenRA.Mods.Common.Traits
readonly Actor self;
public AttackBase(Actor self, AttackBaseInfo info)
+ : base(info)
{
this.self = self;
- Info = info;
var armaments = Exts.Lazy(() => self.TraitsImplementing()
.Where(a => info.Armaments.Contains(a.Info.Name)));
@@ -65,7 +64,7 @@ namespace OpenRA.Mods.Common.Traits
protected virtual bool CanAttack(Actor self, Target target)
{
- if (!self.IsInWorld)
+ if (!self.IsInWorld || IsTraitDisabled)
return false;
if (!HasAnyValidWeapons(target))
@@ -101,7 +100,7 @@ namespace OpenRA.Mods.Common.Traits
get
{
var armament = Armaments.FirstOrDefault(a => a.Weapon.Warheads.Any(w => (w is DamageWarhead)));
- if (armament == null)
+ if (armament == null || IsTraitDisabled)
yield break;
var negativeDamage = (armament.Weapon.Warheads.FirstOrDefault(w => (w is DamageWarhead)) as DamageWarhead).Damage < 0;
@@ -149,6 +148,9 @@ namespace OpenRA.Mods.Common.Traits
public bool HasAnyValidWeapons(Target t)
{
+ if (IsTraitDisabled)
+ return false;
+
if (Info.AttackRequiresEnteringCell && !positionable.Value.CanEnterCell(t.Actor.Location, null, false))
return false;
@@ -157,14 +159,19 @@ namespace OpenRA.Mods.Common.Traits
public WRange GetMaximumRange()
{
- return Armaments.Select(a => a.Weapon.Range).Append(WRange.Zero).Max();
+ if (IsTraitDisabled)
+ return WRange.Zero;
+
+ return Armaments.Where(a => !a.IsTraitDisabled)
+ .Select(a => a.Weapon.Range)
+ .Append(WRange.Zero).Max();
}
public Armament ChooseArmamentForTarget(Target t) { return Armaments.FirstOrDefault(a => a.Weapon.IsValidAgainst(t, self.World, self)); }
public void AttackTarget(Target target, bool queued, bool allowMove)
{
- if (self.IsDisabled())
+ if (self.IsDisabled() || IsTraitDisabled)
return;
if (!target.IsValidFor(self))
diff --git a/OpenRA.Mods.Common/Traits/Cloak.cs b/OpenRA.Mods.Common/Traits/Cloak.cs
index efa06c1532..43b71c9fcb 100644
--- a/OpenRA.Mods.Common/Traits/Cloak.cs
+++ b/OpenRA.Mods.Common/Traits/Cloak.cs
@@ -123,7 +123,7 @@ namespace OpenRA.Mods.Common.Traits
if (!Cloaked || self.Owner.IsAlliedWith(viewer))
return true;
- return self.World.ActorsWithTrait().Any(a => a.Actor.Owner.IsAlliedWith(viewer)
+ return self.World.ActorsWithTrait().Any(a => !a.Trait.IsTraitDisabled && a.Actor.Owner.IsAlliedWith(viewer)
&& Info.CloakTypes.Intersect(a.Trait.Info.CloakTypes).Any()
&& (self.CenterPosition - a.Actor.CenterPosition).Length <= WRange.FromCells(a.Trait.Info.Range).Range);
}
diff --git a/OpenRA.Mods.Common/Traits/DetectCloaked.cs b/OpenRA.Mods.Common/Traits/DetectCloaked.cs
index c66d7ab825..cd5fcb1d6f 100644
--- a/OpenRA.Mods.Common/Traits/DetectCloaked.cs
+++ b/OpenRA.Mods.Common/Traits/DetectCloaked.cs
@@ -13,7 +13,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Actor can reveal Cloak actors in a specified range.")]
- public class DetectCloakedInfo : ITraitInfo
+ public class DetectCloakedInfo : UpgradableTraitInfo, ITraitInfo
{
[Desc("Specific cloak classifications I can reveal.")]
public readonly string[] CloakTypes = { "Cloak" };
@@ -24,13 +24,8 @@ namespace OpenRA.Mods.Common.Traits
public object Create(ActorInitializer init) { return new DetectCloaked(this); }
}
- public class DetectCloaked
+ public class DetectCloaked : UpgradableTrait
{
- public readonly DetectCloakedInfo Info;
-
- public DetectCloaked(DetectCloakedInfo info)
- {
- Info = info;
- }
+ public DetectCloaked(DetectCloakedInfo info) : base(info) { }
}
}
diff --git a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs
index 91d118f0de..2c435d9196 100644
--- a/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs
+++ b/OpenRA.Mods.Common/Traits/Player/PlaceBuilding.cs
@@ -26,7 +26,7 @@ namespace OpenRA.Mods.Common.Traits
{
public void ResolveOrder(Actor self, Order order)
{
- if (order.OrderString == "PlaceBuilding" || order.OrderString == "LineBuild")
+ if (order.OrderString == "PlaceBuilding" || order.OrderString == "LineBuild" || order.OrderString == "PlacePlug")
{
self.World.AddFrameEndTask(w =>
{
@@ -69,6 +69,27 @@ namespace OpenRA.Mods.Common.Traits
playSounds = false;
}
}
+ else if (order.OrderString == "PlacePlug")
+ {
+ var host = self.World.WorldActor.Trait().GetBuildingAt(order.TargetLocation);
+ if (host == null)
+ return;
+
+ var plugInfo = unit.Traits.GetOrDefault();
+ if (plugInfo == null)
+ return;
+
+ var location = host.Location;
+ var pluggable = host.TraitsImplementing()
+ .FirstOrDefault(p => location + p.Info.Offset == order.TargetLocation && p.AcceptsPlug(host, plugInfo.Type));
+
+ if (pluggable == null)
+ return;
+
+ pluggable.EnablePlug(host, plugInfo.Type);
+ foreach (var s in buildingInfo.BuildSounds)
+ Sound.PlayToPlayer(order.Player, s, host.CenterPosition);
+ }
else
{
if (!self.World.CanPlaceBuilding(order.TargetString, buildingInfo, order.TargetLocation, null)
diff --git a/OpenRA.Mods.Common/Traits/Plug.cs b/OpenRA.Mods.Common/Traits/Plug.cs
new file mode 100644
index 0000000000..625d8b4389
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Plug.cs
@@ -0,0 +1,24 @@
+#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.Collections.Generic;
+using System.Linq;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ public class PlugInfo : TraitInfo
+ {
+ [Desc("Plug type (matched against Upgrades in Pluggable)")]
+ public readonly string Type = null;
+ }
+
+ public class Plug { }
+}
diff --git a/OpenRA.Mods.Common/Traits/Pluggable.cs b/OpenRA.Mods.Common/Traits/Pluggable.cs
new file mode 100644
index 0000000000..34804b99d3
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Pluggable.cs
@@ -0,0 +1,79 @@
+#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.Collections.Generic;
+using System.Linq;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ public class PluggableInfo : ITraitInfo, Requires
+ {
+ [Desc("Footprint cell offset where a plug can be placed.")]
+ public readonly CVec Offset = CVec.Zero;
+
+ [FieldLoader.LoadUsing("LoadUpgrades")]
+ [Desc("Upgrades to grant for each accepted plug type.")]
+ public readonly Dictionary Upgrades = null;
+
+ static object LoadUpgrades(MiniYaml y)
+ {
+ MiniYaml upgrades;
+
+ if (!y.ToDictionary().TryGetValue("Upgrades", out upgrades))
+ return new Dictionary();
+
+ return upgrades.Nodes.ToDictionary(
+ kv => kv.Key,
+ kv => FieldLoader.GetValue("(value)", kv.Value.Value));
+ }
+
+ public object Create(ActorInitializer init) { return new Pluggable(init.Self, this); }
+ }
+
+ public class Pluggable
+ {
+ public readonly PluggableInfo Info;
+ readonly UpgradeManager upgradeManager;
+ string active;
+
+ public Pluggable(Actor self, PluggableInfo info)
+ {
+ Info = info;
+ upgradeManager = self.Trait();
+ }
+
+ public bool AcceptsPlug(Actor self, string type)
+ {
+ return active == null && Info.Upgrades.ContainsKey(type);
+ }
+
+ public void EnablePlug(Actor self, string type)
+ {
+ string[] upgrades;
+ if (!Info.Upgrades.TryGetValue(type, out upgrades))
+ return;
+
+ foreach (var u in upgrades)
+ upgradeManager.GrantUpgrade(self, u, this);
+
+ active = type;
+ }
+
+ public void DisablePlug(Actor self, string type)
+ {
+ if (type != active)
+ return;
+
+ foreach (var u in Info.Upgrades[type])
+ upgradeManager.RevokeUpgrade(self, u, this);
+ }
+ }
+}
diff --git a/OpenRA.Mods.Common/Traits/Render/RenderDetectionCircle.cs b/OpenRA.Mods.Common/Traits/Render/RenderDetectionCircle.cs
index 5d38a587e9..96ab52071e 100644
--- a/OpenRA.Mods.Common/Traits/Render/RenderDetectionCircle.cs
+++ b/OpenRA.Mods.Common/Traits/Render/RenderDetectionCircle.cs
@@ -10,6 +10,7 @@
using System.Collections.Generic;
using System.Drawing;
+using System.Linq;
using OpenRA.Graphics;
using OpenRA.Mods.Common.Graphics;
using OpenRA.Traits;
@@ -32,9 +33,17 @@ namespace OpenRA.Mods.Common.Traits
if (self.Owner != self.World.LocalPlayer)
yield break;
+ var range = self.TraitsImplementing()
+ .Where(a => !a.IsTraitDisabled)
+ .Select(a => WRange.FromCells(a.Info.Range))
+ .Append(WRange.Zero).Max();
+
+ if (range == WRange.Zero)
+ yield break;
+
yield return new RangeCircleRenderable(
self.CenterPosition,
- WRange.FromCells(self.Info.Traits.Get().Range),
+ range,
0,
Color.FromArgb(128, Color.LimeGreen),
Color.FromArgb(96, Color.Black));
diff --git a/OpenRA.Mods.Common/Traits/Render/RenderRangeCircle.cs b/OpenRA.Mods.Common/Traits/Render/RenderRangeCircle.cs
index 4788c85955..8cbc3d8150 100644
--- a/OpenRA.Mods.Common/Traits/Render/RenderRangeCircle.cs
+++ b/OpenRA.Mods.Common/Traits/Render/RenderRangeCircle.cs
@@ -27,7 +27,8 @@ namespace OpenRA.Mods.Common.Traits
public IEnumerable Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition)
{
- var armaments = ai.Traits.WithInterface();
+ var armaments = ai.Traits.WithInterface()
+ .Where(a => a.UpgradeMinEnabledLevel == 0);
var range = FallbackRange;
if (armaments.Any())
@@ -69,9 +70,13 @@ namespace OpenRA.Mods.Common.Traits
if (self.Owner != self.World.LocalPlayer)
yield break;
+ var range = attack.GetMaximumRange();
+ if (range == WRange.Zero)
+ yield break;
+
yield return new RangeCircleRenderable(
self.CenterPosition,
- attack.GetMaximumRange(),
+ range,
0,
Color.FromArgb(128, Color.Yellow),
Color.FromArgb(96, Color.Black));
diff --git a/OpenRA.Mods.Common/Traits/Render/WithIdleOverlay.cs b/OpenRA.Mods.Common/Traits/Render/WithIdleOverlay.cs
index f428294f70..41d427f999 100644
--- a/OpenRA.Mods.Common/Traits/Render/WithIdleOverlay.cs
+++ b/OpenRA.Mods.Common/Traits/Render/WithIdleOverlay.cs
@@ -17,7 +17,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Renders a decorative animation on units and buildings.")]
- public class WithIdleOverlayInfo : ITraitInfo, IRenderActorPreviewSpritesInfo, Requires, Requires
+ public class WithIdleOverlayInfo : UpgradableTraitInfo, ITraitInfo, IRenderActorPreviewSpritesInfo, Requires, Requires
{
[Desc("Sequence name to use")]
public readonly string Sequence = "idle-overlay";
@@ -37,6 +37,9 @@ namespace OpenRA.Mods.Common.Traits
public IEnumerable RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
{
+ if (UpgradeMinEnabledLevel > 0)
+ yield break;
+
var body = init.Actor.Traits.Get();
var facing = init.Contains() ? init.Get() : 0;
var anim = new Animation(init.World, image, () => facing);
@@ -48,12 +51,13 @@ namespace OpenRA.Mods.Common.Traits
}
}
- public class WithIdleOverlay : INotifyDamageStateChanged, INotifyBuildComplete, INotifySold, INotifyTransform
+ public class WithIdleOverlay : UpgradableTrait, INotifyDamageStateChanged, INotifyBuildComplete, INotifySold, INotifyTransform
{
Animation overlay;
bool buildComplete;
public WithIdleOverlay(Actor self, WithIdleOverlayInfo info)
+ : base(info)
{
var rs = self.Trait();
var body = self.Trait();
@@ -65,7 +69,7 @@ namespace OpenRA.Mods.Common.Traits
rs.Add("idle_overlay_{0}".F(info.Sequence),
new AnimationWithOffset(overlay,
() => body.LocalToWorld(info.Offset.Rotate(body.QuantizeOrientation(self, self.Orientation))),
- () => !buildComplete,
+ () => IsTraitDisabled || !buildComplete,
() => info.PauseOnLowPower && disabled.Any(d => d.Disabled),
p => WithTurret.ZOffsetFromCenter(self, p, 1)),
info.Palette, info.IsPlayerPalette);
diff --git a/OpenRA.Mods.Common/Traits/Render/WithMuzzleFlash.cs b/OpenRA.Mods.Common/Traits/Render/WithMuzzleFlash.cs
index 8e8a4eea81..253739ce95 100644
--- a/OpenRA.Mods.Common/Traits/Render/WithMuzzleFlash.cs
+++ b/OpenRA.Mods.Common/Traits/Render/WithMuzzleFlash.cs
@@ -17,7 +17,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Renders the MuzzleSequence from the Armament trait.")]
- class WithMuzzleFlashInfo : ITraitInfo, Requires, Requires, Requires
+ class WithMuzzleFlashInfo : UpgradableTraitInfo, ITraitInfo, Requires, Requires, Requires
{
[Desc("Ignore the weapon position, and always draw relative to the center of the actor")]
public readonly bool IgnoreOffset = false;
@@ -25,13 +25,14 @@ namespace OpenRA.Mods.Common.Traits
public object Create(ActorInitializer init) { return new WithMuzzleFlash(init.Self, this); }
}
- class WithMuzzleFlash : INotifyAttack, IRender, ITick
+ class WithMuzzleFlash : UpgradableTrait, INotifyAttack, IRender, ITick
{
Dictionary visible = new Dictionary();
Dictionary anims = new Dictionary();
Func getFacing;
public WithMuzzleFlash(Actor self, WithMuzzleFlashInfo info)
+ : base(info)
{
var render = self.Trait();
var facing = self.TraitOrDefault();
@@ -64,7 +65,7 @@ namespace OpenRA.Mods.Common.Traits
anims.Add(barrel,
new AnimationWithOffset(muzzleFlash,
() => info.IgnoreOffset ? WVec.Zero : armClosure.MuzzleOffset(self, barrel),
- () => !visible[barrel],
+ () => IsTraitDisabled || !visible[barrel],
() => false,
p => WithTurret.ZOffsetFromCenter(self, p, 2)));
}
diff --git a/OpenRA.Mods.Common/Traits/Render/WithTurret.cs b/OpenRA.Mods.Common/Traits/Render/WithTurret.cs
index bd1138950a..e13708d5f0 100644
--- a/OpenRA.Mods.Common/Traits/Render/WithTurret.cs
+++ b/OpenRA.Mods.Common/Traits/Render/WithTurret.cs
@@ -17,7 +17,8 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
[Desc("Renders turrets for units with the Turreted trait.")]
- public class WithTurretInfo : ITraitInfo, IRenderActorPreviewSpritesInfo, Requires, Requires, Requires
+ public class WithTurretInfo : UpgradableTraitInfo, ITraitInfo, IRenderActorPreviewSpritesInfo,
+ Requires, Requires, Requires
{
[Desc("Sequence name to use")]
public readonly string Sequence = "turret";
@@ -35,6 +36,9 @@ namespace OpenRA.Mods.Common.Traits
public IEnumerable RenderPreviewSprites(ActorPreviewInitializer init, RenderSpritesInfo rs, string image, int facings, PaletteReference p)
{
+ if (UpgradeMinEnabledLevel > 0)
+ yield break;
+
var body = init.Actor.Traits.Get();
var t = init.Actor.Traits.WithInterface()
.First(tt => tt.Turret == Turret);
@@ -52,9 +56,8 @@ namespace OpenRA.Mods.Common.Traits
}
}
- public class WithTurret : ITick
+ public class WithTurret : UpgradableTrait, ITick
{
- WithTurretInfo info;
RenderSprites rs;
IBodyOrientation body;
AttackBase ab;
@@ -63,8 +66,8 @@ namespace OpenRA.Mods.Common.Traits
Animation anim;
public WithTurret(Actor self, WithTurretInfo info)
+ : base(info)
{
- this.info = info;
rs = self.Trait();
body = self.Trait();
@@ -76,8 +79,8 @@ namespace OpenRA.Mods.Common.Traits
anim = new Animation(self.World, rs.GetImage(self), () => t.TurretFacing);
anim.Play(info.Sequence);
- rs.Add("turret_{0}".F(info.Turret), new AnimationWithOffset(
- anim, () => TurretOffset(self), null, () => false, p => ZOffsetFromCenter(self, p, 1)));
+ rs.Add("turret_{0}_{1}".F(info.Turret, info.Sequence), new AnimationWithOffset(
+ anim, () => TurretOffset(self), () => IsTraitDisabled, () => false, p => ZOffsetFromCenter(self, p, 1)));
// Restrict turret facings to match the sprite
t.QuantizedFacings = anim.CurrentSequence.Facings;
@@ -85,7 +88,7 @@ namespace OpenRA.Mods.Common.Traits
WVec TurretOffset(Actor self)
{
- if (!info.Recoils)
+ if (!Info.Recoils)
return t.Position(self);
var recoil = arms.Aggregate(WRange.Zero, (a, b) => a + b.Recoil);
@@ -97,10 +100,10 @@ namespace OpenRA.Mods.Common.Traits
public void Tick(Actor self)
{
- if (info.AimSequence == null)
+ if (Info.AimSequence == null)
return;
- var sequence = ab.IsAttacking ? info.AimSequence : info.Sequence;
+ var sequence = ab.IsAttacking ? Info.AimSequence : Info.Sequence;
anim.ReplaceAnim(sequence);
}
diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs
index 6edb273fdc..a1fb516512 100644
--- a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs
+++ b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPower.cs
@@ -12,7 +12,7 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
- public abstract class SupportPowerInfo : ITraitInfo
+ public abstract class SupportPowerInfo : UpgradableTraitInfo, ITraitInfo
{
[Desc("Measured in seconds.")]
public readonly int ChargeTime = 0;
@@ -53,15 +53,14 @@ namespace OpenRA.Mods.Common.Traits
public SupportPowerInfo() { OrderName = GetType().Name + "Order"; }
}
- public class SupportPower
+ public class SupportPower : UpgradableTrait
{
public readonly Actor Self;
- public readonly SupportPowerInfo Info;
protected RadarPing ping;
public SupportPower(Actor self, SupportPowerInfo info)
+ : base(info)
{
- Info = info;
Self = self;
}
diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs
index 395833905e..6f49b1c926 100644
--- a/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs
+++ b/OpenRA.Mods.Common/Traits/SupportPowers/SupportPowerManager.cs
@@ -132,7 +132,7 @@ namespace OpenRA.Mods.Common.Traits
if (!Powers.TryGetValue(key, out sp))
return;
- sp.Disabled = false;
+ sp.PrerequisitesAvailable(true);
}
public void PrerequisitesUnavailable(string key)
@@ -141,7 +141,7 @@ namespace OpenRA.Mods.Common.Traits
if (!Powers.TryGetValue(key, out sp))
return;
- sp.Disabled = true;
+ sp.PrerequisitesAvailable(false);
sp.RemainingTime = sp.TotalTime;
}
@@ -158,17 +158,25 @@ namespace OpenRA.Mods.Common.Traits
public int RemainingTime;
public int TotalTime;
public bool Active { get; private set; }
- public bool Disabled { get; set; }
+ public bool Disabled { get { return !prereqsAvailable || !upgradeAvailable; } }
public SupportPowerInfo Info { get { return Instances.Select(i => i.Info).FirstOrDefault(); } }
public bool Ready { get { return Active && RemainingTime == 0; } }
+ bool upgradeAvailable;
+ bool prereqsAvailable = true;
+
public SupportPowerInstance(string key, SupportPowerManager manager)
{
this.manager = manager;
this.key = key;
}
+ public void PrerequisitesAvailable(bool available)
+ {
+ prereqsAvailable = available;
+ }
+
static bool InstanceDisabled(SupportPower sp)
{
return sp.Self.TraitsImplementing().Any(d => d.Disabled);
@@ -178,6 +186,10 @@ namespace OpenRA.Mods.Common.Traits
bool notifiedReady;
public void Tick()
{
+ upgradeAvailable = Instances.Any(i => !i.IsTraitDisabled);
+ if (!upgradeAvailable)
+ RemainingTime = TotalTime;
+
Active = !Disabled && Instances.Any(i => !i.Self.IsDisabled());
if (!Active)
return;
@@ -226,7 +238,7 @@ namespace OpenRA.Mods.Common.Traits
notifiedCharging = notifiedReady = false;
if (Info.OneShot)
- Disabled = true;
+ PrerequisitesAvailable(false);
}
}
diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml
index b344357523..b0e98a2e10 100644
--- a/mods/ts/rules/defaults.yaml
+++ b/mods/ts/rules/defaults.yaml
@@ -141,6 +141,13 @@
CloakDelay: 90
MustBeDestroyed:
+^BuildingPlug:
+ Building:
+ BuildSounds: place2.aud
+ KillsSelf:
+ RemoveInstead: true
+ RenderSprites:
+
^Infantry:
AppearsOnRadar:
Health:
diff --git a/mods/ts/rules/structures.yaml b/mods/ts/rules/structures.yaml
index 1dead3b5a8..bd4c842663 100644
--- a/mods/ts/rules/structures.yaml
+++ b/mods/ts/rules/structures.yaml
@@ -86,6 +86,46 @@ GAPOWR:
TargetTypes: Ground, C4, DetonateAttack, SpyInfiltrate
ScalePowerWithHealth:
DisabledOverlay:
+ Pluggable@pluga:
+ Offset: 0,1
+ Upgrades:
+ powrup: powrup.a
+ Power@pluga:
+ UpgradeTypes: powrup.a
+ UpgradeMinEnabledLevel: 1
+ Amount: 50
+ WithIdleOverlay@pluga:
+ UpgradeTypes: powrup.a
+ UpgradeMinEnabledLevel: 1
+ Sequence: idle-powrupa
+ Pluggable@plugb:
+ Offset: 1,1
+ Upgrades:
+ powrup: powrup.b
+ WithIdleOverlay@plugb:
+ UpgradeTypes: powrup.b
+ UpgradeMinEnabledLevel: 1
+ Sequence: idle-powrupb
+ Power@plugb:
+ UpgradeTypes: powrup.b
+ UpgradeMinEnabledLevel: 1
+ Amount: 50
+
+GAPOWRUP:
+ Inherits: ^BuildingPlug
+ Valued:
+ Cost: 150
+ Tooltip:
+ Name: Power Turbine
+ Description: Provides extra power generation
+ Buildable:
+ Queue: Defense
+ BuildPaletteOrder: 10
+ Prerequisites: gapowr, ~structures.gdi
+ Plug:
+ Type: powrup
+ Power:
+ Amount: 50
GAPILE:
Inherits: ^Building
@@ -430,7 +470,7 @@ GAWALL:
Inherits: ^Wall
Buildable:
Queue: Defense
- BuildPaletteOrder: 1001
+ BuildPaletteOrder: 20
Prerequisites: ~structures.gdi
SoundOnDamageTransition:
DamagedSounds:
@@ -910,14 +950,13 @@ NASTLH:
Selectable:
Bounds: 124, 64, 15, 13
-#TODO: Placeholder, replace with Component Tower + Vulcan Upgrade
-GAVULC:
+GACTWR:
Inherits: ^Building
Valued:
Cost: 600
Tooltip:
- Name: Vulcan Tower
- Description: Basic base defense. \nRequires no power to operate.\n Strong vs infantry and light armor\n Cannot target Aircraft
+ Name: Component Tower
+ Description: Modular tower for base defenses.
Buildable:
Queue: Defense
BuildPaletteOrder: 30
@@ -938,26 +977,60 @@ GAVULC:
RenderRangeCircle:
RenderDetectionCircle:
DetectCloaked:
+ UpgradeTypes: tower
+ UpgradeMinEnabledLevel: 1
Range: 5
AutoTarget:
Turreted:
ROT: 10
InitialFacing: 50
AttackTurreted:
- WithTurret:
+ UpgradeTypes: tower
+ UpgradeMinEnabledLevel: 1
+ CanPowerDown:
+ WithTurret@VULC:
+ UpgradeTypes: tower.vulcan
+ UpgradeMinEnabledLevel: 1
Recoils: no
- Armament@PRIMARY:
+ Sequence: turret-vulcan
+ WithTurret@ROCKET:
+ UpgradeTypes: tower.rocket
+ UpgradeMinEnabledLevel: 1
+ Recoils: no
+ Sequence: turret-rocket
+ WithTurret@SAM:
+ UpgradeTypes: tower.sam
+ UpgradeMinEnabledLevel: 1
+ Recoils: no
+ Sequence: turret-sam
+ Armament@VULCPRIMARY:
+ UpgradeTypes: tower.vulcan
+ UpgradeMinEnabledLevel: 1
Weapon: VulcanTower
LocalOffset: 768,85,512
MuzzleSequence: muzzle
MuzzleSplitFacings: 8
- Armament@SECONDARY:
+ Armament@VULCSECONDARY:
+ UpgradeTypes: tower.vulcan
+ UpgradeMinEnabledLevel: 1
Name: secondary
Weapon: VulcanTower
LocalOffset: 768,-85,512
MuzzleSequence: muzzle
MuzzleSplitFacings: 8
+ Armament@ROCKET:
+ UpgradeTypes: tower.rocket
+ UpgradeMinEnabledLevel: 1
+ Weapon: RPGTower
+ LocalOffset: 512,-128,512
+ Armament@SAM:
+ UpgradeTypes: tower.sam
+ UpgradeMinEnabledLevel: 1
+ Weapon: SAMTower
+ LocalOffset: 512,0,512
WithMuzzleFlash:
+ UpgradeTypes: tower.vulcan
+ UpgradeMinEnabledLevel: 1
WithIdleOverlay@LIGHTS:
Sequence: idle-lights
LineBuildNode:
@@ -965,106 +1038,67 @@ GAVULC:
-RenderBuilding:
RenderBuildingWall:
Type: wall
+ Power@base:
+ Amount: -10
+ Power@turrets:
+ UpgradeTypes: tower
+ UpgradeMinEnabledLevel: 1
+ Amount: -20
+ Power@samextra:
+ UpgradeTypes: tower.sam
+ UpgradeMinEnabledLevel: 1
+ Amount: -10
+ Pluggable:
+ Upgrades:
+ tower.vulcan: tower, tower.vulcan
+ tower.rocket: tower, tower.rocket
+ tower.sam: tower, tower.sam
+
+GAVULC:
+ Inherits: ^BuildingPlug
+ Valued:
+ Cost: 150
+ Tooltip:
+ Name: Vulcan Tower
+ Description: Basic base defense. \nRequires no power to operate.\n Strong vs infantry and light armor\n Cannot target Aircraft
+ Buildable:
+ Queue: Defense
+ BuildPaletteOrder: 40
+ Prerequisites: gactwr, gapile, ~structures.gdi
+ Plug:
+ Type: tower.vulcan
Power:
Amount: -20
-#TODO: Placeholder, replace with Component Tower + RPG Upgrade
GAROCK:
- Inherits: ^Building
+ Inherits: ^BuildingPlug
Valued:
- Cost: 1000
+ Cost: 600
Tooltip:
- Name: RPG Tower
+ Name: RPG Upgrade
Description: GDI Advanced base defense.\nRequires power to operate.\n Strong vs armored ground units\n Cannot target Aircraft
Buildable:
Queue: Defense
BuildPaletteOrder: 40
- Prerequisites: gapile, ~structures.gdi
- Building:
- Selectable:
- Bounds: 48, 48, 0, -12
- RequiresPower:
- DisabledOverlay:
- -GivesBuildableArea:
- Health:
- HP: 500
- Armor:
- Type: Light
- RevealsShroud:
- Range: 6c0
- BodyOrientation:
- QuantizedFacings: 32
- RenderRangeCircle:
- RenderDetectionCircle:
- DetectCloaked:
- Range: 5
- AutoTarget:
- Turreted:
- ROT: 10
- InitialFacing: 50
- AttackTurreted:
- WithTurret:
- Recoils: no
- Armament:
- Weapon: RPGTower
- LocalOffset: 512,-128,512
- WithIdleOverlay@LIGHTS:
- Sequence: idle-lights
- LineBuildNode:
- Types: turret
- -RenderBuilding:
- RenderBuildingWall:
- Type: wall
+ Prerequisites: gactwr, gapile, ~structures.gdi
+ Plug:
+ Type: tower.rocket
Power:
- Amount: -50
+ Amount: -20
-#TODO: Placeholder, replace with Component Tower + SAM Upgrade
GACSAM:
- Inherits: ^Building
+ Inherits: ^BuildingPlug
Valued:
- Cost: 600
+ Cost: 300
Tooltip:
- Name: S.A.M. Tower
+ Name: SAM. Upgrade
Description: GDI Anti-Air base defense. \nRequires power to operate.\n Strong vs all Aircraft\n Cannot target ground units
Buildable:
Queue: Defense
- BuildPaletteOrder: 60
- Prerequisites: garadr, ~structures.gdi
- Building:
- Selectable:
- Bounds: 48, 48, 0, -12
- RequiresPower:
- DisabledOverlay:
- -GivesBuildableArea:
- Health:
- HP: 500
- Armor:
- Type: Light
- RevealsShroud:
- Range: 6c0
- BodyOrientation:
- QuantizedFacings: 32
- RenderRangeCircle:
- RenderDetectionCircle:
- DetectCloaked:
- Range: 5
- AutoTarget:
- Turreted:
- ROT: 10
- InitialFacing: 50
- AttackTurreted:
- WithTurret:
- Recoils: no
- Armament:
- Weapon: SAMTower
- LocalOffset: 512,0,512
- WithIdleOverlay@LIGHTS:
- Sequence: idle-lights
- LineBuildNode:
- Types: turret
- -RenderBuilding:
- RenderBuildingWall:
- Type: wall
+ BuildPaletteOrder: 40
+ Prerequisites: gactwr, garadr, ~structures.gdi
+ Plug:
+ Type: tower.sam
Power:
Amount: -30
@@ -1291,16 +1325,16 @@ NAMISL:
GAPLUG:
Inherits: ^Building
Valued:
- Cost: 1800
+ Cost: 1000
Tooltip:
Name: GDI Upgrade Center
- Description: Provides the Orbital Ion Cannon support power.\nRequires power to operate.
+ Description: Can be upgraded for additional technology
Selectable:
Bounds: 115,120,0,-20
Buildable:
BuildPaletteOrder: 100
Prerequisites: proc, gatech
- Queue: Defense
+ Queue: Building
Building:
Footprint: xxx xxx
Dimensions: 2,3
@@ -1321,6 +1355,8 @@ GAPLUG:
RevealsShroud:
Range: 6c0
IonCannonPower:
+ UpgradeTypes: plug.ioncannon
+ UpgradeMinEnabledLevel: 1
Icon: ioncannon
Effect: ionbeam
ChargeTime: 180
@@ -1336,6 +1372,42 @@ GAPLUG:
SupportPowerChargeBar:
Power:
Amount: -150
+ Power@ioncannon:
+ UpgradeTypes: plug.ioncannon
+ UpgradeMinEnabledLevel: 1
+ Amount: -100
+ Pluggable@pluga:
+ Offset: 0,2
+ Upgrades:
+ plug.ioncannon: plug.ioncannon, plug.ioncannona
+ WithIdleOverlay@ioncannona:
+ UpgradeTypes: plug.ioncannona
+ UpgradeMinEnabledLevel: 1
+ Sequence: idle-ioncannona
+ Pluggable@plugb:
+ Offset: 1,2
+ Upgrades:
+ plug.ioncannon: plug.ioncannon, plug.ioncannonb
+ WithIdleOverlay@ioncannonb:
+ UpgradeTypes: plug.ioncannonb
+ UpgradeMinEnabledLevel: 1
+ Sequence: idle-ioncannonb
+
+GAPLUG3:
+ Inherits: ^BuildingPlug
+ Valued:
+ Cost: 1500
+ Tooltip:
+ Name: Ion Cannon Uplink
+ Description: Enables use of the Ion Cannon
+ Buildable:
+ Queue: Defense
+ BuildPaletteOrder: 1000
+ Prerequisites: gaplug, gatech, ~structures.gdi
+ Plug:
+ Type: plug.ioncannon
+ Power:
+ Amount: -100
ANYPOWER:
Tooltip:
diff --git a/mods/ts/sequences/structures.yaml b/mods/ts/sequences/structures.yaml
index 2ba5c581c6..ce81145fc3 100644
--- a/mods/ts/sequences/structures.yaml
+++ b/mods/ts/sequences/structures.yaml
@@ -67,6 +67,22 @@ gapowr:
damaged-idle-plug: gtpowr_b
Length: 12
Tick: 200
+ idle-powrupa: gtpowr_b
+ Length: 12
+ Tick: 200
+ Offset: -48, -24
+ damaged-idle-powrupa: gtpowr_b
+ Length: 12
+ Tick: 200
+ Offset: -48, -24
+ idle-powrupb: gtpowr_b
+ Length: 12
+ Tick: 200
+ Offset: -24, -12
+ damaged-idle-powrupb: gtpowr_b
+ Length: 12
+ Tick: 200
+ Offset: -24, -12
make: gtpowrmk
Length: 20
ShadowStart: 20
@@ -74,6 +90,9 @@ gapowr:
Offset: 0, 0
UseTilesetCode: false
+gapowrup:
+ icon: turbicon
+
gapile:
Defaults:
Offset: 0, -24
@@ -654,7 +673,7 @@ nastlh:
Offset: 0, 0
UseTilesetCode: false
-gavulc:
+gactwr:
Defaults:
Offset: 0, -12
UseTilesetCode: true
@@ -667,7 +686,21 @@ gavulc:
Start: 2
ShadowStart: 5
Tick: 400
- turret: gtctwr_b
+ idle-lights: gtctwr_a
+ Length: 6
+ Tick: 200
+ damaged-idle-lights: gtctwr_a
+ Length: 6
+ Tick: 200
+ make: gtctwrmk
+ Length: 11
+ ShadowStart: 11
+ turret-vulcan: gtctwr_b
+ Facings: 32
+ turret-rocket: gtctwr_c
+ Facings: 32
+ UseTilesetCode: false
+ turret-sam: gtctwr_d
Facings: 32
muzzle0: mgun-n
Length: *
@@ -693,74 +726,18 @@ gavulc:
muzzle7: mgun-ne
Length: *
UseTilesetCode: false
- idle-lights: gtctwr_a
- Length: 6
- Tick: 200
- damaged-idle-lights: gtctwr_a
- Length: 6
- Tick: 200
- make: gtctwrmk
- Length: 11
- ShadowStart: 11
- icon: twr1icon
+ icon: towricon
Offset: 0, 0
UseTilesetCode: false
+gavulc:
+ icon: twr1icon
+
garock:
- Defaults:
- Offset: 0, -12
- UseTilesetCode: true
- idle: gtctwr
- ShadowStart: 3
- damaged-idle: gtctwr
- Start: 1
- ShadowStart: 4
- dead: gtctwr
- Start: 2
- ShadowStart: 5
- Tick: 400
- turret: gtctwr_c
- Facings: 32
- idle-lights: gtctwr_a
- Length: 6
- Tick: 200
- damaged-idle-lights: gtctwr_a
- Length: 6
- Tick: 200
- make: gtctwrmk
- Length: 11
- ShadowStart: 11
icon: twr2icon
- Offset: 0, 0
- UseTilesetCode: false
gacsam:
- Defaults:
- Offset: 0, -12
- UseTilesetCode: true
- idle: gtctwr
- ShadowStart: 3
- damaged-idle: gtctwr
- Start: 1
- ShadowStart: 4
- dead: gtctwr
- Start: 2
- ShadowStart: 5
- Tick: 400
- turret: gtctwr_d
- Facings: 32
- idle-lights: gtctwr_a
- Length: 6
- Tick: 200
- damaged-idle-lights: gtctwr_a
- Length: 6
- Tick: 200
- make: gtctwrmk
- Length: 11
- ShadowStart: 11
icon: twr3icon
- Offset: 0, 0
- UseTilesetCode: false
gahpad:
Defaults:
@@ -1038,9 +1015,21 @@ gaplug:
Start: 8
Length: 8
Tick: 120
+ idle-ioncannona: gaplug_f
+ Length: 15
+ Tick: 120
+ Reverses: true
+ Offset: -12, -42
+ idle-ioncannonb: gaplug_f
+ Length: 15
+ Reverses: true
+ Tick: 120
make: gtplugmk
Length: 17
ShadowStart: 17
icon: plugicon
Offset: 0, 0
UseTilesetCode: false
+
+gaplug3:
+ icon: rad3icon
\ No newline at end of file