diff --git a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs index 9fd3b0f47b..14d8b1ba4e 100644 --- a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs +++ b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs @@ -95,9 +95,8 @@ namespace OpenRA.Graphics var cells = viewport.VisibleCells; // Only draw the rows that are visible. - // VisibleCells is clamped to the map, so additional checks are unnecessary - var firstRow = cells.TopLeft.ToMPos(map).V; - var lastRow = cells.BottomRight.ToMPos(map).V + 1; + var firstRow = cells.MapCoords.TopLeft.V; + var lastRow = Math.Min(cells.MapCoords.BottomRight.V + 1, map.MapSize.Y); Game.Renderer.Flush(); diff --git a/OpenRA.Game/Graphics/Viewport.cs b/OpenRA.Game/Graphics/Viewport.cs index 3ab14e4bbe..cf5b369780 100644 --- a/OpenRA.Game/Graphics/Viewport.cs +++ b/OpenRA.Game/Graphics/Viewport.cs @@ -40,6 +40,7 @@ namespace OpenRA.Graphics readonly Rectangle mapBounds; readonly int maxGroundHeight; + readonly Size tileSize; // Viewport geometry (world-px) public int2 CenterLocation { get; private set; } @@ -90,17 +91,18 @@ namespace OpenRA.Graphics { worldRenderer = wr; - // Calculate map bounds in world-px - var b = map.Bounds; + var cells = wr.World.Type == WorldType.Editor ? + map.AllCells : map.CellsInsideBounds; - // Expand to corners of cells - var tl = wr.ScreenPxPosition(map.CenterOfCell(new MPos(b.Left, b.Top).ToCPos(map)) - new WVec(512, 512, 0)); - var br = wr.ScreenPxPosition(map.CenterOfCell(new MPos(b.Right, b.Bottom).ToCPos(map)) + new WVec(511, 511, 0)); + // Calculate map bounds in world-px + var tl = wr.ScreenPxPosition(map.CenterOfCell(cells.TopLeft) - new WVec(512, 512, 0)); + var br = wr.ScreenPxPosition(map.CenterOfCell(cells.BottomRight) + new WVec(511, 511, 0)); mapBounds = Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y); maxGroundHeight = wr.World.TileSet.MaxGroundHeight; CenterLocation = (tl + br) / 2; Zoom = Game.Settings.Graphics.PixelDouble ? 2 : 1; + tileSize = Game.ModData.Manifest.TileSize; } public CPos ViewToWorld(int2 view) @@ -211,7 +213,10 @@ namespace OpenRA.Graphics // Convert to screen coordinates var tl = WorldToViewPx(worldRenderer.ScreenPxPosition(ctl - new WVec(0, 0, ctl.Z))).Clamp(ScreenClip); var br = WorldToViewPx(worldRenderer.ScreenPxPosition(cbr - new WVec(0, 0, cbr.Z))).Clamp(ScreenClip); - return Rectangle.FromLTRB(tl.X, tl.Y, br.X, br.Y); + + // Add an extra one cell fudge in each direction for safety + return Rectangle.FromLTRB(tl.X - tileSize.Width, tl.Y - tileSize.Height, + br.X + tileSize.Width, br.Y + tileSize.Height); } } @@ -225,6 +230,11 @@ namespace OpenRA.Graphics var wtl = worldRenderer.Position(TopLeft); var wbr = worldRenderer.Position(BottomRight); + // Map editor shows the full map (including the area outside the regular bounds) + Func clamp = map.Clamp; + if (worldRenderer.World.Type == WorldType.Editor) + clamp = map.MapTiles.Value.Clamp; + // Due to diamond tile staggering, we need to adjust the top-left bounds outwards by half a cell. if (map.TileShape == TileShape.Diamond) wtl -= new WVec(512, 512, 0); @@ -234,11 +244,11 @@ namespace OpenRA.Graphics var ctl = new MPos(wtl.X / 1024, wtl.Y / dy); var cbr = new MPos(wbr.X / 1024, wbr.Y / dy); - var tl = map.Clamp(ctl.ToCPos(map)); + var tl = clamp(ctl).ToCPos(map.TileShape); // Also need to account for height of cells in rows below the bottom. - var heightPadding = map.TileShape == TileShape.Diamond ? 2 : 0; - var br = map.Clamp(new MPos(cbr.U, cbr.V + heightPadding + maxGroundHeight / 2).ToCPos(map)); + var heightPadding = map.TileShape == TileShape.Diamond ? 3 : 0; + var br = clamp(new MPos(cbr.U, cbr.V + heightPadding + maxGroundHeight / 2 + 1)).ToCPos(map.TileShape); cells = new CellRegion(map.TileShape, tl, br); cellsDirty = false; diff --git a/OpenRA.Game/Map/CellLayer.cs b/OpenRA.Game/Map/CellLayer.cs index 6f5112307e..6825ecb439 100644 --- a/OpenRA.Game/Map/CellLayer.cs +++ b/OpenRA.Game/Map/CellLayer.cs @@ -125,10 +125,31 @@ namespace OpenRA return GetEnumerator(); } + public bool Contains(CPos cell) + { + // .ToMPos() returns the same result if the X and Y coordinates + // are switched. X < Y is invalid in the Diamond coordinate system, + // so we pre-filter these to avoid returning the wrong result + if (Shape == TileShape.Diamond && cell.X < cell.Y) + return false; + + return Contains(cell.ToMPos(Shape)); + } + public bool Contains(MPos uv) { return bounds.Contains(uv.U, uv.V); } + + public CPos Clamp(CPos uv) + { + return Clamp(uv.ToMPos(Shape)).ToCPos(Shape); + } + + public MPos Clamp(MPos uv) + { + return uv.Clamp(new Rectangle(0, 0, Size.Width - 1, Size.Height - 1)); + } } // Helper functions diff --git a/OpenRA.Game/Map/CellRegion.cs b/OpenRA.Game/Map/CellRegion.cs index bae6d7e0bd..7920498a0a 100644 --- a/OpenRA.Game/Map/CellRegion.cs +++ b/OpenRA.Game/Map/CellRegion.cs @@ -217,6 +217,9 @@ namespace OpenRA { return GetEnumerator(); } + + public MPos TopLeft { get { return r.mapTopLeft; } } + public MPos BottomRight { get { return r.mapBottomRight; } } } } } diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index 142d169a37..c733e3d90e 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -630,6 +630,12 @@ namespace OpenRA public bool Contains(CPos cell) { + // .ToMPos() returns the same result if the X and Y coordinates + // are switched. X < Y is invalid in the Diamond coordinate system, + // so we pre-filter these to avoid returning the wrong result + if (TileShape == TileShape.Diamond && cell.X < cell.Y) + return false; + return Contains(cell.ToMPos(this)); } @@ -772,6 +778,12 @@ namespace OpenRA return cell.ToMPos(this).Clamp(bounds).ToCPos(this); } + public MPos Clamp(MPos uv) + { + var bounds = new Rectangle(Bounds.X, Bounds.Y, Bounds.Width - 1, Bounds.Height - 1); + return uv.Clamp(bounds); + } + public CPos ChooseRandomCell(MersenneTwister rand) { var x = rand.Next(Bounds.Left, Bounds.Right); diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs index 0bf3e7cec9..ee4436a73e 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs @@ -36,6 +36,7 @@ namespace OpenRA.Mods.Common.Widgets readonly CVec locationOffset; readonly WVec previewOffset; readonly PlayerReference owner; + readonly CVec[] footprint; int facing = 92; @@ -67,6 +68,14 @@ namespace OpenRA.Mods.Common.Widgets td.Add(new RaceInit(owner.Race)); preview.SetPreview(actor, td); + var ios = actor.Traits.GetOrDefault(); + if (ios != null) + footprint = ios.OccupiedCells(actor, CPos.Zero) + .Select(c => c.Key - CPos.Zero) + .ToArray(); + else + footprint = new CVec[0]; + // The preview widget may be rendered by the higher-level code before it is ticked. // Force a manual tick to ensure the bounds are set correctly for this first draw. Tick(); @@ -85,8 +94,12 @@ namespace OpenRA.Mods.Common.Widgets } var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); - if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down && world.Map.Contains(cell)) + if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down) { + // Check the actor is inside the map + if (!footprint.All(c => world.Map.MapTiles.Value.Contains(cell + locationOffset + c))) + return true; + var newActorReference = new ActorReference(Actor.Name); newActorReference.Add(new OwnerInit(owner.Name)); diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs index 5a8178192a..d8cf8430ba 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorDefaultBrush.cs @@ -65,10 +65,11 @@ namespace OpenRA.Mods.Common.Widgets var underCursor = editorLayer.PreviewsAt(worldRenderer.Viewport.ViewToWorldPx(mi.Location)) .FirstOrDefault(); + var mapResources = world.Map.MapResources.Value; ResourceType type; if (underCursor != null) editorWidget.SetTooltip(underCursor.Tooltip); - else if (world.Map.Contains(cell) && resources.TryGetValue(world.Map.MapResources.Value[cell].Type, out type)) + else if (mapResources.Contains(cell) && resources.TryGetValue(mapResources[cell].Type, out type)) editorWidget.SetTooltip(type.Info.Name); else editorWidget.SetTooltip(null); @@ -84,8 +85,8 @@ namespace OpenRA.Mods.Common.Widgets if (underCursor != null) editorLayer.Remove(underCursor); - if (world.Map.MapResources.Value[cell].Type != 0) - world.Map.MapResources.Value[cell] = new ResourceTile(); + if (mapResources.Contains(cell) && mapResources[cell].Type != 0) + mapResources[cell] = new ResourceTile(); } else if (mi.Button == MouseButton.Left && mi.Event == MouseInputEvent.Down) { diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs index bfd73fe4f6..965d4285c6 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorResourceBrush.cs @@ -82,7 +82,8 @@ namespace OpenRA.Mods.Common.Widgets public bool AllowResourceAt(CPos cell) { - if (!world.Map.Contains(cell)) + var mapResources = world.Map.MapResources.Value; + if (!mapResources.Contains(cell)) return false; var tile = world.Map.MapTiles.Value[cell]; @@ -92,7 +93,7 @@ namespace OpenRA.Mods.Common.Widgets var terrainType = world.TileSet.TerrainInfo[tileInfo.TerrainType]; - if (world.Map.MapResources.Value[cell].Type == ResourceType.ResourceType) + if (mapResources[cell].Type == ResourceType.ResourceType) return false; if (!ResourceType.AllowedTerrainTypes.Contains(terrainType.Type)) diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs index a6984376fc..8ea5e2ca16 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs @@ -79,6 +79,8 @@ namespace OpenRA.Mods.Common.Widgets return true; var map = world.Map; + var mapTiles = map.MapTiles.Value; + var mapHeight = map.MapHeight.Value; var cell = worldRenderer.Viewport.ViewToWorld(mi.Location); if (mi.Event != MouseInputEvent.Down && mi.Event != MouseInputEvent.Move) @@ -87,7 +89,7 @@ namespace OpenRA.Mods.Common.Widgets var rules = map.Rules; var tileset = rules.TileSets[map.Tileset]; var template = tileset.Templates[Template]; - var baseHeight = map.Contains(cell) ? map.MapHeight.Value[cell] : (byte)0; + var baseHeight = mapHeight.Contains(cell) ? mapHeight[cell] : (byte)0; if (mi.Event == MouseInputEvent.Move && PlacementOverlapsSameTemplate(template, cell)) return true; @@ -100,11 +102,11 @@ namespace OpenRA.Mods.Common.Widgets { var index = template.PickAny ? (byte)Game.CosmeticRandom.Next(0, template.TilesCount) : (byte)i; var c = cell + new CVec(x, y); - if (!map.Contains(c)) + if (!mapTiles.Contains(c)) continue; - map.MapTiles.Value[c] = new TerrainTile(Template, index); - map.MapHeight.Value[c] = (byte)(baseHeight + template[index].Height).Clamp(0, world.TileSet.MaxGroundHeight); + mapTiles[c] = new TerrainTile(Template, index); + mapHeight[c] = (byte)(baseHeight + template[index].Height).Clamp(0, world.TileSet.MaxGroundHeight); } } } @@ -115,6 +117,7 @@ namespace OpenRA.Mods.Common.Widgets bool PlacementOverlapsSameTemplate(TerrainTemplateInfo template, CPos cell) { var map = world.Map; + var mapTiles = map.MapTiles.Value; var i = 0; for (var y = 0; y < template.Size.Y; y++) { @@ -123,7 +126,7 @@ namespace OpenRA.Mods.Common.Widgets if (template.Contains(i) && template[i] != null) { var c = cell + new CVec(x, y); - if (map.Contains(c) && map.MapTiles.Value[c].Type == template.Id) + if (mapTiles.Contains(c) && mapTiles[c].Type == template.Id) return true; } } diff --git a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs index 1c6ba077f5..f90f549466 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs @@ -86,11 +86,7 @@ namespace OpenRA.Mods.Common.Traits // so we must also touch all the neighbouring tiles Dirty.Add(cell); foreach (var d in CVec.Directions) - { - var c = cell + d; - if (Map.Contains(c)) - Dirty.Add(c); - } + Dirty.Add(cell + d); } protected virtual string ChooseRandomVariant(ResourceType t) @@ -103,10 +99,16 @@ namespace OpenRA.Mods.Common.Traits // Set density based on the number of neighboring resources var adjacent = 0; var type = Tiles[c].Type; + var resources = Map.MapResources.Value; for (var u = -1; u < 2; u++) + { for (var v = -1; v < 2; v++) - if (Map.MapResources.Value[c + new CVec(u, v)].Type == type.Info.ResourceType) + { + var cell = c + new CVec(u, v); + if (resources.Contains(cell) && resources[cell].Type == type.Info.ResourceType) adjacent++; + } + } return Math.Max(int2.Lerp(0, type.Info.MaxDensity, adjacent, 9), 1); } @@ -139,7 +141,8 @@ namespace OpenRA.Mods.Common.Traits return; foreach (var c in Dirty) - Tiles[c] = UpdateDirtyTile(c); + if (Tiles.Contains(c)) + Tiles[c] = UpdateDirtyTile(c); Dirty.Clear(); diff --git a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs index 84a16d522a..5827cdb329 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs @@ -50,9 +50,14 @@ namespace OpenRA.Mods.Common.Traits { var sum = 0; for (var u = -1; u < 2; u++) + { for (var v = -1; v < 2; v++) - if (content[cell + new CVec(u, v)].Type == t) + { + var c = cell + new CVec(u, v); + if (content.Contains(c) && content[c].Type == t) ++sum; + } + } return sum; } diff --git a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs index 12b84a31be..c08c3a010e 100644 --- a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs @@ -164,7 +164,13 @@ namespace OpenRA.Mods.Common.Traits } DirtyCells(map.AllCells); - visibleUnderShroud = map.Contains; + + // All tiles are visible in the editor + if (w.Type == WorldType.Editor) + visibleUnderShroud = _ => true; + else + visibleUnderShroud = map.Contains; + visibleUnderFog = map.Contains; var shroudSheet = shroudSprites[0].Sheet; diff --git a/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs b/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs index f46354380f..0c2f3cb538 100644 --- a/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs +++ b/OpenRA.Mods.D2k/Traits/World/D2kEditorResourceLayer.cs @@ -65,31 +65,36 @@ namespace OpenRA.Mods.D2k.Traits return D2kResourceLayer.Variants.Keys.Random(Game.CosmeticRandom); } + bool CellContains(CPos c, ResourceType t) + { + return Tiles.Contains(c) && Tiles[c].Type == t; + } + ClearSides FindClearSides(ResourceType t, CPos p) { var ret = ClearSides.None; - if (Tiles[p + new CVec(0, -1)].Type != t) + if (!CellContains(p + new CVec(0, -1), t)) ret |= ClearSides.Top | ClearSides.TopLeft | ClearSides.TopRight; - if (Tiles[p + new CVec(-1, 0)].Type != t) + if (!CellContains(p + new CVec(-1, 0), t)) ret |= ClearSides.Left | ClearSides.TopLeft | ClearSides.BottomLeft; - if (Tiles[p + new CVec(1, 0)].Type != t) + if (!CellContains(p + new CVec(1, 0), t)) ret |= ClearSides.Right | ClearSides.TopRight | ClearSides.BottomRight; - if (Tiles[p + new CVec(0, 1)].Type != t) + if (!CellContains(p + new CVec(0, 1), t)) ret |= ClearSides.Bottom | ClearSides.BottomLeft | ClearSides.BottomRight; - if (Tiles[p + new CVec(-1, -1)].Type != t) + if (!CellContains(p + new CVec(-1, -1), t)) ret |= ClearSides.TopLeft; - if (Tiles[p + new CVec(1, -1)].Type != t) + if (!CellContains(p + new CVec(1, -1), t)) ret |= ClearSides.TopRight; - if (Tiles[p + new CVec(-1, 1)].Type != t) + if (!CellContains(p + new CVec(-1, 1), t)) ret |= ClearSides.BottomLeft; - if (Tiles[p + new CVec(1, 1)].Type != t) + if (!CellContains(p + new CVec(1, 1), t)) ret |= ClearSides.BottomRight; return ret; diff --git a/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs b/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs index dce447e39c..e63d1ea45d 100644 --- a/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs +++ b/OpenRA.Mods.D2k/Traits/World/D2kResourceLayer.cs @@ -96,31 +96,36 @@ namespace OpenRA.Mods.D2k.Traits { ClearSides.Bottom | ClearSides.TopLeft | ClearSides.BottomLeft | ClearSides.BottomRight, 49 }, }; + bool CellContains(CPos c, ResourceType t) + { + return render.Contains(c) && render[c].Type == t; + } + ClearSides FindClearSides(ResourceType t, CPos p) { var ret = ClearSides.None; - if (render[p + new CVec(0, -1)].Type != t) + if (!CellContains(p + new CVec(0, -1), t)) ret |= ClearSides.Top | ClearSides.TopLeft | ClearSides.TopRight; - if (render[p + new CVec(-1, 0)].Type != t) + if (!CellContains(p + new CVec(-1, 0), t)) ret |= ClearSides.Left | ClearSides.TopLeft | ClearSides.BottomLeft; - if (render[p + new CVec(1, 0)].Type != t) + if (!CellContains(p + new CVec(1, 0), t)) ret |= ClearSides.Right | ClearSides.TopRight | ClearSides.BottomRight; - if (render[p + new CVec(0, 1)].Type != t) + if (!CellContains(p + new CVec(0, 1), t)) ret |= ClearSides.Bottom | ClearSides.BottomLeft | ClearSides.BottomRight; - if (render[p + new CVec(-1, -1)].Type != t) + if (!CellContains(p + new CVec(-1, -1), t)) ret |= ClearSides.TopLeft; - if (render[p + new CVec(1, -1)].Type != t) + if (!CellContains(p + new CVec(1, -1), t)) ret |= ClearSides.TopRight; - if (render[p + new CVec(-1, 1)].Type != t) + if (!CellContains(p + new CVec(-1, 1), t)) ret |= ClearSides.BottomLeft; - if (render[p + new CVec(1, 1)].Type != t) + if (!CellContains(p + new CVec(1, 1), t)) ret |= ClearSides.BottomRight; return ret; @@ -128,6 +133,9 @@ namespace OpenRA.Mods.D2k.Traits void UpdateRenderedTileInner(CPos p) { + if (!render.Contains(p)) + return; + var t = render[p]; if (t.Density > 0) {