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