diff --git a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj index f823a8bd3a..e8f263effe 100644 --- a/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj +++ b/OpenRA.Mods.Common/OpenRA.Mods.Common.csproj @@ -307,6 +307,7 @@ + @@ -379,6 +380,7 @@ + diff --git a/OpenRA.Mods.Common/Traits/Player/EnemyWatcher.cs b/OpenRA.Mods.Common/Traits/Player/EnemyWatcher.cs new file mode 100644 index 0000000000..e703837a9e --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Player/EnemyWatcher.cs @@ -0,0 +1,115 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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. For more information, + * see COPYING. + */ +#endregion + +using System; +using System.Collections.Generic; +using System.Drawing; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Tracks neutral and enemy actors' visibility and notifies the player.", + "Attach this to the player actor.")] + class EnemyWatcherInfo : ITraitInfo + { + [Desc("Interval in ticks between scanning for enemies.")] + public readonly int ScanInterval = 25; + + [Desc("Minimal interval in ticks between notifications.")] + public readonly int NotificationInterval = 200; + + public object Create(ActorInitializer init) { return new EnemyWatcher(init.Self, this); } + } + + class EnemyWatcher : ITick + { + readonly EnemyWatcherInfo info; + readonly Lazy radarPings; + + bool announcedAny; + int rescanInterval; + int ticksBeforeNextNotification; + HashSet lastKnownActorIds; + HashSet visibleActorIds; + HashSet playedNotifications; + + public EnemyWatcher(Actor self, EnemyWatcherInfo info) + { + lastKnownActorIds = new HashSet(); + this.info = info; + rescanInterval = info.ScanInterval; + ticksBeforeNextNotification = info.NotificationInterval; + radarPings = Exts.Lazy(() => self.World.WorldActor.Trait()); + } + + public void Tick(Actor self) + { + // TODO: Make the AI handle such notifications and remove Owner.IsBot from this check + // Disable notifications for AI and neutral players (creeps) and for spectators + if (self.Owner.Shroud.Disabled || self.Owner.IsBot || !self.Owner.Playable || self.Owner.PlayerReference.Spectating) + return; + + rescanInterval--; + ticksBeforeNextNotification--; + + if (rescanInterval > 0 || ticksBeforeNextNotification > 0) + return; + + rescanInterval = info.ScanInterval; + + announcedAny = false; + visibleActorIds = new HashSet(); + playedNotifications = new HashSet(); + + 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) + continue; + + // The actor is not currently visible + if (!self.Owner.Shroud.IsVisible(actor.Actor)) + continue; + + visibleActorIds.Add(actor.Actor.ActorID); + + // We already know about this actor + if (lastKnownActorIds.Contains(actor.Actor.ActorID)) + continue; + + // We have already played this type of notification + if (playedNotifications.Contains(actor.Trait.Info.Notification)) + continue; + + Announce(self, actor); + } + + if (announcedAny) + ticksBeforeNextNotification = info.NotificationInterval; + + lastKnownActorIds = visibleActorIds; + } + + void Announce(Actor self, TraitPair announce) + { + // Audio notification + if (self.World.LocalPlayer != null) + Sound.PlayNotification(self.World.Map.Rules, self.World.LocalPlayer, "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 new file mode 100644 index 0000000000..f7277d645a --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Sound/AnnounceOnSeen.cs @@ -0,0 +1,35 @@ +#region Copyright & License Information +/* + * Copyright 2007-2014 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. For more information, + * see COPYING. + */ +#endregion + +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + [Desc("Players will be notified when this actor becomes visible to them.")] + public class AnnounceOnSeenInfo : ITraitInfo + { + [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 object Create(ActorInitializer init) { return new AnnounceOnSeen(this); } + } + + public class AnnounceOnSeen + { + public readonly AnnounceOnSeenInfo Info; + + public AnnounceOnSeen(AnnounceOnSeenInfo info) + { + Info = info; + } + } +} \ No newline at end of file diff --git a/OpenRA.Mods.D2k/Traits/World/WormManager.cs b/OpenRA.Mods.D2k/Traits/World/WormManager.cs index f3a5175656..7f67227259 100644 --- a/OpenRA.Mods.D2k/Traits/World/WormManager.cs +++ b/OpenRA.Mods.D2k/Traits/World/WormManager.cs @@ -43,7 +43,6 @@ namespace OpenRA.Mods.D2k.Traits { readonly WormManagerInfo info; readonly Lazy spawnPointActors; - readonly Lazy radarPings; int spawnCountdown; int wormsPresent; @@ -51,7 +50,6 @@ namespace OpenRA.Mods.D2k.Traits public WormManager(Actor self, WormManagerInfo info) { this.info = info; - radarPings = Exts.Lazy(() => self.World.WorldActor.Trait()); spawnPointActors = Exts.Lazy(() => self.World.ActorsWithTrait().Select(x => x.Actor).ToArray()); } @@ -80,8 +78,6 @@ namespace OpenRA.Mods.D2k.Traits // more we need to reach the defined minimum count. wormLocations.Add(SpawnWorm(self)); } while (wormsPresent < info.Minimum); - - AnnounceWormSign(self, wormLocations); } WPos SpawnWorm(Actor self) @@ -107,17 +103,5 @@ namespace OpenRA.Mods.D2k.Traits { wormsPresent--; } - - void AnnounceWormSign(Actor self, IEnumerable wormLocations) - { - if (self.World.LocalPlayer != null) - Sound.PlayNotification(self.World.Map.Rules, self.World.LocalPlayer, "Speech", info.WormSignNotification, self.World.LocalPlayer.Country.Race); - - if (radarPings.Value == null) - return; - - foreach (var wormLocation in wormLocations) - radarPings.Value.Add(() => true, wormLocation, Color.Red, 50); - } } } diff --git a/mods/d2k/rules/arrakis.yaml b/mods/d2k/rules/arrakis.yaml index cc93955b22..ea6968d4cb 100644 --- a/mods/d2k/rules/arrakis.yaml +++ b/mods/d2k/rules/arrakis.yaml @@ -50,4 +50,7 @@ SANDWORM: Weapon: WormJaw Sandworm: WanderMoveRadius: 5 - IgnoresCloak: \ No newline at end of file + IgnoresCloak: + AnnounceOnSeen: + Notification: WormSign + PingRadar: True \ No newline at end of file diff --git a/mods/d2k/rules/defaults.yaml b/mods/d2k/rules/defaults.yaml index 5facb580e7..f17699fa80 100644 --- a/mods/d2k/rules/defaults.yaml +++ b/mods/d2k/rules/defaults.yaml @@ -50,6 +50,8 @@ UpgradeManager: TemporaryOwnerManager: MustBeDestroyed: + AnnounceOnSeen: + Notification: EnemyUnitsApproaching ^Tank: AppearsOnRadar: @@ -103,6 +105,8 @@ UpgradeManager: TemporaryOwnerManager: MustBeDestroyed: + AnnounceOnSeen: + Notification: EnemyUnitsApproaching ^Husk: Health: @@ -227,6 +231,8 @@ UpgradeMinEnabledLevel: 1 UpgradeManager: MustBeDestroyed: + AnnounceOnSeen: + Notification: EnemyUnitsApproaching ^Plane: AppearsOnRadar: @@ -260,6 +266,8 @@ UpgradeTypes: selfheal UpgradeMinEnabledLevel: 1 UpgradeManager: + AnnounceOnSeen: + Notification: EnemyUnitsApproaching ^Helicopter: Inherits: ^Plane diff --git a/mods/d2k/rules/player.yaml b/mods/d2k/rules/player.yaml index aa08ed5a58..68585dcb96 100644 --- a/mods/d2k/rules/player.yaml +++ b/mods/d2k/rules/player.yaml @@ -72,4 +72,4 @@ Player: ProvidesTechPrerequisite@all: Name: Unrestricted Prerequisites: techlevel.low, techlevel.medium, techlevel.high, techlevel.superweapons - + EnemyWatcher: