From eef941fd42c2235b1412e0def7ef39764889e447 Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 7 Aug 2013 13:48:23 +1200 Subject: [PATCH] Track per-player frozen actors. --- OpenRA.Game/OpenRA.Game.csproj | 1 + OpenRA.Game/Traits/DrawLineToTarget.cs | 18 ++ OpenRA.Game/Traits/Player/FrozenActorLayer.cs | 173 ++++++++++++++++++ OpenRA.Game/WorldUtils.cs | 14 ++ OpenRA.Mods.RA/Effects/FrozenActorProxy.cs | 53 ------ OpenRA.Mods.RA/Modifiers/FrozenUnderFog.cs | 87 +++++++-- OpenRA.Mods.RA/OpenRA.Mods.RA.csproj | 1 - mods/cnc/rules/system.yaml | 1 + mods/d2k/rules/system.yaml | 2 +- mods/ra/rules/system.yaml | 1 + 10 files changed, 277 insertions(+), 74 deletions(-) create mode 100755 OpenRA.Game/Traits/Player/FrozenActorLayer.cs delete mode 100644 OpenRA.Mods.RA/Effects/FrozenActorProxy.cs 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: