diff --git a/OpenRA.Game/Map/CellLayer.cs b/OpenRA.Game/Map/CellLayer.cs index 9b18333442..a78d803455 100644 --- a/OpenRA.Game/Map/CellLayer.cs +++ b/OpenRA.Game/Map/CellLayer.cs @@ -20,6 +20,8 @@ namespace OpenRA { public readonly Size Size; public readonly TileShape Shape; + public event Action CellEntryChanged = null; + readonly T[] entries; public CellLayer(Map map) @@ -48,15 +50,35 @@ namespace OpenRA /// Gets or sets the using cell coordinates public T this[CPos cell] { - get { return entries[Index(cell)]; } - set { entries[Index(cell)] = value; } + get + { + return entries[Index(cell)]; + } + + set + { + entries[Index(cell)] = value; + + if (CellEntryChanged != null) + CellEntryChanged(cell); + } } /// Gets or sets the layer contents using raw map coordinates (not CPos!) public T this[int u, int v] { - get { return entries[Index(u, v)]; } - set { entries[Index(u, v)] = value; } + get + { + return entries[Index(u, v)]; + } + + set + { + entries[Index(u, v)] = value; + + if (CellEntryChanged != null) + CellEntryChanged(Map.MapToCell(Shape, new CPos(u, v))); + } } /// Clears the layer contents with a known value diff --git a/OpenRA.Game/Traits/World/Shroud.cs b/OpenRA.Game/Traits/World/Shroud.cs index 6d501b07aa..6b7037a28b 100644 --- a/OpenRA.Game/Traits/World/Shroud.cs +++ b/OpenRA.Game/Traits/World/Shroud.cs @@ -31,6 +31,23 @@ namespace OpenRA.Traits readonly CellLayer generatedShroudCount; readonly CellLayer explored; + public event Action CellEntryChanged + { + add + { + visibleCount.CellEntryChanged += value; + generatedShroudCount.CellEntryChanged += value; + explored.CellEntryChanged += value; + } + + remove + { + visibleCount.CellEntryChanged -= value; + generatedShroudCount.CellEntryChanged -= value; + explored.CellEntryChanged -= value; + } + } + readonly Lazy fogVisibilities; // Cache of visibility that was added, so no matter what crazy trait code does, it diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 9bca1134c3..dcd64bd350 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -12,6 +12,7 @@ using System; using System.Drawing; using System.Linq; using OpenRA.Graphics; +using OpenRA.Traits; using OpenRA.Widgets; namespace OpenRA.Mods.Common.Widgets @@ -31,16 +32,18 @@ namespace OpenRA.Mods.Common.Widgets int frame; bool hasRadar; bool cachedEnabled; - int updateTicks; float previewScale = 0; int2 previewOrigin = int2.Zero; Rectangle mapRect = Rectangle.Empty; + Sheet radarSheet; + byte[] radarData; + Sprite terrainSprite; - Sprite customTerrainSprite; Sprite actorSprite; Sprite shroudSprite; + Shroud renderShroud; readonly World world; readonly WorldRenderer worldRenderer; @@ -68,19 +71,62 @@ namespace OpenRA.Mods.Common.Widgets previewOrigin = new int2((int)(previewScale * (size - width) / 2), (int)(previewScale * (size - height) / 2)); mapRect = new Rectangle(previewOrigin.X, previewOrigin.Y, (int)(previewScale * width), (int)(previewScale * height)); - // Only needs to be done once - using (var terrainBitmap = Minimap.TerrainBitmap(world.Map.Rules.TileSets[world.Map.Tileset], world.Map)) - { - var r = new Rectangle(0, 0, width, height); - var s = new Size(terrainBitmap.Width, terrainBitmap.Height); - var terrainSheet = new Sheet(s); - terrainSheet.GetTexture().SetData(terrainBitmap); - terrainSprite = new Sprite(terrainSheet, r, TextureChannel.Alpha); + // The four layers are stored in a 2x2 grid within a single texture + radarSheet = new Sheet(new Size(2 * width, 2 * height).NextPowerOf2()); + radarSheet.CreateBuffer(); + radarData = radarSheet.GetData(); - // Data is set in Tick() - customTerrainSprite = new Sprite(new Sheet(s), r, TextureChannel.Alpha); - actorSprite = new Sprite(new Sheet(s), r, TextureChannel.Alpha); - shroudSprite = new Sprite(new Sheet(s), r, TextureChannel.Alpha); + terrainSprite = new Sprite(radarSheet, new Rectangle(0, 0, width, height), TextureChannel.Alpha); + shroudSprite = new Sprite(radarSheet, new Rectangle(width, 0, width, height), TextureChannel.Alpha); + actorSprite = new Sprite(radarSheet, new Rectangle(0, height, width, height), TextureChannel.Alpha); + + // Set initial terrain data + using (var bitmap = Minimap.TerrainBitmap(world.TileSet, world.Map)) + OpenRA.Graphics.Util.FastCopyIntoSprite(terrainSprite, bitmap); + + world.Map.MapTiles.Value.CellEntryChanged += UpdateTerrainCell; + world.Map.CustomTerrain.CellEntryChanged += UpdateTerrainCell; + } + + void UpdateTerrainCell(CPos cell) + { + var stride = radarSheet.Size.Width; + var uv = Map.CellToMap(world.Map.TileShape, cell); + var terrain = world.Map.GetTerrainInfo(cell); + + var dx = terrainSprite.bounds.Left - world.Map.Bounds.Left; + var dy = terrainSprite.bounds.Top - world.Map.Bounds.Top; + + unsafe + { + fixed (byte* _colors = &radarData[0]) + { + var colors = (int*)_colors; + colors[(uv.Y + dy) * stride + uv.X + dx] = terrain.Color.ToArgb(); + } + } + } + + void UpdateShroudCell(CPos cell) + { + var stride = radarSheet.Size.Width; + var uv = Map.CellToMap(world.Map.TileShape, cell); + var dx = shroudSprite.bounds.Left - world.Map.Bounds.Left; + var dy = shroudSprite.bounds.Top - world.Map.Bounds.Top; + + var color = 0; + if (world.ShroudObscures(cell)) + color = Color.Black.ToArgb(); + else if (world.FogObscures(cell)) + color = Color.FromArgb(128, Color.Black).ToArgb(); + + unsafe + { + fixed (byte* _colors = &radarData[0]) + { + var colors = (int*)_colors; + colors[(uv.Y + dy) * stride + uv.X + dx] = color; + } } } @@ -153,9 +199,10 @@ namespace OpenRA.Mods.Common.Widgets var rsr = Game.Renderer.RgbaSpriteRenderer; rsr.DrawSprite(terrainSprite, o, s); - rsr.DrawSprite(customTerrainSprite, o, s); rsr.DrawSprite(actorSprite, o, s); - rsr.DrawSprite(shroudSprite, o, s); + + if (renderShroud != null) + rsr.DrawSprite(shroudSprite, o, s); // Draw viewport rect if (hasRadar) @@ -195,31 +242,67 @@ namespace OpenRA.Mods.Common.Widgets public override void Tick() { - // Update the radar animation even when its closed - // This avoids obviously stale data from being shown when first opened. - // TODO: This delayed updating is a giant hack - --updateTicks; - if (updateTicks <= 0) - { - updateTicks = 12; - using (var bitmap = Minimap.CustomTerrainBitmap(world)) - customTerrainSprite.sheet.GetTexture().SetData(bitmap); - } - - if (updateTicks == 8) - using (var bitmap = Minimap.ActorsBitmap(world)) - actorSprite.sheet.GetTexture().SetData(bitmap); - - if (updateTicks == 4) - using (var bitmap = Minimap.ShroudBitmap(world)) - shroudSprite.sheet.GetTexture().SetData(bitmap); - // Enable/Disable the radar var enabled = IsEnabled(); if (enabled != cachedEnabled) Sound.Play(enabled ? RadarOnlineSound : RadarOfflineSound); cachedEnabled = enabled; + if (enabled) + { + var rp = world.RenderPlayer; + var newRenderShroud = rp != null ? rp.Shroud : null; + if (newRenderShroud != renderShroud) + { + if (renderShroud != null) + renderShroud.CellEntryChanged -= UpdateShroudCell; + + if (newRenderShroud != null) + { + // Redraw the full shroud sprite + using (var bitmap = Minimap.ShroudBitmap(world)) + OpenRA.Graphics.Util.FastCopyIntoSprite(shroudSprite, bitmap); + + // Update the notification binding + newRenderShroud.CellEntryChanged += UpdateShroudCell; + } + + renderShroud = newRenderShroud; + } + + // The actor layer is updated every tick + var stride = radarSheet.Size.Width; + var dx = actorSprite.bounds.Left - world.Map.Bounds.Left; + var dy = actorSprite.bounds.Top - world.Map.Bounds.Top; + + Array.Clear(radarData, 4 * (actorSprite.bounds.Top * stride + actorSprite.bounds.Left), 4 * actorSprite.bounds.Height * stride); + + unsafe + { + fixed (byte* _colors = &radarData[0]) + { + var colors = (int*)_colors; + + foreach (var t in world.ActorsWithTrait()) + { + if (!t.Actor.IsInWorld || world.FogObscures(t.Actor)) + continue; + + var color = t.Trait.RadarSignatureColor(t.Actor); + foreach (var cell in t.Trait.RadarSignatureCells(t.Actor)) + { + var uv = Map.CellToMap(world.Map.TileShape, cell); + + if (world.Map.Bounds.Contains(uv.X, uv.Y)) + colors[(uv.Y + dy) * stride + uv.X + dx] = color.ToArgb(); + } + } + } + } + + radarSheet.CommitData(); + } + var targetFrame = enabled ? AnimationLength : 0; hasRadar = enabled && frame == AnimationLength; if (frame == targetFrame)