From 248e815d99941b6b23f8dfca2c3be73b505fc17b Mon Sep 17 00:00:00 2001 From: Paul Chote Date: Wed, 10 Apr 2013 20:27:33 +1200 Subject: [PATCH] More shroud refactoring. This introduces a hash on Shroud which ShroudRenderer can observe, removing the need to explicitly twiddle a dirty flag between objects. Shroud disabling is now done via RenderPlayer, so enabling the cheat or winning/losing will now give vis equivalent to an observer. --- OpenRA.Game/Graphics/ShroudRenderer.cs | 104 ++++++----- OpenRA.Game/Graphics/WorldRenderer.cs | 3 +- OpenRA.Game/Player.cs | 2 +- OpenRA.Game/Traits/CreatesShroud.cs | 25 ++- OpenRA.Game/Traits/Player/DeveloperMode.cs | 2 +- OpenRA.Game/Traits/World/Shroud.cs | 169 +++++++++--------- OpenRA.Game/World.cs | 26 ++- OpenRA.Mods.RA/ConquestVictoryConditions.cs | 4 +- .../Widgets/Logic/IngameChatLogic.cs | 2 +- 9 files changed, 169 insertions(+), 168 deletions(-) diff --git a/OpenRA.Game/Graphics/ShroudRenderer.cs b/OpenRA.Game/Graphics/ShroudRenderer.cs index 0ee74d8378..af21f5cb69 100644 --- a/OpenRA.Game/Graphics/ShroudRenderer.cs +++ b/OpenRA.Game/Graphics/ShroudRenderer.cs @@ -18,14 +18,10 @@ namespace OpenRA.Graphics Map map; Sprite[] shadowBits = Game.modData.SpriteLoader.LoadAllSprites("shadow"); Sprite[,] sprites, fogSprites; + int shroudHash; - public ShroudRenderer(World world) - { - this.map = world.Map; - - sprites = new Sprite[map.MapSize.X, map.MapSize.Y]; - fogSprites = new Sprite[map.MapSize.X, map.MapSize.Y]; - } + bool initializePalettes = true; + PaletteReference fogPalette, shroudPalette; static readonly byte[][] SpecialShroudTiles = { @@ -47,6 +43,17 @@ namespace OpenRA.Graphics new byte[] { 46 }, }; + public ShroudRenderer(World world) + { + this.map = world.Map; + + sprites = new Sprite[map.MapSize.X, map.MapSize.Y]; + fogSprites = new Sprite[map.MapSize.X, map.MapSize.Y]; + + // Force update on first render + shroudHash = -1; + } + Sprite ChooseShroud(Shroud s, int i, int j) { if (!s.IsExplored(i, j)) @@ -96,9 +103,50 @@ namespace OpenRA.Graphics return shadowBits[SpecialShroudTiles[u ^ uSides][v]]; } - bool initializePalettes = true; - PaletteReference fogPalette, shroudPalette; - internal void Draw(WorldRenderer wr, Player renderPlayer) + void GenerateSprites(Shroud shroud) + { + var hash = shroud != null ? shroud.Hash : 0; + if (shroudHash == hash) + return; + + shroudHash = hash; + if (shroud == null) + { + // Players with no shroud see the whole map so we only need to set the edges + var b = map.Bounds; + for (int i = b.Left; i < b.Right; i++) + for (int j = b.Top; j < b.Bottom; j++) + { + var v = 0; + var u = 0; + + if (j == b.Top) { v |= 1; u |= 3; } + if (i == b.Right - 1) { v |= 2; u |= 6; } + if (j == b.Bottom - 1) { v |= 4; u |= 12; } + if (i == b.Left) { v |= 8; u |= 9; } + + var uSides = u; + if (i == b.Left && j == b.Top) u |= 1; + if (i == b.Right - 1 && j == b.Top) u |= 2; + if (i == b.Right - 1 && j == b.Bottom - 1) u |= 4; + if (i == b.Left && j == b.Bottom - 1) u |= 8; + + sprites[i, j] = fogSprites[i, j] = shadowBits[SpecialShroudTiles[u ^ uSides][v]]; + } + } + else + { + for (int i = map.Bounds.Left; i < map.Bounds.Right; i++) + for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++) + sprites[i, j] = ChooseShroud(shroud, i, j); + + for (int i = map.Bounds.Left; i < map.Bounds.Right; i++) + for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++) + fogSprites[i, j] = ChooseFog(shroud, i, j); + } + } + + internal void Draw(WorldRenderer wr, Shroud shroud) { if (initializePalettes) { @@ -107,42 +155,8 @@ namespace OpenRA.Graphics initializePalettes = false; } - if (renderPlayer == null) - { - // Players with no shroud see the whole map so we only need to set the edges - var b = map.Bounds; - for (int i = b.Left; i < b.Right; i++) - for (int j = b.Top; j < b.Bottom; j++) - { - var v = 0; - var u = 0; + GenerateSprites(shroud); - if (j == b.Top) { v |= 1; u |= 3; } - if (i == b.Right - 1) { v |= 2; u |= 6; } - if (j == b.Bottom - 1) { v |= 4; u |= 12; } - if (i == b.Left) { v |= 8; u |= 9; } - - var uSides = u; - if (i == b.Left && j == b.Top) u |= 1; - if (i == b.Right - 1 && j == b.Top) u |= 2; - if (i == b.Right - 1 && j == b.Bottom - 1) u |= 4; - if (i == b.Left && j == b.Bottom - 1) u |= 8; - - sprites[i, j] = fogSprites[i, j] = shadowBits[SpecialShroudTiles[u ^ uSides][v]]; - } - } - else - { - renderPlayer.Shroud.dirty = false; - - for (int i = map.Bounds.Left; i < map.Bounds.Right; i++) - for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++) - sprites[i, j] = ChooseShroud(renderPlayer.Shroud, i, j); - - for (int i = map.Bounds.Left; i < map.Bounds.Right; i++) - for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++) - fogSprites[i, j] = ChooseFog(renderPlayer.Shroud, i, j); - } var clipRect = Game.viewport.WorldBounds(wr.world); DrawShroud(wr, clipRect, sprites, shroudPalette); if (wr.world.WorldActor.HasTrait()) diff --git a/OpenRA.Game/Graphics/WorldRenderer.cs b/OpenRA.Game/Graphics/WorldRenderer.cs index ddbbf0f9b5..b3dd017af4 100644 --- a/OpenRA.Game/Graphics/WorldRenderer.cs +++ b/OpenRA.Game/Graphics/WorldRenderer.cs @@ -127,7 +127,8 @@ namespace OpenRA.Graphics if (world.OrderGenerator != null) world.OrderGenerator.RenderAfterWorld(this, world); - shroudRenderer.Draw(this, world.RenderPlayer); + var renderShroud = world.RenderPlayer != null ? world.RenderPlayer.Shroud : null; + shroudRenderer.Draw(this, renderShroud); Game.Renderer.DisableScissor(); foreach (var g in world.Selection.Actors.Where(a => !a.Destroyed) diff --git a/OpenRA.Game/Player.cs b/OpenRA.Game/Player.cs index 95132cc81d..f22b003e3a 100644 --- a/OpenRA.Game/Player.cs +++ b/OpenRA.Game/Player.cs @@ -77,7 +77,7 @@ namespace OpenRA } PlayerActor = world.CreateActor("Player", new TypeDictionary { new OwnerInit(this) }); Shroud = PlayerActor.Trait(); - Shroud.Owner = this; + // Enable the bot logic on the host IsBot = botType != null; if (IsBot && Game.IsHost) diff --git a/OpenRA.Game/Traits/CreatesShroud.cs b/OpenRA.Game/Traits/CreatesShroud.cs index da5b644cbf..7cef8baa06 100644 --- a/OpenRA.Game/Traits/CreatesShroud.cs +++ b/OpenRA.Game/Traits/CreatesShroud.cs @@ -32,29 +32,24 @@ namespace OpenRA.Traits public void Tick(Actor self) { // TODO: don't tick all the time. - if(self.Owner == null) return; + if (self.Owner == null) + return; - if (previousLocation != self.Location && v != null) { + var shrouds = self.World.ActorsWithTrait().Select(s => s.Actor.Owner.Shroud); + if (previousLocation != self.Location && v != null) + { previousLocation = self.Location; - var shrouds = self.World.ActorsWithTrait().Select(s => s.Actor.Owner.Shroud); - foreach (var shroud in shrouds) { + foreach (var shroud in shrouds) shroud.UnhideActor(self, v, Info.Range); - } } - if (!self.TraitsImplementing().Any(d => d.Disabled)) { - var shrouds = self.World.ActorsWithTrait().Select(s => s.Actor.Owner.Shroud); - foreach (var shroud in shrouds) { + if (!self.TraitsImplementing().Any(d => d.Disabled)) + foreach (var shroud in shrouds) shroud.HideActor(self, Info.Range); - } - } - else { - var shrouds = self.World.ActorsWithTrait().Select(s => s.Actor.Owner.Shroud); - foreach (var shroud in shrouds) { + else + foreach (var shroud in shrouds) shroud.UnhideActor(self, v, Info.Range); - } - } v = new Shroud.ActorVisibility { vis = Shroud.GetVisOrigins(self).ToArray() diff --git a/OpenRA.Game/Traits/Player/DeveloperMode.cs b/OpenRA.Game/Traits/Player/DeveloperMode.cs index f6492a58af..e432474eaa 100644 --- a/OpenRA.Game/Traits/Player/DeveloperMode.cs +++ b/OpenRA.Game/Traits/Player/DeveloperMode.cs @@ -81,7 +81,7 @@ namespace OpenRA.Traits case "DevShroudDisable": { DisableShroud ^= true; - self.Owner.Shroud.Disabled = DisableShroud; + self.World.RenderPlayer = DisableShroud ? null : self.Owner; break; } case "DevPathDebug": diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index 8682ac9ef3..2a98e97e19 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -12,62 +12,70 @@ using System; using System.Collections.Generic; using System.Drawing; using System.Linq; +using OpenRA.Graphics; namespace OpenRA.Traits { public class ShroudInfo : ITraitInfo { - public object Create(ActorInitializer init) { return new Shroud(init.world); } + public object Create(ActorInitializer init) { return new Shroud(init.self); } } - public class Shroud : ISync + public class Shroud { Map map; + Actor self; - [Sync] public Player Owner; - public int[,] visibleCells; - public bool[,] exploredCells; - public bool[,] foggedCells; - public Rectangle? exploredBounds; - bool disabled = false; - public bool dirty = true; - [Sync] public bool Disabled + int[,] visibleCells; + bool[,] exploredCells; + bool[,] foggedCells; + + public Rectangle ExploredBounds { get; private set; } + + public int Hash { get; private set; } + + public Shroud(Actor self) { - get { return disabled; } - set { disabled = value; Dirty(); } - } + this.self = self; + map = self.World.Map; - public Rectangle? Bounds - { - get { return Disabled ? null : exploredBounds; } - } - - public Action Dirty = () => { }; - - public Shroud(World world) - { - map = world.Map; visibleCells = new int[map.MapSize.X, map.MapSize.Y]; exploredCells = new bool[map.MapSize.X, map.MapSize.Y]; foggedCells = new bool[map.MapSize.X, map.MapSize.Y]; - world.ActorAdded += AddActor; - world.ActorRemoved += RemoveActor; - Dirty += () => dirty = true; + self.World.ActorAdded += AddActor; + self.World.ActorRemoved += RemoveActor; + } + + void Invalidate() + { + Hash = Sync.hash_player(self.Owner) + self.World.FrameNumber * 3; } // cache of positions that were added, so no matter what crazy trait code does, it // can't make us invalid. - public class ActorVisibility { [Sync] public int range; [Sync] public CPos[] vis; } + public class ActorVisibility + { + [Sync] public int range; + [Sync] public CPos[] vis; + } + public Dictionary vis = new Dictionary(); static IEnumerable FindVisibleTiles(World world, CPos a, int r) { var min = a - new CVec(r, r); var max = a + new CVec(r, r); - if (min.X < world.Map.Bounds.Left - 1) min = new CPos(world.Map.Bounds.Left - 1, min.Y); - if (min.Y < world.Map.Bounds.Top - 1) min = new CPos(min.X, world.Map.Bounds.Top - 1); - if (max.X > world.Map.Bounds.Right) max = new CPos(world.Map.Bounds.Right, max.Y); - if (max.Y > world.Map.Bounds.Bottom) max = new CPos(max.X, world.Map.Bounds.Bottom); + if (min.X < world.Map.Bounds.Left - 1) + min = new CPos(world.Map.Bounds.Left - 1, min.Y); + + if (min.Y < world.Map.Bounds.Top - 1) + min = new CPos(min.X, world.Map.Bounds.Top - 1); + + if (max.X > world.Map.Bounds.Right) + max = new CPos(world.Map.Bounds.Right, max.Y); + + if (max.Y > world.Map.Bounds.Bottom) + max = new CPos(max.X, world.Map.Bounds.Bottom); for (var j = min.Y; j <= max.Y; j++) for (var i = min.X; i <= max.X; i++) @@ -77,13 +85,12 @@ namespace OpenRA.Traits public void AddActor(Actor a) { - if (!a.HasTrait()) return; - if (a.Owner == null || Owner == null) return; - if(a.Owner.Stances[Owner] != Stance.Ally) return; + if (!a.HasTrait() || !a.Owner.IsAlliedWith(self.Owner)) + return; ActorVisibility v = a.Sight; - - if (v.range == 0) return; // don't bother for things that can't see + if (v.range == 0) + return; foreach (var p in v.vis) { @@ -95,17 +102,16 @@ namespace OpenRA.Traits } var box = new Rectangle(p.X - v.range, p.Y - v.range, 2 * v.range + 1, 2 * v.range + 1); - exploredBounds = (exploredBounds.HasValue) ? Rectangle.Union(exploredBounds.Value, box) : box; + ExploredBounds = Rectangle.Union(ExploredBounds, box); } - if (!Disabled) - Dirty(); + Invalidate(); } public void HideActor(Actor a, int range) { - if (a.Owner.World.LocalPlayer == null - || a.Owner.Stances[a.Owner.World.LocalPlayer] == Stance.Ally) return; + if (a.Owner.IsAlliedWith(self.Owner)) + return; var v = new ActorVisibility { @@ -116,34 +122,33 @@ namespace OpenRA.Traits foreach (var q in FindVisibleTiles(a.World, p, range)) foggedCells[q.X, q.Y] = visibleCells[q.X, q.Y] > 0; - if (!Disabled) - Dirty(); + Invalidate(); } - public void UnhideActor(Actor a, ActorVisibility v, int range) { - if (a.Owner.World.LocalPlayer == null - || a.Owner.Stances[a.Owner.World.LocalPlayer] == Stance.Ally) return; - - if (v == null) + public void UnhideActor(Actor a, ActorVisibility v, int range) + { + if (a.Owner.IsAlliedWith(self.Owner) || v == null) return; foreach (var p in v.vis) foreach (var q in FindVisibleTiles(a.World, p, range)) foggedCells[q.X, q.Y] = exploredCells[q.X, q.Y]; - if (!Disabled) - Dirty(); + Invalidate(); } - public void MergeShroud(Shroud s) { - for (int i = map.Bounds.Left; i < map.Bounds.Right; i++) { - for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++) { + public void MergeShroud(Shroud s) + { + for (int i = map.Bounds.Left; i < map.Bounds.Right; i++) + { + for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++) + { if (s.exploredCells[i,j] == true) exploredCells[i, j] = true; if (s.foggedCells[i,j] == true) foggedCells[i, j] = true; } - exploredBounds = Rectangle.Union(exploredBounds.Value, s.exploredBounds.Value); + ExploredBounds = Rectangle.Union(ExploredBounds, s.ExploredBounds); } } @@ -159,6 +164,7 @@ namespace OpenRA.Traits foreach (var a in toRemove) RemoveActor(a); } + // Is now our ally; add unit vis if (newStance == Stance.Ally) foreach (var a in w.Actors.Where( a => a.Owner == player )) @@ -170,7 +176,8 @@ namespace OpenRA.Traits int seen = 0; for (int i = map.Bounds.Left; i < map.Bounds.Right; i++) for (int j = map.Bounds.Top; j < map.Bounds.Bottom; j++) - if(foggedCells[i, j]) seen++; + if (foggedCells[i, j]) + seen++; return seen; } @@ -189,34 +196,33 @@ namespace OpenRA.Traits public void RemoveActor(Actor a) { - if (!a.HasTrait())return; - if (a.Owner == null || Owner == null) return; - ActorVisibility v = a.Sight; - - if(a.Owner.Stances[Owner] != Stance.Ally) { - if (a.HasTrait()) { + if (!a.Owner.IsAlliedWith(self.Owner)) + { + if (a.HasTrait()) foreach (var p in v.vis) foreach (var q in FindVisibleTiles(a.World, p, v.range)) foggedCells[q.X, q.Y] = exploredCells[q.X, q.Y]; - } return; } + if (!a.HasTrait()) + return; + foreach (var p in v.vis) foreach (var q in FindVisibleTiles(a.World, p, v.range)) --visibleCells[q.X, q.Y]; - if (!Disabled) - Dirty(); + Invalidate(); } public void UpdateActor(Actor a) { - if (a.Owner.World.LocalPlayer == null - || a.Owner.Stances[a.Owner.World.LocalPlayer] != Stance.Ally) return; + if (!a.Owner.IsAlliedWith(self.Owner)) + return; - RemoveActor(a); AddActor(a); + RemoveActor(a); + AddActor(a); } public void Explore(World world, CPos center, int range) @@ -227,10 +233,9 @@ namespace OpenRA.Traits } var box = new Rectangle(center.X - range, center.Y - range, 2 * range + 1, 2 * range + 1); - exploredBounds = (exploredBounds.HasValue) ? Rectangle.Union(exploredBounds.Value, box) : box; + ExploredBounds = Rectangle.Union(ExploredBounds, box); - if (!Disabled) - Dirty(); + Invalidate(); } public void ExploreAll(World world) @@ -241,10 +246,9 @@ namespace OpenRA.Traits foggedCells[i, j] = true; } } - exploredBounds = world.Map.Bounds; + ExploredBounds = world.Map.Bounds; - if (!Disabled) - Dirty(); + Invalidate(); } public void ResetExploration() @@ -257,8 +261,7 @@ namespace OpenRA.Traits for (var i = 0; i <= foggedCells.GetUpperBound(0); i++) foggedCells[i, j] = visibleCells[i, j] > 0; - if (!Disabled) - Dirty(); + Invalidate(); } public bool IsExplored(CPos xy) { return IsExplored(xy.X, xy.Y); } @@ -267,26 +270,17 @@ namespace OpenRA.Traits if (!map.IsInMap(x, y)) return false; - if (Disabled) - return true; - return foggedCells[x,y]; } public bool IsExplored(Actor a) { - if (Owner == null) - return true; - return GetVisOrigins(a).Any(o => IsExplored(o)); } public bool IsVisible(CPos xy) { return IsVisible(xy.X, xy.Y); } public bool IsVisible(int x, int y) { - if (Disabled) - return true; - // Visibility is allowed to extend beyond the map cordon so that // the fog tiles are not visible at the edge of the world if (x < 0 || x >= map.MapSize.X || y < 0 || y >= map.MapSize.Y) @@ -298,15 +292,14 @@ namespace OpenRA.Traits // Actors are hidden under shroud, but not under fog by default public bool IsVisible(Actor a) { - // I need to pass in the current shroud, otherwise we're just checking that true==true - if (a.TraitsImplementing().Any(t => !t.IsVisible(a, Owner))) + if (a.TraitsImplementing().Any(t => !t.IsVisible(a, self.Owner))) return false; - return Disabled || a.Owner.Stances[Owner] == Stance.Ally || IsExplored(a); + return a.Owner.IsAlliedWith(self.Owner) || IsExplored(a); } public bool IsTargetable(Actor a) { - if (a.TraitsImplementing().Any(t => !t.IsVisible(a, Owner))) + if (a.TraitsImplementing().Any(t => !t.IsVisible(a, self.Owner))) return false; return GetVisOrigins(a).Any(o => IsVisible(o)); diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index e9a8d17816..7143784bff 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -41,26 +41,24 @@ namespace OpenRA public void AddPlayer(Player p) { Players.Add(p); } public Player LocalPlayer { get; private set; } - public bool Observer { get { return LocalPlayer == null; } } - Player renderPlayer; - public Player RenderPlayer + public Player RenderPlayer; + public bool FogObscures(Actor a) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(a); } + public bool FogObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsVisible(p); } + public bool ShroudObscures(Actor a) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(a); } + public bool ShroudObscures(CPos p) { return RenderPlayer != null && !RenderPlayer.Shroud.IsExplored(p); } + + public Rectangle? VisibleBounds { - get { return renderPlayer; } - set + get { - renderPlayer = value; - if (renderPlayer != null) - renderPlayer.Shroud.Dirty(); + if (RenderPlayer == null) + return null; + + return RenderPlayer.Shroud.ExploredBounds; } } - public Rectangle? VisibleBounds { get { return renderPlayer != null ? renderPlayer.Shroud.Bounds : null; } } - public bool FogObscures(Actor a) { return renderPlayer != null && !renderPlayer.Shroud.IsVisible(a); } - public bool FogObscures(CPos p) { return renderPlayer != null && !renderPlayer.Shroud.IsVisible(p); } - public bool ShroudObscures(Actor a) { return renderPlayer != null && !renderPlayer.Shroud.IsExplored(a); } - public bool ShroudObscures(CPos p) { return renderPlayer != null && !renderPlayer.Shroud.IsExplored(p); } - public void SetLocalPlayer(string pr) { if (orderManager.Connection is ReplayConnection) diff --git a/OpenRA.Mods.RA/ConquestVictoryConditions.cs b/OpenRA.Mods.RA/ConquestVictoryConditions.cs index 0334fba8f8..975df3b842 100644 --- a/OpenRA.Mods.RA/ConquestVictoryConditions.cs +++ b/OpenRA.Mods.RA/ConquestVictoryConditions.cs @@ -62,9 +62,9 @@ namespace OpenRA.Mods.RA foreach (var a in self.World.Actors.Where(a => a.Owner == self.Owner)) a.Kill(a); - self.Owner.Shroud.Disabled = true; if (self.Owner == self.World.LocalPlayer) { + self.World.RenderPlayer = null; Game.RunAfterDelay(Info.NotificationDelay, () => { if (Game.IsCurrentWorld(self.World)) @@ -79,9 +79,9 @@ namespace OpenRA.Mods.RA self.Owner.WinState = WinState.Won; Game.Debug("{0} is victorious.".F(self.Owner.PlayerName)); - self.Owner.Shroud.Disabled = true; if (self.Owner == self.World.LocalPlayer) { + self.World.RenderPlayer = null; Game.RunAfterDelay(Info.NotificationDelay, () => Sound.PlayNotification(self.Owner, "Speech", "Win", self.Owner.Country.Race)); } } diff --git a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs index 8ef9c850cd..9dfbf91184 100644 --- a/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs +++ b/OpenRA.Mods.RA/Widgets/Logic/IngameChatLogic.cs @@ -30,7 +30,7 @@ namespace OpenRA.Mods.RA.Widgets.Logic private bool teamChat = false; internal bool TeamChat { - get { return World.Observer ? false : teamChat; } + get { return World.LocalPlayer == null ? false : teamChat; } set { teamChat = value; } }