diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index d9412b941e..6a50dc87c3 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -303,11 +303,16 @@ namespace OpenRA.Traits public interface ICrushable { - void OnCrush(Actor crusher); - void WarnCrush(Actor crusher); bool CrushableBy(HashSet crushClasses, Player owner); } + [RequireExplicitImplementation] + public interface INotifyCrushed + { + void OnCrush(Actor self, Actor crusher, HashSet crushClasses); + void WarnCrush(Actor self, Actor crusher, HashSet crushClasses); + } + public interface ITraitInfoInterface { } public interface ITraitInfo : ITraitInfoInterface { object Create(ActorInitializer init); } diff --git a/OpenRA.Mods.Common/Traits/Crates/Crate.cs b/OpenRA.Mods.Common/Traits/Crates/Crate.cs index 220d025014..213d7581de 100644 --- a/OpenRA.Mods.Common/Traits/Crates/Crate.cs +++ b/OpenRA.Mods.Common/Traits/Crates/Crate.cs @@ -38,7 +38,8 @@ namespace OpenRA.Mods.Common.Traits bool IOccupySpaceInfo.SharesCell { get { return false; } } } - class Crate : ITick, IPositionable, ICrushable, ISync, INotifyParachuteLanded, INotifyAddedToWorld, INotifyRemovedFromWorld + class Crate : ITick, IPositionable, ICrushable, ISync, + INotifyParachuteLanded, INotifyAddedToWorld, INotifyRemovedFromWorld, INotifyCrushed { readonly Actor self; readonly CrateInfo info; @@ -56,36 +57,14 @@ namespace OpenRA.Mods.Common.Traits SetPosition(self, init.Get()); } - public void WarnCrush(Actor crusher) { } + void INotifyCrushed.WarnCrush(Actor self, Actor crusher, HashSet crushClasses) { } - public void OnCrush(Actor crusher) + void INotifyCrushed.OnCrush(Actor self, Actor crusher, HashSet crushClasses) { - if (collected) + if (!CrushableBy(crushClasses, crusher.Owner)) return; - var crateActions = self.TraitsImplementing(); - - self.Dispose(); - collected = true; - - if (crateActions.Any()) - { - var shares = crateActions.Select(a => Pair.New(a, a.GetSelectionSharesOuter(crusher))); - - var totalShares = shares.Sum(a => a.Second); - var n = self.World.SharedRandom.Next(totalShares); - - foreach (var s in shares) - { - if (n < s.Second) - { - s.First.Activate(crusher); - return; - } - else - n -= s.Second; - } - } + OnCrushInner(crusher); } public void OnLanded() @@ -110,11 +89,41 @@ namespace OpenRA.Mods.Common.Traits // Destroy the crate if none of the units in the cell are valid collectors if (collector != null) - OnCrush(collector); + OnCrushInner(collector); else self.Dispose(); } + void OnCrushInner(Actor crusher) + { + if (collected) + return; + + var crateActions = self.TraitsImplementing(); + + self.Dispose(); + collected = true; + + if (crateActions.Any()) + { + var shares = crateActions.Select(a => Pair.New(a, a.GetSelectionSharesOuter(crusher))); + + var totalShares = shares.Sum(a => a.Second); + var n = self.World.SharedRandom.Next(totalShares); + + foreach (var s in shares) + { + if (n < s.Second) + { + s.First.Activate(crusher); + return; + } + + n -= s.Second; + } + } + } + public void Tick(Actor self) { if (info.Lifetime != 0 && self.IsInWorld && ++ticks >= info.Lifetime * 25) diff --git a/OpenRA.Mods.Common/Traits/Crushable.cs b/OpenRA.Mods.Common/Traits/Crushable.cs index f429ca82f1..6d4f4e0ee7 100644 --- a/OpenRA.Mods.Common/Traits/Crushable.cs +++ b/OpenRA.Mods.Common/Traits/Crushable.cs @@ -29,7 +29,7 @@ namespace OpenRA.Mods.Common.Traits public object Create(ActorInitializer init) { return new Crushable(init.Self, this); } } - class Crushable : ICrushable + class Crushable : ICrushable, INotifyCrushed { readonly Actor self; readonly CrushableInfo info; @@ -40,16 +40,23 @@ namespace OpenRA.Mods.Common.Traits this.info = info; } - public void WarnCrush(Actor crusher) + void INotifyCrushed.WarnCrush(Actor self, Actor crusher, HashSet crushClasses) { + if (!CrushableBy(crushClasses, crusher.Owner)) + return; + var mobile = self.TraitOrDefault(); if (mobile != null && self.World.SharedRandom.Next(100) <= info.WarnProbability) mobile.Nudge(self, crusher, true); } - public void OnCrush(Actor crusher) + void INotifyCrushed.OnCrush(Actor self, Actor crusher, HashSet crushClasses) { + if (!CrushableBy(crushClasses, crusher.Owner)) + return; + Game.Sound.Play(info.CrushSound, crusher.CenterPosition); + var wda = self.TraitsImplementing() .FirstOrDefault(s => s.Info.CrushedSequence != null); if (wda != null) diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index 46df88d34c..304109a2c2 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -594,10 +594,10 @@ namespace OpenRA.Mods.Common.Traits if (!self.IsAtGroundLevel()) return; - var crushables = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self) - .SelectMany(a => a.TraitsImplementing().Where(b => b.CrushableBy(Info.Crushes, self.Owner))); - foreach (var crushable in crushables) - crushable.WarnCrush(self); + var notifiers = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self) + .SelectMany(a => a.TraitsImplementing().Select(t => new TraitPair(a, t))); + foreach (var notifyCrushed in notifiers) + notifyCrushed.Trait.WarnCrush(notifyCrushed.Actor, self, Info.Crushes); } public void FinishedMoving(Actor self) @@ -606,10 +606,10 @@ namespace OpenRA.Mods.Common.Traits if (!self.IsAtGroundLevel()) return; - var crushables = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self) - .SelectMany(a => a.TraitsImplementing().Where(c => c.CrushableBy(Info.Crushes, self.Owner))); - foreach (var crushable in crushables) - crushable.OnCrush(self); + var notifiers = self.World.ActorMap.GetActorsAt(ToCell).Where(a => a != self) + .SelectMany(a => a.TraitsImplementing().Select(t => new TraitPair(a, t))); + foreach (var notifyCrushed in notifiers) + notifyCrushed.Trait.OnCrush(notifyCrushed.Actor, self, Info.Crushes); } public int MovementSpeedForCell(Actor self, CPos cell) diff --git a/OpenRA.Mods.RA/Traits/Mine.cs b/OpenRA.Mods.RA/Traits/Mine.cs index caed682f1b..a291c4fb1a 100644 --- a/OpenRA.Mods.RA/Traits/Mine.cs +++ b/OpenRA.Mods.RA/Traits/Mine.cs @@ -20,24 +20,25 @@ namespace OpenRA.Mods.RA.Traits public readonly bool AvoidFriendly = true; public readonly HashSet DetonateClasses = new HashSet(); - public object Create(ActorInitializer init) { return new Mine(init, this); } + public object Create(ActorInitializer init) { return new Mine(this); } } - class Mine : ICrushable + class Mine : ICrushable, INotifyCrushed { - readonly Actor self; readonly MineInfo info; - public Mine(ActorInitializer init, MineInfo info) + public Mine(MineInfo info) { - self = init.Self; this.info = info; } - public void WarnCrush(Actor crusher) { } + void INotifyCrushed.WarnCrush(Actor self, Actor crusher, HashSet crushClasses) { } - public void OnCrush(Actor crusher) + void INotifyCrushed.OnCrush(Actor self, Actor crusher, HashSet crushClasses) { + if (!CrushableBy(crushClasses, crusher.Owner)) + return; + if (crusher.Info.HasTraitInfo() || (self.Owner.Stances[crusher.Owner] == Stance.Ally && info.AvoidFriendly)) return;