diff --git a/OpenRA.Game/OpenRA.Game.csproj b/OpenRA.Game/OpenRA.Game.csproj
index e3b4c1c2d7..e44917e4c5 100644
--- a/OpenRA.Game/OpenRA.Game.csproj
+++ b/OpenRA.Game/OpenRA.Game.csproj
@@ -234,6 +234,7 @@
+
diff --git a/OpenRA.Game/Traits/DrawLineToTarget.cs b/OpenRA.Game/Traits/DrawLineToTarget.cs
index bf8baabfb4..319bf94fce 100644
--- a/OpenRA.Game/Traits/DrawLineToTarget.cs
+++ b/OpenRA.Game/Traits/DrawLineToTarget.cs
@@ -120,6 +120,24 @@ namespace OpenRA.Traits
line.SetTarget(self, target, color, display);
});
}
+
+ public static void SetTargetLine(this Actor self, FrozenActor target, Color color, bool display)
+ {
+ if (self.Owner != self.World.LocalPlayer)
+ return;
+
+ self.World.AddFrameEndTask(w =>
+ {
+ if (self.Destroyed)
+ return;
+
+ target.Flash();
+
+ var line = self.TraitOrDefault();
+ if (line != null)
+ line.SetTarget(self, Target.FromPos(target.CenterPosition), color, display);
+ });
+ }
}
}
diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs
new file mode 100755
index 0000000000..850d69d865
--- /dev/null
+++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs
@@ -0,0 +1,173 @@
+#region Copyright & License Information
+/*
+ * Copyright 2007-2013 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 System.Linq;
+using OpenRA.FileFormats;
+using OpenRA.Graphics;
+using OpenRA.Traits;
+
+namespace OpenRA.Traits
+{
+ public class FrozenActorLayerInfo : ITraitInfo
+ {
+ [Desc("Size of partition bins (screen pixels)")]
+ public readonly int BinSize = 250;
+
+ public object Create(ActorInitializer init) { return new FrozenActorLayer(init.world, this); }
+ }
+
+ public class FrozenActor
+ {
+ public readonly IEnumerable Footprint;
+ public readonly WPos CenterPosition;
+ public readonly Rectangle Bounds;
+ readonly Actor actor;
+
+ public IRenderable[] Renderables { set; private get; }
+ public Player Owner;
+
+ public string TooltipName;
+ public Player TooltipOwner;
+
+ public int HP;
+ public DamageState DamageState;
+
+ public bool Visible;
+
+ public FrozenActor(Actor self, IEnumerable footprint)
+ {
+ actor = self;
+ Footprint = footprint;
+ CenterPosition = self.CenterPosition;
+ Bounds = self.Bounds.Value;
+ }
+
+ public uint ID { get { return actor.ActorID; } }
+ public bool IsValid { get { return Owner != null; } }
+ public ActorInfo Info { get { return actor.Info; } }
+ public Actor Actor { get { return !actor.IsDead() ? actor : null; } }
+
+ int flashTicks;
+ public void Tick(World world, Shroud shroud)
+ {
+ Visible = !Footprint.Any(c => shroud.IsVisible(c));
+
+ if (flashTicks > 0)
+ flashTicks--;
+ }
+
+ public void Flash()
+ {
+ flashTicks = 5;
+ }
+
+ public IEnumerable Render(WorldRenderer wr)
+ {
+ if (Renderables == null)
+ return SpriteRenderable.None;
+
+ if (flashTicks > 0 && flashTicks % 2 == 0)
+ {
+ var highlight = wr.Palette("highlight");
+ return Renderables.Concat(Renderables.Where(r => !r.IsDecoration)
+ .Select(r => r.WithPalette(highlight)));
+ }
+ return Renderables;
+ }
+ }
+
+ public class FrozenActorLayer : IRender, ITick, ISync
+ {
+ [Sync] public int VisibilityHash;
+ [Sync] public int FrozenHash;
+
+ readonly FrozenActorLayerInfo info;
+ Dictionary frozen;
+ List[,] bins;
+
+ public FrozenActorLayer(World world, FrozenActorLayerInfo info)
+ {
+ this.info = info;
+ frozen = new Dictionary();
+ bins = new List[
+ world.Map.MapSize.X * Game.CellSize / info.BinSize,
+ world.Map.MapSize.Y * Game.CellSize / info.BinSize];
+
+ for (var j = 0; j <= bins.GetUpperBound(1); j++)
+ for (var i = 0; i <= bins.GetUpperBound(0); i++)
+ bins[i, j] = new List();
+ }
+
+ public void Add(FrozenActor fa)
+ {
+ frozen.Add(fa.ID, fa);
+
+ var top = (int)Math.Max(0, fa.Bounds.Top / info.BinSize);
+ var left = (int)Math.Max(0, fa.Bounds.Left / info.BinSize);
+ var bottom = (int)Math.Min(bins.GetUpperBound(1), fa.Bounds.Bottom / info.BinSize);
+ var right = (int)Math.Min(bins.GetUpperBound(0), fa.Bounds.Right / info.BinSize);
+ for (var j = top; j <= bottom; j++)
+ for (var i = left; i <= right; i++)
+ bins[i, j].Add(fa);
+ }
+
+ public void Tick(Actor self)
+ {
+ var remove = new List();
+ VisibilityHash = 0;
+ FrozenHash = 0;
+
+ foreach (var kv in frozen)
+ {
+ FrozenHash += (int)kv.Key;
+
+ kv.Value.Tick(self.World, self.Owner.Shroud);
+ if (kv.Value.Visible)
+ VisibilityHash += (int)kv.Key;
+
+ if (!kv.Value.Visible && kv.Value.Actor == null)
+ remove.Add(kv.Key);
+ }
+
+ foreach (var r in remove)
+ {
+ foreach (var bin in bins)
+ bin.Remove(frozen[r]);
+ frozen.Remove(r);
+ }
+ }
+
+ public virtual IEnumerable Render(Actor self, WorldRenderer wr)
+ {
+ return frozen.Values
+ .Where(f => f.Visible)
+ .SelectMany(ff => ff.Render(wr));
+ }
+
+ public IEnumerable FrozenActorsAt(int2 pxPos)
+ {
+ var x = (pxPos.X / info.BinSize).Clamp(0, bins.GetUpperBound(0));
+ var y = (pxPos.Y / info.BinSize).Clamp(0, bins.GetUpperBound(1));
+ return bins[x, y].Where(p => p.Bounds.Contains(pxPos) && p.IsValid);
+ }
+
+ public FrozenActor FromID(uint id)
+ {
+ FrozenActor ret;
+ if (!frozen.TryGetValue(id, out ret))
+ return null;
+
+ return ret;
+ }
+ }
+}
diff --git a/OpenRA.Game/WorldUtils.cs b/OpenRA.Game/WorldUtils.cs
index 965c6d8d33..94f635ba1d 100755
--- a/OpenRA.Game/WorldUtils.cs
+++ b/OpenRA.Game/WorldUtils.cs
@@ -27,6 +27,20 @@ namespace OpenRA
return FindActorsInBox(world, loc, loc).Where(a => !world.FogObscures(a));
}
+ public static readonly IEnumerable NoFrozenActors = new FrozenActor[0].AsEnumerable();
+ public static IEnumerable FindFrozenActorsAtMouse(this World world, int2 mouseLocation)
+ {
+ if (world.RenderPlayer == null)
+ return NoFrozenActors;
+
+ var frozenLayer = world.RenderPlayer.PlayerActor.TraitOrDefault();
+ if (frozenLayer == null)
+ return NoFrozenActors;
+
+ var loc = Game.viewport.ViewToWorldPx(mouseLocation).ToInt2();
+ return frozenLayer.FrozenActorsAt(loc);
+ }
+
public static IEnumerable FindActorsInBox(this World world, CPos tl, CPos br)
{
return world.FindActorsInBox(tl.TopLeft, br.BottomRight);
diff --git a/OpenRA.Mods.RA/Effects/FrozenActorProxy.cs b/OpenRA.Mods.RA/Effects/FrozenActorProxy.cs
deleted file mode 100644
index 96fe2d82e0..0000000000
--- a/OpenRA.Mods.RA/Effects/FrozenActorProxy.cs
+++ /dev/null
@@ -1,53 +0,0 @@
-#region Copyright & License Information
-/*
- * Copyright 2007-2013 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 System.Linq;
-using OpenRA.Effects;
-using OpenRA.Graphics;
-using OpenRA.Traits;
-
-namespace OpenRA.Mods.RA.Effects
-{
- public class FrozenActorProxy : IEffect
- {
- readonly Actor self;
- readonly IEnumerable footprint;
- IRenderable[] renderables;
-
- public FrozenActorProxy(Actor self, IEnumerable footprint)
- {
- this.self = self;
- this.footprint = footprint;
- }
-
- public void Tick(World world) { }
- public void SetRenderables(IEnumerable r)
- {
- renderables = r.Select(rr => rr).ToArray();
- }
-
- public IEnumerable Render(WorldRenderer wr)
- {
- if (renderables == null)
- return SpriteRenderable.None;
-
- if (footprint.Any(c => !wr.world.FogObscures(c)))
- {
- if (self.Destroyed)
- self.World.AddFrameEndTask(w => w.Remove(this));
-
- return SpriteRenderable.None;
- }
-
- return renderables;
- }
- }
-}
diff --git a/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs b/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs
index 8544ae2ffa..78a7ffa5f9 100644
--- a/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs
+++ b/OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs
@@ -9,7 +9,9 @@
#endregion
using System.Collections.Generic;
+using System.Drawing;
using System.Linq;
+using OpenRA.FileFormats;
using OpenRA.Graphics;
using OpenRA.Mods.RA.Buildings;
using OpenRA.Mods.RA.Effects;
@@ -24,50 +26,97 @@ namespace OpenRA.Mods.RA
public object Create(ActorInitializer init) { return new FrozenUnderFog(init, this); }
}
- public class FrozenUnderFog : IRenderModifier, IVisibilityModifier, ITickRender
+ public class FrozenUnderFog : IRenderModifier, IVisibilityModifier, ITick, ITickRender, ISync
{
- FrozenActorProxy proxy;
+ [Sync] public int VisibilityHash;
+
+ bool initialized, startsRevealed;
IEnumerable footprint;
- bool visible, cacheFirstFrame;
+ Lazy tooltip;
+ Lazy health;
+
+ Dictionary visible;
+ Dictionary frozen;
public FrozenUnderFog(ActorInitializer init, FrozenUnderFogInfo info)
{
- footprint = FootprintUtils.Tiles(init.self);
- proxy = new FrozenActorProxy(init.self, footprint);
- init.world.AddFrameEndTask(w => w.Add(proxy));
-
// Spawned actors (e.g. building husks) shouldn't be revealed
- cacheFirstFrame = info.StartsRevealed && !init.Contains();
+ startsRevealed = info.StartsRevealed && !init.Contains();
+ footprint = FootprintUtils.Tiles(init.self);
+ tooltip = Lazy.New(() => init.self.TraitsImplementing().FirstOrDefault());
+ tooltip = Lazy.New(() => init.self.TraitsImplementing().FirstOrDefault());
+ health = Lazy.New(() => init.self.TraitOrDefault());
+
+ frozen = new Dictionary();
+ visible = init.world.Players.ToDictionary(p => p, p => false);
}
public bool IsVisible(Actor self, Player byPlayer)
{
- return byPlayer == null || footprint.Any(c => byPlayer.Shroud.IsVisible(c));
+ return byPlayer == null || visible[byPlayer];
}
- public void TickRender(WorldRenderer wr, Actor self)
+ public void Tick(Actor self)
{
if (self.Destroyed)
return;
- visible = IsVisible(self, self.World.RenderPlayer);
-
- if (cacheFirstFrame)
+ VisibilityHash = 0;
+ foreach (var p in self.World.Players)
{
- visible = true;
- cacheFirstFrame = false;
+ visible[p] = footprint.Any(c => p.Shroud.IsVisible(c));
+ if (visible[p])
+ VisibilityHash += p.ClientIndex;
}
- if (visible)
+ if (!initialized)
{
- var comparer = new RenderableComparer(wr);
- proxy.SetRenderables(self.Render(wr).OrderBy(r => r, comparer));
+ foreach (var p in self.World.Players)
+ {
+ visible[p] |= startsRevealed;
+ frozen[p] = new FrozenActor(self, footprint);
+ p.PlayerActor.Trait().Add(frozen[p]);
+ }
+
+ initialized = true;
}
+
+ foreach (var player in self.World.Players)
+ {
+ if (!visible[player])
+ continue;
+
+ frozen[player].Owner = self.Owner;
+
+ if (health.Value != null)
+ {
+ frozen[player].HP = health.Value.HP;
+ frozen[player].DamageState = health.Value.DamageState;
+ }
+
+ if (tooltip.Value != null)
+ {
+ frozen[player].TooltipName = tooltip.Value.Name();
+ frozen[player].TooltipOwner = tooltip.Value.Owner();
+ }
+ }
+ }
+
+ public void TickRender(WorldRenderer wr, Actor self)
+ {
+ if (self.Destroyed || !initialized || !visible.Any(v => v.Value))
+ return;
+
+ // Force a copy of the underlying data
+ var renderables = self.Render(wr).Select(rr => rr).ToArray();
+ foreach (var player in self.World.Players)
+ if (visible[player])
+ frozen[player].Renderables = renderables;
}
public IEnumerable ModifyRender(Actor self, WorldRenderer wr, IEnumerable r)
{
- return visible ? r : SpriteRenderable.None;
+ return IsVisible(self, self.World.RenderPlayer) ? r : SpriteRenderable.None;
}
}
}
\ No newline at end of file
diff --git a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
index 3dc9d57449..3bf6aed2f6 100644
--- a/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
+++ b/OpenRA.Mods.RA/OpenRA.Mods.RA.csproj
@@ -462,7 +462,6 @@
-
diff --git a/mods/cnc/rules/system.yaml b/mods/cnc/rules/system.yaml
index 99c425c102..01ba2734af 100644
--- a/mods/cnc/rules/system.yaml
+++ b/mods/cnc/rules/system.yaml
@@ -203,6 +203,7 @@ Player:
BaseAttackNotifier:
Shroud:
PlayerStatistics:
+ FrozenActorLayer:
World:
LoadWidgetAtGameStart:
diff --git a/mods/d2k/rules/system.yaml b/mods/d2k/rules/system.yaml
index 2d6a543416..23da9915c6 100644
--- a/mods/d2k/rules/system.yaml
+++ b/mods/d2k/rules/system.yaml
@@ -269,7 +269,7 @@ Player:
RemapIndex: 255, 254, 253, 252, 251, 250, 249, 248, 247, 246, 245, 244, 243, 242, 241, 240
BaseAttackNotifier:
Shroud:
- Shroud: false
+ FrozenActorLayer:
HarvesterAttackNotifier:
PlayerStatistics:
diff --git a/mods/ra/rules/system.yaml b/mods/ra/rules/system.yaml
index 6529debd7f..6be1a8d901 100644
--- a/mods/ra/rules/system.yaml
+++ b/mods/ra/rules/system.yaml
@@ -524,6 +524,7 @@ Player:
RemapIndex: 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95
GpsWatcher:
Shroud:
+ FrozenActorLayer:
BaseAttackNotifier:
PlayerStatistics: