diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 5144480a84..3ba740ad9a 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -509,7 +509,7 @@
-
+
diff --git a/OpenRA.Mods.Common/Traits/Sound/AnnounceOnBuild.cs b/OpenRA.Mods.Common/Traits/Sound/AnnounceOnBuild.cs
deleted file mode 100644
index 6866e99866..0000000000
--- a/OpenRA.Mods.Common/Traits/Sound/AnnounceOnBuild.cs
+++ /dev/null
@@ -1,52 +0,0 @@
-#region Copyright & License Information
-/*
- * Copyright 2007-2018 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.Traits;
-
-namespace OpenRA.Mods.Common.Traits.Sound
-{
- [Desc("Play the Build voice of this actor when trained.")]
- public class AnnounceOnBuildInfo : ITraitInfo
- {
- [Desc("Voice to use when built/trained.")]
- [VoiceReference] public readonly string Voice = "Build";
-
- [Desc("Player stances who can hear this voice.")]
- public readonly Stance ValidStances = Stance.Ally | Stance.Neutral | Stance.Enemy;
-
- [Desc("Play the voice to the owning player even if Stance.Ally is not included in ValidStances")]
- public readonly bool PlayToOwner = true;
-
- public object Create(ActorInitializer init) { return new AnnounceOnBuild(init.Self, this); }
- }
-
- public class AnnounceOnBuild : INotifyBuildComplete
- {
- readonly AnnounceOnBuildInfo info;
-
- public AnnounceOnBuild(Actor self, AnnounceOnBuildInfo info)
- {
- this.info = info;
- }
-
- void INotifyBuildComplete.BuildingComplete(Actor self)
- {
- var player = self.World.LocalPlayer ?? self.World.RenderPlayer;
- if (player == null)
- return;
-
- if (info.ValidStances.HasStance(self.Owner.Stances[player]))
- self.PlayVoice(info.Voice);
- else if (info.PlayToOwner && self.Owner == player)
- self.PlayVoice(info.Voice);
- }
- }
-}
diff --git a/OpenRA.Mods.Common/Traits/Sound/VoiceAnnouncement.cs b/OpenRA.Mods.Common/Traits/Sound/VoiceAnnouncement.cs
new file mode 100644
index 0000000000..45a9a9ea8d
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Sound/VoiceAnnouncement.cs
@@ -0,0 +1,61 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2018 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.Traits;
+
+namespace OpenRA.Mods.Common.Traits.Sound
+{
+ [Desc("Plays a voice clip when the trait is enabled.")]
+ public class VoiceAnnouncementInfo : ConditionalTraitInfo
+ {
+ [FieldLoader.Require]
+ [Desc("Voice to play.")]
+ [VoiceReference] public readonly string Voice = null;
+
+ [Desc("Player stances who can hear this voice.")]
+ public readonly Stance ValidStances = Stance.Ally | Stance.Neutral | Stance.Enemy;
+
+ [Desc("Play the voice to the owning player even if Stance.Ally is not included in ValidStances.")]
+ public readonly bool PlayToOwner = true;
+
+ [Desc("Disable the announcement after it has been triggered.")]
+ public readonly bool OneShot = false;
+
+ public override object Create(ActorInitializer init) { return new VoiceAnnouncement(this); }
+ }
+
+ public class VoiceAnnouncement : ConditionalTrait
+ {
+ bool triggered;
+
+ public VoiceAnnouncement(VoiceAnnouncementInfo info)
+ : base(info) { }
+
+ protected override void TraitEnabled(Actor self)
+ {
+ if (IsTraitDisabled)
+ return;
+
+ if (Info.OneShot && triggered)
+ return;
+
+ triggered = true;
+ var player = self.World.LocalPlayer ?? self.World.RenderPlayer;
+ if (player == null)
+ return;
+
+ if (Info.ValidStances.HasStance(self.Owner.Stances[player]))
+ self.PlayVoice(Info.Voice);
+ else if (Info.PlayToOwner && self.Owner == player)
+ self.PlayVoice(Info.Voice);
+ }
+ }
+}
diff --git a/OpenRA.Mods.Common/UpdateRules/Rules/20180923/RemovedNotifyBuildComplete.cs b/OpenRA.Mods.Common/UpdateRules/Rules/20180923/RemovedNotifyBuildComplete.cs
index 179fcf8fef..69c279e5f8 100644
--- a/OpenRA.Mods.Common/UpdateRules/Rules/20180923/RemovedNotifyBuildComplete.cs
+++ b/OpenRA.Mods.Common/UpdateRules/Rules/20180923/RemovedNotifyBuildComplete.cs
@@ -16,13 +16,14 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules
{
public class RemovedNotifyBuildComplete : UpdateRule
{
- public override string Name { get { return "Render traits are no longer automatically disabled during Building make-animations"; } }
+ public override string Name { get { return "Traits are no longer automatically disabled during Building make-animations"; } }
public override string Description
{
get
{
return "Traits are no longer force-disabled while the WithMakeAnimation trait is active.\n" +
"This affects the With*Animation, With*Overlay, Gate, and ConyardChronoReturn traits.\n" +
+ "The AnnounceOnBuild trait has been replaced with a new VoiceAnnouncement trait.\n" +
"Affected actors are listed so that conditions may be manually defined.";
}
}
@@ -52,7 +53,8 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules
"WithCrumbleOverlay",
"WithDeliveryOverlay",
"Gate",
- "ConyardChronoReturn"
+ "ConyardChronoReturn",
+ "VoiceAnnouncement"
};
readonly Dictionary> locations = new Dictionary>();
@@ -70,6 +72,13 @@ namespace OpenRA.Mods.Common.UpdateRules.Rules
public override IEnumerable UpdateActorNode(ModData modData, MiniYamlNode actorNode)
{
+ foreach (var announce in actorNode.ChildrenMatching("AnnounceOnBuild"))
+ {
+ announce.RenameKey("VoiceAnnouncement");
+ if (announce.LastChildMatching("Voice") == null)
+ announce.AddNode("Voice", "Build");
+ }
+
var used = new List();
foreach (var t in Traits)
if (actorNode.LastChildMatching(t) != null)
diff --git a/mods/cnc/rules/infantry.yaml b/mods/cnc/rules/infantry.yaml
index b460f8858f..f770a01b33 100644
--- a/mods/cnc/rules/infantry.yaml
+++ b/mods/cnc/rules/infantry.yaml
@@ -227,7 +227,8 @@ RMBO:
WithInfantryBody:
DefaultAttackSequence: shoot
IdleSequences: idle1,idle2,idle3
- AnnounceOnBuild:
+ VoiceAnnouncement:
+ Voice: Build
AnnounceOnKill:
Voiced:
VoiceSet: CommandoVoice
diff --git a/mods/ra/rules/infantry.yaml b/mods/ra/rules/infantry.yaml
index 80b24eb735..689f36f254 100644
--- a/mods/ra/rules/infantry.yaml
+++ b/mods/ra/rules/infantry.yaml
@@ -371,7 +371,8 @@ E7:
WithInfantryBody:
DefaultAttackSequence: shoot
StandSequences: stand
- AnnounceOnBuild:
+ VoiceAnnouncement:
+ Voice: Build
AnnounceOnKill:
DetectCloaked:
CloakTypes: Cloak, Hijacker