diff --git a/OpenRA.Mods.Cnc/Effects/GpsDot.cs b/OpenRA.Mods.Cnc/Effects/GpsDot.cs deleted file mode 100644 index 220eb0c659..0000000000 --- a/OpenRA.Mods.Cnc/Effects/GpsDot.cs +++ /dev/null @@ -1,149 +0,0 @@ -#region Copyright & License Information -/* - * Copyright 2007-2017 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; -using System.Collections.Generic; -using OpenRA.Effects; -using OpenRA.Graphics; -using OpenRA.Mods.Cnc.Traits; -using OpenRA.Mods.Common.Traits; -using OpenRA.Primitives; -using OpenRA.Traits; - -namespace OpenRA.Mods.Cnc.Effects -{ - [Desc("Attach this to actors to render pictograms while hidden.")] - class GpsDotInfo : ITraitInfo - { - [Desc("Sprite collection for symbols.")] - public readonly string Image = "gpsdot"; - - [Desc("Sprite used for this actor.")] - [SequenceReference("Image")] public readonly string String = "Infantry"; - - [PaletteReference(true)] public readonly string IndicatorPalettePrefix = "player"; - - public object Create(ActorInitializer init) - { - return new GpsDot(init.Self, this); - } - } - - class GpsDot : IEffect, IEffectAboveShroud - { - readonly Actor self; - readonly GpsDotInfo info; - readonly Animation anim; - - readonly PlayerDictionary dotStates; - readonly Lazy huf; - readonly Lazy fuf; - readonly Lazy disguise; - readonly Lazy cloak; - readonly Cache frozen; - - class DotState - { - public readonly GpsWatcher Gps; - public bool IsTargetable; - public bool ShouldRender; - public DotState(GpsWatcher gps) - { - Gps = gps; - } - } - - public GpsDot(Actor self, GpsDotInfo info) - { - this.self = self; - this.info = info; - anim = new Animation(self.World, info.Image); - anim.PlayRepeating(info.String); - - self.World.AddFrameEndTask(w => w.Add(this)); - - huf = Exts.Lazy(() => self.TraitOrDefault()); - fuf = Exts.Lazy(() => self.TraitOrDefault()); - disguise = Exts.Lazy(() => self.TraitOrDefault()); - cloak = Exts.Lazy(() => self.TraitOrDefault()); - - frozen = new Cache(p => p.PlayerActor.Trait()); - dotStates = new PlayerDictionary(self.World, player => new DotState(player.PlayerActor.Trait())); - } - - public bool IsDotVisible(Player toPlayer) - { - return dotStates[toPlayer].IsTargetable; - } - - bool IsTargetableBy(Player toPlayer, out bool shouldRenderIndicator) - { - shouldRenderIndicator = false; - - if (cloak.Value != null && cloak.Value.Cloaked) - return false; - - if (disguise.Value != null && disguise.Value.Disguised) - return false; - - if (huf.Value != null && !huf.Value.IsVisible(self, toPlayer) - && toPlayer.Shroud.IsExplored(self.CenterPosition)) - { - var f1 = FrozenActorForPlayer(toPlayer); - shouldRenderIndicator = f1 == null || !f1.HasRenderables; - return true; - } - - if (fuf.Value == null) - return false; - - var f2 = FrozenActorForPlayer(toPlayer); - if (f2 == null) - return false; - - shouldRenderIndicator = !f2.HasRenderables; - - return f2.Visible && !f2.Shrouded && toPlayer.Shroud.IsExplored(self.CenterPosition); - } - - FrozenActor FrozenActorForPlayer(Player player) - { - return frozen[player].FromID(self.ActorID); - } - - void IEffect.Tick(World world) - { - if (self.Disposed) - world.AddFrameEndTask(w => w.Remove(this)); - - for (var playerIndex = 0; playerIndex < dotStates.Count; playerIndex++) - { - var state = dotStates[playerIndex]; - var shouldRender = false; - if (self.IsInWorld && !self.IsDead) - state.IsTargetable = (state.Gps.Granted || state.Gps.GrantedAllies) && IsTargetableBy(world.Players[playerIndex], out shouldRender); - - state.ShouldRender = state.IsTargetable && shouldRender; - } - } - - IEnumerable IEffect.Render(WorldRenderer wr) { return SpriteRenderable.None; } - - IEnumerable IEffectAboveShroud.RenderAboveShroud(WorldRenderer wr) - { - if (self.World.RenderPlayer == null || !dotStates[self.World.RenderPlayer].ShouldRender || self.Disposed) - return SpriteRenderable.None; - - var palette = wr.Palette(info.IndicatorPalettePrefix + self.Owner.InternalName); - return anim.Render(self.CenterPosition, palette); - } - } -} diff --git a/OpenRA.Mods.Cnc/Effects/GpsDotEffect.cs b/OpenRA.Mods.Cnc/Effects/GpsDotEffect.cs new file mode 100644 index 0000000000..a12f7de04b --- /dev/null +++ b/OpenRA.Mods.Cnc/Effects/GpsDotEffect.cs @@ -0,0 +1,112 @@ +#region Copyright & License Information +/* + * Copyright 2007-2017 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.Collections.Generic; +using System.Linq; +using OpenRA.Effects; +using OpenRA.Graphics; +using OpenRA.Mods.Cnc.Traits; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Cnc.Effects +{ + class GpsDotEffect : IEffect, IEffectAboveShroud + { + readonly Actor actor; + readonly GpsDotInfo info; + readonly Animation anim; + + readonly PlayerDictionary dotStates; + readonly IDefaultVisibility visibility; + readonly IVisibilityModifier[] visibilityModifiers; + + class DotState + { + public readonly GpsWatcher Watcher; + public readonly FrozenActor FrozenActor; + public bool Visible; + public DotState(Actor a, GpsWatcher watcher, FrozenActorLayer frozenLayer) + { + Watcher = watcher; + if (frozenLayer != null) + FrozenActor = frozenLayer.FromID(a.ActorID); + } + } + + public GpsDotEffect(Actor actor, GpsDotInfo info) + { + this.actor = actor; + this.info = info; + anim = new Animation(actor.World, info.Image); + anim.PlayRepeating(info.String); + + visibility = actor.Trait(); + visibilityModifiers = actor.TraitsImplementing().ToArray(); + + dotStates = new PlayerDictionary(actor.World, + p => new DotState(actor, p.PlayerActor.Trait(), p.PlayerActor.TraitOrDefault())); + } + + bool ShouldRender(DotState state, Player toPlayer) + { + // Hide the indicator if no watchers are available + if (!state.Watcher.Granted && !state.Watcher.GrantedAllies) + return false; + + // Hide the indicator if a frozen actor portrait is visible + if (state.FrozenActor != null && state.FrozenActor.HasRenderables) + return false; + + // Hide the indicator if the unit appears to be owned by an allied player + if (actor.EffectiveOwner != null && actor.EffectiveOwner.Owner != null && + toPlayer.IsAlliedWith(actor.EffectiveOwner.Owner)) + return false; + + // Hide indicator if the actor wouldn't otherwise be visible if there wasn't fog + foreach (var visibilityModifier in visibilityModifiers) + if (!visibilityModifier.IsVisible(actor, toPlayer)) + return false; + + // Hide the indicator behind shroud + if (!toPlayer.Shroud.IsExplored(actor.CenterPosition)) + return false; + + return !visibility.IsVisible(actor, toPlayer) && toPlayer.Shroud.IsExplored(actor.CenterPosition); + } + + void IEffect.Tick(World world) + { + for (var playerIndex = 0; playerIndex < dotStates.Count; playerIndex++) + { + var state = dotStates[playerIndex]; + state.Visible = ShouldRender(state, world.Players[playerIndex]); + } + } + + IEnumerable IEffect.Render(WorldRenderer wr) + { + return SpriteRenderable.None; + } + + IEnumerable IEffectAboveShroud.RenderAboveShroud(WorldRenderer wr) + { + if (actor.World.RenderPlayer == null || !dotStates[actor.World.RenderPlayer].Visible) + return SpriteRenderable.None; + + var effectiveOwner = actor.EffectiveOwner != null && actor.EffectiveOwner.Owner != null ? + actor.EffectiveOwner.Owner : actor.Owner; + + var palette = wr.Palette(info.IndicatorPalettePrefix + effectiveOwner.InternalName); + return anim.Render(actor.CenterPosition, palette); + } + } +} diff --git a/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj b/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj index ea5ca9273c..7cd81fde78 100644 --- a/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj +++ b/OpenRA.Mods.Cnc/OpenRA.Mods.Cnc.csproj @@ -100,7 +100,7 @@ - + @@ -158,6 +158,7 @@ + diff --git a/OpenRA.Mods.Cnc/Traits/GpsDot.cs b/OpenRA.Mods.Cnc/Traits/GpsDot.cs new file mode 100644 index 0000000000..b203e52afd --- /dev/null +++ b/OpenRA.Mods.Cnc/Traits/GpsDot.cs @@ -0,0 +1,53 @@ +#region Copyright & License Information +/* + * Copyright 2007-2017 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 OpenRA.Graphics; +using OpenRA.Mods.Cnc.Effects; +using OpenRA.Traits; + +namespace OpenRA.Mods.Cnc.Traits +{ + [Desc("Show an indicator revealing the actor underneath the fog when a GPSWatcher is activated.")] + class GpsDotInfo : ITraitInfo + { + [Desc("Sprite collection for symbols.")] + public readonly string Image = "gpsdot"; + + [Desc("Sprite used for this actor.")] + [SequenceReference("Image")] public readonly string String = "Infantry"; + + [PaletteReference(true)] public readonly string IndicatorPalettePrefix = "player"; + + public object Create(ActorInitializer init) { return new GpsDot(this); } + } + + class GpsDot : INotifyAddedToWorld, INotifyRemovedFromWorld + { + readonly GpsDotInfo info; + GpsDotEffect effect; + + public GpsDot(GpsDotInfo info) + { + this.info = info; + } + + void INotifyAddedToWorld.AddedToWorld(Actor self) + { + effect = new GpsDotEffect(self, info); + self.World.AddFrameEndTask(w => w.Add(effect)); + } + + void INotifyRemovedFromWorld.RemovedFromWorld(Actor self) + { + self.World.AddFrameEndTask(w => w.Remove(effect)); + } + } +}