diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 719514767f..a88b76b5f8 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -48,6 +48,8 @@ namespace OpenRA public Player[] Players = new Player[0]; + public event Action RenderPlayerChanged; + public void SetPlayers(IEnumerable players, Player localPlayer) { if (Players.Length > 0) @@ -84,7 +86,12 @@ namespace OpenRA set { if (LocalPlayer == null || LocalPlayer.UnlockedRenderPlayer) + { renderPlayer = value; + + if (RenderPlayerChanged != null) + RenderPlayerChanged(value); + } } } diff --git a/OpenRA.Mods.Common/Traits/Player/PlayerRadarTerrain.cs b/OpenRA.Mods.Common/Traits/Player/PlayerRadarTerrain.cs new file mode 100644 index 0000000000..8df649c8d7 --- /dev/null +++ b/OpenRA.Mods.Common/Traits/Player/PlayerRadarTerrain.cs @@ -0,0 +1,119 @@ +#region Copyright & License Information +/* + * Copyright 2007-2019 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.Graphics; +using OpenRA.Primitives; +using OpenRA.Traits; + +namespace OpenRA.Mods.Common.Traits +{ + public class PlayerRadarTerrainInfo : ITraitInfo, Requires + { + public object Create(ActorInitializer init) + { + return new PlayerRadarTerrain(init.Self); + } + } + + public class PlayerRadarTerrain : IWorldLoaded + { + public bool IsInitialized { get; private set; } + + readonly World world; + CellLayer> terrainColor; + readonly HashSet dirtyTerrainCells = new HashSet(); + readonly Shroud shroud; + + public event Action CellTerrainColorChanged = null; + + public PlayerRadarTerrain(Actor self) + { + world = self.World; + shroud = self.Trait(); + shroud.CellsChanged += OnShroudCellsChanged; + } + + void OnShroudCellsChanged(IEnumerable puvs) + { + foreach (var puv in puvs) + { + foreach (var uv in world.Map.Unproject(puv)) + { + if (dirtyTerrainCells.Contains(uv)) + { + UpdateTerrainCellColor(uv); + dirtyTerrainCells.Remove(uv); + } + } + } + } + + void UpdateTerrainCell(CPos cell) + { + var uv = cell.ToMPos(world.Map); + if (!world.Map.CustomTerrain.Contains(uv)) + return; + + if (!shroud.IsVisible(uv)) + dirtyTerrainCells.Add(uv); + else + UpdateTerrainCellColor(uv); + } + + void UpdateTerrainCellColor(MPos uv) + { + terrainColor[uv] = GetColor(world.Map, uv); + + if (CellTerrainColorChanged != null) + CellTerrainColorChanged(uv); + } + + public void WorldLoaded(World w, WorldRenderer wr) + { + terrainColor = new CellLayer>(w.Map); + + w.AddFrameEndTask(_ => + { + // Set initial terrain data + foreach (var uv in world.Map.AllCells.MapCoords) + UpdateTerrainCellColor(uv); + + world.Map.Tiles.CellEntryChanged += UpdateTerrainCell; + world.Map.CustomTerrain.CellEntryChanged += UpdateTerrainCell; + + IsInitialized = true; + }); + } + + public Pair this[MPos uv] + { + get { return terrainColor[uv]; } + } + + public static Pair GetColor(Map map, MPos uv) + { + var custom = map.CustomTerrain[uv]; + int leftColor, rightColor; + if (custom == byte.MaxValue) + { + var type = map.Rules.TileSet.GetTileInfo(map.Tiles[uv]); + leftColor = type != null ? type.LeftColor.ToArgb() : Color.Black.ToArgb(); + rightColor = type != null ? type.RightColor.ToArgb() : Color.Black.ToArgb(); + } + else + leftColor = rightColor = map.Rules.TileSet[custom].Color.ToArgb(); + + return Pair.New(leftColor, rightColor); + } + } +} diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 7f28d2c57d..f803cc0eb0 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -57,6 +57,8 @@ namespace OpenRA.Mods.Common.Widgets Sprite actorSprite; Sprite shroudSprite; Shroud renderShroud; + PlayerRadarTerrain playerRadarTerrain; + Player currentPlayer; public string SoundUp { get; private set; } public string SoundDown { get; private set; } @@ -66,7 +68,9 @@ namespace OpenRA.Mods.Common.Widgets { this.world = world; this.worldRenderer = worldRenderer; + radarPings = world.WorldActor.TraitOrDefault(); + MarkShroudDirty(world.Map.AllCells.MapCoords.Select(uv => (PPos)uv)); isRectangularIsometric = world.Map.Grid.Type == MapGridType.RectangularIsometric; cellWidth = isRectangularIsometric ? 2 : 1; @@ -76,6 +80,11 @@ namespace OpenRA.Mods.Common.Widgets previewWidth = 2 * previewWidth - 1; } + void CellTerrainColorChanged(MPos puv) + { + UpdateTerrainColor(puv); + } + public override void Initialize(WidgetArgs args) { base.Initialize(args); @@ -87,12 +96,72 @@ namespace OpenRA.Mods.Common.Widgets MapBoundsChanged(); - // Set initial terrain data - foreach (var cell in world.Map.AllCells) - UpdateTerrainCell(cell); + if (world.Type == WorldType.Regular) + SetPlayer(world.LocalPlayer ?? world.RenderPlayer); + else + { + // Set initial terrain data + foreach (var uv in world.Map.AllCells.MapCoords) + UpdateTerrainColor(uv); - world.Map.Tiles.CellEntryChanged += UpdateTerrainCell; - world.Map.CustomTerrain.CellEntryChanged += UpdateTerrainCell; + world.Map.Tiles.CellEntryChanged += UpdateTerrainCell; + world.Map.CustomTerrain.CellEntryChanged += UpdateTerrainCell; + } + + world.RenderPlayerChanged += WorldOnRenderPlayerChanged; + } + + void UpdateTerrainCell(CPos cell) + { + var uv = cell.ToMPos(world.Map); + + UpdateTerrainColor(uv); + } + + void WorldOnRenderPlayerChanged(Player player) + { + SetPlayer(player); + + // Set initial terrain data + foreach (var uv in world.Map.AllCells.MapCoords) + UpdateTerrainColor(uv); + } + + void SetPlayer(Player player) + { + currentPlayer = player; + + var newRenderShroud = player != null ? player.Shroud : null; + if (newRenderShroud != renderShroud) + { + if (renderShroud != null) + renderShroud.CellsChanged -= MarkShroudDirty; + + if (newRenderShroud != null) + { + // Redraw the full shroud sprite + MarkShroudDirty(world.Map.AllCells.MapCoords.Select(uv => (PPos)uv)); + + // Update the notification binding + newRenderShroud.CellsChanged += MarkShroudDirty; + } + + renderShroud = newRenderShroud; + } + + var newPlayerRadarTerrain = + currentPlayer != null ? currentPlayer.PlayerActor.TraitOrDefault() : null; + + if (newPlayerRadarTerrain != playerRadarTerrain) + { + if (playerRadarTerrain != null) + playerRadarTerrain.CellTerrainColorChanged -= CellTerrainColorChanged; + + if (newPlayerRadarTerrain != null) + newPlayerRadarTerrain.CellTerrainColorChanged += CellTerrainColorChanged; + + playerRadarTerrain = newPlayerRadarTerrain; + } } void MapBoundsChanged() @@ -132,23 +201,11 @@ namespace OpenRA.Mods.Common.Widgets actorSprite = new Sprite(radarSheet, new Rectangle(b.Location + new Size(0, previewHeight), b.Size), TextureChannel.RGBA); } - void UpdateTerrainCell(CPos cell) + void UpdateTerrainColor(MPos uv) { - var uv = cell.ToMPos(world.Map); - - if (!world.Map.CustomTerrain.Contains(uv)) - return; - - var custom = world.Map.CustomTerrain[uv]; - int leftColor, rightColor; - if (custom == byte.MaxValue) - { - var type = world.Map.Rules.TileSet.GetTileInfo(world.Map.Tiles[uv]); - leftColor = type != null ? type.LeftColor.ToArgb() : Color.Black.ToArgb(); - rightColor = type != null ? type.RightColor.ToArgb() : Color.Black.ToArgb(); - } - else - leftColor = rightColor = world.Map.Rules.TileSet[custom].Color.ToArgb(); + var colorPair = playerRadarTerrain != null && playerRadarTerrain.IsInitialized ? playerRadarTerrain[uv] : PlayerRadarTerrain.GetColor(world.Map, uv); + var leftColor = colorPair.First; + var rightColor = colorPair.Second; var stride = radarSheet.Size.Width; @@ -341,25 +398,6 @@ namespace OpenRA.Mods.Common.Widgets if (enabled) { - var rp = world.RenderPlayer; - var newRenderShroud = rp != null ? rp.Shroud : null; - if (newRenderShroud != renderShroud) - { - if (renderShroud != null) - renderShroud.CellsChanged -= MarkShroudDirty; - - if (newRenderShroud != null) - { - // Redraw the full shroud sprite - MarkShroudDirty(world.Map.AllCells.MapCoords.Select(uv => (PPos)uv)); - - // Update the notification binding - newRenderShroud.CellsChanged += MarkShroudDirty; - } - - renderShroud = newRenderShroud; - } - // The actor layer is updated every tick var stride = radarSheet.Size.Width; Array.Clear(radarData, 4 * actorSprite.Bounds.Top * stride, 4 * actorSprite.Bounds.Height * stride); @@ -451,8 +489,11 @@ namespace OpenRA.Mods.Common.Widgets public override void Removed() { base.Removed(); - world.Map.Tiles.CellEntryChanged -= UpdateTerrainCell; - world.Map.CustomTerrain.CellEntryChanged -= UpdateTerrainCell; + + if (playerRadarTerrain != null) + playerRadarTerrain.CellTerrainColorChanged -= CellTerrainColorChanged; + + world.RenderPlayerChanged -= WorldOnRenderPlayerChanged; Dispose(); } diff --git a/mods/cnc/rules/player.yaml b/mods/cnc/rules/player.yaml index a9854749f0..d6bf155de5 100644 --- a/mods/cnc/rules/player.yaml +++ b/mods/cnc/rules/player.yaml @@ -60,3 +60,4 @@ Player: PlayerExperience: ConditionManager: GameSaveViewportManager: + PlayerRadarTerrain: diff --git a/mods/d2k/rules/player.yaml b/mods/d2k/rules/player.yaml index 4c723114d6..270073b163 100644 --- a/mods/d2k/rules/player.yaml +++ b/mods/d2k/rules/player.yaml @@ -152,3 +152,4 @@ Player: PlayerExperience: ConditionManager: GameSaveViewportManager: + PlayerRadarTerrain: diff --git a/mods/ra/rules/player.yaml b/mods/ra/rules/player.yaml index f3e118c6b3..daf30d5be8 100644 --- a/mods/ra/rules/player.yaml +++ b/mods/ra/rules/player.yaml @@ -151,3 +151,4 @@ Player: PlayerExperience: ConditionManager: GameSaveViewportManager: + PlayerRadarTerrain: diff --git a/mods/ts/rules/player.yaml b/mods/ts/rules/player.yaml index 3975ca18b4..9dd6874209 100644 --- a/mods/ts/rules/player.yaml +++ b/mods/ts/rules/player.yaml @@ -124,3 +124,4 @@ Player: PlayerExperience: ConditionManager: GameSaveViewportManager: + PlayerRadarTerrain: