diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 20bde1192b..933a24b273 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -402,7 +402,6 @@
-
@@ -836,6 +835,7 @@
+
diff --git a/OpenRA.Mods.Common/Orders/GlobalButtonOrderGenerator.cs b/OpenRA.Mods.Common/Orders/GlobalButtonOrderGenerator.cs
index 6b8f79c4a6..1808a103e4 100644
--- a/OpenRA.Mods.Common/Orders/GlobalButtonOrderGenerator.cs
+++ b/OpenRA.Mods.Common/Orders/GlobalButtonOrderGenerator.cs
@@ -33,6 +33,11 @@ namespace OpenRA.Mods.Common.Orders
return OrderInner(world, mi);
}
+ protected virtual bool IsValidTrait(T t)
+ {
+ return Exts.IsTraitEnabled(t);
+ }
+
protected IEnumerable OrderInner(World world, MouseInput mi)
{
if (mi.Button == MouseButton.Left)
@@ -40,7 +45,7 @@ namespace OpenRA.Mods.Common.Orders
var underCursor = world.ScreenMap.ActorsAtMouse(mi)
.Select(a => a.Actor)
.FirstOrDefault(a => a.Owner == world.LocalPlayer && a.TraitsImplementing()
- .Any(Exts.IsTraitEnabled));
+ .Any(IsValidTrait));
if (underCursor == null)
yield break;
@@ -66,10 +71,15 @@ namespace OpenRA.Mods.Common.Orders
public abstract string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi);
}
- public class PowerDownOrderGenerator : GlobalButtonOrderGenerator
+ public class PowerDownOrderGenerator : GlobalButtonOrderGenerator
{
public PowerDownOrderGenerator() : base("PowerDown") { }
+ protected override bool IsValidTrait(ToggleConditionOnOrder t)
+ {
+ return !t.IsTraitDisabled && !t.IsTraitPaused;
+ }
+
public override string GetCursor(World world, CPos cell, int2 worldPixel, MouseInput mi)
{
mi.Button = MouseButton.Left;
diff --git a/OpenRA.Mods.Common/Traits/Conditions/ToggleConditionOnOrder.cs b/OpenRA.Mods.Common/Traits/Conditions/ToggleConditionOnOrder.cs
new file mode 100644
index 0000000000..0805014196
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Conditions/ToggleConditionOnOrder.cs
@@ -0,0 +1,112 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2017 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 OpenRA.Mods.Common.Effects;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits
+{
+ [Desc("Toggles a condition on and off when a specified order type is received.")]
+ public class ToggleConditionOnOrderInfo : PausableConditionalTraitInfo
+ {
+ [FieldLoader.Require]
+ [GrantedConditionReference]
+ [Desc("Condition to grant.")]
+ public readonly string Condition = null;
+
+ [FieldLoader.Require]
+ [Desc("Order name that toggles the condition.")]
+ public readonly string OrderName = null;
+
+ public readonly string EnabledSound = null;
+ public readonly string EnabledSpeech = null;
+
+ public readonly string DisabledSound = null;
+ public readonly string DisabledSpeech = null;
+
+ public override object Create(ActorInitializer init) { return new ToggleConditionOnOrder(init.Self, this); }
+ }
+
+ public class ToggleConditionOnOrder : PausableConditionalTrait, IResolveOrder
+ {
+ ConditionManager conditionManager;
+ int conditionToken = ConditionManager.InvalidConditionToken;
+
+ // If the trait is paused this may be true with no condition granted
+ [Sync] bool enabled = false;
+
+ public ToggleConditionOnOrder(Actor self, ToggleConditionOnOrderInfo info)
+ : base(info) { }
+
+ protected override void Created(Actor self)
+ {
+ base.Created(self);
+
+ conditionManager = self.TraitOrDefault();
+ }
+
+ void SetCondition(Actor self, bool granted)
+ {
+ if (conditionManager == null)
+ return;
+
+ if (granted && conditionToken == ConditionManager.InvalidConditionToken)
+ {
+ conditionToken = conditionManager.GrantCondition(self, Info.Condition);
+
+ if (Info.EnabledSound != null)
+ Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Sounds", Info.EnabledSound, self.Owner.Faction.InternalName);
+
+ if (Info.EnabledSpeech != null)
+ Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", Info.EnabledSpeech, self.Owner.Faction.InternalName);
+ }
+ else if (!granted && conditionToken != ConditionManager.InvalidConditionToken)
+ {
+ conditionToken = conditionManager.RevokeCondition(self, conditionToken);
+
+ if (Info.DisabledSound != null)
+ Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Sounds", Info.DisabledSound, self.Owner.Faction.InternalName);
+
+ if (Info.DisabledSpeech != null)
+ Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", Info.DisabledSpeech, self.Owner.Faction.InternalName);
+ }
+ }
+
+ void IResolveOrder.ResolveOrder(Actor self, Order order)
+ {
+ if (!IsTraitDisabled && !IsTraitPaused && order.OrderString == Info.OrderName)
+ {
+ enabled = !enabled;
+ SetCondition(self, enabled);
+ }
+ }
+
+ protected override void TraitDisabled(Actor self)
+ {
+ // Disabling the trait resets the condition
+ enabled = false;
+ SetCondition(self, false);
+ }
+
+ protected override void TraitPaused(Actor self)
+ {
+ // Pausing the trait removes the condition
+ // but does not reset the enabled value
+ SetCondition(self, false);
+ }
+
+ protected override void TraitResumed(Actor self)
+ {
+ // Unpausing the trait restores the previous state
+ SetCondition(self, enabled);
+ }
+ }
+}
diff --git a/OpenRA.Mods.Common/Traits/Power/CanPowerDown.cs b/OpenRA.Mods.Common/Traits/Power/CanPowerDown.cs
deleted file mode 100644
index 1b52a7de88..0000000000
--- a/OpenRA.Mods.Common/Traits/Power/CanPowerDown.cs
+++ /dev/null
@@ -1,125 +0,0 @@
-#region Copyright & License Information
-/*
- * Copyright 2007-2017 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 OpenRA.Mods.Common.Effects;
-using OpenRA.Traits;
-
-namespace OpenRA.Mods.Common.Traits
-{
- [Desc("The player can disable the power individually on this actor.")]
- public class CanPowerDownInfo : ConditionalTraitInfo, Requires
- {
- [FieldLoader.Require]
- [GrantedConditionReference]
- [Desc("Condition to grant.")]
- public readonly string PowerdownCondition = null;
-
- [Desc("Restore power when this trait is disabled.")]
- public readonly bool CancelWhenDisabled = false;
-
- public readonly string PowerupSound = null;
- public readonly string PowerdownSound = null;
-
- public readonly string PowerupSpeech = null;
- public readonly string PowerdownSpeech = null;
-
- public override object Create(ActorInitializer init) { return new CanPowerDown(init.Self, this); }
- }
-
- public class CanPowerDown : ConditionalTrait, IPowerModifier, IResolveOrder, INotifyOwnerChanged
- {
- [Sync] bool isPoweredDown = false;
- PowerManager power;
-
- ConditionManager conditionManager;
- int conditionToken = ConditionManager.InvalidConditionToken;
-
- public CanPowerDown(Actor self, CanPowerDownInfo info)
- : base(info)
- {
- power = self.Owner.PlayerActor.Trait();
- }
-
- protected override void Created(Actor self)
- {
- base.Created(self);
-
- conditionManager = self.TraitOrDefault();
- }
-
- protected override void TraitEnabled(Actor self)
- {
- Update(self);
- power.UpdateActor(self);
- }
-
- void Update(Actor self)
- {
- if (conditionManager == null)
- return;
-
- if (isPoweredDown && conditionToken == ConditionManager.InvalidConditionToken)
- conditionToken = conditionManager.GrantCondition(self, Info.PowerdownCondition);
- else if (!isPoweredDown && conditionToken != ConditionManager.InvalidConditionToken)
- conditionToken = conditionManager.RevokeCondition(self, conditionToken);
- }
-
- void IResolveOrder.ResolveOrder(Actor self, Order order)
- {
- if (!IsTraitDisabled && order.OrderString == "PowerDown")
- {
- isPoweredDown = !isPoweredDown;
-
- if (Info.PowerupSound != null && isPoweredDown)
- Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Sounds", Info.PowerupSound, self.Owner.Faction.InternalName);
-
- if (Info.PowerdownSound != null && !isPoweredDown)
- Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Sounds", Info.PowerdownSound, self.Owner.Faction.InternalName);
-
- if (Info.PowerupSpeech != null && isPoweredDown)
- Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", Info.PowerupSpeech, self.Owner.Faction.InternalName);
-
- if (Info.PowerdownSpeech != null && !isPoweredDown)
- Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", Info.PowerdownSpeech, self.Owner.Faction.InternalName);
-
- Update(self);
- power.UpdateActor(self);
- }
- }
-
- int IPowerModifier.GetPowerModifier()
- {
- return !IsTraitDisabled && isPoweredDown ? 0 : 100;
- }
-
- void INotifyOwnerChanged.OnOwnerChanged(Actor self, Player oldOwner, Player newOwner)
- {
- power = newOwner.PlayerActor.Trait();
- }
-
- protected override void TraitDisabled(Actor self)
- {
- if (!isPoweredDown || !Info.CancelWhenDisabled)
- return;
-
- isPoweredDown = false;
-
- if (Info.PowerupSound != null)
- Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Sound", Info.PowerupSound, self.Owner.Faction.InternalName);
-
- if (Info.PowerupSpeech != null)
- Game.Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", Info.PowerupSpeech, self.Owner.Faction.InternalName);
-
- Update(self);
- power.UpdateActor(self);
- }
- }
-}
diff --git a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
index a921f98ba5..7e7996a30c 100644
--- a/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
+++ b/OpenRA.Mods.Common/UtilityCommands/UpgradeRules.cs
@@ -25,6 +25,9 @@ namespace OpenRA.Mods.Common.UtilityCommands
static void RenameNodeKey(MiniYamlNode node, string key)
{
+ if (node == null)
+ return;
+
var parts = node.Key.Split('@');
node.Key = key;
if (parts.Length > 1)
@@ -1569,6 +1572,40 @@ namespace OpenRA.Mods.Common.UtilityCommands
}
}
+ // CanPowerDown was replaced with a more general trait for toggling a condition
+ if (engineVersion < 20171225)
+ {
+ var cpd = node.Value.Nodes.FirstOrDefault(n => n.Key.StartsWith("CanPowerDown", StringComparison.Ordinal));
+ if (cpd != null)
+ {
+ RenameNodeKey(cpd, "ToggleConditionOnOrder");
+
+ RenameNodeKey(cpd.Value.Nodes.FirstOrDefault(n => n.Key == "PowerupSound"), "DisabledSound");
+ RenameNodeKey(cpd.Value.Nodes.FirstOrDefault(n => n.Key == "PowerupSpeech"), "DisabledSpeech");
+ RenameNodeKey(cpd.Value.Nodes.FirstOrDefault(n => n.Key == "PowerdownSound"), "EnabledSound");
+ RenameNodeKey(cpd.Value.Nodes.FirstOrDefault(n => n.Key == "PowerdownSpeech"), "EnabledSpeech");
+ cpd.Value.Nodes.Add(new MiniYamlNode("OrderName", "PowerDown"));
+
+ var condition = cpd.Value.Nodes.FirstOrDefault(n => n.Key == "PowerdownCondition");
+ if (condition != null)
+ RenameNodeKey(condition, "Condition");
+ else
+ cpd.Value.Nodes.Add(new MiniYamlNode("Condition", "powerdown"));
+
+ if (cpd.Value.Nodes.RemoveAll(n => n.Key == "CancelWhenDisabled") > 0)
+ {
+ Console.WriteLine("CancelWhenDisabled was removed when CanPowerDown was replaced by ToggleConditionOnOrder");
+ Console.WriteLine("Use PauseOnCondition instead of RequiresCondition to replicate the behavior of 'false'.");
+ }
+
+ node.Value.Nodes.Add(new MiniYamlNode("PowerMultiplier@POWERDOWN", new MiniYaml("", new List()
+ {
+ new MiniYamlNode("RequiresCondition", condition.Value.Value),
+ new MiniYamlNode("Modifier", "0")
+ })));
+ }
+ }
+
UpgradeActorRules(modData, engineVersion, ref node.Value.Nodes, node, depth + 1);
}
diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml
index be2599458b..dfc0a26e14 100644
--- a/mods/d2k/rules/defaults.yaml
+++ b/mods/d2k/rules/defaults.yaml
@@ -474,13 +474,17 @@
GrantCondition@IDISABLE:
RequiresCondition: lowpower || powerdown
Condition: disabled
- CanPowerDown:
- PowerupSound: EnablePower
- PowerdownSound: DisablePower
- PowerdownCondition: powerdown
+ ToggleConditionOnOrder:
+ DisabledSound: EnablePower
+ EnabledSound: DisablePower
+ Condition: powerdown
+ OrderName: PowerDown
WithDecoration@POWERDOWN:
Image: poweroff
Sequence: offline
Palette: chrome
RequiresCondition: powerdown
ReferencePoint: Center
+ PowerMultiplier@POWERDOWN:
+ RequiresCondition: powerdown
+ Modifier: 0
diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml
index 9e8139b6c9..98bc2ac0fa 100644
--- a/mods/ra/rules/defaults.yaml
+++ b/mods/ra/rules/defaults.yaml
@@ -1100,16 +1100,20 @@
GrantCondition@IDISABLE:
RequiresCondition: lowpower || powerdown
Condition: disabled
- CanPowerDown:
- PowerupSound: EnablePower
- PowerdownSound: DisablePower
- PowerdownCondition: powerdown
+ ToggleConditionOnOrder:
+ DisabledSound: EnablePower
+ EnabledSound: DisablePower
+ Condition: powerdown
+ OrderName: PowerDown
WithDecoration@POWERDOWN:
Image: poweroff
Sequence: offline
Palette: chrome
RequiresCondition: powerdown
ReferencePoint: Center
+ PowerMultiplier@POWERDOWN:
+ RequiresCondition: powerdown
+ Modifier: 0
^DisabledByPowerOutage:
WithColoredOverlay@IDISABLE:
diff --git a/mods/ts/rules/defaults.yaml b/mods/ts/rules/defaults.yaml
index db8ca06929..7c23f27114 100644
--- a/mods/ts/rules/defaults.yaml
+++ b/mods/ts/rules/defaults.yaml
@@ -1226,16 +1226,20 @@
GrantCondition@IDISABLE:
RequiresCondition: powerdown
Condition: disabled
- CanPowerDown:
- PowerupSpeech: EnablePower
- PowerdownSpeech: DisablePower
- PowerdownCondition: powerdown
+ ToggleConditionOnOrder:
+ DisabledSpeech: EnablePower
+ EnabledSpeech: DisablePower
+ Condition: powerdown
+ OrderName: PowerDown
WithDecoration@POWERDOWN:
Image: poweroff
Sequence: offline
Palette: mouse
RequiresCondition: powerdown
ReferencePoint: Center
+ PowerMultiplier@POWERDOWN:
+ RequiresCondition: powerdown
+ Modifier: 0
^DisableOnLowPowerOrPowerDown:
Inherits@LOWPOWER: ^DisableOnLowPower