diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index 850b5e92ff..0ed83fa185 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -104,6 +104,7 @@ namespace OpenRA.Traits public interface INotifyEffectiveOwnerChanged { void OnEffectiveOwnerChanged(Actor self, Player oldEffectiveOwner, Player newEffectiveOwner); } public interface INotifyCapture { void OnCapture(Actor self, Actor captor, Player oldOwner, Player newOwner); } public interface INotifyInfiltrated { void Infiltrated(Actor self, Actor infiltrator); } + public interface INotifyDiscovered { void OnDiscovered(Actor self, Player discoverer, bool playNotification); } public interface IDisableMove { bool MoveDisabled(Actor self); } public interface ISeedableResource { void Seed(Actor self); } diff --git a/OpenRA.Mods.Common/Traits/Player/EnemyWatcher.cs b/OpenRA.Mods.Common/Traits/Player/EnemyWatcher.cs index 257995df43..115bfce887 100644 --- a/OpenRA.Mods.Common/Traits/Player/EnemyWatcher.cs +++ b/OpenRA.Mods.Common/Traits/Player/EnemyWatcher.cs @@ -8,9 +8,7 @@ */ #endregion -using System; using System.Collections.Generic; -using System.Drawing; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -25,13 +23,13 @@ namespace OpenRA.Mods.Common.Traits [Desc("Minimal ticks in-between notifications.")] public readonly int NotificationInterval = 750; - public object Create(ActorInitializer init) { return new EnemyWatcher(init.Self, this); } + public object Create(ActorInitializer init) { return new EnemyWatcher(this); } } class EnemyWatcher : ITick { readonly EnemyWatcherInfo info; - readonly Lazy radarPings; + readonly HashSet discoveredPlayers; bool announcedAny; int rescanInterval; @@ -40,15 +38,16 @@ namespace OpenRA.Mods.Common.Traits HashSet visibleActorIds; HashSet playedNotifications; - public EnemyWatcher(Actor self, EnemyWatcherInfo info) + public EnemyWatcher(EnemyWatcherInfo info) { lastKnownActorIds = new HashSet(); + discoveredPlayers = new HashSet(); this.info = info; rescanInterval = info.ScanInterval; ticksBeforeNextNotification = info.NotificationInterval; - radarPings = Exts.Lazy(() => self.World.WorldActor.Trait()); } + // Here self is the player actor public void Tick(Actor self) { // TODO: Make the AI handle such notifications and remove Owner.IsBot from this check @@ -70,9 +69,9 @@ namespace OpenRA.Mods.Common.Traits foreach (var actor in self.World.ActorsWithTrait()) { - // We only care about enemy actors (creeps should be enemies) - if ((actor.Actor.EffectiveOwner != null && self.Owner.Stances[actor.Actor.EffectiveOwner.Owner] != Stance.Enemy) - || self.Owner.Stances[actor.Actor.Owner] != Stance.Enemy) + // We don't want notifications for allied actors + if ((actor.Actor.EffectiveOwner != null && self.Owner.Stances[actor.Actor.EffectiveOwner.Owner] == Stance.Ally) + || self.Owner.Stances[actor.Actor.Owner] == Stance.Ally) continue; if (actor.Actor.IsDead || !actor.Actor.IsInWorld) @@ -88,12 +87,28 @@ namespace OpenRA.Mods.Common.Traits if (lastKnownActorIds.Contains(actor.Actor.ActorID)) continue; + var notificationPlayed = playedNotifications.Contains(actor.Trait.Info.Notification); + + // Notify the actor that he has been discovered + foreach (var trait in actor.Actor.TraitsImplementing()) + trait.OnDiscovered(actor.Actor, self.Owner, !notificationPlayed); + + var discoveredPlayer = actor.Actor.Owner; + if (!discoveredPlayers.Contains(discoveredPlayer)) + { + // Notify the actor's owner that he has been discovered + foreach (var trait in discoveredPlayer.PlayerActor.TraitsImplementing()) + trait.OnDiscovered(actor.Actor, self.Owner, false); + + discoveredPlayers.Add(discoveredPlayer); + } + // We have already played this type of notification - if (playedNotifications.Contains(actor.Trait.Info.Notification)) + if (notificationPlayed) continue; - if (self.Owner == self.World.RenderPlayer) - Announce(self, actor); + playedNotifications.Add(actor.Trait.Info.Notification); + announcedAny = true; } if (announcedAny) @@ -101,18 +116,5 @@ namespace OpenRA.Mods.Common.Traits lastKnownActorIds = visibleActorIds; } - - void Announce(Actor self, TraitPair announce) - { - // Audio notification - Sound.PlayNotification(self.World.Map.Rules, self.Owner, "Speech", announce.Trait.Info.Notification, self.Owner.Country.Race); - - // Radar notificaion - if (announce.Trait.Info.PingRadar && radarPings.Value != null) - radarPings.Value.Add(() => true, announce.Actor.CenterPosition, Color.Red, 50); - - playedNotifications.Add(announce.Trait.Info.Notification); - announcedAny = true; - } } } \ No newline at end of file diff --git a/OpenRA.Mods.Common/Traits/Sound/AnnounceOnSeen.cs b/OpenRA.Mods.Common/Traits/Sound/AnnounceOnSeen.cs index f7277d645a..09eff580ec 100644 --- a/OpenRA.Mods.Common/Traits/Sound/AnnounceOnSeen.cs +++ b/OpenRA.Mods.Common/Traits/Sound/AnnounceOnSeen.cs @@ -8,6 +8,8 @@ */ #endregion +using System; +using System.Drawing; using OpenRA.Traits; namespace OpenRA.Mods.Common.Traits @@ -18,18 +20,43 @@ namespace OpenRA.Mods.Common.Traits [Desc("Should there be a radar ping on enemies' radar at the actor's location when they see him")] public readonly bool PingRadar = false; - public readonly string Notification = "EnemyUnitSighted"; + public readonly string Notification = null; - public object Create(ActorInitializer init) { return new AnnounceOnSeen(this); } + public readonly bool AnnounceNeutrals = false; + + public object Create(ActorInitializer init) { return new AnnounceOnSeen(init.Self, this); } } - public class AnnounceOnSeen + public class AnnounceOnSeen : INotifyDiscovered { public readonly AnnounceOnSeenInfo Info; - public AnnounceOnSeen(AnnounceOnSeenInfo info) + readonly Lazy radarPings; + + public AnnounceOnSeen(Actor self, AnnounceOnSeenInfo info) { Info = info; + radarPings = Exts.Lazy(() => self.World.WorldActor.Trait()); + } + + public void OnDiscovered(Actor self, Player discoverer, bool playNotification) + { + if (!playNotification || discoverer != self.World.RenderPlayer) + return; + + // Hack to disable notifications for neutral actors so some custom maps don't need fixing + if (!Info.AnnounceNeutrals && + ((self.EffectiveOwner != null && discoverer.Stances[self.EffectiveOwner.Owner] != Stance.Enemy) + || discoverer.Stances[self.Owner] != Stance.Enemy)) + return; + + // Audio notification + if (discoverer != null && !string.IsNullOrEmpty(Info.Notification)) + Sound.PlayNotification(self.World.Map.Rules, discoverer, "Speech", Info.Notification, discoverer.Country.Race); + + // Radar notificaion + if (Info.PingRadar && radarPings.Value != null) + radarPings.Value.Add(() => true, self.CenterPosition, Color.Red, 50); } } } \ No newline at end of file diff --git a/mods/ra/rules/aircraft.yaml b/mods/ra/rules/aircraft.yaml index 2128a90ce6..f19312b90f 100644 --- a/mods/ra/rules/aircraft.yaml +++ b/mods/ra/rules/aircraft.yaml @@ -34,6 +34,7 @@ BADR: -EjectOnDeath: -GpsDot: RejectsOrders: + -AnnounceOnSeen: BADR.Bomber: Inherits: ^Plane @@ -350,4 +351,5 @@ U2: Offset: -1c43,0,0 Interval: 2 RejectsOrders: + -AnnounceOnSeen: diff --git a/mods/ra/rules/defaults.yaml b/mods/ra/rules/defaults.yaml index 2508e3a02e..e9a74dfdb5 100644 --- a/mods/ra/rules/defaults.yaml +++ b/mods/ra/rules/defaults.yaml @@ -72,6 +72,8 @@ TimedUpgradeBar: Upgrade: invulnerability MustBeDestroyed: + AnnounceOnSeen: + Notification: EnemyUnitsApproaching ^Tank: AppearsOnRadar: @@ -157,6 +159,8 @@ GroundCorpsePalette: WaterCorpseSequence: WaterCorpsePalette: + AnnounceOnSeen: + Notification: EnemyUnitsApproaching ^Infantry: AppearsOnRadar: @@ -247,6 +251,8 @@ UpgradeMinEnabledLevel: 1 UpgradeManager: MustBeDestroyed: + AnnounceOnSeen: + Notification: EnemyUnitsApproaching ^Ship: AppearsOnRadar: @@ -301,6 +307,8 @@ Upgrade: invulnerability UpgradeMinEnabledLevel: 1 MustBeDestroyed: + AnnounceOnSeen: + Notification: EnemyUnitsApproaching ^Plane: AppearsOnRadar: @@ -358,6 +366,8 @@ Upgrade: invulnerability WithShadow: MustBeDestroyed: + AnnounceOnSeen: + Notification: EnemyUnitsApproaching ^Helicopter: Inherits: ^Plane @@ -366,6 +376,8 @@ GpsDot: String: Helicopter Hovers: + AnnounceOnSeen: + Notification: EnemyUnitsApproaching ^Building: AppearsOnRadar: diff --git a/mods/ra/rules/player.yaml b/mods/ra/rules/player.yaml index 34b56d4772..8ef6285292 100644 --- a/mods/ra/rules/player.yaml +++ b/mods/ra/rules/player.yaml @@ -73,4 +73,5 @@ Player: Name: Unrestricted Prerequisites: techlevel.infonly, techlevel.low, techlevel.medium, techlevel.unrestricted GlobalUpgradeManager: + EnemyWatcher: