diff --git a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs index 7e85f79a1e..1c4c8ee924 100644 --- a/OpenRA.Game/Traits/Player/FrozenActorLayer.cs +++ b/OpenRA.Game/Traits/Player/FrozenActorLayer.cs @@ -155,14 +155,15 @@ namespace OpenRA.Traits // PERF: Avoid LINQ. foreach (var puv in Footprint) { - if (shroud.IsVisible(puv)) + var cv = shroud.GetVisibility(puv); + if (cv.HasFlag(Shroud.CellVisibility.Visible)) { Visible = false; Shrouded = false; break; } - if (Shrouded && shroud.IsExplored(puv)) + if (Shrouded && cv.HasFlag(Shroud.CellVisibility.Explored)) Shrouded = false; } diff --git a/OpenRA.Game/Traits/Player/Shroud.cs b/OpenRA.Game/Traits/Player/Shroud.cs index 01b050871d..d6a4744b20 100644 --- a/OpenRA.Game/Traits/Player/Shroud.cs +++ b/OpenRA.Game/Traits/Player/Shroud.cs @@ -83,6 +83,10 @@ namespace OpenRA.Traits } } + // Visible is not a super set of Explored. IsExplored may return false even if IsVisible returns true. + [Flags] + public enum CellVisibility : byte { Hidden = 0x0, Explored = 0x1, Visible = 0x2 } + readonly Actor self; readonly ShroudInfo info; readonly Map map; @@ -423,5 +427,53 @@ namespace OpenRA.Traits // about explored here: any of the CellLayers would have been suitable. return explored.Contains(uv); } + + // PERF: Combine IsExplored and IsVisible. + public CellVisibility GetVisibility(PPos puv) + { + var state = CellVisibility.Hidden; + + if (Disabled) + { + if (FogEnabled) + { + // Shroud disabled, Fog enabled + if (resolvedType.Contains(puv)) + { + state |= CellVisibility.Explored; + + if (resolvedType[puv] == ShroudCellType.Visible) + state |= CellVisibility.Visible; + } + } + else if (map.Contains(puv)) + state |= CellVisibility.Explored | CellVisibility.Visible; + } + else + { + if (FogEnabled) + { + // Shroud and Fog enabled + if (resolvedType.Contains(puv)) + { + var rt = resolvedType[puv]; + if (rt == ShroudCellType.Visible) + state |= CellVisibility.Explored | CellVisibility.Visible; + else if (rt > ShroudCellType.Shroud) + state |= CellVisibility.Explored; + } + } + else if (resolvedType.Contains(puv)) + { + // We do not set Explored since IsExplored may return false. + state |= CellVisibility.Visible; + + if (resolvedType[puv] > ShroudCellType.Shroud) + state |= CellVisibility.Explored; + } + } + + return state; + } } } diff --git a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs index 9281cb03e4..c91319cf49 100644 --- a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs @@ -81,6 +81,19 @@ namespace OpenRA.Mods.Common.Traits All = Top | Right | Bottom | Left } + // Index into neighbors array. + enum Neighbor + { + Top = 0, + Right, + Bottom, + Left, + TopLeft, + TopRight, + BottomRight, + BottomLeft + } + readonly struct TileInfo { public readonly float3 ScreenPosition; @@ -96,17 +109,20 @@ namespace OpenRA.Mods.Common.Traits readonly ShroudRendererInfo info; readonly World world; readonly Map map; - readonly Edges notVisibleEdges; + readonly (Edges, Edges) notVisibleEdgesPair; readonly byte variantStride; readonly byte[] edgesToSpriteIndexOffset; + // PERF: Allocate once. + readonly Shroud.CellVisibility[] neighbors = new Shroud.CellVisibility[8]; + readonly CellLayer tileInfos; readonly CellLayer cellsDirty; bool anyCellDirty; readonly (Sprite Sprite, float Scale, float Alpha)[] fogSprites, shroudSprites; Shroud shroud; - Func visibleUnderShroud, visibleUnderFog; + Func cellVisibility; TerrainSpriteLayer shroudLayer, fogLayer; PaletteReference shroudPaletteReference, fogPaletteReference; bool disposed; @@ -161,16 +177,26 @@ namespace OpenRA.Mods.Common.Traits } } + int spriteCount; + if (info.UseExtendedIndex) + { + notVisibleEdgesPair = (Edges.AllSides, Edges.AllSides); + spriteCount = (int)Edges.All; + } + else + { + notVisibleEdgesPair = (Edges.AllCorners, Edges.AllCorners); + spriteCount = (int)Edges.AllCorners; + } + // Mapping of shrouded directions -> sprite index - edgesToSpriteIndexOffset = new byte[(byte)(info.UseExtendedIndex ? Edges.All : Edges.AllCorners) + 1]; + edgesToSpriteIndexOffset = new byte[spriteCount + 1]; for (var i = 0; i < info.Index.Length; i++) edgesToSpriteIndexOffset[info.Index[i]] = (byte)i; if (info.OverrideFullShroud != null) edgesToSpriteIndexOffset[info.OverrideShroudIndex] = (byte)(variantStride - 1); - notVisibleEdges = info.UseExtendedIndex ? Edges.AllSides : Edges.AllCorners; - world.RenderPlayerChanged += WorldOnRenderPlayerChanged; } @@ -188,11 +214,9 @@ namespace OpenRA.Mods.Common.Traits // All tiles are visible in the editor if (w.Type == WorldType.Editor) - visibleUnderShroud = _ => true; + cellVisibility = puv => Shroud.CellVisibility.Visible; else - visibleUnderShroud = puv => map.Contains(puv); - - visibleUnderFog = puv => map.Contains(puv); + cellVisibility = puv => (map.Contains(puv) ? Shroud.CellVisibility.Visible | Shroud.CellVisibility.Explored : Shroud.CellVisibility.Hidden); var shroudBlend = shroudSprites[0].Sprite.BlendMode; if (shroudSprites.Any(s => s.Sprite.BlendMode != shroudBlend)) @@ -211,33 +235,61 @@ namespace OpenRA.Mods.Common.Traits WorldOnRenderPlayerChanged(world.RenderPlayer); } - Edges GetEdges(PPos puv, Func isVisible) + Shroud.CellVisibility[] GetNeighborsVisbility(PPos puv) { - if (!isVisible(puv)) - return notVisibleEdges; - var cell = ((MPos)puv).ToCPos(map); + neighbors[(int)Neighbor.Top] = cellVisibility((PPos)(cell + new CVec(0, -1)).ToMPos(map)); + neighbors[(int)Neighbor.Right] = cellVisibility((PPos)(cell + new CVec(1, 0)).ToMPos(map)); + neighbors[(int)Neighbor.Bottom] = cellVisibility((PPos)(cell + new CVec(0, 1)).ToMPos(map)); + neighbors[(int)Neighbor.Left] = cellVisibility((PPos)(cell + new CVec(-1, 0)).ToMPos(map)); + neighbors[(int)Neighbor.TopLeft] = cellVisibility((PPos)(cell + new CVec(-1, -1)).ToMPos(map)); + neighbors[(int)Neighbor.TopRight] = cellVisibility((PPos)(cell + new CVec(1, -1)).ToMPos(map)); + neighbors[(int)Neighbor.BottomRight] = cellVisibility((PPos)(cell + new CVec(1, 1)).ToMPos(map)); + neighbors[(int)Neighbor.BottomLeft] = cellVisibility((PPos)(cell + new CVec(-1, 1)).ToMPos(map)); + + return neighbors; + } + + Edges GetEdges(Shroud.CellVisibility[] neighbors, Shroud.CellVisibility visibleMask) + { // If a side is shrouded then we also count the corners. - var edge = Edges.None; - if (!isVisible((PPos)(cell + new CVec(0, -1)).ToMPos(map))) edge |= Edges.Top; - if (!isVisible((PPos)(cell + new CVec(1, 0)).ToMPos(map))) edge |= Edges.Right; - if (!isVisible((PPos)(cell + new CVec(0, 1)).ToMPos(map))) edge |= Edges.Bottom; - if (!isVisible((PPos)(cell + new CVec(-1, 0)).ToMPos(map))) edge |= Edges.Left; + var edges = Edges.None; + if ((neighbors[(int)Neighbor.Top] & visibleMask) == 0) edges |= Edges.Top; + if ((neighbors[(int)Neighbor.Right] & visibleMask) == 0) edges |= Edges.Right; + if ((neighbors[(int)Neighbor.Bottom] & visibleMask) == 0) edges |= Edges.Bottom; + if ((neighbors[(int)Neighbor.Left] & visibleMask) == 0) edges |= Edges.Left; - var ucorner = edge & Edges.AllCorners; - if (!isVisible((PPos)(cell + new CVec(-1, -1)).ToMPos(map))) edge |= Edges.TopLeft; - if (!isVisible((PPos)(cell + new CVec(1, -1)).ToMPos(map))) edge |= Edges.TopRight; - if (!isVisible((PPos)(cell + new CVec(1, 1)).ToMPos(map))) edge |= Edges.BottomRight; - if (!isVisible((PPos)(cell + new CVec(-1, 1)).ToMPos(map))) edge |= Edges.BottomLeft; + var ucorner = edges & Edges.AllCorners; + if ((neighbors[(int)Neighbor.TopLeft] & visibleMask) == 0) edges |= Edges.TopLeft; + if ((neighbors[(int)Neighbor.TopRight] & visibleMask) == 0) edges |= Edges.TopRight; + if ((neighbors[(int)Neighbor.BottomRight] & visibleMask) == 0) edges |= Edges.BottomRight; + if ((neighbors[(int)Neighbor.BottomLeft] & visibleMask) == 0) edges |= Edges.BottomLeft; // RA provides a set of frames for tiles with shrouded // corners but unshrouded edges. We want to detect this // situation without breaking the edge -> corner enabling // in other combinations. The XOR turns off the corner - // bits that are enabled twice, which gives the behavior + // bits that are enabled twice, which gives the sprite offset // we want here. - return info.UseExtendedIndex ? edge ^ ucorner : edge & Edges.AllCorners; + return info.UseExtendedIndex ? edges ^ ucorner : edges & Edges.AllCorners; + } + + (Edges, Edges) GetEdges(PPos puv) + { + var cv = cellVisibility(puv); + + // If a cell is covered by shroud, then all neigbhors are covered by shroud and fog. + if (cv == Shroud.CellVisibility.Hidden) + return notVisibleEdgesPair; + + var ncv = GetNeighborsVisbility(puv); + + // If a cell is covered by fog, then all neigbhors are as well. + var edgesFog = cv.HasFlag(Shroud.CellVisibility.Visible) ? GetEdges(ncv, Shroud.CellVisibility.Visible) : notVisibleEdgesPair.Item2; + + var edgesShroud = GetEdges(ncv, Shroud.CellVisibility.Explored | Shroud.CellVisibility.Visible); + return (edgesShroud, edgesFog); } void WorldOnRenderPlayerChanged(Player player) @@ -251,14 +303,13 @@ namespace OpenRA.Mods.Common.Traits if (newShroud != null) { - visibleUnderShroud = puv => newShroud.IsExplored(puv); - visibleUnderFog = puv => newShroud.IsVisible(puv); + cellVisibility = puv => newShroud.GetVisibility(puv); newShroud.OnShroudChanged += UpdateShroudCell; } else { - visibleUnderShroud = puv => map.Contains(puv); - visibleUnderFog = puv => map.Contains(puv); + // Visible under shroud: Explored. Visible under fog: Visible. + cellVisibility = puv => (map.Contains(puv) ? Shroud.CellVisibility.Visible | Shroud.CellVisibility.Explored : Shroud.CellVisibility.Hidden); } shroud = newShroud; @@ -287,12 +338,13 @@ namespace OpenRA.Mods.Common.Traits cellsDirty[uv] = false; var tileInfo = tileInfos[uv]; - var shroudSprite = GetSprite(shroudSprites, GetEdges(puv, visibleUnderShroud), tileInfo.Variant); + var (edgesShroud, edgesFog) = GetEdges(puv); + var shroudSprite = GetSprite(shroudSprites, edgesShroud, tileInfo.Variant); var shroudPos = tileInfo.ScreenPosition; if (shroudSprite.Sprite != null) shroudPos += shroudSprite.Sprite.Offset - 0.5f * shroudSprite.Sprite.Size; - var fogSprite = GetSprite(fogSprites, GetEdges(puv, visibleUnderFog), tileInfo.Variant); + var fogSprite = GetSprite(fogSprites, edgesFog, tileInfo.Variant); var fogPos = tileInfo.ScreenPosition; if (fogSprite.Sprite != null) fogPos += fogSprite.Sprite.Offset - 0.5f * fogSprite.Sprite.Size; diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index dfe998974a..6664ccf06e 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -22,6 +22,9 @@ namespace OpenRA.Mods.Common.Widgets { public sealed class RadarWidget : Widget, IDisposable { + public readonly int ColorFog = Color.FromArgb(128, Color.Black).ToArgb(); + public readonly int ColorShroud = Color.Black.ToArgb(); + public string WorldInteractionController = null; public int AnimationLength = 5; public string RadarOnlineSound = null; @@ -240,10 +243,11 @@ namespace OpenRA.Mods.Common.Widgets void UpdateShroudCell(PPos puv) { var color = 0; - if (!currentPlayer.Shroud.IsExplored(puv)) - color = Color.Black.ToArgb(); - else if (!currentPlayer.Shroud.IsVisible(puv)) - color = Color.FromArgb(128, Color.Black).ToArgb(); + var cv = currentPlayer.Shroud.GetVisibility(puv); + if (cv == Shroud.CellVisibility.Hidden) + color = ColorShroud; + else if (cv.HasFlag(Shroud.CellVisibility.Visible)) + color = ColorFog; var stride = radarSheet.Size.Width; unsafe