#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.Collections.Generic; 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 ticks in-between notifications.")] public readonly int NotificationInterval = 750; public object Create(ActorInitializer init) { return new EnemyWatcher(this); } } class EnemyWatcher : ITick { readonly EnemyWatcherInfo info; readonly HashSet discoveredPlayers; bool announcedAny; int rescanInterval; int ticksBeforeNextNotification; HashSet lastKnownActorIds; HashSet visibleActorIds; HashSet playedNotifications; public EnemyWatcher(EnemyWatcherInfo info) { lastKnownActorIds = new HashSet(); discoveredPlayers = new HashSet(); this.info = info; rescanInterval = 0; ticksBeforeNextNotification = 0; } // 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 // 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) return; rescanInterval = info.ScanInterval; announcedAny = false; visibleActorIds = new HashSet(); playedNotifications = new HashSet(); foreach (var actor in self.World.ActorsWithTrait()) { // We don't want notifications for allied actors or actors disguised as such if (actor.Actor.AppearsFriendlyTo(self)) continue; if (actor.Actor.IsDead || !actor.Actor.IsInWorld) continue; // The actor is not currently visible if (!self.Owner.CanViewActor(actor.Actor)) continue; visibleActorIds.Add(actor.Actor.ActorID); // We already know about this actor if (lastKnownActorIds.Contains(actor.Actor.ActorID)) continue; // Should we play a sound notification? var playNotification = !playedNotifications.Contains(actor.Trait.Info.Notification) && ticksBeforeNextNotification <= 0; // Notify the actor that he has been discovered foreach (var trait in actor.Actor.TraitsImplementing()) trait.OnDiscovered(actor.Actor, self.Owner, playNotification); 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); } if (!playNotification) continue; playedNotifications.Add(actor.Trait.Info.Notification); announcedAny = true; } if (announcedAny) ticksBeforeNextNotification = info.NotificationInterval; lastKnownActorIds = visibleActorIds; } } }