diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
index 42565c99f5..aad4f5aa23 100644
--- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
+++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj
@@ -428,6 +428,7 @@
+
@@ -480,6 +481,7 @@
+
diff --git a/OpenRA.Mods.Common/Traits/AmmoPool.cs b/OpenRA.Mods.Common/Traits/AmmoPool.cs
index 8dc8268375..0e7cfa3c1e 100644
--- a/OpenRA.Mods.Common/Traits/AmmoPool.cs
+++ b/OpenRA.Mods.Common/Traits/AmmoPool.cs
@@ -98,12 +98,14 @@ namespace OpenRA.Mods.Common.Traits
return true;
}
- public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
+ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)
{
if (a != null && a.Info.AmmoPoolName == Info.Name)
TakeAmmo();
}
+ void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
+
public void Tick(Actor self)
{
if (!Info.SelfReloads)
diff --git a/OpenRA.Mods.Common/Traits/Armament.cs b/OpenRA.Mods.Common/Traits/Armament.cs
index 9ca212cef2..2ec8a9a614 100644
--- a/OpenRA.Mods.Common/Traits/Armament.cs
+++ b/OpenRA.Mods.Common/Traits/Armament.cs
@@ -223,6 +223,9 @@ namespace OpenRA.Mods.Common.Traits
GuidedTarget = target
};
+ foreach (var na in self.TraitsImplementing())
+ na.PreparingAttack(self, target, this, barrel);
+
ScheduleDelayedAction(Info.FireDelay, () =>
{
if (args.Weapon.Projectile != null)
diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs
index 924f74f89a..a5dc0457a8 100644
--- a/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs
+++ b/OpenRA.Mods.Common/Traits/Attack/AttackBase.cs
@@ -145,6 +145,9 @@ namespace OpenRA.Mods.Common.Traits
self.SetTargetLine(target, Color.Red);
AttackTarget(target, order.Queued, true, forceAttack);
}
+
+ if (order.OrderString == "Stop")
+ self.CancelActivity();
}
static Target TargetFromOrder(Actor self, Order order)
diff --git a/OpenRA.Mods.Common/Traits/Attack/AttackCharge.cs b/OpenRA.Mods.Common/Traits/Attack/AttackCharge.cs
index 9e51574b96..6c7582ac19 100644
--- a/OpenRA.Mods.Common/Traits/Attack/AttackCharge.cs
+++ b/OpenRA.Mods.Common/Traits/Attack/AttackCharge.cs
@@ -64,25 +64,19 @@ namespace OpenRA.Mods.Common.Traits
return base.CanAttack(self, target);
}
- public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
+ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)
{
--charges;
timeToRecharge = info.ReloadDelay;
}
+ void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
+
public override Activity GetAttackActivity(Actor self, Target newTarget, bool allowMove, bool forceAttack)
{
return new ChargeAttack(this, newTarget);
}
- public override void ResolveOrder(Actor self, Order order)
- {
- base.ResolveOrder(self, order);
-
- if (order.OrderString == "Stop")
- self.CancelActivity();
- }
-
class ChargeAttack : Activity
{
readonly AttackCharge attack;
diff --git a/OpenRA.Mods.Common/Traits/Cloak.cs b/OpenRA.Mods.Common/Traits/Cloak.cs
index 0a92925b8b..086e411076 100644
--- a/OpenRA.Mods.Common/Traits/Cloak.cs
+++ b/OpenRA.Mods.Common/Traits/Cloak.cs
@@ -98,6 +98,8 @@ namespace OpenRA.Mods.Common.Traits
void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel) { if (Info.UncloakOn.HasFlag(UncloakType.Attack)) Uncloak(); }
+ void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
+
void INotifyDamageStateChanged.DamageStateChanged(Actor self, AttackInfo e)
{
damageDisabled = e.DamageState >= DamageState.Critical;
diff --git a/OpenRA.Mods.Common/Traits/Infantry/ScaredyCat.cs b/OpenRA.Mods.Common/Traits/Infantry/ScaredyCat.cs
index 4547d985c2..11907d83ef 100644
--- a/OpenRA.Mods.Common/Traits/Infantry/ScaredyCat.cs
+++ b/OpenRA.Mods.Common/Traits/Infantry/ScaredyCat.cs
@@ -82,12 +82,14 @@ namespace OpenRA.Mods.Common.Traits
Panic();
}
- public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
+ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)
{
if (self.World.SharedRandom.Next(100 / info.AttackPanicChance) == 0)
Panic();
}
+ void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
+
public int GetSpeedModifier()
{
return Panicking ? info.PanicSpeedModifier : 100;
diff --git a/OpenRA.Mods.Common/Traits/Render/WithAttackAnimation.cs b/OpenRA.Mods.Common/Traits/Render/WithAttackAnimation.cs
index 6dd64ab1b2..fec5116581 100644
--- a/OpenRA.Mods.Common/Traits/Render/WithAttackAnimation.cs
+++ b/OpenRA.Mods.Common/Traits/Render/WithAttackAnimation.cs
@@ -28,6 +28,12 @@ namespace OpenRA.Mods.Common.Traits.Render
[Desc("Shown while reloading.")]
[SequenceReference(null, true)] public readonly string ReloadPrefix = null;
+ [Desc("Delay in ticks before animation starts, either relative to attack preparation or attack.")]
+ public readonly int Delay = 0;
+
+ [Desc("Should the animation be delayed relative to preparation or actual attack?")]
+ public readonly AttackDelayType DelayRelativeTo = AttackDelayType.Preparation;
+
public object Create(ActorInitializer init) { return new WithAttackAnimation(init, this); }
}
@@ -38,6 +44,8 @@ namespace OpenRA.Mods.Common.Traits.Render
readonly Armament armament;
readonly WithSpriteBody wsb;
+ int tick;
+
public WithAttackAnimation(ActorInitializer init, WithAttackAnimationInfo info)
{
this.info = info;
@@ -47,14 +55,39 @@ namespace OpenRA.Mods.Common.Traits.Render
wsb = init.Self.Trait();
}
- public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
+ void PlayAttackAnimation(Actor self)
{
if (!string.IsNullOrEmpty(info.AttackSequence))
wsb.PlayCustomAnimation(self, info.AttackSequence, () => wsb.CancelCustomAnimation(self));
}
- public void Tick(Actor self)
+ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)
{
+ if (info.DelayRelativeTo == AttackDelayType.Attack)
+ {
+ if (info.Delay > 0)
+ tick = info.Delay;
+ else
+ PlayAttackAnimation(self);
+ }
+ }
+
+ void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel)
+ {
+ if (info.DelayRelativeTo == AttackDelayType.Preparation)
+ {
+ if (info.Delay > 0)
+ tick = info.Delay;
+ else
+ PlayAttackAnimation(self);
+ }
+ }
+
+ void ITick.Tick(Actor self)
+ {
+ if (info.Delay > 0 && --tick == 0)
+ PlayAttackAnimation(self);
+
if (string.IsNullOrEmpty(info.AimSequence) && string.IsNullOrEmpty(info.ReloadPrefix))
return;
diff --git a/OpenRA.Mods.D2k/Traits/Render/WithAttackOverlay.cs b/OpenRA.Mods.Common/Traits/Render/WithAttackOverlay.cs
similarity index 60%
rename from OpenRA.Mods.D2k/Traits/Render/WithAttackOverlay.cs
rename to OpenRA.Mods.Common/Traits/Render/WithAttackOverlay.cs
index f4004bac5c..80a868ee20 100644
--- a/OpenRA.Mods.D2k/Traits/Render/WithAttackOverlay.cs
+++ b/OpenRA.Mods.Common/Traits/Render/WithAttackOverlay.cs
@@ -10,11 +10,9 @@
#endregion
using OpenRA.Graphics;
-using OpenRA.Mods.Common.Traits;
-using OpenRA.Mods.Common.Traits.Render;
using OpenRA.Traits;
-namespace OpenRA.Mods.D2k.Traits.Render
+namespace OpenRA.Mods.Common.Traits.Render
{
[Desc("Rendered together with an attack.")]
public class WithAttackOverlayInfo : ITraitInfo, Requires
@@ -29,16 +27,23 @@ namespace OpenRA.Mods.D2k.Traits.Render
[Desc("Custom palette is a player palette BaseName")]
public readonly bool IsPlayerPalette = false;
+ [Desc("Delay in ticks before overlay starts, either relative to attack preparation or attack.")]
+ public readonly int Delay = 0;
+
+ [Desc("Should the overlay be delayed relative to preparation or actual attack?")]
+ public readonly AttackDelayType DelayRelativeTo = AttackDelayType.Preparation;
+
public object Create(ActorInitializer init) { return new WithAttackOverlay(init, this); }
}
- public class WithAttackOverlay : INotifyAttack
+ public class WithAttackOverlay : INotifyAttack, ITick
{
readonly Animation overlay;
readonly RenderSprites renderSprites;
readonly WithAttackOverlayInfo info;
bool attacking;
+ int tick;
public WithAttackOverlay(ActorInitializer init, WithAttackOverlayInfo info)
{
@@ -52,10 +57,38 @@ namespace OpenRA.Mods.D2k.Traits.Render
info.Palette, info.IsPlayerPalette);
}
- public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
+ void PlayOverlay(Actor self)
{
attacking = true;
overlay.PlayThen(info.Sequence, () => attacking = false);
}
+
+ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)
+ {
+ if (info.DelayRelativeTo == AttackDelayType.Attack)
+ {
+ if (info.Delay > 0)
+ tick = info.Delay;
+ else
+ PlayOverlay(self);
+ }
+ }
+
+ void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel)
+ {
+ if (info.DelayRelativeTo == AttackDelayType.Preparation)
+ {
+ if (info.Delay > 0)
+ tick = info.Delay;
+ else
+ PlayOverlay(self);
+ }
+ }
+
+ void ITick.Tick(Actor self)
+ {
+ if (info.Delay > 0 && --tick == 0)
+ PlayOverlay(self);
+ }
}
}
\ No newline at end of file
diff --git a/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs b/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs
index dfe0f5125e..4ff82366cb 100644
--- a/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs
+++ b/OpenRA.Mods.Common/Traits/Render/WithInfantryBody.cs
@@ -105,11 +105,13 @@ namespace OpenRA.Mods.Common.Traits.Render
}
}
- public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
+ void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel)
{
Attacking(self, target);
}
+ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel) { }
+
public virtual void Tick(Actor self)
{
if (rsm != null)
diff --git a/OpenRA.Mods.Common/Traits/Render/WithMuzzleOverlay.cs b/OpenRA.Mods.Common/Traits/Render/WithMuzzleOverlay.cs
index d29e5de2a7..e5044a556b 100644
--- a/OpenRA.Mods.Common/Traits/Render/WithMuzzleOverlay.cs
+++ b/OpenRA.Mods.Common/Traits/Render/WithMuzzleOverlay.cs
@@ -75,7 +75,7 @@ namespace OpenRA.Mods.Common.Traits.Render
}
}
- public void Attacking(Actor self, Target target, Armament a, Barrel barrel)
+ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)
{
if (a == null)
return;
@@ -94,6 +94,8 @@ namespace OpenRA.Mods.Common.Traits.Render
anims[barrel].Animation.PlayThen(sequence, () => visible[barrel] = false);
}
+ void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
+
public IEnumerable Render(Actor self, WorldRenderer wr)
{
foreach (var arm in armaments)
diff --git a/OpenRA.Mods.Common/Traits/Sound/AttackSounds.cs b/OpenRA.Mods.Common/Traits/Sound/AttackSounds.cs
new file mode 100644
index 0000000000..9e4e81339c
--- /dev/null
+++ b/OpenRA.Mods.Common/Traits/Sound/AttackSounds.cs
@@ -0,0 +1,80 @@
+#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.Linq;
+using OpenRA.Traits;
+
+namespace OpenRA.Mods.Common.Traits.Sound
+{
+ [Desc("Played when preparing for an attack or attacking.")]
+ public class AttackSoundsInfo : UpgradableTraitInfo
+ {
+ [Desc("Play a randomly selected sound from this list when preparing for an attack or attacking.")]
+ public readonly string[] Sounds = { };
+
+ [Desc("Delay in ticks before sound starts, either relative to attack preparation or attack.")]
+ public readonly int Delay = 0;
+
+ [Desc("Should the sound be delayed relative to preparation or actual attack?")]
+ public readonly AttackDelayType DelayRelativeTo = AttackDelayType.Preparation;
+
+ public override object Create(ActorInitializer init) { return new AttackSounds(init, this); }
+ }
+
+ public class AttackSounds : UpgradableTrait, INotifyAttack, ITick
+ {
+ readonly AttackSoundsInfo info;
+ int tick;
+
+ public AttackSounds(ActorInitializer init, AttackSoundsInfo info)
+ : base(info)
+ {
+ this.info = info;
+ }
+
+ void PlaySound(Actor self)
+ {
+ if (info.Sounds.Any())
+ Game.Sound.Play(info.Sounds.Random(self.World.SharedRandom), self.CenterPosition);
+ }
+
+ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel)
+ {
+ if (info.DelayRelativeTo == AttackDelayType.Attack)
+ {
+ if (info.Delay > 0)
+ tick = info.Delay;
+ else
+ PlaySound(self);
+ }
+ }
+
+ void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel)
+ {
+ if (info.DelayRelativeTo == AttackDelayType.Preparation)
+ {
+ if (info.Delay > 0)
+ tick = info.Delay;
+ else
+ PlaySound(self);
+ }
+ }
+
+ void ITick.Tick(Actor self)
+ {
+ if (IsTraitDisabled)
+ return;
+
+ if (info.Delay > 0 && --tick == 0)
+ PlaySound(self);
+ }
+ }
+}
\ No newline at end of file
diff --git a/OpenRA.Mods.Common/TraitsInterfaces.cs b/OpenRA.Mods.Common/TraitsInterfaces.cs
index c4ef8fe336..3a963ff077 100644
--- a/OpenRA.Mods.Common/TraitsInterfaces.cs
+++ b/OpenRA.Mods.Common/TraitsInterfaces.cs
@@ -19,6 +19,8 @@ using OpenRA.Traits;
namespace OpenRA.Mods.Common.Traits
{
+ public enum AttackDelayType { Preparation, Attack }
+
public interface IQuantizeBodyOrientationInfo : ITraitInfo
{
int QuantizedBodyFacings(ActorInfo ai, SequenceProvider sequenceProvider, string race);
@@ -34,7 +36,13 @@ namespace OpenRA.Mods.Common.Traits
IEnumerable Render(WorldRenderer wr, World w, ActorInfo ai, WPos centerPosition);
}
- public interface INotifyAttack { void Attacking(Actor self, Target target, Armament a, Barrel barrel); }
+ [RequireExplicitImplementation]
+ public interface INotifyAttack
+ {
+ void Attacking(Actor self, Target target, Armament a, Barrel barrel);
+ void PreparingAttack(Actor self, Target target, Armament a, Barrel barrel);
+ }
+
public interface INotifyBurstComplete { void FiredBurst(Actor self, Target target, Armament a); }
public interface INotifyCharging { void Charging(Actor self, Target target); }
public interface INotifyChat { bool OnChat(string from, string message); }
diff --git a/OpenRA.Mods.D2k/Activities/SwallowActor.cs b/OpenRA.Mods.D2k/Activities/SwallowActor.cs
index 242db18501..9e58d7821f 100644
--- a/OpenRA.Mods.D2k/Activities/SwallowActor.cs
+++ b/OpenRA.Mods.D2k/Activities/SwallowActor.cs
@@ -92,7 +92,10 @@ namespace OpenRA.Mods.D2k.Activities
});
foreach (var notify in self.TraitsImplementing())
+ {
+ notify.PreparingAttack(self, target, null, null);
notify.Attacking(self, target, null, null);
+ }
return true;
}
diff --git a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj
index 2fb1091836..1f1107c965 100644
--- a/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj
+++ b/OpenRA.Mods.D2k/OpenRA.Mods.D2k.csproj
@@ -98,7 +98,6 @@
-
diff --git a/OpenRA.Mods.RA/Traits/Disguise.cs b/OpenRA.Mods.RA/Traits/Disguise.cs
index 6ae7e41a7b..1a39180949 100644
--- a/OpenRA.Mods.RA/Traits/Disguise.cs
+++ b/OpenRA.Mods.RA/Traits/Disguise.cs
@@ -197,6 +197,8 @@ namespace OpenRA.Mods.RA.Traits
}
}
- public void Attacking(Actor self, Target target, Armament a, Barrel barrel) { DisguiseAs(null); }
+ void INotifyAttack.PreparingAttack(Actor self, Target target, Armament a, Barrel barrel) { }
+
+ void INotifyAttack.Attacking(Actor self, Target target, Armament a, Barrel barrel) { DisguiseAs(null); }
}
}
\ No newline at end of file