From 9c1d13af10d8d61c7a9aeca5aa8ba1c1576ebdbd Mon Sep 17 00:00:00 2001 From: George Date: Mon, 31 Oct 2022 23:50:35 +1100 Subject: [PATCH] Implementation of map format interfaces IMap, IMapTiles, IMapResources, and IMapElevation --- OpenRA.Game/CPos.cs | 2 +- OpenRA.Game/GameRules/WeaponInfo.cs | 10 +- OpenRA.Game/Graphics/TerrainSpriteLayer.cs | 6 +- OpenRA.Game/Graphics/Viewport.cs | 5 +- OpenRA.Game/Graphics/WorldRenderer.cs | 5 +- OpenRA.Game/MPos.cs | 2 +- OpenRA.Game/Map/CellLayer.cs | 2 +- OpenRA.Game/Map/CellLayerBase.cs | 2 +- OpenRA.Game/Map/IMap.cs | 78 ++ OpenRA.Game/Map/Map.cs | 938 +-------------- OpenRA.Game/Map/MapGrid.cs | 2 +- OpenRA.Game/Map/MapPreview.cs | 23 +- OpenRA.Game/Map/ProjectedCellLayer.cs | 2 +- OpenRA.Game/Map/ProjectedCellRegion.cs | 4 +- OpenRA.Game/ModData.cs | 5 +- OpenRA.Game/Player.cs | 2 +- OpenRA.Game/Scripting/ScriptContext.cs | 2 +- OpenRA.Game/Support/ExceptionHandler.cs | 2 +- OpenRA.Game/Traits/Player/Shroud.cs | 6 +- OpenRA.Game/Traits/TraitsInterfaces.cs | 2 +- OpenRA.Game/World.cs | 10 +- OpenRA.Mods.Cnc/Activities/LeapAttack.cs | 6 +- OpenRA.Mods.Cnc/Activities/Teleport.cs | 6 +- OpenRA.Mods.Cnc/Traits/Minelayer.cs | 18 +- .../Traits/Render/WithBuildingBib.cs | 4 +- .../Render/WithLandingCraftAnimation.cs | 8 +- .../Traits/SupportPowers/AttackOrderPower.cs | 6 +- .../Traits/SupportPowers/ChronoshiftPower.cs | 14 +- .../Traits/SupportPowers/DropPodsPower.cs | 7 +- .../Traits/World/JumpjetActorLayer.cs | 8 +- .../Traits/World/TSEditorResourceLayer.cs | 17 +- .../Traits/World/TSResourceLayer.cs | 10 +- .../Traits/World/TSTiberiumRenderer.cs | 2 +- .../Traits/World/TSVeinsRenderer.cs | 27 +- .../Traits/World/WithResourceAnimation.cs | 8 +- .../ImportRedAlertLegacyMapCommand.cs | 6 +- .../UtilityCommands/ImportTSMapCommand.cs | 40 +- .../ImportTiberianDawnLegacyMapCommand.cs | 4 +- OpenRA.Mods.Common/AIUtils.cs | 2 +- OpenRA.Mods.Common/Activities/Air/Land.cs | 12 +- OpenRA.Mods.Common/Activities/Move/Move.cs | 7 +- .../Activities/Move/MoveAdjacentTo.cs | 6 +- .../Activities/Move/MoveWithinRange.cs | 2 +- OpenRA.Mods.Common/Activities/UnloadCargo.cs | 2 +- .../EditorBrushes/EditorActorBrush.cs | 2 +- .../EditorBrushes/EditorCopyPasteBrush.cs | 18 +- .../EditorBrushes/EditorTileBrush.cs | 30 +- .../Graphics/ModelRenderable.cs | 8 +- OpenRA.Mods.Common/Lint/CheckActors.cs | 4 +- OpenRA.Mods.Common/Lint/CheckMapCordon.cs | 2 +- OpenRA.Mods.Common/Lint/CheckMapMetadata.cs | 13 +- OpenRA.Mods.Common/Lint/CheckMapTiles.cs | 2 +- OpenRA.Mods.Common/Lint/CheckOwners.cs | 6 +- OpenRA.Mods.Common/Lint/CheckPlayers.cs | 8 +- .../Lint/CheckUnknownTraitFields.cs | 4 +- .../Lint/CheckUnknownWeaponFields.cs | 4 +- .../Lint/CheckWorldAndPlayerInherits.cs | 4 +- OpenRA.Mods.Common/MapFormats/DefaultMap.cs | 1039 +++++++++++++++-- .../Pathfinder/CellInfoLayerPool.cs | 4 +- .../Pathfinder/DensePathGraph.cs | 2 +- .../Pathfinder/HierarchicalPathFinder.cs | 7 +- OpenRA.Mods.Common/Projectiles/Bullet.cs | 12 +- OpenRA.Mods.Common/Projectiles/Missile.cs | 15 +- .../Scripting/Global/MediaGlobal.cs | 4 +- .../Scripting/Global/UserInterfaceGlobal.cs | 4 +- OpenRA.Mods.Common/Terrain/DefaultTerrain.cs | 12 +- OpenRA.Mods.Common/Terrain/TerrainInfo.cs | 2 +- OpenRA.Mods.Common/Traits/Air/Aircraft.cs | 41 +- .../Traits/Air/AttackAircraft.cs | 6 +- .../Traits/AppearsOnMapPreview.cs | 4 +- OpenRA.Mods.Common/Traits/AttackMove.cs | 4 +- .../Traits/BlocksProjectiles.cs | 6 +- .../BotModuleLogic/BaseBuilderQueueManager.cs | 9 +- .../BotModuleLogic/SupportPowerDecision.cs | 11 +- .../Traits/BotModules/HarvesterBotModule.cs | 5 +- .../BotModules/SupportPowerBotModule.cs | 4 +- .../Traits/Buildings/Building.cs | 8 +- .../Traits/Buildings/BuildingInfluence.cs | 2 +- .../Traits/Buildings/BuildingUtils.cs | 6 +- .../FootprintPlaceBuildingPreview.cs | 5 +- .../Traits/Buildings/FreeActorWithDelivery.cs | 8 +- .../Traits/Buildings/RallyPoint.cs | 5 +- .../Buildings/TransformsIntoAircraft.cs | 8 +- .../Traits/Buildings/TransformsIntoMobile.cs | 9 +- OpenRA.Mods.Common/Traits/Carryall.cs | 8 +- .../Conditions/GrantConditionOnDeploy.cs | 4 +- .../Conditions/GrantConditionOnTerrain.cs | 6 +- OpenRA.Mods.Common/Traits/Crates/Crate.cs | 9 +- OpenRA.Mods.Common/Traits/Husk.cs | 11 +- .../Traits/IsometricSelectable.cs | 9 +- OpenRA.Mods.Common/Traits/Mobile.cs | 18 +- .../PaletteFromEmbeddedSpritePalette.cs | 2 +- .../Traits/Palettes/PaletteFromFile.cs | 3 +- .../Palettes/PaletteFromGimpOrJascFile.cs | 3 +- .../Traits/Palettes/PaletteFromPng.cs | 3 +- OpenRA.Mods.Common/Traits/Parachutable.cs | 5 +- .../Traits/Player/PlayerRadarTerrain.cs | 10 +- .../Traits/ProductionFromMapEdge.cs | 9 +- .../Traits/ProductionParadrop.cs | 5 +- .../Render/CustomTerrainDebugOverlay.cs | 7 +- .../Traits/Render/LeavesTrails.cs | 9 +- .../Render/WithAircraftLandingEffect.cs | 2 +- .../Traits/SupportPowers/AirstrikePower.cs | 6 +- .../Traits/SupportPowers/ParatroopersPower.cs | 8 +- .../Traits/SupportPowers/SpawnActorPower.cs | 6 +- OpenRA.Mods.Common/Traits/Wanders.cs | 7 +- OpenRA.Mods.Common/Traits/World/ActorMap.cs | 2 +- .../Traits/World/BuildableTerrainOverlay.cs | 21 +- .../Traits/World/CellTriggerOverlay.cs | 6 +- .../World/CliffBackImpassabilityLayer.cs | 11 +- .../Traits/World/CrateSpawner.cs | 12 +- .../Traits/World/CreateMapPlayers.cs | 4 +- .../Traits/World/EditorActorLayer.cs | 4 +- .../Traits/World/EditorCursorLayer.cs | 2 +- .../Traits/World/EditorResourceLayer.cs | 36 +- .../Traits/World/EditorSelectionLayer.cs | 8 +- .../Traits/World/ElevatedBridgeLayer.cs | 2 +- .../World/HierarchicalPathFinderOverlay.cs | 13 +- .../Traits/World/LegacyBridgeLayer.cs | 11 +- OpenRA.Mods.Common/Traits/World/Locomotor.cs | 9 +- .../Traits/World/MapStartingLocations.cs | 8 +- .../Traits/World/ResourceLayer.cs | 12 +- .../Traits/World/ResourceRenderer.cs | 4 +- .../Traits/World/ShroudRenderer.cs | 8 +- .../Traits/World/SpawnMapActors.cs | 2 +- .../Traits/World/SubterraneanActorLayer.cs | 6 +- .../Traits/World/TerrainGeometryOverlay.cs | 10 +- .../Traits/World/TerrainLighting.cs | 8 +- .../Traits/World/TerrainRenderer.cs | 12 +- .../Traits/World/TerrainTunnelLayer.cs | 2 +- OpenRA.Mods.Common/Util.cs | 6 +- .../UtilityCommands/CheckYaml.cs | 12 +- .../DumpSequenceSheetsCommand.cs | 2 +- .../UtilityCommands/ExtractMapRules.cs | 23 +- .../UtilityCommands/ImportLegacyMapCommand.cs | 22 +- .../UtilityCommands/LintInterfaces.cs | 2 +- .../UtilityCommands/ResizeMapCommand.cs | 7 +- .../UtilityCommands/Utilities.cs | 1 + .../Warheads/CreateEffectWarhead.cs | 9 +- .../Warheads/CreateResourceWarhead.cs | 7 +- .../Warheads/DestroyResourceWarhead.cs | 7 +- .../Warheads/FireClusterWarhead.cs | 2 +- .../Warheads/LeaveSmudgeWarhead.cs | 9 +- .../Logic/Editor/LayerSelectorLogic.cs | 2 + .../Widgets/Logic/Editor/MapEditorLogic.cs | 5 +- .../Widgets/Logic/Editor/NewMapLogic.cs | 13 +- .../Widgets/Logic/GameSaveBrowserLogic.cs | 4 +- .../Widgets/Logic/Ingame/DebugLogic.cs | 3 +- .../Logic/Ingame/GameInfoBriefingLogic.cs | 2 +- .../Widgets/Logic/Ingame/GameInfoLogic.cs | 6 +- .../Hotkeys/EditorQuickSaveHotkeyLogic.cs | 10 +- .../Widgets/Logic/Ingame/IngameMenuLogic.cs | 2 +- .../Ingame/ObserverShroudSelectorLogic.cs | 2 +- OpenRA.Mods.Common/Widgets/RadarWidget.cs | 12 +- .../D2kActorPreviewPlaceBuildingPreview.cs | 7 +- .../Traits/Buildings/D2kBuilding.cs | 5 +- OpenRA.Mods.D2k/Traits/Sandworm.cs | 7 +- OpenRA.Mods.D2k/Traits/SpiceBloom.cs | 11 +- .../Traits/World/BuildableTerrainLayer.cs | 5 +- .../UtilityCommands/D2kMapImporter.cs | 11 +- mods/modcontent/mod.yaml | 2 + 161 files changed, 1722 insertions(+), 1538 deletions(-) create mode 100644 OpenRA.Game/Map/IMap.cs diff --git a/OpenRA.Game/CPos.cs b/OpenRA.Game/CPos.cs index 396c15081011..1ff8c31f5f10 100644 --- a/OpenRA.Game/CPos.cs +++ b/OpenRA.Game/CPos.cs @@ -66,7 +66,7 @@ public override string ToString() return X + "," + Y + "," + Layer; } - public MPos ToMPos(Map map) + public MPos ToMPos(IMap map) { return ToMPos(map.Grid.Type); } diff --git a/OpenRA.Game/GameRules/WeaponInfo.cs b/OpenRA.Game/GameRules/WeaponInfo.cs index e2d221e3ade0..8772d0b1a106 100644 --- a/OpenRA.Game/GameRules/WeaponInfo.cs +++ b/OpenRA.Game/GameRules/WeaponInfo.cs @@ -180,6 +180,8 @@ public bool IsValidTarget(BitSet targetTypes) /// Checks if the weapon is valid against (can target) the target. public bool IsValidAgainst(in Target target, World world, Actor firedBy) { + var map = world.Map; + if (target.Type == TargetType.Actor) return IsValidAgainst(target.Actor, firedBy); @@ -188,15 +190,15 @@ public bool IsValidAgainst(in Target target, World world, Actor firedBy) if (target.Type == TargetType.Terrain) { - var dat = world.Map.DistanceAboveTerrain(target.CenterPosition); + var dat = map.DistanceAboveTerrain(target.CenterPosition); if (dat > AirThreshold) return IsValidTarget(TargetTypeAir); - var cell = world.Map.CellContaining(target.CenterPosition); - if (!world.Map.Contains(cell)) + var cell = map.CellContaining(target.CenterPosition); + if (!map.Contains(cell)) return false; - var cellInfo = world.Map.GetTerrainInfo(cell); + var cellInfo = map.GetTerrainInfo(cell); if (!IsValidTarget(cellInfo.TargetTypes)) return false; diff --git a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs index 941285517e5f..d3737bbcc326 100644 --- a/OpenRA.Game/Graphics/TerrainSpriteLayer.cs +++ b/OpenRA.Game/Graphics/TerrainSpriteLayer.cs @@ -32,7 +32,7 @@ public sealed class TerrainSpriteLayer : IDisposable readonly bool restrictToBounds; readonly WorldRenderer worldRenderer; - readonly Map map; + readonly IMap map; readonly PaletteReference[] palettes; @@ -88,7 +88,7 @@ public void Update(CPos cell, Sprite sprite, PaletteReference palette, float sca var xyz = float3.Zero; if (sprite != null) { - var cellOrigin = map.CenterOfCell(cell) - new WVec(0, 0, map.Grid.Ramps[map.Ramp[cell]].CenterHeightOffset); + var cellOrigin = map.CenterOfCell(cell) - new WVec(0, 0, map.Grid.Ramps[((IMapElevation)map).Ramp[cell]].CenterHeightOffset); xyz = worldRenderer.Screen3DPosition(cellOrigin) + scale * (sprite.Offset - 0.5f * sprite.Size); } @@ -177,7 +177,7 @@ public void Update(MPos uv, Sprite sprite, PaletteReference palette, in float3 p } // The vertex buffer does not have geometry for cells outside the map - if (!map.Tiles.Contains(uv)) + if (!((IMapTiles)map).Tiles.Contains(uv)) return; var offset = rowStride * uv.V + 6 * uv.U; diff --git a/OpenRA.Game/Graphics/Viewport.cs b/OpenRA.Game/Graphics/Viewport.cs index f55520f69aac..892447a35ffb 100644 --- a/OpenRA.Game/Graphics/Viewport.cs +++ b/OpenRA.Game/Graphics/Viewport.cs @@ -134,7 +134,7 @@ public ScrollDirection GetBlockedDirections() return ret; } - public Viewport(WorldRenderer wr, Map map) + public Viewport(WorldRenderer wr, IMap map) { worldRenderer = wr; var grid = Game.ModData.Manifest.Get(); @@ -242,6 +242,7 @@ public CPos ViewToWorld(int2 view) { var world = worldRenderer.Viewport.ViewToWorldPx(view); var map = worldRenderer.World.Map; + var mapRamp = ((IMapElevation)map).Ramp; var candidates = CandidateMouseoverCells(world).ToList(); foreach (var uv in candidates) @@ -251,7 +252,7 @@ public CPos ViewToWorld(int2 view) var s = worldRenderer.ScreenPxPosition(p); if (Math.Abs(s.X - world.X) <= tileSize.Width && Math.Abs(s.Y - world.Y) <= tileSize.Height) { - var ramp = map.Grid.Ramps[map.Ramp.Contains(uv) ? map.Ramp[uv] : 0]; + var ramp = map.Grid.Ramps[mapRamp.Contains(uv) ? mapRamp[uv] : 0]; var pos = map.CenterOfCell(uv.ToCPos(map)) - new WVec(0, 0, ramp.CenterHeightOffset); var screen = ramp.Corners.Select(c => worldRenderer.ScreenPxPosition(pos + c)).ToArray(); if (screen.PolygonContains(world)) diff --git a/OpenRA.Game/Graphics/WorldRenderer.cs b/OpenRA.Game/Graphics/WorldRenderer.cs index 27608d6f982c..b2dc850d5558 100644 --- a/OpenRA.Game/Graphics/WorldRenderer.cs +++ b/OpenRA.Game/Graphics/WorldRenderer.cs @@ -48,8 +48,9 @@ public sealed class WorldRenderer : IDisposable internal WorldRenderer(ModData modData, World world) { World = world; - TileSize = World.Map.Grid.TileSize; - TileScale = World.Map.Grid.Type == MapGridType.RectangularIsometric ? 1448 : 1024; + var map = World.Map; + TileSize = map.Grid.TileSize; + TileScale = map.Grid.Type == MapGridType.RectangularIsometric ? 1448 : 1024; Viewport = new Viewport(this, world.Map); createPaletteReference = CreatePaletteReference; diff --git a/OpenRA.Game/MPos.cs b/OpenRA.Game/MPos.cs index 9793f0926ed6..fa00452dba70 100644 --- a/OpenRA.Game/MPos.cs +++ b/OpenRA.Game/MPos.cs @@ -37,7 +37,7 @@ public MPos Clamp(Rectangle r) public override string ToString() { return U + "," + V; } - public CPos ToCPos(Map map) + public CPos ToCPos(IMap map) { return ToCPos(map.Grid.Type); } diff --git a/OpenRA.Game/Map/CellLayer.cs b/OpenRA.Game/Map/CellLayer.cs index 9644bbdd39a3..37a80edf823e 100644 --- a/OpenRA.Game/Map/CellLayer.cs +++ b/OpenRA.Game/Map/CellLayer.cs @@ -19,7 +19,7 @@ public sealed class CellLayer : CellLayerBase { public event Action CellEntryChanged = null; - public CellLayer(Map map) + public CellLayer(IMap map) : base(map) { } public CellLayer(MapGridType gridType, Size size) diff --git a/OpenRA.Game/Map/CellLayerBase.cs b/OpenRA.Game/Map/CellLayerBase.cs index d66b638f01d5..305334f0b248 100644 --- a/OpenRA.Game/Map/CellLayerBase.cs +++ b/OpenRA.Game/Map/CellLayerBase.cs @@ -24,7 +24,7 @@ public abstract class CellLayerBase : IEnumerable protected readonly T[] Entries; protected readonly Rectangle Bounds; - public CellLayerBase(Map map) + public CellLayerBase(IMap map) : this(map.Grid.Type, new Size(map.MapSize.X, map.MapSize.Y)) { } public CellLayerBase(MapGridType gridType, Size size) diff --git a/OpenRA.Game/Map/IMap.cs b/OpenRA.Game/Map/IMap.cs new file mode 100644 index 000000000000..18bce33d924c --- /dev/null +++ b/OpenRA.Game/Map/IMap.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using OpenRA.Graphics; +using OpenRA.Primitives; +using OpenRA.Support; +using OpenRA.Traits; + +namespace OpenRA +{ + public interface IMap : IDisposable + { + Dictionary ReplacedInvalidTerrainTiles { get; } + MapGrid Grid { get; } + int2 MapSize { get; } + string Tileset { get; } + Ruleset Rules { get; } + SequenceSet Sequences { get; } + CellRegion AllCells { get; } + List AllEdgeCells { get; } + WDist CellHeightStep { get; } + CellLayer CustomTerrain { get; } + WPos ProjectedBottomRight { get; } + PPos[] ProjectedCells { get; } + WPos ProjectedTopLeft { get; } + Rectangle Bounds { get; } + int MapFormat { get; } + byte GetTerrainIndex(CPos cell); + TerrainTypeInfo GetTerrainInfo(CPos cell); + void NewSize(Size size, ITerrainInfo terrainInfo); + WVec Offset(CVec delta, int dz); + WAngle FacingBetween(CPos cell, CPos towards, WAngle fallbackfacing); + IEnumerable FindTilesInAnnulus(CPos center, int minRange, int maxRange, bool allowOutsideBounds = false); + IEnumerable FindTilesInCircle(CPos center, int maxRange, bool allowOutsideBounds = false); + CPos CellContaining(WPos pos); + PPos ProjectedCellCovering(WPos pos); + WDist DistanceAboveTerrain(WPos pos); + WPos CenterOfCell(CPos cell); + WPos CenterOfSubCell(CPos cell, SubCell subCell); + CPos ChooseClosestEdgeCell(CPos cell); + MPos ChooseClosestEdgeCell(MPos uv); + CPos ChooseClosestMatchingEdgeCell(CPos cell, Func match); + CPos ChooseRandomCell(MersenneTwister rand); + CPos ChooseRandomEdgeCell(MersenneTwister rand); + CPos Clamp(CPos cell); + MPos Clamp(MPos uv); + PPos Clamp(PPos puv); + bool Contains(CPos cell); + bool Contains(MPos uv); + bool Contains(PPos puv); + WDist DistanceToEdge(WPos pos, in WVec dir); + PPos[] ProjectedCellsCovering(MPos uv); + + event Action CellProjectionChanged; + byte ProjectedHeight(PPos puv); + void Resize(int width, int height); + void SetBounds(PPos tl, PPos br); + WRot TerrainOrientation(CPos cell); + List Unproject(PPos puv); + (Color Left, Color Right) GetTerrainColorPair(MPos uv); + byte[] SavePreview(); + } + + public interface IMapElevation : IMap + { + CellLayer Ramp { get; } + CellLayer Height { get; } + } + + public interface IMapTiles : IMap + { + CellLayer Tiles { get; } + } + + public interface IMapResource : IMap + { + CellLayer Resources { get; } + } +} diff --git a/OpenRA.Game/Map/Map.cs b/OpenRA.Game/Map/Map.cs index d676521de159..4b397174849f 100644 --- a/OpenRA.Game/Map/Map.cs +++ b/OpenRA.Game/Map/Map.cs @@ -12,20 +12,11 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; using System.Text.RegularExpressions; -using OpenRA.FileFormats; using OpenRA.FileSystem; -using OpenRA.Graphics; -using OpenRA.Primitives; -using OpenRA.Support; -using OpenRA.Traits; namespace OpenRA { - [Flags] public enum MapVisibility { @@ -49,14 +40,11 @@ public abstract class Map : IReadOnlyFileSystem public string RequiresMod; public string Title; public string Author; - public string Tileset; public bool LockPreview; - public Rectangle Bounds; public MapVisibility Visibility = MapVisibility.Lobby; public string[] Categories = { "Conquest" }; public string[] Translations; - - public int2 MapSize { get; protected set; } + public readonly MapGrid Grid; // Player and actor yaml. Public for access by the map importers and lint checks. public List PlayerDefinitions = new List(); @@ -73,51 +61,16 @@ public abstract class Map : IReadOnlyFileSystem public readonly Dictionary ReplacedInvalidTerrainTiles = new Dictionary(); - // Generated data - public readonly MapGrid Grid; public IReadOnlyPackage Package { get; protected set; } public string Uid { get; protected set; } - - public Ruleset Rules { get; private set; } - public SequenceSet Sequences { get; private set; } - - public bool InvalidCustomRules { get; private set; } - public Exception InvalidCustomRulesException { get; private set; } - - /// - /// The top-left of the playable area in projected world coordinates - /// This is a hacky workaround for legacy functionality. Do not use for new code. - /// - public WPos ProjectedTopLeft { get; private set; } - - /// - /// The bottom-right of the playable area in projected world coordinates - /// This is a hacky workaround for legacy functionality. Do not use for new code. - /// - public WPos ProjectedBottomRight { get; private set; } - - public CellLayer Tiles { get; private set; } - public CellLayer Resources { get; private set; } - public CellLayer Height { get; private set; } - public CellLayer Ramp { get; private set; } - public CellLayer CustomTerrain { get; private set; } - - public PPos[] ProjectedCells { get; private set; } - public CellRegion AllCells { get; private set; } - public List AllEdgeCells { get; private set; } - - public event Action CellProjectionChanged; + public bool InvalidCustomRules { get; protected set; } + public Exception InvalidCustomRulesException { get; protected set; } // Internal data - readonly ModData modData; - CellLayer cachedTerrainIndexes; - bool initializedCellProjection; - CellLayer cellProjection; - CellLayer> inverseCellProjection; - CellLayer projectedHeight; - Rectangle projectionSafeBounds; - - internal Translation Translation; +#pragma warning disable IDE1006 // Naming Styles + protected readonly ModData modData; + protected Translation Translation; +#pragma warning restore IDE1006 // Naming Styles public abstract void Save(IReadWritePackage package); @@ -152,878 +105,6 @@ protected Map(ModData modData) // Empty rules that can be added to by the importers. // Will be dropped on save if nothing is added to it RuleDefinitions = new MiniYaml(""); - - /* - Tiles = new CellLayer(this); - Resources = new CellLayer(this); - Height = new CellLayer(this); - Ramp = new CellLayer(this); - - if (Grid.MaximumTerrainHeight > 0) - { - Tiles.CellEntryChanged += UpdateRamp; - Tiles.CellEntryChanged += UpdateProjection; - Height.CellEntryChanged += UpdateProjection; - Tiles.CellEntryChanged += UpdateProjection; - Tiles.CellEntryChanged += UpdateRamp; - }*/ - } - - protected void PostInit() - { - try - { - Rules = Ruleset.Load(modData, this, Tileset, RuleDefinitions, WeaponDefinitions, - VoiceDefinitions, NotificationDefinitions, MusicDefinitions, ModelSequenceDefinitions); - } - catch (Exception e) - { - Log.Write("debug", "Failed to load rules for {0} with error {1}", Title, e); - InvalidCustomRules = true; - InvalidCustomRulesException = e; - Rules = Ruleset.LoadDefaultsForTileSet(modData, Tileset); - } - - Sequences = new SequenceSet(this, modData, Tileset, SequenceDefinitions); - Translation = new Translation(Game.Settings.Player.Language, Translations, this); - - var tl = new MPos(0, 0).ToCPos(this); - var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this); - AllCells = new CellRegion(Grid.Type, tl, br); - - var btl = new PPos(Bounds.Left, Bounds.Top); - var bbr = new PPos(Bounds.Right - 1, Bounds.Bottom - 1); - SetBounds(btl, bbr); - - CustomTerrain = new CellLayer(this); - foreach (var uv in AllCells.MapCoords) - CustomTerrain[uv] = byte.MaxValue; - - // Replace invalid tiles and cache ramp state - var terrainInfo = Rules.TerrainInfo; - foreach (var uv in AllCells.MapCoords) - { - if (!terrainInfo.TryGetTerrainInfo(Tiles[uv], out var info)) - { - ReplacedInvalidTerrainTiles[uv.ToCPos(this)] = Tiles[uv]; - Tiles[uv] = terrainInfo.DefaultTerrainTile; - info = terrainInfo.GetTerrainInfo(terrainInfo.DefaultTerrainTile); - } - - Ramp[uv] = info.RampType; - } - - AllEdgeCells = UpdateEdgeCells(); - - // Invalidate the entry for a cell if anything could cause the terrain index to change. - void InvalidateTerrainIndex(CPos c) - { - if (cachedTerrainIndexes != null) - cachedTerrainIndexes[c] = InvalidCachedTerrainIndex; - } - - // Even though the cache is lazily initialized, we must attach these event handlers on init. - // This ensures our handler to invalidate the cache runs first, - // so other listeners to these same events will get correct data when calling GetTerrainIndex. - CustomTerrain.CellEntryChanged += InvalidateTerrainIndex; - Tiles.CellEntryChanged += InvalidateTerrainIndex; - } - - void UpdateRamp(CPos cell) - { - Ramp[cell] = Rules.TerrainInfo.GetTerrainInfo(Tiles[cell]).RampType; - } - - void InitializeCellProjection() - { - if (initializedCellProjection) - return; - - initializedCellProjection = true; - - cellProjection = new CellLayer(this); - inverseCellProjection = new CellLayer>(this); - projectedHeight = new CellLayer(this); - - // Initialize collections - foreach (var cell in AllCells) - { - var uv = cell.ToMPos(Grid.Type); - cellProjection[uv] = Array.Empty(); - inverseCellProjection[uv] = new List(1); - } - - // Initialize projections - foreach (var cell in AllCells) - UpdateProjection(cell); - } - - void UpdateProjection(CPos cell) - { - MPos uv; - - if (Grid.MaximumTerrainHeight == 0) - { - uv = cell.ToMPos(Grid.Type); - cellProjection[cell] = new[] { (PPos)uv }; - var inverse = inverseCellProjection[uv]; - inverse.Clear(); - inverse.Add(uv); - CellProjectionChanged?.Invoke(cell); - return; - } - - if (!initializedCellProjection) - InitializeCellProjection(); - - uv = cell.ToMPos(Grid.Type); - - // Remove old reverse projection - foreach (var puv in cellProjection[uv]) - { - var temp = (MPos)puv; - inverseCellProjection[temp].Remove(uv); - projectedHeight[temp] = ProjectedCellHeightInner(puv); - } - - var projected = ProjectCellInner(uv); - cellProjection[uv] = projected; - - foreach (var puv in projected) - { - var temp = (MPos)puv; - inverseCellProjection[temp].Add(uv); - - var height = ProjectedCellHeightInner(puv); - projectedHeight[temp] = height; - - // Propagate height up cliff faces - while (true) - { - temp = new MPos(temp.U, temp.V - 1); - if (!inverseCellProjection.Contains(temp) || inverseCellProjection[temp].Count > 0) - break; - - projectedHeight[temp] = height; - } - } - - CellProjectionChanged?.Invoke(cell); - } - - byte ProjectedCellHeightInner(PPos puv) - { - while (inverseCellProjection.Contains((MPos)puv)) - { - var inverse = inverseCellProjection[(MPos)puv]; - if (inverse.Count > 0) - { - // The original games treat the top of cliffs the same way as the bottom - // This information isn't stored in the map data, so query the offset from the tileset - var temp = inverse.MaxBy(uv => uv.V); - return (byte)(Height[temp] - Rules.TerrainInfo.GetTerrainInfo(Tiles[temp]).Height); - } - - // Try the next cell down if this is a cliff face - puv = new PPos(puv.U, puv.V + 1); - } - - return 0; - } - - PPos[] ProjectCellInner(MPos uv) - { - var mapHeight = Height; - if (!mapHeight.Contains(uv)) - return NoProjectedCells; - - // Any changes to this function should be reflected when setting projectionSafeBounds. - var height = mapHeight[uv]; - if (height == 0) - return new[] { (PPos)uv }; - - // Odd-height ramps get bumped up a level to the next even height layer - if ((height & 1) == 1 && Ramp[uv] != 0) - height += 1; - - var candidates = new List(); - - // Odd-height level tiles are equally covered by four projected tiles - if ((height & 1) == 1) - { - if ((uv.V & 1) == 1) - candidates.Add(new PPos(uv.U + 1, uv.V - height)); - else - candidates.Add(new PPos(uv.U - 1, uv.V - height)); - - candidates.Add(new PPos(uv.U, uv.V - height)); - candidates.Add(new PPos(uv.U, uv.V - height + 1)); - candidates.Add(new PPos(uv.U, uv.V - height - 1)); - } - else - candidates.Add(new PPos(uv.U, uv.V - height)); - - return candidates.Where(c => mapHeight.Contains((MPos)c)).ToArray(); - } - - var previewData = SavePreview(); - if (Package != toPackage || !Enumerable.SequenceEqual(previewData, Package.GetStream("map.png").ReadAllBytes())) - toPackage.Update("map.png", previewData); - } - - var binaryData = SaveBinaryData(); - if (Package != toPackage || !Enumerable.SequenceEqual(binaryData, Package.GetStream("map.bin").ReadAllBytes())) - toPackage.Update("map.bin", binaryData); - - public (Color Left, Color Right) GetTerrainColorPair(MPos uv) - { - var terrainInfo = Rules.TerrainInfo; - var type = terrainInfo.GetTerrainInfo(Tiles[uv]); - var left = type.GetColor(Game.CosmeticRandom); - var right = type.GetColor(Game.CosmeticRandom); - - if (terrainInfo.MinHeightColorBrightness != 1.0f || terrainInfo.MaxHeightColorBrightness != 1.0f) - { - var scale = float2.Lerp(terrainInfo.MinHeightColorBrightness, terrainInfo.MaxHeightColorBrightness, Height[uv] * 1f / Grid.MaximumTerrainHeight); - left = Color.FromArgb((int)(scale * left.R).Clamp(0, 255), (int)(scale * left.G).Clamp(0, 255), (int)(scale * left.B).Clamp(0, 255)); - right = Color.FromArgb((int)(scale * right.R).Clamp(0, 255), (int)(scale * right.G).Clamp(0, 255), (int)(scale * right.B).Clamp(0, 255)); - } - - return (left, right); - } - - public byte[] SavePreview() - { - var actorTypes = Rules.Actors.Values.Where(a => a.HasTraitInfo()); - var actors = ActorDefinitions.Where(a => actorTypes.Any(ai => ai.Name == a.Value.Value)); - var positions = new List<(MPos Position, Color Color)>(); - foreach (var actor in actors) - { - var s = new ActorReference(actor.Value.Value, actor.Value.ToDictionary()); - - var ai = Rules.Actors[actor.Value.Value]; - var impsis = ai.TraitInfos(); - foreach (var impsi in impsis) - impsi.PopulateMapPreviewSignatureCells(this, ai, s, positions); - } - - // ResourceLayer is on world actor, which isn't caught above, so an extra check for it. - var worldActorInfo = Rules.Actors[SystemActors.World]; - var worldimpsis = worldActorInfo.TraitInfos(); - foreach (var worldimpsi in worldimpsis) - worldimpsi.PopulateMapPreviewSignatureCells(this, worldActorInfo, null, positions); - - var isRectangularIsometric = Grid.Type == MapGridType.RectangularIsometric; - - var top = int.MaxValue; - var bottom = int.MinValue; - - if (Grid.MaximumTerrainHeight > 0) - { - // The minimap is drawn in cell space, so we need to - // unproject the PPos bounds to find the MPos boundaries. - // This matches the calculation in RadarWidget that is used ingame - for (var x = Bounds.Left; x < Bounds.Right; x++) - { - var allTop = Unproject(new PPos(x, Bounds.Top)); - var allBottom = Unproject(new PPos(x, Bounds.Bottom)); - if (allTop.Count > 0) - top = Math.Min(top, allTop.MinBy(uv => uv.V).V); - - if (allBottom.Count > 0) - bottom = Math.Max(bottom, allBottom.MaxBy(uv => uv.V).V); - } - } - else - { - // If the mod uses flat maps, MPos == PPos and we can take the bounds rect directly - top = Bounds.Top; - bottom = Bounds.Bottom; - } - - var width = Bounds.Width; - var height = bottom - top; - - var bitmapWidth = width; - if (isRectangularIsometric) - bitmapWidth = 2 * bitmapWidth - 1; - - var stride = bitmapWidth * 4; - var pxStride = 4; - var minimapData = new byte[stride * height]; - (Color Left, Color Right) terrainColor = default; - - for (var y = 0; y < height; y++) - { - for (var x = 0; x < width; x++) - { - var uv = new MPos(x + Bounds.Left, y + top); - - // FirstOrDefault will return a (MPos.Zero, Color.Transparent) if positions is empty - var actorColor = positions.FirstOrDefault(ap => ap.Position == uv).Color; - if (actorColor.A == 0) - terrainColor = GetTerrainColorPair(uv); - - if (isRectangularIsometric) - { - // Odd rows are shifted right by 1px - var dx = uv.V & 1; - var xOffset = pxStride * (2 * x + dx); - if (x + dx > 0) - { - var z = y * stride + xOffset - pxStride; - var c = actorColor.A == 0 ? terrainColor.Left : actorColor; - minimapData[z++] = c.R; - minimapData[z++] = c.G; - minimapData[z++] = c.B; - minimapData[z] = c.A; - } - - if (xOffset < stride) - { - var z = y * stride + xOffset; - var c = actorColor.A == 0 ? terrainColor.Right : actorColor; - minimapData[z++] = c.R; - minimapData[z++] = c.G; - minimapData[z++] = c.B; - minimapData[z] = c.A; - } - } - else - { - var z = y * stride + pxStride * x; - var c = actorColor.A == 0 ? terrainColor.Left : actorColor; - minimapData[z++] = c.R; - minimapData[z++] = c.G; - minimapData[z++] = c.B; - minimapData[z] = c.A; - } - } - } - - var png = new Png(minimapData, SpriteFrameType.Rgba32, bitmapWidth, height); - return png.Save(); - } - - public bool Contains(CPos cell) - { - if (Grid.Type == MapGridType.RectangularIsometric) - { - // .ToMPos() returns the same result if the X and Y coordinates - // are switched. X < Y is invalid in the RectangularIsometric coordinate system, - // so we pre-filter these to avoid returning the wrong result - if (cell.X < cell.Y) - return false; - } - else - { - // If the mod uses flat & rectangular maps, ToMPos and Contains(MPos) create unnecessary cost. - // Just check if CPos is within map bounds. - if (Grid.MaximumTerrainHeight == 0) - return Bounds.Contains(cell.X, cell.Y); - } - - return Contains(cell.ToMPos(this)); - } - - public bool Contains(MPos uv) - { - // The first check ensures that the cell is within the valid map region, avoiding - // potential crashes in deeper code. All CellLayers have the same geometry, and - // CustomTerrain is convenient. - return CustomTerrain.Contains(uv) && ContainsAllProjectedCellsCovering(uv); - } - - bool ContainsAllProjectedCellsCovering(MPos uv) - { - // PERF: Checking the bounds directly here is the same as calling Contains((PPos)uv) but saves an allocation - if (Grid.MaximumTerrainHeight == 0) - return Bounds.Contains(uv.U, uv.V); - - // PERF: Most cells lie within a region where no matter their height, - // all possible projected cells would remain in the map area. - // For these, we can do a fast-path check. - if (projectionSafeBounds.Contains(uv.U, uv.V)) - return true; - - // Now we need to do a slow-check. Determine the actual projected tiles - // as they may or may not be in bounds depending on height. - // If the cell has no valid projection, then we're off the map. - var projectedCells = ProjectedCellsCovering(uv); - if (projectedCells.Length == 0) - return false; - - foreach (var puv in projectedCells) - if (!Contains(puv)) - return false; - - return true; - } - - public bool Contains(PPos puv) - { - return Bounds.Contains(puv.U, puv.V); - } - - public WPos CenterOfCell(CPos cell) - { - if (Grid.Type == MapGridType.Rectangular) - return new WPos(1024 * cell.X + 512, 1024 * cell.Y + 512, 0); - - // Convert from isometric cell position (x, y) to world position (u, v): - // (a) Consider the relationships: - // - Center of origin cell is (512, 512) - // - +x adds (512, 512) to world pos - // - +y adds (-512, 512) to world pos - // (b) Therefore: - // - ax + by adds (a - b) * 512 + 512 to u - // - ax + by adds (a + b) * 512 + 512 to v - // (c) u, v coordinates run diagonally to the cell axes, and we define - // 1024 as the length projected onto the primary cell axis - // - 512 * sqrt(2) = 724 - var z = Height.TryGetValue(cell, out var height) ? 724 * height + Grid.Ramps[Ramp[cell]].CenterHeightOffset : 0; - return new WPos(724 * (cell.X - cell.Y + 1), 724 * (cell.X + cell.Y + 1), z); - } - - public WPos CenterOfSubCell(CPos cell, SubCell subCell) - { - var index = (int)subCell; - if (index >= 0 && index < Grid.SubCellOffsets.Length) - { - var center = CenterOfCell(cell); - var offset = Grid.SubCellOffsets[index]; - if (Ramp.TryGetValue(cell, out var ramp) && ramp != 0) - { - var r = Grid.Ramps[ramp]; - offset += new WVec(0, 0, r.HeightOffset(offset.X, offset.Y) - r.CenterHeightOffset); - } - - return center + offset; - } - - return CenterOfCell(cell); - } - - public WDist DistanceAboveTerrain(WPos pos) - { - if (Grid.Type == MapGridType.Rectangular) - return new WDist(pos.Z); - - // Apply ramp offset - var cell = CellContaining(pos); - var offset = pos - CenterOfCell(cell); - - if (Ramp.TryGetValue(cell, out var ramp) && ramp != 0) - { - var r = Grid.Ramps[ramp]; - return new WDist(offset.Z + r.CenterHeightOffset - r.HeightOffset(offset.X, offset.Y)); - } - - return new WDist(offset.Z); - } - - public WRot TerrainOrientation(CPos cell) - { - if (Ramp.TryGetValue(cell, out var ramp)) - return Grid.Ramps[ramp].Orientation; - - return WRot.None; - } - - public WVec Offset(CVec delta, int dz) - { - if (Grid.Type == MapGridType.Rectangular) - return new WVec(1024 * delta.X, 1024 * delta.Y, 0); - - return new WVec(724 * (delta.X - delta.Y), 724 * (delta.X + delta.Y), 724 * dz); - } - - /// - /// The size of the map Height step in world units - /// - /// RectangularIsometric defines 1024 units along the diagonal axis, - /// giving a half-tile height step of sqrt(2) * 512 - public WDist CellHeightStep => new WDist(Grid.Type == MapGridType.RectangularIsometric ? 724 : 512); - - public CPos CellContaining(WPos pos) - { - if (Grid.Type == MapGridType.Rectangular) - return new CPos(pos.X / 1024, pos.Y / 1024); - - // Convert from world position to isometric cell position: - // (a) Subtract ([1/2 cell], [1/2 cell]) to move the rotation center to the middle of the corner cell - // (b) Rotate axes by -pi/4 to align the world axes with the cell axes - // (c) Apply an offset so that the integer division by [1 cell] rounds in the right direction: - // (i) u is always positive, so add [1/2 cell] (which then partially cancels the -[1 cell] term from the rotation) - // (ii) v can be negative, so we need to be careful about rounding directions. We add [1/2 cell] *away from 0* (negative if y > x). - // (e) Divide by [1 cell] to bring into cell coords. - // The world axes are rotated relative to the cell axes, so the standard cell size (1024) is increased by a factor of sqrt(2) - var u = (pos.Y + pos.X - 724) / 1448; - var v = (pos.Y - pos.X + (pos.Y > pos.X ? 724 : -724)) / 1448; - return new CPos(u, v); - } - - public PPos ProjectedCellCovering(WPos pos) - { - var projectedPos = pos - new WVec(0, pos.Z, pos.Z); - return (PPos)CellContaining(projectedPos).ToMPos(Grid.Type); - } - - static readonly PPos[] NoProjectedCells = Array.Empty(); - public PPos[] ProjectedCellsCovering(MPos uv) - { - if (!initializedCellProjection) - InitializeCellProjection(); - - if (!cellProjection.Contains(uv)) - return NoProjectedCells; - - return cellProjection[uv]; - } - - public List Unproject(PPos puv) - { - var uv = (MPos)puv; - - if (!initializedCellProjection) - InitializeCellProjection(); - - if (!inverseCellProjection.Contains(uv)) - return new List(); - - return inverseCellProjection[uv]; - } - - public byte ProjectedHeight(PPos puv) - { - return projectedHeight[(MPos)puv]; - } - - public WAngle FacingBetween(CPos cell, CPos towards, WAngle fallbackfacing) - { - var delta = CenterOfCell(towards) - CenterOfCell(cell); - if (delta.HorizontalLengthSquared == 0) - return fallbackfacing; - - return delta.Yaw; - } - - public void Resize(int width, int height) - { - var oldMapTiles = Tiles; - var oldMapResources = Resources; - var oldMapHeight = Height; - var oldMapRamp = Ramp; - var newSize = new Size(width, height); - - Tiles = CellLayer.Resize(oldMapTiles, newSize, oldMapTiles[MPos.Zero]); - Resources = CellLayer.Resize(oldMapResources, newSize, oldMapResources[MPos.Zero]); - Height = CellLayer.Resize(oldMapHeight, newSize, oldMapHeight[MPos.Zero]); - Ramp = CellLayer.Resize(oldMapRamp, newSize, oldMapRamp[MPos.Zero]); - MapSize = new int2(newSize); - - var tl = new MPos(0, 0); - var br = new MPos(MapSize.X - 1, MapSize.Y - 1); - AllCells = new CellRegion(Grid.Type, tl.ToCPos(this), br.ToCPos(this)); - SetBounds(new PPos(tl.U + 1, tl.V + 1), new PPos(br.U - 1, br.V - 1)); - } - - public void NewSize(Size size, ITerrainInfo terrainInfo) - { - Tiles = new CellLayer(Grid.Type, size); - Resources = new CellLayer(Grid.Type, size); - Height = new CellLayer(Grid.Type, size); - Ramp = new CellLayer(Grid.Type, size); - Tiles.Clear(terrainInfo.DefaultTerrainTile); - - if (Grid.MaximumTerrainHeight > 0) - Height.CellEntryChanged += UpdateProjection; - { - Tiles.CellEntryChanged += UpdateProjection; - Tiles.CellEntryChanged += UpdateRamp; - } - } - - public void SetBounds(PPos tl, PPos br) - { - // The tl and br coordinates are inclusive, but the Rectangle - // is exclusive. Pad the right and bottom edges to match. - Bounds = Rectangle.FromLTRB(tl.U, tl.V, br.U + 1, br.V + 1); - - // See ProjectCellInner to see how any given position may be projected. - // U: May gain or lose 1, so bring in the left and right edge by 1. - // V: For an even height tile, this ranges from 0 to height - // For an odd tile, the height may get rounded up to next even. - // Then also it projects to four tiles which adds one more to the possible height change. - // So we get a range of 0 to height + 1 + 1. - // As the height only goes upwards, we only need to make room at the top of the map and not the bottom. - var maxHeight = Grid.MaximumTerrainHeight; - if ((maxHeight & 1) == 1) - maxHeight += 2; - projectionSafeBounds = Rectangle.FromLTRB( - Bounds.Left + 1, - Bounds.Top + maxHeight, - Bounds.Right - 1, - Bounds.Bottom); - - // Directly calculate the projected map corners in world units avoiding unnecessary - // conversions. This abuses the definition that the width of the cell along the x world axis - // is always 1024 or 1448 units, and that the height of two rows is 2048 for classic cells and 724 - // for isometric cells. - if (Grid.Type == MapGridType.RectangularIsometric) - { - ProjectedTopLeft = new WPos(tl.U * 1448, tl.V * 724, 0); - ProjectedBottomRight = new WPos(br.U * 1448 - 1, (br.V + 1) * 724 - 1, 0); - } - else - { - ProjectedTopLeft = new WPos(tl.U * 1024, tl.V * 1024, 0); - ProjectedBottomRight = new WPos(br.U * 1024 - 1, (br.V + 1) * 1024 - 1, 0); - } - - // PERF: This enumeration isn't going to change during the game - ProjectedCells = new ProjectedCellRegion(this, tl, br).ToArray(); - } - - public byte GetTerrainIndex(CPos cell) - { - // Lazily initialize a cache for terrain indexes. - if (cachedTerrainIndexes == null) - { - cachedTerrainIndexes = new CellLayer(this); - cachedTerrainIndexes.Clear(InvalidCachedTerrainIndex); - } - - var uv = cell.ToMPos(this); - var terrainIndex = cachedTerrainIndexes[uv]; - - // PERF: Cache terrain indexes per cell on demand. - if (terrainIndex == InvalidCachedTerrainIndex) - { - var custom = CustomTerrain[uv]; - terrainIndex = cachedTerrainIndexes[uv] = custom != byte.MaxValue ? custom : Rules.TerrainInfo.GetTerrainInfo(Tiles[uv]).TerrainType; - } - - return (byte)terrainIndex; - } - - public TerrainTypeInfo GetTerrainInfo(CPos cell) - { - return Rules.TerrainInfo.TerrainTypes[GetTerrainIndex(cell)]; - } - - public CPos Clamp(CPos cell) - { - return Clamp(cell.ToMPos(this)).ToCPos(this); - } - - public MPos Clamp(MPos uv) - { - if (Grid.MaximumTerrainHeight == 0) - return (MPos)Clamp((PPos)uv); - - // Already in bounds, so don't need to do anything. - if (ContainsAllProjectedCellsCovering(uv)) - return uv; - - // Clamping map coordinates is trickier than it might first look! - // This needs to handle three nasty cases: - // * The requested cell is well outside the map region - // * The requested cell is near the top edge inside the map but outside the projected layer - // * The clamped projected cell lands on a cliff face with no associated map cell - // - // Handling these cases properly requires abuse of our knowledge of the projection transform. - // - // The U coordinate doesn't change significantly in the projection, so clamp this - // straight away and ensure the point is somewhere inside the map - uv = cellProjection.Clamp(new MPos(uv.U.Clamp(Bounds.Left, Bounds.Right), uv.V)); - - // Project this guessed cell and take the first available cell - // If it is projected outside the layer, then make another guess. - var allProjected = ProjectedCellsCovering(uv); - var projected = allProjected.Length > 0 ? allProjected.First() - : new PPos(uv.U, uv.V.Clamp(Bounds.Top, Bounds.Bottom)); - - // Clamp the projected cell to the map area - projected = Clamp(projected); - - // Project the cell back into map coordinates. - // This may fail if the projected cell covered a cliff or another feature - // where there is a large change in terrain height. - var unProjected = Unproject(projected); - if (unProjected.Count == 0) - { - // Adjust V until we find a cell that works - for (var x = 2; x <= 2 * Grid.MaximumTerrainHeight; x++) - { - var dv = ((x & 1) == 1 ? 1 : -1) * x / 2; - var test = new PPos(projected.U, projected.V + dv); - if (!Contains(test)) - continue; - - unProjected = Unproject(test); - if (unProjected.Count > 0) - break; - } - - // This shouldn't happen. But if it does, return the original value and hope the caller doesn't explode. - if (unProjected.Count == 0) - { - Log.Write("debug", "Failed to clamp map cell {0} to map bounds", uv); - return uv; - } - } - - return projected.V == Bounds.Bottom ? unProjected.MaxBy(x => x.V) : unProjected.MinBy(x => x.V); - } - - public PPos Clamp(PPos puv) - { - var bounds = new Rectangle(Bounds.X, Bounds.Y, Bounds.Width - 1, Bounds.Height - 1); - return puv.Clamp(bounds); - } - - public CPos ChooseRandomCell(MersenneTwister rand) - { - List cells; - do - { - var u = rand.Next(Bounds.Left, Bounds.Right); - var v = rand.Next(Bounds.Top, Bounds.Bottom); - - cells = Unproject(new PPos(u, v)); - } - while (cells.Count == 0); - - return cells.Random(rand).ToCPos(Grid.Type); - } - - public CPos ChooseClosestEdgeCell(CPos cell) - { - return ChooseClosestEdgeCell(cell.ToMPos(Grid.Type)).ToCPos(Grid.Type); - } - - public MPos ChooseClosestEdgeCell(MPos uv) - { - var allProjected = ProjectedCellsCovering(uv); - - PPos edge; - if (allProjected.Length > 0) - { - var puv = allProjected.First(); - var horizontalBound = (puv.U - Bounds.Left < Bounds.Width / 2) ? Bounds.Left : Bounds.Right; - var verticalBound = (puv.V - Bounds.Top < Bounds.Height / 2) ? Bounds.Top : Bounds.Bottom; - - var du = Math.Abs(horizontalBound - puv.U); - var dv = Math.Abs(verticalBound - puv.V); - - edge = du < dv ? new PPos(horizontalBound, puv.V) : new PPos(puv.U, verticalBound); - } - else - edge = new PPos(Bounds.Left, Bounds.Top); - - var unProjected = Unproject(edge); - if (unProjected.Count == 0) - { - // Adjust V until we find a cell that works - for (var x = 2; x <= 2 * Grid.MaximumTerrainHeight; x++) - { - var dv = ((x & 1) == 1 ? 1 : -1) * x / 2; - var test = new PPos(edge.U, edge.V + dv); - if (!Contains(test)) - continue; - - unProjected = Unproject(test); - if (unProjected.Count > 0) - break; - } - - // This shouldn't happen. But if it does, return the original value and hope the caller doesn't explode. - if (unProjected.Count == 0) - { - Log.Write("debug", "Failed to find closest edge for map cell {0}", uv); - return uv; - } - } - - return edge.V == Bounds.Bottom ? unProjected.MaxBy(x => x.V) : unProjected.MinBy(x => x.V); - } - - public CPos ChooseClosestMatchingEdgeCell(CPos cell, Func match) - { - return AllEdgeCells.OrderBy(c => (cell - c).Length).FirstOrDefault(c => match(c)); - } - - List UpdateEdgeCells() - { - var edgeCells = new List(); - var unProjected = new List(); - var bottom = Bounds.Bottom - 1; - for (var u = Bounds.Left; u < Bounds.Right; u++) - { - unProjected = Unproject(new PPos(u, Bounds.Top)); - if (unProjected.Count > 0) - edgeCells.Add(unProjected.MinBy(x => x.V).ToCPos(Grid.Type)); - - unProjected = Unproject(new PPos(u, bottom)); - if (unProjected.Count > 0) - edgeCells.Add(unProjected.MaxBy(x => x.V).ToCPos(Grid.Type)); - } - - for (var v = Bounds.Top; v < Bounds.Bottom; v++) - { - unProjected = Unproject(new PPos(Bounds.Left, v)); - if (unProjected.Count > 0) - edgeCells.Add((v == bottom ? unProjected.MaxBy(x => x.V) : unProjected.MinBy(x => x.V)).ToCPos(Grid.Type)); - - unProjected = Unproject(new PPos(Bounds.Right - 1, v)); - if (unProjected.Count > 0) - edgeCells.Add((v == bottom ? unProjected.MaxBy(x => x.V) : unProjected.MinBy(x => x.V)).ToCPos(Grid.Type)); - } - - return edgeCells; - } - - public CPos ChooseRandomEdgeCell(MersenneTwister rand) - { - return AllEdgeCells.Random(rand); - } - - public WDist DistanceToEdge(WPos pos, in WVec dir) - { - var projectedPos = pos - new WVec(0, pos.Z, pos.Z); - var x = dir.X == 0 ? int.MaxValue : ((dir.X < 0 ? ProjectedTopLeft.X : ProjectedBottomRight.X) - projectedPos.X) / dir.X; - var y = dir.Y == 0 ? int.MaxValue : ((dir.Y < 0 ? ProjectedTopLeft.Y : ProjectedBottomRight.Y) - projectedPos.Y) / dir.Y; - return new WDist(Math.Min(x, y) * dir.Length); - } - - // Both ranges are inclusive because everything that calls it is designed for maxRange being inclusive: - // it rounds the actual distance up to the next integer so that this call - // will return any cells that intersect with the requested range circle. - // The returned positions are sorted by distance from the center. - public IEnumerable FindTilesInAnnulus(CPos center, int minRange, int maxRange, bool allowOutsideBounds = false) - { - if (maxRange < minRange) - throw new ArgumentOutOfRangeException(nameof(maxRange), "Maximum range is less than the minimum range."); - - if (maxRange >= Grid.TilesByDistance.Length) - throw new ArgumentOutOfRangeException(nameof(maxRange), - $"The requested range ({maxRange}) cannot exceed the value of MaximumTileSearchRange ({Grid.MaximumTileSearchRange})"); - - for (var i = minRange; i <= maxRange; i++) - { - foreach (var offset in Grid.TilesByDistance[i]) - { - var t = offset + center; - if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t)) - yield return t; - } - } - } - - public IEnumerable FindTilesInCircle(CPos center, int maxRange, bool allowOutsideBounds = false) - { - return FindTilesInAnnulus(center, 0, maxRange, allowOutsideBounds); } public Stream Open(string filename) @@ -1079,10 +160,5 @@ public string Translate(string key, IDictionary args = null) return modData.Translation.GetString(key, args); } - - public void Dispose() - { - Sequences.Dispose(); - } } } diff --git a/OpenRA.Game/Map/MapGrid.cs b/OpenRA.Game/Map/MapGrid.cs index 5af9ff279d6d..7f8913cbacfa 100644 --- a/OpenRA.Game/Map/MapGrid.cs +++ b/OpenRA.Game/Map/MapGrid.cs @@ -126,7 +126,7 @@ public class MapGrid : IGlobalModData public CellRamp[] Ramps { get; } - internal readonly CVec[][] TilesByDistance; + public readonly CVec[][] TilesByDistance; public MapGrid(MiniYaml yaml) { diff --git a/OpenRA.Game/Map/MapPreview.cs b/OpenRA.Game/Map/MapPreview.cs index 8559c2cd62e0..31ddc3f1b89d 100644 --- a/OpenRA.Game/Map/MapPreview.cs +++ b/OpenRA.Game/Map/MapPreview.cs @@ -159,7 +159,7 @@ public InnerData Clone() } static readonly CPos[] NoSpawns = Array.Empty(); - readonly MapCache cache; + public readonly MapCache Cache; readonly ModData modData; public readonly string Uid; @@ -203,7 +203,7 @@ public Sprite GetMinimap() if (!generatingMinimap && Status == MapStatus.Available) { generatingMinimap = true; - cache.CacheMinimap(this); + Cache.CacheMinimap(this); } return null; @@ -231,7 +231,7 @@ public Ruleset LoadRuleset() public MapPreview(ModData modData, string uid, MapGridType gridType, MapCache cache) { - this.cache = cache; + Cache = cache; this.modData = modData; Uid = uid; @@ -258,7 +258,7 @@ public MapPreview(ModData modData, string uid, MapGridType gridType, MapCache ca public MapPreview(Map map, ModData modData) { this.modData = modData; - cache = modData.MapCache; + Cache = modData.MapCache; Uid = map.Uid; Package = map.Package; @@ -273,17 +273,16 @@ public MapPreview(Map map, ModData modData) innerData = new InnerData { - // TO DO: Check if this MapFormat retrieval is correct and working - MapFormat = Map.GetMapFormat(Package), + MapFormat = ((IMap)map).MapFormat, Title = map.Title, Categories = map.Categories, Author = map.Author, - TileSet = map.Tileset, + TileSet = ((IMap)map).Tileset, Players = mapPlayers, PlayerCount = mapPlayers.Players.Count(x => x.Value.Playable), SpawnPoints = spawns.ToArray(), - GridType = map.Grid.Type, - Bounds = map.Bounds, + GridType = ((IMap)map).Grid.Type, + Bounds = ((IMap)map).Bounds, Preview = null, Status = MapStatus.Available, Class = MapClassification.Unknown, @@ -341,7 +340,7 @@ public void UpdateRemoteSearch(MapStatus status, MiniYaml yaml, Action p.Value == MapClassification.User); + var installLocation = Cache.MapLocations.FirstOrDefault(p => p.Value == MapClassification.User); if (!(installLocation.Key is IReadWritePackage mapInstallPackage)) { Log.Write("debug", "Map install directory not found"); diff --git a/OpenRA.Game/Map/ProjectedCellLayer.cs b/OpenRA.Game/Map/ProjectedCellLayer.cs index c62df59db0c8..7464f78fabc6 100644 --- a/OpenRA.Game/Map/ProjectedCellLayer.cs +++ b/OpenRA.Game/Map/ProjectedCellLayer.cs @@ -17,7 +17,7 @@ public sealed class ProjectedCellLayer : CellLayerBase { public int MaxIndex => Size.Width * Size.Height; - public ProjectedCellLayer(Map map) + public ProjectedCellLayer(IMap map) : base(map) { } public ProjectedCellLayer(MapGridType gridType, Size size) diff --git a/OpenRA.Game/Map/ProjectedCellRegion.cs b/OpenRA.Game/Map/ProjectedCellRegion.cs index e484e4b98415..3296be44a081 100644 --- a/OpenRA.Game/Map/ProjectedCellRegion.cs +++ b/OpenRA.Game/Map/ProjectedCellRegion.cs @@ -27,7 +27,7 @@ public class ProjectedCellRegion : IEnumerable readonly MPos mapTopLeft; readonly MPos mapBottomRight; - public ProjectedCellRegion(Map map, PPos topLeft, PPos bottomRight) + public ProjectedCellRegion(IMap map, PPos topLeft, PPos bottomRight) { TopLeft = topLeft; BottomRight = bottomRight; @@ -45,7 +45,7 @@ public ProjectedCellRegion(Map map, PPos topLeft, PPos bottomRight) var heightOffset = map.Grid.Type == MapGridType.RectangularIsometric ? maxHeight : maxHeight / 2; // Use the map Height data array to clamp the bottom coordinate so it doesn't overflow the map - mapBottomRight = map.Height.Clamp(new MPos(bottomRight.U, bottomRight.V + heightOffset)); + mapBottomRight = ((IMapElevation)map).Height.Clamp(new MPos(bottomRight.U, bottomRight.V + heightOffset)); } public bool Contains(PPos puv) diff --git a/OpenRA.Game/ModData.cs b/OpenRA.Game/ModData.cs index 7d47629c454e..3b0c427b8b76 100644 --- a/OpenRA.Game/ModData.cs +++ b/OpenRA.Game/ModData.cs @@ -155,17 +155,18 @@ public Map PrepareMap(string uid) if (MapCache[uid].Status != MapStatus.Available) throw new InvalidDataException($"Invalid map uid: {uid}"); + // TODO: Make loader use IMap interface instead of Map Map map; using (new Support.PerfTimer("Map")) map = MapLoader.Load(this, MapCache[uid].Package); // Reinitialize all our assets InitializeLoaders(map); - map.Sequences.LoadSprites(); + ((IMap)map).Sequences.LoadSprites(); // Load music with map assets mounted using (new Support.PerfTimer("Map.Music")) - foreach (var entry in map.Rules.Music) + foreach (var entry in ((IMap)map).Rules.Music) entry.Value.Load(map); return map; diff --git a/OpenRA.Game/Player.cs b/OpenRA.Game/Player.cs index fc6556cbd12d..2e4b6441e33b 100644 --- a/OpenRA.Game/Player.cs +++ b/OpenRA.Game/Player.cs @@ -156,7 +156,7 @@ public Player(World world, Session.Client client, PlayerReference pr, MersenneTw InternalName = pr.Name; PlayerReference = pr; - inMissionMap = world.Map.Visibility.HasFlag(MapVisibility.MissionSelector); + inMissionMap = ((Map)world.Map).Visibility.HasFlag(MapVisibility.MissionSelector); // Real player or host-created bot if (client != null) diff --git a/OpenRA.Game/Scripting/ScriptContext.cs b/OpenRA.Game/Scripting/ScriptContext.cs index 421addf371eb..4f05515a511f 100644 --- a/OpenRA.Game/Scripting/ScriptContext.cs +++ b/OpenRA.Game/Scripting/ScriptContext.cs @@ -201,7 +201,7 @@ public sealed class ScriptContext : IDisposable using (var loadScript = (LuaFunction)runtime.Globals["ExecuteSandboxedScript"]) { foreach (var s in scripts) - loadScript.Call(s, world.Map.Open(s).ReadAllText()).Dispose(); + loadScript.Call(s, ((Map)world.Map).Open(s).ReadAllText()).Dispose(); } } diff --git a/OpenRA.Game/Support/ExceptionHandler.cs b/OpenRA.Game/Support/ExceptionHandler.cs index c4813f02d825..45c08c3a6a86 100644 --- a/OpenRA.Game/Support/ExceptionHandler.cs +++ b/OpenRA.Game/Support/ExceptionHandler.cs @@ -38,7 +38,7 @@ public static void HandleFatalError(Exception ex) if (Game.OrderManager != null && Game.OrderManager.World != null && Game.OrderManager.World.Map != null) { var map = Game.OrderManager.World.Map; - Log.Write("exception", $"on map {map.Uid} ({map.Title} by {map.Author})."); + Log.Write("exception", $"on map {((Map)map).Uid} ({((Map)map).Title} by {((Map)map).Author})."); } Log.Write("exception", $"Date: {DateTime.UtcNow:u}"); diff --git a/OpenRA.Game/Traits/Player/Shroud.cs b/OpenRA.Game/Traits/Player/Shroud.cs index e4fed9614fef..a9ab6166b9aa 100644 --- a/OpenRA.Game/Traits/Player/Shroud.cs +++ b/OpenRA.Game/Traits/Player/Shroud.cs @@ -95,7 +95,7 @@ public ShroudSource(SourceType type, PPos[] projectedCells) public enum CellVisibility : byte { Hidden = 0x0, Explored = 0x1, Visible = 0x2 } readonly ShroudInfo info; - readonly Map map; + readonly IMap map; // Individual shroud modifier sources (type and area) readonly Dictionary sources = new Dictionary(); @@ -233,7 +233,7 @@ void ITick.Tick(Actor self) disabledChanged = false; } - public static IEnumerable ProjectedCellsInRange(Map map, WPos pos, WDist minRange, WDist maxRange, int maxHeightDelta = -1) + public static IEnumerable ProjectedCellsInRange(IMap map, WPos pos, WDist minRange, WDist maxRange, int maxHeightDelta = -1) { // Account for potential extra half-cell from odd-height terrain var r = (maxRange.Length + 1023 + 512) / 1024; @@ -257,7 +257,7 @@ public static IEnumerable ProjectedCellsInRange(Map map, WPos pos, WDist m } } - public static IEnumerable ProjectedCellsInRange(Map map, CPos cell, WDist range, int maxHeightDelta = -1) + public static IEnumerable ProjectedCellsInRange(IMap map, CPos cell, WDist range, int maxHeightDelta = -1) { return ProjectedCellsInRange(map, map.CenterOfCell(cell), WDist.Zero, range, maxHeightDelta); } diff --git a/OpenRA.Game/Traits/TraitsInterfaces.cs b/OpenRA.Game/Traits/TraitsInterfaces.cs index c80bae7af70c..99664c930ba1 100644 --- a/OpenRA.Game/Traits/TraitsInterfaces.cs +++ b/OpenRA.Game/Traits/TraitsInterfaces.cs @@ -272,7 +272,7 @@ public interface ISelectionDecorations public interface IMapPreviewSignatureInfo : ITraitInfoInterface { - void PopulateMapPreviewSignatureCells(Map map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer); + void PopulateMapPreviewSignatureCells(IMap map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer); } public interface IOccupySpaceInfo : ITraitInfoInterface diff --git a/OpenRA.Game/World.cs b/OpenRA.Game/World.cs index 8b83e5bf61ca..4a70718579ee 100644 --- a/OpenRA.Game/World.cs +++ b/OpenRA.Game/World.cs @@ -135,7 +135,7 @@ void SetLocalPlayer(Player localPlayer) public readonly Actor WorldActor; - public readonly Map Map; + public readonly IMap Map; public readonly IActorMap ActorMap; public readonly ScreenMap ScreenMap; @@ -194,7 +194,7 @@ internal World(string mapUID, ModData modData, OrderManager orderManager, WorldT Type = type; OrderManager = orderManager; using (new PerfTimer("PrepareMap")) - Map = modData.PrepareMap(mapUID); + Map = (IMap)modData.PrepareMap(mapUID); if (string.IsNullOrEmpty(modData.Manifest.DefaultOrderGenerator)) throw new InvalidDataException("mod.yaml must define a DefaultOrderGenerator"); @@ -213,7 +213,7 @@ internal World(string mapUID, ModData modData, OrderManager orderManager, WorldT SharedRandom = new MersenneTwister(orderManager.LobbyInfo.GlobalSettings.RandomSeed); LocalRandom = new MersenneTwister(); - ModelCache = modData.ModelSequenceLoader.CacheModels(Map, modData, Map.Rules.ModelSequences); + ModelCache = modData.ModelSequenceLoader.CacheModels((FileSystem.IReadOnlyFileSystem)Map, modData, Map.Rules.ModelSequences); var worldActorType = type == WorldType.Editor ? SystemActors.EditorWorld : SystemActors.World; WorldActor = CreateActor(worldActorType.ToString(), new TypeDictionary()); @@ -238,8 +238,8 @@ internal World(string mapUID, ModData modData, OrderManager orderManager, WorldT Mod = Game.ModData.Manifest.Id, Version = Game.ModData.Manifest.Metadata.Version, - MapUid = Map.Uid, - MapTitle = Map.Title + MapUid = ((Map)Map).Uid, + MapTitle = ((Map)Map).Title }; RulesContainTemporaryBlocker = Map.Rules.Actors.Any(a => a.Value.HasTraitInfo()); diff --git a/OpenRA.Mods.Cnc/Activities/LeapAttack.cs b/OpenRA.Mods.Cnc/Activities/LeapAttack.cs index 2ab0f27b51b5..1a67f4fe438b 100644 --- a/OpenRA.Mods.Cnc/Activities/LeapAttack.cs +++ b/OpenRA.Mods.Cnc/Activities/LeapAttack.cs @@ -128,9 +128,9 @@ public override bool Tick(Actor self) // to avoid continuous facing adjustments as the target moves var targetMobile = target.Actor.TraitOrDefault(); var targetSubcell = targetMobile != null ? targetMobile.ToSubCell : SubCell.Any; - - var destination = self.World.Map.CenterOfSubCell(target.Actor.Location, targetSubcell); - var origin = self.World.Map.CenterOfSubCell(self.Location, mobile.FromSubCell); + var map = self.World.Map; + var destination = map.CenterOfSubCell(target.Actor.Location, targetSubcell); + var origin = map.CenterOfSubCell(self.Location, mobile.FromSubCell); var desiredFacing = (destination - origin).Yaw; if (mobile.Facing != desiredFacing) { diff --git a/OpenRA.Mods.Cnc/Activities/Teleport.cs b/OpenRA.Mods.Cnc/Activities/Teleport.cs index 899b3a37d09b..c8ea59cf1221 100644 --- a/OpenRA.Mods.Cnc/Activities/Teleport.cs +++ b/OpenRA.Mods.Cnc/Activities/Teleport.cs @@ -117,10 +117,12 @@ public override bool Tick(Actor self) CPos? ChooseBestDestinationCell(Actor self, CPos destination) { + var map = self.World.Map; + if (teleporter == null) return null; - var restrictTo = maximumDistance == null ? null : self.World.Map.FindTilesInCircle(self.Location, maximumDistance.Value); + var restrictTo = maximumDistance == null ? null : map.FindTilesInCircle(self.Location, maximumDistance.Value); if (maximumDistance != null) destination = restrictTo.MinBy(x => (x - destination).LengthSquared); @@ -130,7 +132,7 @@ public override bool Tick(Actor self) return destination; var max = maximumDistance != null ? maximumDistance.Value : teleporter.World.Map.Grid.MaximumTileSearchRange; - foreach (var tile in self.World.Map.FindTilesInCircle(destination, max)) + foreach (var tile in map.FindTilesInCircle(destination, max)) { if (teleporter.Owner.Shroud.IsExplored(tile) && (restrictTo == null || restrictTo.Contains(tile)) diff --git a/OpenRA.Mods.Cnc/Traits/Minelayer.cs b/OpenRA.Mods.Cnc/Traits/Minelayer.cs index d7351fd2e6e7..c3e3b1bf5cda 100644 --- a/OpenRA.Mods.Cnc/Traits/Minelayer.cs +++ b/OpenRA.Mods.Cnc/Traits/Minelayer.cs @@ -194,13 +194,15 @@ static IEnumerable GetMinefieldCells(CPos start, CPos end, WDist depth) public bool IsCellAcceptable(Actor self, CPos cell) { - if (!self.World.Map.Contains(cell)) + var map = self.World.Map; + + if (!map.Contains(cell)) return false; if (Info.TerrainTypes.Count == 0) return true; - var terrainType = self.World.Map.GetTerrainInfo(cell).Type; + var terrainType = map.GetTerrainInfo(cell).Type; return Info.TerrainTypes.Contains(terrainType); } @@ -297,6 +299,7 @@ protected override void SelectionChanged(World world, IEnumerable selecte protected override IEnumerable Render(WorldRenderer wr, World world) { yield break; } protected override IEnumerable RenderAboveShroud(WorldRenderer wr, World world) { + var map = world.Map; var minelayer = minelayers.FirstOrDefault(m => m.IsInWorld && !m.IsDead); if (minelayer == null) yield break; @@ -309,11 +312,12 @@ protected override IEnumerable RenderAboveShroud(WorldRenderer wr, var movement = minelayer.Trait(); var mobile = movement as Mobile; var pal = wr.Palette(TileSet.TerrainPaletteInternalName); + foreach (var c in minefield) { var tile = validTile; var alpha = validAlpha; - if (!world.Map.Contains(c)) + if (!map.Contains(c)) { tile = blockedTile; alpha = blockedAlpha; @@ -335,7 +339,7 @@ protected override IEnumerable RenderAboveShroud(WorldRenderer wr, alpha = blockedAlpha; } - yield return new SpriteRenderable(tile, world.Map.CenterOfCell(c), WVec.Zero, -511, pal, 1f, alpha, float3.Ones, TintModifiers.IgnoreWorldTint, true); + yield return new SpriteRenderable(tile, map.CenterOfCell(c), WVec.Zero, -511, pal, 1f, alpha, float3.Ones, TintModifiers.IgnoreWorldTint, true); } } @@ -363,11 +367,13 @@ public BeginMinefieldOrderTargeter(string cursor) public bool CanTarget(Actor self, in Target target, ref TargetModifiers modifiers, ref string cursor) { + var map = self.World.Map; + if (target.Type != TargetType.Terrain) return false; - var location = self.World.Map.CellContaining(target.CenterPosition); - if (!self.World.Map.Contains(location)) + var location = map.CellContaining(target.CenterPosition); + if (!map.Contains(location)) return false; cursor = this.cursor; diff --git a/OpenRA.Mods.Cnc/Traits/Render/WithBuildingBib.cs b/OpenRA.Mods.Cnc/Traits/Render/WithBuildingBib.cs index 645e0bcb51d4..4a542fa8838d 100644 --- a/OpenRA.Mods.Cnc/Traits/Render/WithBuildingBib.cs +++ b/OpenRA.Mods.Cnc/Traits/Render/WithBuildingBib.cs @@ -56,7 +56,7 @@ public IEnumerable RenderPreviewSprites(ActorPreviewInitializer i // Some mods may define terrain-specific bibs var sequence = Sequence; - if (map.Tiles.Contains(cell)) + if (((IMapTiles)map).Tiles.Contains(cell)) { var terrain = map.GetTerrainInfo(cell).Type; var testSequence = Sequence + "-" + terrain; @@ -117,7 +117,7 @@ void INotifyAddedToWorld.AddedToWorld(Actor self) anim.IsDecoration = true; // Z-order is one set to the top of the footprint - var offset = self.World.Map.CenterOfCell(cell) - self.World.Map.CenterOfCell(location) - centerOffset; + var offset = map.CenterOfCell(cell) - map.CenterOfCell(location) - centerOffset; var awo = new AnimationWithOffset(anim, () => offset, null, -(offset.Y + centerOffset.Y + 512)); anims.Add(awo); rs.Add(awo, info.Palette); diff --git a/OpenRA.Mods.Cnc/Traits/Render/WithLandingCraftAnimation.cs b/OpenRA.Mods.Cnc/Traits/Render/WithLandingCraftAnimation.cs index 53385062cf74..940415798c03 100644 --- a/OpenRA.Mods.Cnc/Traits/Render/WithLandingCraftAnimation.cs +++ b/OpenRA.Mods.Cnc/Traits/Render/WithLandingCraftAnimation.cs @@ -56,11 +56,13 @@ public WithLandingCraftAnimation(ActorInitializer init, WithLandingCraftAnimatio public bool ShouldBeOpen() { - if (move.CurrentMovementTypes != MovementType.None || self.World.Map.DistanceAboveTerrain(self.CenterPosition).Length > 0) + var map = self.World.Map; + + if (move.CurrentMovementTypes != MovementType.None || map.DistanceAboveTerrain(self.CenterPosition).Length > 0) return false; - return cargo.CurrentAdjacentCells.Any(c => self.World.Map.Contains(c) - && info.OpenTerrainTypes.Contains(self.World.Map.GetTerrainInfo(c).Type)); + return cargo.CurrentAdjacentCells.Any(c => map.Contains(c) + && info.OpenTerrainTypes.Contains(map.GetTerrainInfo(c).Type)); } void Open() diff --git a/OpenRA.Mods.Cnc/Traits/SupportPowers/AttackOrderPower.cs b/OpenRA.Mods.Cnc/Traits/SupportPowers/AttackOrderPower.cs index b8975b271beb..ce0add118d7a 100644 --- a/OpenRA.Mods.Cnc/Traits/SupportPowers/AttackOrderPower.cs +++ b/OpenRA.Mods.Cnc/Traits/SupportPowers/AttackOrderPower.cs @@ -101,10 +101,12 @@ public SelectAttackPowerTarget(Actor self, string order, SupportPowerManager man bool IsValidTarget(World world, CPos cell) { - var pos = world.Map.CenterOfCell(cell); + var map = world.Map; + + var pos = map.CenterOfCell(cell); var range = attack.GetMaximumRange().LengthSquared; - return world.Map.Contains(cell) && instance.Instances.Any(a => !a.IsTraitPaused && (a.Self.CenterPosition - pos).HorizontalLengthSquared < range); + return map.Contains(cell) && instance.Instances.Any(a => !a.IsTraitPaused && (a.Self.CenterPosition - pos).HorizontalLengthSquared < range); } protected override IEnumerable OrderInner(World world, CPos cell, int2 worldPixel, MouseInput mi) diff --git a/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs b/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs index 884f71a650c2..f0eeac48e139 100644 --- a/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs +++ b/OpenRA.Mods.Cnc/Traits/SupportPowers/ChronoshiftPower.cs @@ -89,8 +89,7 @@ public override void Activate(Actor self, Order order, SupportPowerManager manag var targetDelta = self.World.Map.CellContaining(order.Target.CenterPosition) - order.ExtraLocation; foreach (var target in UnitsInRange(order.ExtraLocation)) { - var cs = target.TraitsImplementing() - .FirstEnabledConditionalTraitOrDefault(); + var cs = target.TraitsImplementing().FirstEnabledConditionalTraitOrDefault(); if (cs == null) continue; @@ -129,11 +128,12 @@ public bool SimilarTerrain(CPos xy, CPos sourceLocation) { var a = se.Current; var b = de.Current; + var map = Self.World.Map; if (!Self.Owner.Shroud.IsExplored(a) || !Self.Owner.Shroud.IsExplored(b)) return false; - if (Self.World.Map.GetTerrainIndex(a) != Self.World.Map.GetTerrainIndex(b)) + if (map.GetTerrainIndex(a) != map.GetTerrainIndex(b)) return false; } @@ -302,6 +302,7 @@ protected override IEnumerable RenderAboveShroud(WorldRenderer wr, { var xy = wr.Viewport.ViewToWorld(Viewport.LastMousePos); var palette = wr.Palette(power.Info.IconPalette); + var wrMapCell = wr.World.Map; // Destination tiles var delta = xy - sourceLocation; @@ -310,7 +311,7 @@ protected override IEnumerable RenderAboveShroud(WorldRenderer wr, var isValid = manager.Self.Owner.Shroud.IsExplored(t + delta); var tile = isValid ? validTile : invalidTile; var alpha = isValid ? validAlpha : invalidAlpha; - yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(t + delta), WVec.Zero, -511, palette, 1f, alpha, float3.Ones, TintModifiers.IgnoreWorldTint, true); + yield return new SpriteRenderable(tile, wrMapCell.CenterOfCell(t + delta), WVec.Zero, -511, palette, 1f, alpha, float3.Ones, TintModifiers.IgnoreWorldTint, true); } // Unit previews @@ -323,10 +324,11 @@ protected override IEnumerable RenderAboveShroud(WorldRenderer wr, unit.Trait().CanChronoshiftTo(unit, targetCell); var tile = canEnter ? validTile : invalidTile; var alpha = canEnter ? validAlpha : invalidAlpha; - yield return new SpriteRenderable(tile, wr.World.Map.CenterOfCell(targetCell), WVec.Zero, -511, palette, 1f, alpha, float3.Ones, TintModifiers.IgnoreWorldTint, true); + yield return new SpriteRenderable(tile, wrMapCell.CenterOfCell(targetCell), WVec.Zero, -511, palette, 1f, alpha, float3.Ones, TintModifiers.IgnoreWorldTint, true); } - var offset = world.Map.CenterOfCell(xy) - world.Map.CenterOfCell(sourceLocation); + var map = world.Map; + var offset = map.CenterOfCell(xy) - map.CenterOfCell(sourceLocation); if (unit.CanBeViewedByPlayer(manager.Self.Owner)) foreach (var r in unit.Render(wr)) yield return r.OffsetBy(offset); diff --git a/OpenRA.Mods.Cnc/Traits/SupportPowers/DropPodsPower.cs b/OpenRA.Mods.Cnc/Traits/SupportPowers/DropPodsPower.cs index d0892af80a3a..b5c1e8591e5f 100644 --- a/OpenRA.Mods.Cnc/Traits/SupportPowers/DropPodsPower.cs +++ b/OpenRA.Mods.Cnc/Traits/SupportPowers/DropPodsPower.cs @@ -101,12 +101,13 @@ public void SendDropPods(Actor self, Order order, WAngle facing) var approachRotation = WRot.FromYaw(facing); var fallsToEarthInfo = actorInfo.TraitInfo(); var delta = new WVec(0, -altitude * aircraftInfo.Speed / fallsToEarthInfo.Velocity.Length, 0).Rotate(approachRotation); + var map = self.World.Map; self.World.AddFrameEndTask(w => { var target = order.Target.CenterPosition; - var targetCell = self.World.Map.CellContaining(target); - var podLocations = self.World.Map.FindTilesInCircle(targetCell, info.PodScatter) + var targetCell = map.CellContaining(target); + var podLocations = map.FindTilesInCircle(targetCell, info.PodScatter) .Where(c => aircraftInfo.LandableTerrainTypes.Contains(w.Map.GetTerrainInfo(c).Type) && !self.World.ActorMap.GetActorsAt(c).Any()); @@ -133,7 +134,7 @@ public void SendDropPods(Actor self, Order order, WAngle facing) var unitType = info.UnitTypes.Random(self.World.SharedRandom); var podLocation = podLocations.Random(self.World.SharedRandom); var podTarget = Target.FromCell(w, podLocation); - var location = self.World.Map.CenterOfCell(podLocation) - delta + new WVec(0, 0, altitude); + var location = map.CenterOfCell(podLocation) - delta + new WVec(0, 0, altitude); var pod = w.CreateActor(false, unitType, new TypeDictionary { diff --git a/OpenRA.Mods.Cnc/Traits/World/JumpjetActorLayer.cs b/OpenRA.Mods.Cnc/Traits/World/JumpjetActorLayer.cs index 123dd465d714..8570b31b0f7a 100644 --- a/OpenRA.Mods.Cnc/Traits/World/JumpjetActorLayer.cs +++ b/OpenRA.Mods.Cnc/Traits/World/JumpjetActorLayer.cs @@ -32,7 +32,7 @@ public class JumpjetActorLayerInfo : TraitInfo, ICustomMovementLayerInfo public class JumpjetActorLayer : ICustomMovementLayer { - readonly Map map; + readonly IMap map; readonly byte terrainIndex; readonly CellLayer height; @@ -42,7 +42,7 @@ public JumpjetActorLayer(Actor self, JumpjetActorLayerInfo info) map = self.World.Map; terrainIndex = self.World.Map.Rules.TerrainInfo.GetTerrainIndex(info.TerrainType); height = new CellLayer(map); - var cellHeight = self.World.Map.CellHeightStep.Length; + var cellHeight = map.CellHeightStep.Length; foreach (var c in map.AllCells) { var neighbourCount = 0; @@ -56,7 +56,7 @@ public JumpjetActorLayer(Actor self, JumpjetActorLayerInfo info) continue; neighbourCount++; - neighbourHeight += map.Height[neighbour]; + neighbourHeight += ((IMapElevation)map).Height[neighbour]; } } @@ -85,7 +85,7 @@ bool ValidTransitionCell(CPos cell, LocomotorInfo li) if (jli.JumpjetTransitionOnRamps) return true; - return map.Ramp[cell] == 0; + return ((IMapElevation)map).Ramp[cell] == 0; } short ICustomMovementLayer.EntryMovementCost(LocomotorInfo li, CPos cell) diff --git a/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs b/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs index 4c80e9ee740b..e090ea695c98 100644 --- a/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs +++ b/OpenRA.Mods.Cnc/Traits/World/TSEditorResourceLayer.cs @@ -42,6 +42,8 @@ public TSEditorResourceLayer(Actor self, TSEditorResourceLayerInfo info) bool IsValidVeinNeighbour(CPos cell, CPos neighbour) { + var mapRamp = ((IMapElevation)Map).Ramp; + if (!Map.Contains(neighbour)) return false; @@ -50,22 +52,22 @@ bool IsValidVeinNeighbour(CPos cell, CPos neighbour) return true; // Neighbour must be flat or a cardinal slope, unless the resource cell itself is a slope - if (Map.Ramp[cell] == 0 && Map.Ramp[neighbour] > 4) + if (mapRamp[cell] == 0 && mapRamp[neighbour] > 4) return false; var terrainInfo = Map.Rules.TerrainInfo; - var terrainType = terrainInfo.TerrainTypes[terrainInfo.GetTerrainInfo(Map.Tiles[neighbour]).TerrainType].Type; + var terrainType = terrainInfo.TerrainTypes[terrainInfo.GetTerrainInfo(((IMapTiles)Map).Tiles[neighbour]).TerrainType].Type; return info.ResourceTypes[info.VeinType].AllowedTerrainTypes.Contains(terrainType); } protected override bool AllowResourceAt(string resourceType, CPos cell) { - var mapResources = Map.Resources; + var mapResources = ((IMapResource)Map).Resources; if (!mapResources.Contains(cell)) return false; // Resources are allowed on flat terrain and cardinal slopes - if (Map.Ramp[cell] > 4) + if (((IMapElevation)Map).Ramp[cell] > 4) return false; if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) @@ -73,7 +75,7 @@ protected override bool AllowResourceAt(string resourceType, CPos cell) // Ignore custom terrain types when spawning resources in the editor var terrainInfo = Map.Rules.TerrainInfo; - var terrainType = terrainInfo.TerrainTypes[terrainInfo.GetTerrainInfo(Map.Tiles[cell]).TerrainType].Type; + var terrainType = terrainInfo.TerrainTypes[terrainInfo.GetTerrainInfo(((IMapTiles)Map).Tiles[cell]).TerrainType].Type; if (!resourceInfo.AllowedTerrainTypes.Contains(terrainType)) return false; @@ -93,16 +95,17 @@ protected override bool AllowResourceAt(string resourceType, CPos cell) protected override int AddResource(string resourceType, CPos cell, int amount = 1) { + var mapResource = (IMapResource)Map; var added = base.AddResource(resourceType, cell, amount); // Update neighbouring cells if needed to provide space for vein borders var resourceIsVeins = resourceType == info.VeinType; foreach (var c in Common.Util.ExpandFootprint(cell, false)) { - if (!Map.Resources.Contains(c)) + if (!mapResource.Resources.Contains(c)) continue; - var resourceIndex = Map.Resources[c].Type; + var resourceIndex = mapResource.Resources[c].Type; if (resourceIndex == 0 || !ResourceTypesByIndex.TryGetValue(resourceIndex, out var neighourResourceType)) neighourResourceType = null; diff --git a/OpenRA.Mods.Cnc/Traits/World/TSResourceLayer.cs b/OpenRA.Mods.Cnc/Traits/World/TSResourceLayer.cs index 7218ee14e262..333a7eb78858 100644 --- a/OpenRA.Mods.Cnc/Traits/World/TSResourceLayer.cs +++ b/OpenRA.Mods.Cnc/Traits/World/TSResourceLayer.cs @@ -84,6 +84,8 @@ bool IsValidResourceNeighbour(CPos cell, CPos neighbour) bool IsValidVeinNeighbour(CPos cell, CPos neighbour) { + var mapRamp = ((IMapElevation)Map).Ramp; + if (!Map.Contains(neighbour)) return false; @@ -92,7 +94,7 @@ bool IsValidVeinNeighbour(CPos cell, CPos neighbour) return true; // Neighbour must be flat or a cardinal slope, unless the resource cell itself is a slope - if (Map.Ramp[cell] == 0 && Map.Ramp[neighbour] > 4) + if (mapRamp[cell] == 0 && mapRamp[neighbour] > 4) return false; // Neighbour must be have a compatible terrain type (which also implies no other resources) @@ -103,11 +105,13 @@ bool IsValidVeinNeighbour(CPos cell, CPos neighbour) protected override bool AllowResourceAt(string resourceType, CPos cell) { + var mapRamp = ((IMapElevation)Map).Ramp; + if (!Map.Contains(cell)) return false; // Resources are allowed on flat terrain and cardinal slopes - if (Map.Ramp[cell] > 4) + if (mapRamp[cell] > 4) return false; if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) @@ -118,7 +122,7 @@ protected override bool AllowResourceAt(string resourceType, CPos cell) // Ensure there is space for the vein border tiles (not needed on ramps) var check = resourceType == info.VeinType ? (Func)IsValidVeinNeighbour : IsValidResourceNeighbour; - var blockedByNeighbours = Map.Ramp[cell] == 0 && !Common.Util.ExpandFootprint(cell, false).All(c => check(cell, c)); + var blockedByNeighbours = mapRamp[cell] == 0 && !Common.Util.ExpandFootprint(cell, false).All(c => check(cell, c)); return !blockedByNeighbours && (resourceType == info.VeinType || !BuildingInfluence.AnyBuildingAt(cell)); } diff --git a/OpenRA.Mods.Cnc/Traits/World/TSTiberiumRenderer.cs b/OpenRA.Mods.Cnc/Traits/World/TSTiberiumRenderer.cs index f4cf619ec3bc..fc2583f322f9 100644 --- a/OpenRA.Mods.Cnc/Traits/World/TSTiberiumRenderer.cs +++ b/OpenRA.Mods.Cnc/Traits/World/TSTiberiumRenderer.cs @@ -79,7 +79,7 @@ protected override void WorldLoaded(World w, WorldRenderer wr) protected override ISpriteSequence ChooseVariant(string resourceType, CPos cell) { Dictionary> variants; - switch (world.Map.Ramp[cell]) + switch (((IMapElevation)world.Map).Ramp[cell]) { case 1: variants = ramp1Variants; break; case 2: variants = ramp2Variants; break; diff --git a/OpenRA.Mods.Cnc/Traits/World/TSVeinsRenderer.cs b/OpenRA.Mods.Cnc/Traits/World/TSVeinsRenderer.cs index 5bac585b41cb..16c86a8a8dcb 100644 --- a/OpenRA.Mods.Cnc/Traits/World/TSVeinsRenderer.cs +++ b/OpenRA.Mods.Cnc/Traits/World/TSVeinsRenderer.cs @@ -46,7 +46,7 @@ public class TSVeinsRendererInfo : TraitInfo, Requires, IMap [Desc("Actor types that should be treated as veins for adjacency.")] public readonly HashSet VeinholeActors = new HashSet { }; - void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer) + void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(IMap map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer) { var resourceLayer = ai.TraitInfoOrDefault(); if (resourceLayer == null) @@ -57,7 +57,7 @@ void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInf var veinholeCells = new HashSet(); - foreach (var kv in map.ActorDefinitions) + foreach (var kv in ((Map)map).ActorDefinitions) { var type = kv.Value.Value; if (!VeinholeActors.Contains(type)) @@ -71,6 +71,8 @@ void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInf } var terrainInfo = map.Rules.TerrainInfo; + var mapResources = ((IMapResource)map).Resources; + var mapRamp = ((IMapElevation)map).Ramp; var info = terrainInfo.TerrainTypes[terrainInfo.GetTerrainIndex(terrainType)]; for (var i = 0; i < map.MapSize.X; i++) @@ -80,7 +82,7 @@ void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInf var uv = new MPos(i, j); // Cell contains veins - if (map.Resources[uv].Type == resourceIndex) + if (mapResources[uv].Type == resourceIndex) { destinationBuffer.Add((uv, info.Color)); continue; @@ -88,15 +90,15 @@ void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInf // Cell is a vein border if it is flat and adjacent to at least one cell // that is also flat and contains veins (borders are not drawn next to slope vein cells) - var isBorder = map.Ramp[uv] == 0 && Common.Util.ExpandFootprint(uv.ToCPos(map), false).Any(c => + var isBorder = mapRamp[uv] == 0 && Common.Util.ExpandFootprint(uv.ToCPos(map), false).Any(c => { - if (!map.Resources.Contains(c)) + if (!mapResources.Contains(c)) return false; if (veinholeCells.Contains(c)) return true; - return map.Resources[c].Type == resourceIndex && map.Ramp[c] == 0; + return mapResources[c].Type == resourceIndex && mapRamp[c] == 0; }); if (isBorder) @@ -174,8 +176,9 @@ public TSVeinsRenderer(Actor self, TSVeinsRendererInfo info) resourceLayer.Info.TryGetTerrainType(info.ResourceType, out var terrainType); veinRadarColor = terrainInfo.TerrainTypes[terrainInfo.GetTerrainIndex(terrainType)].Color; - renderIndices = new CellLayer(world.Map); - borders = new CellLayer(world.Map); + var map = world.Map; + renderIndices = new CellLayer(map); + borders = new CellLayer(map); } void AddDirtyCell(CPos cell, string resourceType) @@ -219,7 +222,7 @@ int[] CalculateCellIndices(ResourceLayerContents contents, CPos cell) if (contents.Type != info.ResourceType || contents.Density == 0) return null; - var ramp = world.Map.Ramp[cell]; + var ramp = ((IMapElevation)world.Map).Ramp[cell]; switch (ramp) { case 1: return Ramp1Indices; @@ -263,13 +266,13 @@ bool HasBorder(CPos cell) return false; // Draw the vein border if this is a flat cell with veins, or a veinhole - return (world.Map.Ramp[cell] == 0 && renderIndices[cell] != null) || veinholeCells.Contains(cell); + return (((IMapElevation)world.Map).Ramp[cell] == 0 && renderIndices[cell] != null) || veinholeCells.Contains(cell); } Adjacency CalculateBorders(CPos cell) { // Borders are only valid on flat cells - if (world.Map.Ramp[cell] != 0) + if (((IMapElevation)world.Map).Ramp[cell] != 0) return Adjacency.None; var ret = Adjacency.None; @@ -300,7 +303,7 @@ void UpdateRenderedSprite(CPos cell, int[] indices) void UpdateBorderSprite(CPos cell) { // Borders are never drawn on ramps or in cells that contain resources - if (HasBorder(cell) || world.Map.Ramp[cell] != 0) + if (HasBorder(cell) || ((IMapElevation)world.Map).Ramp[cell] != 0) return; var adjacency = CalculateBorders(cell); diff --git a/OpenRA.Mods.Cnc/Traits/World/WithResourceAnimation.cs b/OpenRA.Mods.Cnc/Traits/World/WithResourceAnimation.cs index 65a5fc62a3e0..b88e93531bd3 100644 --- a/OpenRA.Mods.Cnc/Traits/World/WithResourceAnimation.cs +++ b/OpenRA.Mods.Cnc/Traits/World/WithResourceAnimation.cs @@ -77,16 +77,18 @@ void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) void ITick.Tick(Actor self) { + var map = world.Map; + if (--ticks > 0) return; var cells = new HashSet(); foreach (var uv in worldRenderer.Viewport.AllVisibleCells.CandidateMapCoords) { - if (!world.Map.Contains(uv)) + if (!map.Contains(uv)) return; - var cell = uv.ToCPos(world.Map); + var cell = uv.ToCPos(map); var type = resourceRenderer.GetRenderedResourceType(cell); if (type != null && info.Types.Contains(type)) cells.Add(cell); @@ -95,7 +97,7 @@ void ITick.Tick(Actor self) var ratio = Common.Util.RandomInRange(world.LocalRandom, info.Ratio); var positions = cells.Shuffle(world.LocalRandom) .Take(Math.Max(1, cells.Count * ratio / 100)) - .Select(x => world.Map.CenterOfCell(x)); + .Select(x => map.CenterOfCell(x)); foreach (var position in positions) world.AddFrameEndTask(w => w.Add(new SpriteEffect(position, w, info.Image, info.Sequences.Random(w.LocalRandom), info.Palette))); diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportRedAlertLegacyMapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportRedAlertLegacyMapCommand.cs index e8eda8ab33a1..e3a1784d247c 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportRedAlertLegacyMapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportRedAlertLegacyMapCommand.cs @@ -78,7 +78,7 @@ void UnpackTileData(MemoryStream ms) for (var j = 0; j < MapSize; j++) for (var i = 0; i < MapSize; i++) - Map.Tiles[new CPos(i, j)] = new TerrainTile(types[i, j], ms.ReadUInt8()); + ((IMapTiles)Map).Tiles[new CPos(i, j)] = new TerrainTile(types[i, j], ms.ReadUInt8()); } static readonly string[] OverlayActors = new string[] @@ -106,7 +106,7 @@ void UnpackOverlayData(MemoryStream ms) res = OverlayResourceMapping[RedAlertOverlayNames[o]]; var cell = new CPos(i, j); - Map.Resources[cell] = new ResourceTile(res.Type, res.Index); + ((IMapResource)Map).Resources[cell] = new ResourceTile(res.Type, res.Index); if (o != 255 && OverlayActors.Contains(RedAlertOverlayNames[o])) { @@ -239,7 +239,7 @@ public override void ReadPacks(IniFile file, string filename) public override void ReadActors(IniFile file) { base.ReadActors(file); - LoadActors(file, "SHIPS", Players, Map); + LoadActors(file, "SHIPS", Players, (IMap)Map); } public override void SaveWaypoint(int waypointNumber, ActorReference waypointReference) diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs index 06eb4d0bbbd2..2887e37288ac 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs @@ -333,7 +333,7 @@ static void UnpackLCW(byte[] src, byte[] dest, byte[] temp) } } - static void ReadTiles(Map map, IniFile file, int2 fullSize) + static void ReadTiles(IMap map, IniFile file, int2 fullSize) { var terrainInfo = (ITemplatedTerrainInfo)Game.ModData.DefaultTerrainInfo[map.Tileset]; var mapSection = file.GetSection("IsoMapPack5"); @@ -360,19 +360,21 @@ static void ReadTiles(Map map, IniFile file, int2 fullSize) var mapCell = new MPos(dx / 2, dy); var cell = mapCell.ToCPos(map); - if (map.Tiles.Contains(cell)) + if (((IMapTiles)map).Tiles.Contains(cell)) { if (!terrainInfo.Templates.ContainsKey(tilenum)) tilenum = subtile = 0; - map.Tiles[cell] = new TerrainTile(tilenum, subtile); - map.Height[cell] = z; + ((IMapTiles)map).Tiles[cell] = new TerrainTile(tilenum, subtile); + ((IMapElevation)map).Height[cell] = z; } } } - static void ReadOverlay(Map map, IniFile file, int2 fullSize) + static void ReadOverlay(IMap map, IniFile file, int2 fullSize) { + var mapResource = (IMapResource)map; + var overlaySection = file.GetSection("OverlayPack"); var overlayCompressed = Convert.FromBase64String(string.Concat(overlaySection.Select(kvp => kvp.Value))); var overlayPack = new byte[1 << 18]; @@ -398,7 +400,7 @@ static void ReadOverlay(Map map, IniFile file, int2 fullSize) var rx = (ushort)((dx + dy) / 2 + 1); var ry = (ushort)(dy - rx + fullSize.X + 1); - if (!map.Resources.Contains(uv)) + if (!mapResource.Resources.Contains(uv)) continue; overlayIndex[uv] = rx + 512 * ry; @@ -456,7 +458,7 @@ static void ReadOverlay(Map map, IniFile file, int2 fullSize) ar.Add(new HealthInit(health)); } - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, ar.Save())); + ((Map)map).ActorDefinitions.Add(new MiniYamlNode("Actor" + ((Map)map).ActorDefinitions.Count, ar.Save())); continue; } @@ -470,7 +472,7 @@ static void ReadOverlay(Map map, IniFile file, int2 fullSize) continue; // Pick half or full density based on the frame - map.Resources[cell] = new ResourceTile(3, (byte)(frame == 52 ? 1 : 2)); + mapResource.Resources[cell] = new ResourceTile(3, (byte)(frame == 52 ? 1 : 2)); continue; } @@ -481,7 +483,7 @@ static void ReadOverlay(Map map, IniFile file, int2 fullSize) if (resourceType != 0) { - map.Resources[cell] = new ResourceTile(resourceType, overlayDataPack[overlayIndex[cell]]); + mapResource.Resources[cell] = new ResourceTile(resourceType, overlayDataPack[overlayIndex[cell]]); continue; } @@ -489,7 +491,7 @@ static void ReadOverlay(Map map, IniFile file, int2 fullSize) } } - static void ReadWaypoints(Map map, IniFile file, int2 fullSize) + static void ReadWaypoints(IMap map, IniFile file, int2 fullSize) { var waypointsSection = file.GetSection("Waypoints", true); foreach (var kv in waypointsSection) @@ -507,11 +509,11 @@ static void ReadWaypoints(Map map, IniFile file, int2 fullSize) new OwnerInit("Neutral") }; - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, ar.Save())); + ((Map)map).ActorDefinitions.Add(new MiniYamlNode("Actor" + ((Map)map).ActorDefinitions.Count, ar.Save())); } } - static void ReadTerrainActors(Map map, IniFile file, int2 fullSize) + static void ReadTerrainActors(IMap map, IniFile file, int2 fullSize) { var terrainSection = file.GetSection("Terrain", true); foreach (var kv in terrainSection) @@ -533,11 +535,11 @@ static void ReadTerrainActors(Map map, IniFile file, int2 fullSize) if (!map.Rules.Actors.ContainsKey(name)) Console.WriteLine($"Ignoring unknown actor type: `{name}`"); else - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, ar.Save())); + ((Map)map).ActorDefinitions.Add(new MiniYamlNode("Actor" + ((Map)map).ActorDefinitions.Count, ar.Save())); } } - static void ReadActors(Map map, IniFile file, string type, int2 fullSize) + static void ReadActors(IMap map, IniFile file, string type, int2 fullSize) { var structuresSection = file.GetSection(type, true); foreach (var kv in structuresSection) @@ -593,11 +595,11 @@ static void ReadActors(Map map, IniFile file, string type, int2 fullSize) if (!map.Rules.Actors.ContainsKey(name)) Console.WriteLine($"Ignoring unknown actor type: `{name}`"); else - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + map.ActorDefinitions.Count, ar.Save())); + ((Map)map).ActorDefinitions.Add(new MiniYamlNode("Actor" + ((Map)map).ActorDefinitions.Count, ar.Save())); } } - static void ReadLighting(Map map, IniFile file) + static void ReadLighting(IMap map, IniFile file) { var lightingTypes = new Dictionary() { @@ -637,14 +639,14 @@ static void ReadLighting(Map map, IniFile file) if (lightingNodes.Count > 0) { - map.RuleDefinitions.Nodes.Add(new MiniYamlNode("^BaseWorld", new MiniYaml("", new List() + ((Map)map).RuleDefinitions.Nodes.Add(new MiniYamlNode("^BaseWorld", new MiniYaml("", new List() { new MiniYamlNode("TerrainLighting", new MiniYaml("", lightingNodes)) }))); } } - static void ReadLamps(Map map, IniFile file) + static void ReadLamps(IMap map, IniFile file) { var lightingTypes = new Dictionary() { @@ -677,7 +679,7 @@ static void ReadLamps(Map map, IniFile file) if (lightingNodes.Count > 0) { - map.RuleDefinitions.Nodes.Add(new MiniYamlNode(lamp, new MiniYaml("", new List() + ((Map)map).RuleDefinitions.Nodes.Add(new MiniYamlNode(lamp, new MiniYaml("", new List() { new MiniYamlNode("TerrainLightSource", new MiniYaml("", lightingNodes)) }))); diff --git a/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianDawnLegacyMapCommand.cs b/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianDawnLegacyMapCommand.cs index 954e262271e1..6b395c040253 100644 --- a/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianDawnLegacyMapCommand.cs +++ b/OpenRA.Mods.Cnc/UtilityCommands/ImportTiberianDawnLegacyMapCommand.cs @@ -64,7 +64,7 @@ void UnpackTileData(Stream ms) { var type = ms.ReadUInt8(); var index = ms.ReadUInt8(); - Map.Tiles[new CPos(i, j)] = new TerrainTile(type, index); + ((IMapTiles)Map).Tiles[new CPos(i, j)] = new TerrainTile(type, index); } } } @@ -97,7 +97,7 @@ void ReadOverlay(IniFile file) if (OverlayResourceMapping.ContainsKey(type)) res = OverlayResourceMapping[type]; - Map.Resources[cell] = new ResourceTile(res.Type, res.Index); + ((IMapResource)Map).Resources[cell] = new ResourceTile(res.Type, res.Index); if (OverlayActors.Contains(type)) { var ar = new ActorReference(type) diff --git a/OpenRA.Mods.Common/AIUtils.cs b/OpenRA.Mods.Common/AIUtils.cs index adc120ecd3ed..e4e4af9ee338 100644 --- a/OpenRA.Mods.Common/AIUtils.cs +++ b/OpenRA.Mods.Common/AIUtils.cs @@ -22,7 +22,7 @@ public enum WaterCheck { NotChecked, EnoughWater, NotEnoughWater, DontCheck } public static class AIUtils { - public static bool IsAreaAvailable(World world, Player player, Map map, int radius, HashSet terrainTypes) + public static bool IsAreaAvailable(World world, Player player, IMap map, int radius, HashSet terrainTypes) { var cells = world.ActorsHavingTrait().Where(a => a.Owner == player); diff --git a/OpenRA.Mods.Common/Activities/Air/Land.cs b/OpenRA.Mods.Common/Activities/Air/Land.cs index ebcbecc79892..bc389924db8f 100644 --- a/OpenRA.Mods.Common/Activities/Air/Land.cs +++ b/OpenRA.Mods.Common/Activities/Air/Land.cs @@ -76,6 +76,8 @@ protected override void OnFirstRun(Actor self) public override bool Tick(Actor self) { + var map = self.World.Map; + if (IsCanceling || target.Type == TargetType.Invalid) { if (landingInitiated) @@ -88,7 +90,7 @@ public override bool Tick(Actor self) var continueLanding = shouldLand && self.CurrentActivity.IsCanceling && self.CurrentActivity.NextActivity == null; if (!continueLanding) { - var dat = self.World.Map.DistanceAboveTerrain(aircraft.CenterPosition); + var dat = map.DistanceAboveTerrain(aircraft.CenterPosition); if (dat > aircraft.LandAltitude && dat < aircraft.Info.CruiseAltitude) { QueueChild(new TakeOff(self)); @@ -107,7 +109,7 @@ public override bool Tick(Actor self) // Reevaluate target position in case the target has moved. targetPosition = target.CenterPosition + offset; - landingCell = self.World.Map.CellContaining(targetPosition); + landingCell = map.CellContaining(targetPosition); // We are already at the landing location. if ((targetPosition - pos).LengthSquared == 0) @@ -129,7 +131,7 @@ public override bool Tick(Actor self) { target = Target.FromCell(self.World, newLocation.Value); targetPosition = target.CenterPosition + offset; - landingCell = self.World.Map.CellContaining(targetPosition); + landingCell = map.CellContaining(targetPosition); } } @@ -237,7 +239,7 @@ public override bool Tick(Actor self) // Final descent. if (aircraft.Info.VTOL) { - var landAltitude = self.World.Map.DistanceAboveTerrain(targetPosition) + aircraft.LandAltitude; + var landAltitude = map.DistanceAboveTerrain(targetPosition) + aircraft.LandAltitude; if (Fly.VerticalTakeOffOrLandTick(self, aircraft, aircraft.Facing, landAltitude)) return false; @@ -255,7 +257,7 @@ public override bool Tick(Actor self) return true; } - var landingAlt = self.World.Map.DistanceAboveTerrain(targetPosition) + aircraft.LandAltitude; + var landingAlt = map.DistanceAboveTerrain(targetPosition) + aircraft.LandAltitude; Fly.FlyTick(self, aircraft, d.Yaw, landingAlt); return false; diff --git a/OpenRA.Mods.Common/Activities/Move/Move.cs b/OpenRA.Mods.Common/Activities/Move/Move.cs index 10f70a395014..27c10dc5f9a3 100644 --- a/OpenRA.Mods.Common/Activities/Move/Move.cs +++ b/OpenRA.Mods.Common/Activities/Move/Move.cs @@ -412,6 +412,7 @@ abstract class MovePart : Activity public override bool Tick(Actor self) { var mobile = Move.mobile; + var map = self.World.Map; // Only move by a full speed step if we didn't already move this tick. // If we did, we limit the move to any carried-over leftover progress. @@ -449,13 +450,13 @@ public override bool Tick(Actor self) // Smoothly interpolate over terrain orientation changes if (FromTerrainOrientation.HasValue && progress < terrainOrientationMargin) { - var currentCellOrientation = self.World.Map.TerrainOrientation(mobile.FromCell); + var currentCellOrientation = map.TerrainOrientation(mobile.FromCell); var orientation = WRot.SLerp(FromTerrainOrientation.Value, currentCellOrientation, progress, terrainOrientationMargin); mobile.SetTerrainRampOrientation(orientation); } else if (ToTerrainOrientation.HasValue && Distance - progress < terrainOrientationMargin) { - var currentCellOrientation = self.World.Map.TerrainOrientation(mobile.FromCell); + var currentCellOrientation = map.TerrainOrientation(mobile.FromCell); var orientation = WRot.SLerp(ToTerrainOrientation.Value, currentCellOrientation, Distance - progress, terrainOrientationMargin); mobile.SetTerrainRampOrientation(orientation); } @@ -478,7 +479,7 @@ class MoveFirstHalf : MovePart WRot? fromTerrainOrientation, WRot? toTerrainOrientation, int terrainOrientationMargin, int carryoverProgress, bool movingOnGroundLayer) : base(move, from, to, fromFacing, toFacing, fromTerrainOrientation, toTerrainOrientation, terrainOrientationMargin, carryoverProgress, movingOnGroundLayer) { } - static bool IsTurn(Mobile mobile, CPos nextCell, Map map) + static bool IsTurn(Mobile mobile, CPos nextCell, IMap map) { // Some actors with a limited number of sprite facings should never move along curved trajectories. if (mobile.Info.AlwaysTurnInPlace) diff --git a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs index 25366d9eedd2..e7c74a9fd2b3 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveAdjacentTo.cs @@ -32,6 +32,8 @@ public class MoveAdjacentTo : Activity public MoveAdjacentTo(Actor self, in Target target, WPos? initialTargetPosition = null, Color? targetLineColor = null) { + var map = self.World.Map; + this.target = target; this.targetLineColor = targetLineColor; Mobile = self.Trait(); @@ -43,12 +45,12 @@ public MoveAdjacentTo(Actor self, in Target target, WPos? initialTargetPosition || target.Type == TargetType.FrozenActor || target.Type == TargetType.Terrain) { lastVisibleTarget = Target.FromPos(target.CenterPosition); - lastVisibleTargetLocation = self.World.Map.CellContaining(target.CenterPosition); + lastVisibleTargetLocation = map.CellContaining(target.CenterPosition); } else if (initialTargetPosition.HasValue) { lastVisibleTarget = Target.FromPos(initialTargetPosition.Value); - lastVisibleTargetLocation = self.World.Map.CellContaining(initialTargetPosition.Value); + lastVisibleTargetLocation = map.CellContaining(initialTargetPosition.Value); } } diff --git a/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs b/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs index 991b7915fc2c..0021fb37bffe 100644 --- a/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs +++ b/OpenRA.Mods.Common/Activities/Move/MoveWithinRange.cs @@ -20,7 +20,7 @@ public class MoveWithinRange : MoveAdjacentTo { readonly WDist maxRange; readonly WDist minRange; - readonly Map map; + readonly IMap map; readonly int maxCells; readonly int minCells; diff --git a/OpenRA.Mods.Common/Activities/UnloadCargo.cs b/OpenRA.Mods.Common/Activities/UnloadCargo.cs index feadf52a262b..1c45160d05b7 100644 --- a/OpenRA.Mods.Common/Activities/UnloadCargo.cs +++ b/OpenRA.Mods.Common/Activities/UnloadCargo.cs @@ -83,7 +83,7 @@ protected override void OnFirstRun(Actor self) } else if (mobile != null) { - var cell = self.World.Map.Clamp(this.self.World.Map.CellContaining(destination.CenterPosition)); + var cell = self.World.Map.Clamp(self.World.Map.CellContaining(destination.CenterPosition)); QueueChild(new Move(self, cell, unloadRange)); } diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs index 4e910c01fc54..5e47512d60b3 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorActorBrush.cs @@ -59,7 +59,7 @@ public bool HandleMouseInput(MouseInput mi) { // Check the actor is inside the map var actor = editorCursor.Actor; - if (!actor.Footprint.All(c => world.Map.Tiles.Contains(c.Key))) + if (!actor.Footprint.All(c => ((IMapTiles)world.Map).Tiles.Contains(c.Key))) return true; var action = new AddActorAction(editorLayer, actor.Export()); diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs index 65d19018b713..4a41c660b242 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs @@ -109,10 +109,12 @@ public bool HandleMouseInput(MouseInput mi) void Copy(CellRegion source, CVec offset) { - var gridType = worldRenderer.World.Map.Grid.Type; - var mapTiles = worldRenderer.World.Map.Tiles; - var mapHeight = worldRenderer.World.Map.Height; - var mapResources = worldRenderer.World.Map.Resources; + var map = worldRenderer.World.Map; + var gridType = map.Grid.Type; + var mapResource = (IMapResource)worldRenderer.World.Map; + var mapTiles = ((IMapTiles)map).Tiles; + var mapHeight = ((IMapElevation)map).Height; + var mapResources = mapResource.Resources; var dest = new CellRegion(gridType, source.TopLeft + offset, source.BottomRight + offset); @@ -147,7 +149,7 @@ void Copy(CellRegion source, CVec offset) } } - var action = new CopyPasteEditorAction(copyFilters, worldRenderer.World.Map, tiles, previews, editorLayer, dest); + var action = new CopyPasteEditorAction(copyFilters, (Map)worldRenderer.World.Map, tiles, previews, editorLayer, dest); editorActionManager.Add(action); } @@ -201,9 +203,9 @@ class CopyPasteEditorAction : IEditorAction this.editorLayer = editorLayer; this.dest = dest; - mapTiles = map.Tiles; - mapHeight = map.Height; - mapResources = map.Resources; + mapTiles = ((IMapTiles)map).Tiles; + mapHeight = ((IMapElevation)map).Height; + mapResources = ((IMapResource)map).Resources; Text = $"Copied {tiles.Count} tiles"; } diff --git a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs index 721bc6bbae5d..70f40027c496 100644 --- a/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs +++ b/OpenRA.Mods.Common/EditorBrushes/EditorTileBrush.cs @@ -115,7 +115,7 @@ void FloodFillWithBrush(CPos cell) if (!map.Contains(cell)) return; - var mapTiles = map.Tiles; + var mapTiles = ((IMapTiles)map).Tiles; var replace = mapTiles[cell]; if (replace.Type == Template) @@ -127,7 +127,7 @@ void FloodFillWithBrush(CPos cell) bool PlacementOverlapsSameTemplate(TerrainTemplateInfo template, CPos cell) { var map = world.Map; - var mapTiles = map.Tiles; + var mapTiles = ((IMapTiles)map).Tiles; var i = 0; for (var y = 0; y < template.Size.Y; y++) { @@ -158,13 +158,13 @@ class PaintTileEditorAction : IEditorAction public string Text { get; } readonly ushort template; - readonly Map map; + readonly IMap map; readonly CPos cell; readonly Queue undoTiles = new Queue(); readonly TerrainTemplateInfo terrainTemplate; - public PaintTileEditorAction(ushort template, Map map, CPos cell) + public PaintTileEditorAction(ushort template, IMap map, CPos cell) { this.template = template; this.map = map; @@ -182,8 +182,8 @@ public void Execute() public void Do() { - var mapTiles = map.Tiles; - var mapHeight = map.Height; + var mapTiles = ((IMapTiles)map).Tiles; + var mapHeight = ((IMapElevation)map).Height; var baseHeight = mapHeight.Contains(cell) ? mapHeight[cell] : (byte)0; var i = 0; @@ -209,8 +209,8 @@ public void Do() public void Undo() { - var mapTiles = map.Tiles; - var mapHeight = map.Height; + var mapTiles = ((IMapTiles)map).Tiles; + var mapHeight = ((IMapElevation)map).Height; while (undoTiles.Count > 0) { @@ -227,13 +227,13 @@ class FloodFillEditorAction : IEditorAction public string Text { get; } readonly ushort template; - readonly Map map; + readonly IMap map; readonly CPos cell; readonly Queue undoTiles = new Queue(); readonly TerrainTemplateInfo terrainTemplate; - public FloodFillEditorAction(ushort template, Map map, CPos cell) + public FloodFillEditorAction(ushort template, IMap map, CPos cell) { this.template = template; this.map = map; @@ -253,7 +253,7 @@ public void Do() { var queue = new Queue(); var touched = new CellLayer(map); - var mapTiles = map.Tiles; + var mapTiles = ((IMapTiles)map).Tiles; var replace = mapTiles[cell]; void MaybeEnqueue(CPos newCell) @@ -317,8 +317,8 @@ CPos FindEdge(CPos refCell, CVec direction) public void Undo() { - var mapTiles = map.Tiles; - var mapHeight = map.Height; + var mapTiles = ((IMapTiles)map).Tiles; + var mapHeight = ((IMapElevation)map).Height; while (undoTiles.Count > 0) { @@ -331,8 +331,8 @@ public void Undo() void PaintSingleCell(CPos cellToPaint) { - var mapTiles = map.Tiles; - var mapHeight = map.Height; + var mapTiles = ((IMapTiles)map).Tiles; + var mapHeight = ((IMapElevation)map).Height; var baseHeight = mapHeight.Contains(cellToPaint) ? mapHeight[cellToPaint] : (byte)0; var i = 0; diff --git a/OpenRA.Mods.Common/Graphics/ModelRenderable.cs b/OpenRA.Mods.Common/Graphics/ModelRenderable.cs index da7d8f14f7dc..f4d218cfb7a2 100644 --- a/OpenRA.Mods.Common/Graphics/ModelRenderable.cs +++ b/OpenRA.Mods.Common/Graphics/ModelRenderable.cs @@ -149,7 +149,8 @@ public void Render(WorldRenderer wr) // HACK: The previous hack isn't sufficient for the ramp type that is half flat and half // sloped towards the camera. Offset it by another half cell to avoid clipping. var cell = map.CellContaining(model.Pos); - if (map.Ramp.Contains(cell) && map.Ramp[cell] == 7) + var mapRamp = ((IMapElevation)map).Ramp; + if (mapRamp.Contains(cell) && mapRamp[cell] == 7) pxOrigin += new float3(0, 0, 0.5f * map.Grid.TileSize.Height); var shadowOrigin = pxOrigin - groundZ * new float2(renderProxy.ShadowDirection, 1); @@ -176,8 +177,9 @@ public void Render(WorldRenderer wr) public void RenderDebugGeometry(WorldRenderer wr) { - var groundPos = model.Pos - new WVec(0, 0, wr.World.Map.DistanceAboveTerrain(model.Pos).Length); - var groundZ = wr.World.Map.Grid.TileSize.Height * (groundPos.Z - model.Pos.Z) / 1024f; + var map = wr.World.Map; + var groundPos = model.Pos - new WVec(0, 0, map.DistanceAboveTerrain(model.Pos).Length); + var groundZ = map.Grid.TileSize.Height * (groundPos.Z - model.Pos.Z) / 1024f; var pxOrigin = wr.Screen3DPosition(model.Pos); var shadowOrigin = pxOrigin - groundZ * new float2(renderProxy.ShadowDirection, 1); diff --git a/OpenRA.Mods.Common/Lint/CheckActors.cs b/OpenRA.Mods.Common/Lint/CheckActors.cs index ee7d0e7205ad..89fef880dd19 100644 --- a/OpenRA.Mods.Common/Lint/CheckActors.cs +++ b/OpenRA.Mods.Common/Lint/CheckActors.cs @@ -16,9 +16,9 @@ namespace OpenRA.Mods.Common.Lint { public class CheckActors : ILintMapPass { - public void Run(Action emitError, Action emitWarning, ModData modData, Map map) + public void Run(Action emitError, Action emitWarning, ModData modData, IMap map) { - var actorTypes = map.ActorDefinitions.Select(a => a.Value.Value); + var actorTypes = ((Map)map).ActorDefinitions.Select(a => a.Value.Value); foreach (var actor in actorTypes) if (!map.Rules.Actors.Keys.Contains(actor.ToLowerInvariant())) emitError($"Actor {actor} is not defined by any rule."); diff --git a/OpenRA.Mods.Common/Lint/CheckMapCordon.cs b/OpenRA.Mods.Common/Lint/CheckMapCordon.cs index 86c494ab41ca..0cfe6e259d65 100644 --- a/OpenRA.Mods.Common/Lint/CheckMapCordon.cs +++ b/OpenRA.Mods.Common/Lint/CheckMapCordon.cs @@ -15,7 +15,7 @@ namespace OpenRA.Mods.Common.Lint { public class CheckMapCordon : ILintMapPass { - public void Run(Action emitError, Action emitWarning, ModData modData, Map map) + public void Run(Action emitError, Action emitWarning, ModData modData, IMap map) { if (map.Bounds.Left == 0 || map.Bounds.Top == 0 || map.Bounds.Right == map.MapSize.X || map.Bounds.Bottom == map.MapSize.Y) diff --git a/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs b/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs index 617701a70b4a..348b27d9b612 100644 --- a/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs +++ b/OpenRA.Mods.Common/Lint/CheckMapMetadata.cs @@ -10,17 +10,17 @@ #endregion using System; -using OpenRA.Server; using OpenRA.Mods.Common.MapFormats; +using OpenRA.Server; namespace OpenRA.Mods.Common.Lint { public class CheckMapMetadata : ILintMapPass, ILintServerMapPass { - void ILintMapPass.Run(Action emitError, Action emitWarning, ModData modData, Map map) + void ILintMapPass.Run(Action emitError, Action emitWarning, ModData modData, IMap imap) { - var defaultMap = map as DefaultMap; - Run(emitError, map.Author, map.Title, map.Categories, defaultMap.MapFormat); + var map = (Map)imap; + Run(emitError, map.Author, map.Title, map.Categories, (imap as DefaultMap).MapFormat); } void ILintServerMapPass.Run(Action emitError, Action emitWarning, ModData modData, MapPreview mapPreview, Ruleset mapRules) @@ -30,9 +30,8 @@ void ILintServerMapPass.Run(Action emitError, Action emitWarning void Run(Action emitError, string author, string title, string[] categories, int mapFormat) { - if (mapFormat != DefaultMap.SupportedMapFormat) - emitError("Map format {0} does not match the supported version {1}." - .F(mapFormat, DefaultMap.SupportedMapFormat)); + if (mapFormat < DefaultMap.SupportedMapFormat) + emitError($"Map format {mapFormat} does not match the supported version {DefaultMap.CurrentMapFormat}."); if (author == null) emitError("Map does not define a valid author."); diff --git a/OpenRA.Mods.Common/Lint/CheckMapTiles.cs b/OpenRA.Mods.Common/Lint/CheckMapTiles.cs index b51b60068ee3..6b4e089fdb4d 100644 --- a/OpenRA.Mods.Common/Lint/CheckMapTiles.cs +++ b/OpenRA.Mods.Common/Lint/CheckMapTiles.cs @@ -15,7 +15,7 @@ namespace OpenRA.Mods.Common.Lint { public class CheckMapTiles : ILintMapPass { - public void Run(Action emitError, Action emitWarning, ModData modData, Map map) + public void Run(Action emitError, Action emitWarning, ModData modData, IMap map) { foreach (var kv in map.ReplacedInvalidTerrainTiles) emitError($"Cell {kv.Key} references invalid terrain tile {kv.Value}."); diff --git a/OpenRA.Mods.Common/Lint/CheckOwners.cs b/OpenRA.Mods.Common/Lint/CheckOwners.cs index b834e3db091b..c59f352469d6 100644 --- a/OpenRA.Mods.Common/Lint/CheckOwners.cs +++ b/OpenRA.Mods.Common/Lint/CheckOwners.cs @@ -17,9 +17,9 @@ namespace OpenRA.Mods.Common.Lint { public class CheckOwners : ILintMapPass { - public void Run(Action emitError, Action emitWarning, ModData modData, Map map) + public void Run(Action emitError, Action emitWarning, ModData modData, IMap map) { - var playerNames = new MapPlayers(map.PlayerDefinitions).Players.Values + var playerNames = new MapPlayers(((Map)map).PlayerDefinitions).Players.Values .Select(p => p.Name) .ToHashSet(); @@ -28,7 +28,7 @@ public void Run(Action emitError, Action emitWarning, ModData mo .Where(a => a.Value.HasTraitInfo()) .ToDictionary(a => a.Key, a => a.Value.TraitInfo()); - foreach (var kv in map.ActorDefinitions) + foreach (var kv in ((Map)map).ActorDefinitions) { var actorReference = new ActorReference(kv.Value.Value, kv.Value.ToDictionary()); var ownerInit = actorReference.GetOrDefault(); diff --git a/OpenRA.Mods.Common/Lint/CheckPlayers.cs b/OpenRA.Mods.Common/Lint/CheckPlayers.cs index 1cf977601671..2b35232f6cf8 100644 --- a/OpenRA.Mods.Common/Lint/CheckPlayers.cs +++ b/OpenRA.Mods.Common/Lint/CheckPlayers.cs @@ -20,17 +20,17 @@ namespace OpenRA.Mods.Common.Lint { public class CheckPlayers : ILintMapPass, ILintServerMapPass { - void ILintMapPass.Run(Action emitError, Action emitWarning, ModData modData, Map map) + void ILintMapPass.Run(Action emitError, Action emitWarning, ModData modData, IMap map) { - var players = new MapPlayers(map.PlayerDefinitions); + var players = new MapPlayers(((Map)map).PlayerDefinitions); var spawns = new List(); - foreach (var kv in map.ActorDefinitions.Where(d => d.Value.Value == "mpspawn")) + foreach (var kv in ((Map)map).ActorDefinitions.Where(d => d.Value.Value == "mpspawn")) { var s = new ActorReference(kv.Value.Value, kv.Value.ToDictionary()); spawns.Add(s.Get().Value); } - Run(emitError, emitWarning, players, map.Visibility, map.Rules.Actors[SystemActors.World], spawns.ToArray()); + Run(emitError, emitWarning, players, ((Map)map).Visibility, map.Rules.Actors[SystemActors.World], spawns.ToArray()); } void ILintServerMapPass.Run(Action emitError, Action emitWarning, ModData modData, MapPreview map, Ruleset mapRules) diff --git a/OpenRA.Mods.Common/Lint/CheckUnknownTraitFields.cs b/OpenRA.Mods.Common/Lint/CheckUnknownTraitFields.cs index 57171dbe3821..eebd4af6a19b 100644 --- a/OpenRA.Mods.Common/Lint/CheckUnknownTraitFields.cs +++ b/OpenRA.Mods.Common/Lint/CheckUnknownTraitFields.cs @@ -24,9 +24,9 @@ void ILintPass.Run(Action emitError, Action emitWarning, ModData CheckActors(MiniYaml.FromStream(modData.DefaultFileSystem.Open(f), f), emitError, modData); } - void ILintMapPass.Run(Action emitError, Action emitWarning, ModData modData, Map map) + void ILintMapPass.Run(Action emitError, Action emitWarning, ModData modData, IMap map) { - CheckMapYaml(emitError, modData, map, map.RuleDefinitions); + CheckMapYaml(emitError, modData, (Map)map, ((Map)map).RuleDefinitions); } void ILintServerMapPass.Run(Action emitError, Action emitWarning, ModData modData, MapPreview map, Ruleset mapRules) diff --git a/OpenRA.Mods.Common/Lint/CheckUnknownWeaponFields.cs b/OpenRA.Mods.Common/Lint/CheckUnknownWeaponFields.cs index a8ec77aaee4a..872ef772b702 100644 --- a/OpenRA.Mods.Common/Lint/CheckUnknownWeaponFields.cs +++ b/OpenRA.Mods.Common/Lint/CheckUnknownWeaponFields.cs @@ -25,9 +25,9 @@ void ILintPass.Run(Action emitError, Action emitWarning, ModData CheckWeapons(MiniYaml.FromStream(modData.DefaultFileSystem.Open(f), f), emitError, emitWarning, modData); } - void ILintMapPass.Run(Action emitError, Action emitWarning, ModData modData, Map map) + void ILintMapPass.Run(Action emitError, Action emitWarning, ModData modData, IMap map) { - CheckMapYaml(emitError, emitWarning, modData, map, map.WeaponDefinitions); + CheckMapYaml(emitError, emitWarning, modData, (Map)map, ((Map)map).WeaponDefinitions); } void ILintServerMapPass.Run(Action emitError, Action emitWarning, ModData modData, MapPreview map, Ruleset mapRules) diff --git a/OpenRA.Mods.Common/Lint/CheckWorldAndPlayerInherits.cs b/OpenRA.Mods.Common/Lint/CheckWorldAndPlayerInherits.cs index 72b9593ff6ea..e04740f66439 100644 --- a/OpenRA.Mods.Common/Lint/CheckWorldAndPlayerInherits.cs +++ b/OpenRA.Mods.Common/Lint/CheckWorldAndPlayerInherits.cs @@ -29,9 +29,9 @@ void ILintPass.Run(Action emitError, Action emitWarning, ModData Run(emitError, nodes); } - void ILintMapPass.Run(Action emitError, Action emitWarning, ModData modData, Map map) + void ILintMapPass.Run(Action emitError, Action emitWarning, ModData modData, IMap map) { - CheckMapYaml(emitError, modData, map, map.RuleDefinitions); + CheckMapYaml(emitError, modData, (Map)map, ((Map)map).RuleDefinitions); } void ILintServerMapPass.Run(Action emitError, Action emitWarning, ModData modData, MapPreview map, Ruleset mapRules) diff --git a/OpenRA.Mods.Common/MapFormats/DefaultMap.cs b/OpenRA.Mods.Common/MapFormats/DefaultMap.cs index d2a725d50fe8..9d8db55698e3 100644 --- a/OpenRA.Mods.Common/MapFormats/DefaultMap.cs +++ b/OpenRA.Mods.Common/MapFormats/DefaultMap.cs @@ -17,7 +17,10 @@ using System.Text; using OpenRA.FileFormats; using OpenRA.FileSystem; +using OpenRA.Graphics; using OpenRA.Primitives; +using OpenRA.Support; +using OpenRA.Traits; namespace OpenRA.Mods.Common.MapFormats { @@ -47,7 +50,7 @@ public void UpdatePreview(ModData modData, MapPreview mp, IReadOnlyPackage p, IR } } - struct BinaryDataHeader + readonly struct BinaryDataHeader { public readonly byte Format; public readonly uint TilesOffset; @@ -108,7 +111,7 @@ public MapField(string key, string fieldName = null, bool required = true, strin t == typeof(MiniYaml) ? Type.MiniYaml : Type.Normal; } - public void Deserialize(Map map, List nodes) + public void Deserialize(IMap map, List nodes) { var node = nodes.FirstOrDefault(n => n.Key == key); if (node == null) @@ -139,7 +142,7 @@ public void Deserialize(Map map, List nodes) } } - public void Serialize(Map map, List nodes) + public void Serialize(IMap map, List nodes) { var value = field != null ? field.GetValue(map) : property.GetValue(map, null); if (type == Type.NodeList) @@ -163,11 +166,55 @@ public void Serialize(Map map, List nodes) } } - public class DefaultMap : Map + public class DefaultMap : Map, IMap, IMapResource, IMapElevation, IMapTiles { - //public const int SupportedMapFormat = 11; + public const int SupportedMapFormat = 11; + public const int CurrentMapFormat = 12; - public const int SupportedMapFormat = 12; + protected CellLayer cachedTerrainIndexes; + protected bool initializedCellProjection; + protected CellLayer cellProjection; + protected CellLayer> inverseCellProjection; + protected CellLayer projectedHeight; + protected Rectangle projectionSafeBounds; + + /// + /// The top-left of the playable area in projected world coordinates + /// This is a hacky workaround for legacy functionality. Do not use for new code. + /// + public WPos ProjectedTopLeft { get; private set; } + + /// + /// The bottom-right of the playable area in projected world coordinates + /// This is a hacky workaround for legacy functionality. Do not use for new code. + /// + public WPos ProjectedBottomRight { get; private set; } + + public CellLayer Tiles { get; protected set; } + public string Tileset; + string IMap.Tileset => Tileset; + public Ruleset Rules { get; protected set; } + public SequenceSet Sequences { get; private set; } + Ruleset IMap.Rules => Rules; + public CellLayer Resources { get; protected set; } + public CellLayer Height { get; protected set; } + public CellLayer Ramp { get; protected set; } + public CellLayer CustomTerrain { get; private set; } + + public PPos[] ProjectedCells { get; private set; } + public CellRegion AllCells { get; private set; } + public List AllEdgeCells { get; private set; } + + public event Action CellProjectionChanged; + Dictionary IMap.ReplacedInvalidTerrainTiles => ReplacedInvalidTerrainTiles; + public int2 MapSize { get; protected set; } + MapGrid IMap.Grid => Grid; + int2 IMap.MapSize => MapSize; + protected CVec[][] GridTilesByDistance => Grid.TilesByDistance; + public Rectangle Bounds; + Rectangle IMap.Bounds => Bounds; + + // Generated data /// Defines the order of the fields in map.yaml static readonly MapField[] YamlFields = @@ -192,14 +239,20 @@ public class DefaultMap : Map new MapField("Voices", "VoiceDefinitions", required: false), new MapField("Music", "MusicDefinitions", required: false), new MapField("Notifications", "NotificationDefinitions", required: false), - //new MapField("Translations", "TranslationDefinitions", required: false) }; // Format versions public int MapFormat { get; private set; } + int IMap.MapFormat => MapFormat; + public readonly byte TileFormat = 2; public static string ComputeUID(IReadOnlyPackage package) + { + return ComputeUID(package, GetMapFormat(package)); + } + + public static string ComputeUID(IReadOnlyPackage package, int format) { // UID is calculated by taking an SHA1 of the yaml and binary data var requiredFiles = new[] { "map.yaml", "map.bin" }; @@ -212,12 +265,12 @@ public static string ComputeUID(IReadOnlyPackage package) try { foreach (var filename in contents) - if (filename.EndsWith(".yaml") || filename.EndsWith(".bin") || filename.EndsWith(".lua")) + if (filename.EndsWith(".yaml") || filename.EndsWith(".bin") || filename.EndsWith(".lua") || (format >= 12 && filename == "map.png")) streams.Add(package.GetStream(filename)); // Take the SHA1 if (streams.Count == 0) - return CryptoUtil.SHA1Hash(new byte[0]); + return CryptoUtil.SHA1Hash(Array.Empty()); var merged = streams[0]; for (var i = 1; i < streams.Count; i++) @@ -239,18 +292,15 @@ public static string ComputeUID(IReadOnlyPackage package) public DefaultMap(ModData modData, ITerrainInfo terrainInfo, int width, int height) : base(modData) { - var tileRef = terrainInfo.DefaultTerrainTile; + var size = new Size(width, height); Title = "Name your map here"; Author = "Your name here"; + MapSize = new int2(size); Tileset = terrainInfo.Id; - //Resize(width, height); - - NewSize(new Size(width, height), terrainInfo); - - Tiles.Clear(tileRef); + NewSize(size, terrainInfo); PostInit(); } @@ -267,29 +317,31 @@ public DefaultMap(ModData modData, IReadOnlyPackage package) foreach (var field in YamlFields) field.Deserialize(this, yaml.Nodes); - if (MapFormat != SupportedMapFormat) + if (MapFormat < SupportedMapFormat) throw new InvalidDataException("Map format {0} is not supported.\n File: {1}".F(MapFormat, package.Name)); PlayerDefinitions = MiniYaml.NodesOrEmpty(yaml, "Players"); ActorDefinitions = MiniYaml.NodesOrEmpty(yaml, "Actors"); - //Resize(MapSize.X, MapSize.Y); - Ruleset rules; try { rules = Ruleset.Load(modData, this, Tileset, RuleDefinitions, WeaponDefinitions, - VoiceDefinitions, NotificationDefinitions, MusicDefinitions, SequenceDefinitions, ModelSequenceDefinitions); + VoiceDefinitions, NotificationDefinitions, MusicDefinitions, ModelSequenceDefinitions); } catch (Exception e) { Log.Write("debug", "Failed to load rules for {0} with error {1}", Title, e); - rules = Ruleset.LoadDefaultsForTileSet(modData, Tileset); } - NewSize(new Size(MapSize.X, MapSize.Y), rules.TerrainInfo); + var size = new Size(MapSize.X, MapSize.Y); + + Tiles = new CellLayer(Grid.Type, size); + Resources = new CellLayer(Grid.Type, size); + Height = new CellLayer(Grid.Type, size); + Ramp = new CellLayer(Grid.Type, size); - /*using (var s = Package.GetStream("map.bin")) + using (var s = Package.GetStream("map.bin")) { var header = new BinaryDataHeader(s, MapSize); if (header.TilesOffset > 0) @@ -304,7 +356,7 @@ public DefaultMap(ModData modData, IReadOnlyPackage package) // TODO: Remember to remove this when rewriting tile variants / PickAny if (index == byte.MaxValue) - index = (byte)(i % 4 + (j % 4) * 4); + index = (byte)(i % 4 + j % 4 * 4); Tiles[new MPos(i, j)] = new TerrainTile(tile, index); } @@ -332,61 +384,206 @@ public DefaultMap(ModData modData, IReadOnlyPackage package) for (var j = 0; j < MapSize.Y; j++) Height[new MPos(i, j)] = s.ReadUInt8().Clamp((byte)0, Grid.MaximumTerrainHeight); } - }*/ + } - using (var s = Package.GetStream("map.bin")) + PostInit(); + + Uid = ComputeUID(Package, MapFormat); + } + + protected void PostInit() + { + try { - var header = new BinaryDataHeader(s, MapSize); - if (header.TilesOffset > 0) + Rules = Ruleset.Load(modData, this, Tileset, RuleDefinitions, WeaponDefinitions, + VoiceDefinitions, NotificationDefinitions, MusicDefinitions, ModelSequenceDefinitions); + } + catch (Exception e) + { + Log.Write("debug", "Failed to load rules for {0} with error {1}", Title, e); + InvalidCustomRules = true; + InvalidCustomRulesException = e; + Rules = Ruleset.LoadDefaultsForTileSet(modData, Tileset); + } + + Sequences = new SequenceSet(this, modData, Tileset, SequenceDefinitions); + Translation = new Translation(Game.Settings.Player.Language, Translations, this); + + var tl = new MPos(0, 0).ToCPos(this); + var br = new MPos(MapSize.X - 1, MapSize.Y - 1).ToCPos(this); + AllCells = new CellRegion(Grid.Type, tl, br); + + var btl = new PPos(Bounds.Left, Bounds.Top); + var bbr = new PPos(Bounds.Right - 1, Bounds.Bottom - 1); + SetBounds(btl, bbr); + + CustomTerrain = new CellLayer(this); + foreach (var uv in AllCells.MapCoords) + CustomTerrain[uv] = byte.MaxValue; + + // Replace invalid tiles and cache ramp state + var terrainInfo = Rules.TerrainInfo; + foreach (var uv in AllCells.MapCoords) + { + if (!terrainInfo.TryGetTerrainInfo(Tiles[uv], out var info)) { - s.Position = header.TilesOffset; - for (var i = 0; i < MapSize.X; i++) - { - for (var j = 0; j < MapSize.Y; j++) - { - var tile = s.ReadUInt16(); - var index = s.ReadUInt8(); + ReplacedInvalidTerrainTiles[uv.ToCPos(this)] = Tiles[uv]; + Tiles[uv] = terrainInfo.DefaultTerrainTile; + info = terrainInfo.GetTerrainInfo(terrainInfo.DefaultTerrainTile); + } - // TODO: Remember to remove this when rewriting tile variants / PickAny - if (index == byte.MaxValue) - index = (byte)(i % 4 + (j % 4) * 4); + Ramp[uv] = info.RampType; + } - Tiles[new MPos(i, j)] = new TerrainTile(tile, index); - } - } + AllEdgeCells = UpdateEdgeCells(); + + // Invalidate the entry for a cell if anything could cause the terrain index to change. + void InvalidateTerrainIndex(CPos c) + { + if (cachedTerrainIndexes != null) + cachedTerrainIndexes[c] = InvalidCachedTerrainIndex; + } + + // Even though the cache is lazily initialized, we must attach these event handlers on init. + // This ensures our handler to invalidate the cache runs first, + // so other listeners to these same events will get correct data when calling GetTerrainIndex. + CustomTerrain.CellEntryChanged += InvalidateTerrainIndex; + Tiles.CellEntryChanged += InvalidateTerrainIndex; + } + + public (Color Left, Color Right) GetTerrainColorPair(MPos uv) + { + var terrainInfo = Rules.TerrainInfo; + var type = terrainInfo.GetTerrainInfo(Tiles[uv]); + var left = type.GetColor(Game.CosmeticRandom); + var right = type.GetColor(Game.CosmeticRandom); + + if (terrainInfo.MinHeightColorBrightness != 1.0f || terrainInfo.MaxHeightColorBrightness != 1.0f) + { + var scale = float2.Lerp(terrainInfo.MinHeightColorBrightness, terrainInfo.MaxHeightColorBrightness, Height[uv] * 1f / Grid.MaximumTerrainHeight); + left = Color.FromArgb((int)(scale * left.R).Clamp(0, 255), (int)(scale * left.G).Clamp(0, 255), (int)(scale * left.B).Clamp(0, 255)); + right = Color.FromArgb((int)(scale * right.R).Clamp(0, 255), (int)(scale * right.G).Clamp(0, 255), (int)(scale * right.B).Clamp(0, 255)); + } + + return (left, right); + } + + public byte[] SavePreview() + { + var actorTypes = Rules.Actors.Values.Where(a => a.HasTraitInfo()); + var actors = ActorDefinitions.Where(a => actorTypes.Any(ai => ai.Name == a.Value.Value)); + var positions = new List<(MPos Position, Color Color)>(); + foreach (var actor in actors) + { + var s = new ActorReference(actor.Value.Value, actor.Value.ToDictionary()); + + var ai = Rules.Actors[actor.Value.Value]; + var impsis = ai.TraitInfos(); + foreach (var impsi in impsis) + impsi.PopulateMapPreviewSignatureCells(this, ai, s, positions); + } + + // ResourceLayer is on world actor, which isn't caught above, so an extra check for it. + var worldActorInfo = Rules.Actors[SystemActors.World]; + var worldimpsis = worldActorInfo.TraitInfos(); + foreach (var worldimpsi in worldimpsis) + worldimpsi.PopulateMapPreviewSignatureCells(this, worldActorInfo, null, positions); + + var isRectangularIsometric = Grid.Type == MapGridType.RectangularIsometric; + + var top = int.MaxValue; + var bottom = int.MinValue; + + if (Grid.MaximumTerrainHeight > 0) + { + // The minimap is drawn in cell space, so we need to + // unproject the PPos bounds to find the MPos boundaries. + // This matches the calculation in RadarWidget that is used ingame + for (var x = Bounds.Left; x < Bounds.Right; x++) + { + var allTop = Unproject(new PPos(x, Bounds.Top)); + var allBottom = Unproject(new PPos(x, Bounds.Bottom)); + if (allTop.Count > 0) + top = Math.Min(top, allTop.MinBy(uv => uv.V).V); + + if (allBottom.Count > 0) + bottom = Math.Max(bottom, allBottom.MaxBy(uv => uv.V).V); } + } + else + { + // If the mod uses flat maps, MPos == PPos and we can take the bounds rect directly + top = Bounds.Top; + bottom = Bounds.Bottom; + } - if (header.ResourcesOffset > 0) + var width = Bounds.Width; + var height = bottom - top; + + var bitmapWidth = width; + if (isRectangularIsometric) + bitmapWidth = 2 * bitmapWidth - 1; + + var stride = bitmapWidth * 4; + var pxStride = 4; + var minimapData = new byte[stride * height]; + (Color Left, Color Right) terrainColor = default; + + for (var y = 0; y < height; y++) + { + for (var x = 0; x < width; x++) { - s.Position = header.ResourcesOffset; - for (var i = 0; i < MapSize.X; i++) + var uv = new MPos(x + Bounds.Left, y + top); + + // FirstOrDefault will return a (MPos.Zero, Color.Transparent) if positions is empty + var actorColor = positions.FirstOrDefault(ap => ap.Position == uv).Color; + if (actorColor.A == 0) + terrainColor = GetTerrainColorPair(uv); + + if (isRectangularIsometric) { - for (var j = 0; j < MapSize.Y; j++) + // Odd rows are shifted right by 1px + var dx = uv.V & 1; + var xOffset = pxStride * (2 * x + dx); + if (x + dx > 0) { - var type = s.ReadUInt8(); - var density = s.ReadUInt8(); - Resources[new MPos(i, j)] = new ResourceTile(type, density); + var z = y * stride + xOffset - pxStride; + var c = actorColor.A == 0 ? terrainColor.Left : actorColor; + minimapData[z++] = c.R; + minimapData[z++] = c.G; + minimapData[z++] = c.B; + minimapData[z] = c.A; } - } - } - if (header.HeightsOffset > 0) - { - s.Position = header.HeightsOffset; - for (var i = 0; i < MapSize.X; i++) - for (var j = 0; j < MapSize.Y; j++) - Height[new MPos(i, j)] = s.ReadUInt8().Clamp((byte)0, Grid.MaximumTerrainHeight); + if (xOffset < stride) + { + var z = y * stride + xOffset; + var c = actorColor.A == 0 ? terrainColor.Right : actorColor; + minimapData[z++] = c.R; + minimapData[z++] = c.G; + minimapData[z++] = c.B; + minimapData[z] = c.A; + } + } + else + { + var z = y * stride + pxStride * x; + var c = actorColor.A == 0 ? terrainColor.Left : actorColor; + minimapData[z++] = c.R; + minimapData[z++] = c.G; + minimapData[z++] = c.B; + minimapData[z] = c.A; + } } } - PostInit(); - - Uid = ComputeUID(Package); + var png = new Png(minimapData, SpriteFrameType.Rgba32, bitmapWidth, height); + return png.Save(); } public override void Save(IReadWritePackage toPackage) { - MapFormat = SupportedMapFormat; + MapFormat = CurrentMapFormat; var root = new List(); foreach (var field in YamlFields) @@ -402,16 +599,28 @@ public override void Save(IReadWritePackage toPackage) toPackage.Update(file, Package.GetStream(file).ReadAllBytes()); if (!LockPreview) - toPackage.Update("map.png", SavePreview()); + { + var previewData = SavePreview(); + if (Package != toPackage || !Enumerable.SequenceEqual(previewData, Package.GetStream("map.png").ReadAllBytes())) + toPackage.Update("map.png", previewData); + } // Update the package with the new map data var s = root.WriteToString(); toPackage.Update("map.yaml", Encoding.UTF8.GetBytes(s)); toPackage.Update("map.bin", SaveBinaryData()); + var textData = Encoding.UTF8.GetBytes(root.WriteToString()); + if (Package != toPackage || !Enumerable.SequenceEqual(textData, Package.GetStream("map.yaml").ReadAllBytes())) + toPackage.Update("map.yaml", textData); + + var binaryData = SaveBinaryData(); + if (Package != toPackage || !Enumerable.SequenceEqual(binaryData, Package.GetStream("map.bin").ReadAllBytes())) + toPackage.Update("map.bin", binaryData); + Package = toPackage; // Update UID to match the newly saved data - Uid = ComputeUID(toPackage); + Uid = ComputeUID(toPackage, MapFormat); } public static void UpdatePreview(ModData modData, MapPreview mp, IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification, string[] mapCompatibility, MapGridType gridType) @@ -422,7 +631,6 @@ public static void UpdatePreview(ModData modData, MapPreview mp, IReadOnlyPackag if (yamlStream == null) throw new FileNotFoundException("Required file map.yaml not present in this map"); - // yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml")).ToDictionary(); yaml = new MiniYaml(null, MiniYaml.FromStream(yamlStream, "map.yaml", stringPool: modData.MapCache.StringPool)).ToDictionary(); } @@ -430,11 +638,10 @@ public static void UpdatePreview(ModData modData, MapPreview mp, IReadOnlyPackag newData.GridType = gridType; newData.Class = classification; - MiniYaml temp; - if (yaml.TryGetValue("MapFormat", out temp)) + if (yaml.TryGetValue("MapFormat", out var temp)) { var format = FieldLoader.GetValue("MapFormat", temp.Value); - if (format != SupportedMapFormat) + if (format < SupportedMapFormat) throw new InvalidDataException("Map format {0} is not supported.".F(format)); } @@ -456,7 +663,7 @@ public static void UpdatePreview(ModData modData, MapPreview mp, IReadOnlyPackag if (yaml.TryGetValue("Visibility", out temp)) newData.Visibility = FieldLoader.GetValue("Visibility", temp.Value); - string requiresMod = string.Empty; + var requiresMod = string.Empty; if (yaml.TryGetValue("RequiresMod", out temp)) requiresMod = temp.Value; @@ -466,8 +673,7 @@ public static void UpdatePreview(ModData modData, MapPreview mp, IReadOnlyPackag try { // Actor definitions may change if the map format changes - MiniYaml actorDefinitions; - if (yaml.TryGetValue("Actors", out actorDefinitions)) + if (yaml.TryGetValue("Actors", out var actorDefinitions)) { var spawns = new List(); foreach (var kv in actorDefinitions.Nodes.Where(d => d.Value.Value == "mpspawn")) @@ -479,19 +685,18 @@ public static void UpdatePreview(ModData modData, MapPreview mp, IReadOnlyPackag newData.SpawnPoints = spawns.ToArray(); } else - newData.SpawnPoints = new CPos[0]; + newData.SpawnPoints = Array.Empty(); } catch (Exception) { - newData.SpawnPoints = new CPos[0]; + newData.SpawnPoints = Array.Empty(); newData.Status = MapStatus.Unavailable; } try { // Player definitions may change if the map format changes - MiniYaml playerDefinitions; - if (yaml.TryGetValue("Players", out playerDefinitions)) + if (yaml.TryGetValue("Players", out var playerDefinitions)) { newData.Players = new MapPlayers(playerDefinitions.Nodes); newData.PlayerCount = newData.Players.Players.Count(x => x.Value.Playable); @@ -502,34 +707,677 @@ public static void UpdatePreview(ModData modData, MapPreview mp, IReadOnlyPackag newData.Status = MapStatus.Unavailable; } - /*newData.SetRulesetGenerator(modData, () => - { - var ruleDefinitions = LoadRuleSection(yaml, "Rules"); - var weaponDefinitions = LoadRuleSection(yaml, "Weapons"); - var voiceDefinitions = LoadRuleSection(yaml, "Voices"); - var musicDefinitions = LoadRuleSection(yaml, "Music"); - var notificationDefinitions = LoadRuleSection(yaml, "Notifications"); - var sequenceDefinitions = LoadRuleSection(yaml, "Sequences"); - var modelSequenceDefinitions = LoadRuleSection(yaml, "ModelSequences"); - var rules = Ruleset.Load(modData, mp, mp.TileSet, ruleDefinitions, weaponDefinitions, - voiceDefinitions, notificationDefinitions, musicDefinitions, sequenceDefinitions, modelSequenceDefinitions); - var flagged = Ruleset.DefinesUnsafeCustomRules(modData, mp, ruleDefinitions, - weaponDefinitions, voiceDefinitions, notificationDefinitions, sequenceDefinitions); - return Pair.New(rules, flagged); - });*/ + newData.SetCustomRules(modData, mp, yaml); - if (p.Contains("map.png")) + if (mp.Cache.LoadPreviewImages && p.Contains("map.png")) using (var dataStream = p.GetStream("map.png")) newData.Preview = new Png(dataStream); + + newData.ModifiedDate = File.GetLastWriteTime(p.Name); + } + + public bool Contains(CPos cell) + { + if (Grid.Type == MapGridType.RectangularIsometric) + { + // .ToMPos() returns the same result if the X and Y coordinates + // are switched. X < Y is invalid in the RectangularIsometric coordinate system, + // so we pre-filter these to avoid returning the wrong result + if (cell.X < cell.Y) + return false; + } + else + { + // If the mod uses flat & rectangular maps, ToMPos and Contains(MPos) create unnecessary cost. + // Just check if CPos is within map bounds. + if (Grid.MaximumTerrainHeight == 0) + return Bounds.Contains(cell.X, cell.Y); + } + + return Contains(cell.ToMPos(this)); + } + + public bool Contains(MPos uv) + { + // The first check ensures that the cell is within the valid map region, avoiding + // potential crashes in deeper code. All CellLayers have the same geometry, and + // CustomTerrain is convenient. + return CustomTerrain.Contains(uv) && ContainsAllProjectedCellsCovering(uv); + } + + bool ContainsAllProjectedCellsCovering(MPos uv) + { + // PERF: Checking the bounds directly here is the same as calling Contains((PPos)uv) but saves an allocation + if (Grid.MaximumTerrainHeight == 0) + return Bounds.Contains(uv.U, uv.V); + + // PERF: Most cells lie within a region where no matter their height, + // all possible projected cells would remain in the map area. + // For these, we can do a fast-path check. + if (projectionSafeBounds.Contains(uv.U, uv.V)) + return true; + + // Now we need to do a slow-check. Determine the actual projected tiles + // as they may or may not be in bounds depending on height. + // If the cell has no valid projection, then we're off the map. + var projectedCells = ProjectedCellsCovering(uv); + if (projectedCells.Length == 0) + return false; + + foreach (var puv in projectedCells) + if (!Contains(puv)) + return false; + + return true; + } + + public bool Contains(PPos puv) + { + return Bounds.Contains(puv.U, puv.V); + } + + public WPos CenterOfCell(CPos cell) + { + if (Grid.Type == MapGridType.Rectangular) + return new WPos(1024 * cell.X + 512, 1024 * cell.Y + 512, 0); + + // Convert from isometric cell position (x, y) to world position (u, v): + // (a) Consider the relationships: + // - Center of origin cell is (512, 512) + // - +x adds (512, 512) to world pos + // - +y adds (-512, 512) to world pos + // (b) Therefore: + // - ax + by adds (a - b) * 512 + 512 to u + // - ax + by adds (a + b) * 512 + 512 to v + // (c) u, v coordinates run diagonally to the cell axes, and we define + // 1024 as the length projected onto the primary cell axis + // - 512 * sqrt(2) = 724 + var z = Height.TryGetValue(cell, out var height) ? 724 * height + Grid.Ramps[Ramp[cell]].CenterHeightOffset : 0; + return new WPos(724 * (cell.X - cell.Y + 1), 724 * (cell.X + cell.Y + 1), z); + } + + public WPos CenterOfSubCell(CPos cell, SubCell subCell) + { + var index = (int)subCell; + if (index >= 0 && index < Grid.SubCellOffsets.Length) + { + var center = CenterOfCell(cell); + var offset = Grid.SubCellOffsets[index]; + if (Ramp.TryGetValue(cell, out var ramp) && ramp != 0) + { + var r = Grid.Ramps[ramp]; + offset += new WVec(0, 0, r.HeightOffset(offset.X, offset.Y) - r.CenterHeightOffset); + } + + return center + offset; + } + + return CenterOfCell(cell); + } + + public WRot TerrainOrientation(CPos cell) + { + if (Ramp.TryGetValue(cell, out var ramp)) + return Grid.Ramps[ramp].Orientation; + + return WRot.None; + } + + public WVec Offset(CVec delta, int dz) + { + if (Grid.Type == MapGridType.Rectangular) + return new WVec(1024 * delta.X, 1024 * delta.Y, 0); + + return new WVec(724 * (delta.X - delta.Y), 724 * (delta.X + delta.Y), 724 * dz); + } + + /// + /// The size of the map Height step in world units + /// + /// RectangularIsometric defines 1024 units along the diagonal axis, + /// giving a half-tile height step of sqrt(2) * 512 + public WDist CellHeightStep => new WDist(Grid.Type == MapGridType.RectangularIsometric ? 724 : 512); + + public CPos CellContaining(WPos pos) + { + if (Grid.Type == MapGridType.Rectangular) + return new CPos(pos.X / 1024, pos.Y / 1024); + + // Convert from world position to isometric cell position: + // (a) Subtract ([1/2 cell], [1/2 cell]) to move the rotation center to the middle of the corner cell + // (b) Rotate axes by -pi/4 to align the world axes with the cell axes + // (c) Apply an offset so that the integer division by [1 cell] rounds in the right direction: + // (i) u is always positive, so add [1/2 cell] (which then partially cancels the -[1 cell] term from the rotation) + // (ii) v can be negative, so we need to be careful about rounding directions. We add [1/2 cell] *away from 0* (negative if y > x). + // (e) Divide by [1 cell] to bring into cell coords. + // The world axes are rotated relative to the cell axes, so the standard cell size (1024) is increased by a factor of sqrt(2) + var u = (pos.Y + pos.X - 724) / 1448; + var v = (pos.Y - pos.X + (pos.Y > pos.X ? 724 : -724)) / 1448; + return new CPos(u, v); + } + + public PPos ProjectedCellCovering(WPos pos) + { + var projectedPos = pos - new WVec(0, pos.Z, pos.Z); + return (PPos)CellContaining(projectedPos).ToMPos(Grid.Type); + } + + public PPos[] ProjectedCellsCovering(MPos uv) + { + if (!initializedCellProjection) + InitializeCellProjection(); + + if (!cellProjection.Contains(uv)) + return NoProjectedCells; + + return cellProjection[uv]; + } + + public List Unproject(PPos puv) + { + var uv = (MPos)puv; + + if (!initializedCellProjection) + InitializeCellProjection(); + + if (!inverseCellProjection.Contains(uv)) + return new List(); + + return inverseCellProjection[uv]; } - static MiniYaml LoadRuleSection(Dictionary yaml, string section) + public byte ProjectedHeight(PPos puv) { - MiniYaml node; - if (!yaml.TryGetValue(section, out node)) - return null; + return projectedHeight[(MPos)puv]; + } - return node; + void UpdateRamp(CPos cell) + { + Ramp[cell] = Rules.TerrainInfo.GetTerrainInfo(Tiles[cell]).RampType; + } + + void InitializeCellProjection() + { + if (initializedCellProjection) + return; + + initializedCellProjection = true; + + cellProjection = new CellLayer(this); + inverseCellProjection = new CellLayer>(this); + projectedHeight = new CellLayer(this); + + // Initialize collections + foreach (var cell in AllCells) + { + var uv = cell.ToMPos(Grid.Type); + cellProjection[uv] = Array.Empty(); + inverseCellProjection[uv] = new List(1); + } + + // Initialize projections + foreach (var cell in AllCells) + UpdateProjection(cell); + } + + void UpdateProjection(CPos cell) + { + MPos uv; + + if (!initializedCellProjection) + InitializeCellProjection(); + + if (Grid.MaximumTerrainHeight == 0) + { + uv = cell.ToMPos(Grid.Type); + cellProjection[cell] = new[] { (PPos)uv }; + var inverse = inverseCellProjection[uv]; + inverse.Clear(); + inverse.Add(uv); + CellProjectionChanged?.Invoke(cell); + return; + } + + uv = cell.ToMPos(Grid.Type); + + // Remove old reverse projection + foreach (var puv in cellProjection[uv]) + { + var temp = (MPos)puv; + inverseCellProjection[temp].Remove(uv); + projectedHeight[temp] = ProjectedCellHeightInner(puv); + } + + var projected = ProjectCellInner(uv); + cellProjection[uv] = projected; + + foreach (var puv in projected) + { + var temp = (MPos)puv; + inverseCellProjection[temp].Add(uv); + + var height = ProjectedCellHeightInner(puv); + projectedHeight[temp] = height; + + // Propagate height up cliff faces + while (true) + { + temp = new MPos(temp.U, temp.V - 1); + if (!inverseCellProjection.Contains(temp) || inverseCellProjection[temp].Count > 0) + break; + + projectedHeight[temp] = height; + } + } + + CellProjectionChanged?.Invoke(cell); + } + + byte ProjectedCellHeightInner(PPos puv) + { + while (inverseCellProjection.Contains((MPos)puv)) + { + var inverse = inverseCellProjection[(MPos)puv]; + if (inverse.Count > 0) + { + // The original games treat the top of cliffs the same way as the bottom + // This information isn't stored in the map data, so query the offset from the tileset + var temp = inverse.MaxBy(uv => uv.V); + return (byte)(Height[temp] - Rules.TerrainInfo.GetTerrainInfo(Tiles[temp]).Height); + } + + // Try the next cell down if this is a cliff face + puv = new PPos(puv.U, puv.V + 1); + } + + return 0; + } + + PPos[] ProjectCellInner(MPos uv) + { + var mapHeight = Height; + if (!mapHeight.Contains(uv)) + return NoProjectedCells; + + // Any changes to this function should be reflected when setting projectionSafeBounds. + var height = mapHeight[uv]; + if (height == 0) + return new[] { (PPos)uv }; + + // Odd-height ramps get bumped up a level to the next even height layer + if ((height & 1) == 1 && Ramp[uv] != 0) + height += 1; + + var candidates = new List(); + + // Odd-height level tiles are equally covered by four projected tiles + if ((height & 1) == 1) + { + if ((uv.V & 1) == 1) + candidates.Add(new PPos(uv.U + 1, uv.V - height)); + else + candidates.Add(new PPos(uv.U - 1, uv.V - height)); + + candidates.Add(new PPos(uv.U, uv.V - height)); + candidates.Add(new PPos(uv.U, uv.V - height + 1)); + candidates.Add(new PPos(uv.U, uv.V - height - 1)); + } + else + candidates.Add(new PPos(uv.U, uv.V - height)); + + return candidates.Where(c => mapHeight.Contains((MPos)c)).ToArray(); + } + + public TerrainTypeInfo GetTerrainInfo(CPos cell) + { + return Rules.TerrainInfo.TerrainTypes[GetTerrainIndex(cell)]; + } + + public CPos Clamp(CPos cell) + { + return Clamp(cell.ToMPos(this)).ToCPos(this); + } + + public MPos Clamp(MPos uv) + { + if (Grid.MaximumTerrainHeight == 0) + return (MPos)Clamp((PPos)uv); + + // Already in bounds, so don't need to do anything. + if (ContainsAllProjectedCellsCovering(uv)) + return uv; + + // Clamping map coordinates is trickier than it might first look! + // This needs to handle three nasty cases: + // * The requested cell is well outside the map region + // * The requested cell is near the top edge inside the map but outside the projected layer + // * The clamped projected cell lands on a cliff face with no associated map cell + // + // Handling these cases properly requires abuse of our knowledge of the projection transform. + // + // The U coordinate doesn't change significantly in the projection, so clamp this + // straight away and ensure the point is somewhere inside the map + uv = cellProjection.Clamp(new MPos(uv.U.Clamp(Bounds.Left, Bounds.Right), uv.V)); + + // Project this guessed cell and take the first available cell + // If it is projected outside the layer, then make another guess. + var allProjected = ProjectedCellsCovering(uv); + var projected = allProjected.Length > 0 ? allProjected.First() + : new PPos(uv.U, uv.V.Clamp(Bounds.Top, Bounds.Bottom)); + + // Clamp the projected cell to the map area + projected = Clamp(projected); + + // Project the cell back into map coordinates. + // This may fail if the projected cell covered a cliff or another feature + // where there is a large change in terrain height. + var unProjected = Unproject(projected); + if (unProjected.Count == 0) + { + // Adjust V until we find a cell that works + for (var x = 2; x <= 2 * Grid.MaximumTerrainHeight; x++) + { + var dv = ((x & 1) == 1 ? 1 : -1) * x / 2; + var test = new PPos(projected.U, projected.V + dv); + if (!Contains(test)) + continue; + + unProjected = Unproject(test); + if (unProjected.Count > 0) + break; + } + + // This shouldn't happen. But if it does, return the original value and hope the caller doesn't explode. + if (unProjected.Count == 0) + { + Log.Write("debug", "Failed to clamp map cell {0} to map bounds", uv); + return uv; + } + } + + return projected.V == Bounds.Bottom ? unProjected.MaxBy(x => x.V) : unProjected.MinBy(x => x.V); + } + + public PPos Clamp(PPos puv) + { + var bounds = new Rectangle(Bounds.X, Bounds.Y, Bounds.Width - 1, Bounds.Height - 1); + return puv.Clamp(bounds); + } + + public CPos ChooseRandomCell(MersenneTwister rand) + { + List cells; + do + { + var u = rand.Next(Bounds.Left, Bounds.Right); + var v = rand.Next(Bounds.Top, Bounds.Bottom); + + cells = Unproject(new PPos(u, v)); + } + while (cells.Count == 0); + + return cells.Random(rand).ToCPos(Grid.Type); + } + + public CPos ChooseClosestEdgeCell(CPos cell) + { + return ChooseClosestEdgeCell(cell.ToMPos(Grid.Type)).ToCPos(Grid.Type); + } + + public MPos ChooseClosestEdgeCell(MPos uv) + { + var allProjected = ProjectedCellsCovering(uv); + + PPos edge; + if (allProjected.Length > 0) + { + var puv = allProjected.First(); + var horizontalBound = (puv.U - Bounds.Left < Bounds.Width / 2) ? Bounds.Left : Bounds.Right; + var verticalBound = (puv.V - Bounds.Top < Bounds.Height / 2) ? Bounds.Top : Bounds.Bottom; + + var du = Math.Abs(horizontalBound - puv.U); + var dv = Math.Abs(verticalBound - puv.V); + + edge = du < dv ? new PPos(horizontalBound, puv.V) : new PPos(puv.U, verticalBound); + } + else + edge = new PPos(Bounds.Left, Bounds.Top); + + var unProjected = Unproject(edge); + if (unProjected.Count == 0) + { + // Adjust V until we find a cell that works + for (var x = 2; x <= 2 * Grid.MaximumTerrainHeight; x++) + { + var dv = ((x & 1) == 1 ? 1 : -1) * x / 2; + var test = new PPos(edge.U, edge.V + dv); + if (!Contains(test)) + continue; + + unProjected = Unproject(test); + if (unProjected.Count > 0) + break; + } + + // This shouldn't happen. But if it does, return the original value and hope the caller doesn't explode. + if (unProjected.Count == 0) + { + Log.Write("debug", "Failed to find closest edge for map cell {0}", uv); + return uv; + } + } + + return edge.V == Bounds.Bottom ? unProjected.MaxBy(x => x.V) : unProjected.MinBy(x => x.V); + } + + public CPos ChooseClosestMatchingEdgeCell(CPos cell, Func match) + { + return AllEdgeCells.OrderBy(c => (cell - c).Length).FirstOrDefault(c => match(c)); + } + + List UpdateEdgeCells() + { + var edgeCells = new List(); + var unProjected = new List(); + var bottom = Bounds.Bottom - 1; + for (var u = Bounds.Left; u < Bounds.Right; u++) + { + unProjected = Unproject(new PPos(u, Bounds.Top)); + if (unProjected.Count > 0) + edgeCells.Add(unProjected.MinBy(x => x.V).ToCPos(Grid.Type)); + + unProjected = Unproject(new PPos(u, bottom)); + if (unProjected.Count > 0) + edgeCells.Add(unProjected.MaxBy(x => x.V).ToCPos(Grid.Type)); + } + + for (var v = Bounds.Top; v < Bounds.Bottom; v++) + { + unProjected = Unproject(new PPos(Bounds.Left, v)); + if (unProjected.Count > 0) + edgeCells.Add((v == bottom ? unProjected.MaxBy(x => x.V) : unProjected.MinBy(x => x.V)).ToCPos(Grid.Type)); + + unProjected = Unproject(new PPos(Bounds.Right - 1, v)); + if (unProjected.Count > 0) + edgeCells.Add((v == bottom ? unProjected.MaxBy(x => x.V) : unProjected.MinBy(x => x.V)).ToCPos(Grid.Type)); + } + + return edgeCells; + } + + protected static readonly PPos[] NoProjectedCells = Array.Empty(); + + public void Resize(int width, int height) + { + var oldMapTiles = Tiles; + var oldMapResources = Resources; + var oldMapHeight = Height; + var oldMapRamp = Ramp; + var newSize = new Size(width, height); + + Tiles = CellLayer.Resize(oldMapTiles, newSize, oldMapTiles[MPos.Zero]); + Resources = CellLayer.Resize(oldMapResources, newSize, oldMapResources[MPos.Zero]); + Height = CellLayer.Resize(oldMapHeight, newSize, oldMapHeight[MPos.Zero]); + Ramp = CellLayer.Resize(oldMapRamp, newSize, oldMapRamp[MPos.Zero]); + MapSize = new int2(newSize); + + var tl = new MPos(0, 0); + var br = new MPos(MapSize.X - 1, MapSize.Y - 1); + AllCells = new CellRegion(Grid.Type, tl.ToCPos(this), br.ToCPos(this)); + SetBounds(new PPos(tl.U + 1, tl.V + 1), new PPos(br.U - 1, br.V - 1)); + } + + public void NewSize(Size size, ITerrainInfo terrainInfo) + { + Tiles = new CellLayer(Grid.Type, size); + Resources = new CellLayer(Grid.Type, size); + Height = new CellLayer(Grid.Type, size); + Ramp = new CellLayer(Grid.Type, size); + Tiles.Clear(terrainInfo.DefaultTerrainTile); + + if (Grid.MaximumTerrainHeight > 0) + { + Tiles.CellEntryChanged += UpdateRamp; + Tiles.CellEntryChanged += UpdateProjection; + Height.CellEntryChanged += UpdateProjection; + } + + var tl = new MPos(0, 0); + var br = new MPos(MapSize.X - 1, MapSize.Y - 1); + AllCells = new CellRegion(Grid.Type, tl.ToCPos(this), br.ToCPos(this)); + SetBounds(new PPos(tl.U + 1, tl.V + 1), new PPos(br.U - 1, br.V - 1)); + } + + public void SetBounds(PPos tl, PPos br) + { + // The tl and br coordinates are inclusive, but the Rectangle + // is exclusive. Pad the right and bottom edges to match. + Bounds = Rectangle.FromLTRB(tl.U, tl.V, br.U + 1, br.V + 1); + + // See ProjectCellInner to see how any given position may be projected. + // U: May gain or lose 1, so bring in the left and right edge by 1. + // V: For an even height tile, this ranges from 0 to height + // For an odd tile, the height may get rounded up to next even. + // Then also it projects to four tiles which adds one more to the possible height change. + // So we get a range of 0 to height + 1 + 1. + // As the height only goes upwards, we only need to make room at the top of the map and not the bottom. + var maxHeight = Grid.MaximumTerrainHeight; + if ((maxHeight & 1) == 1) + maxHeight += 2; + projectionSafeBounds = Rectangle.FromLTRB( + Bounds.Left + 1, + Bounds.Top + maxHeight, + Bounds.Right - 1, + Bounds.Bottom); + + // Directly calculate the projected map corners in world units avoiding unnecessary + // conversions. This abuses the definition that the width of the cell along the x world axis + // is always 1024 or 1448 units, and that the height of two rows is 2048 for classic cells and 724 + // for isometric cells. + if (Grid.Type == MapGridType.RectangularIsometric) + { + ProjectedTopLeft = new WPos(tl.U * 1448, tl.V * 724, 0); + ProjectedBottomRight = new WPos(br.U * 1448 - 1, (br.V + 1) * 724 - 1, 0); + } + else + { + ProjectedTopLeft = new WPos(tl.U * 1024, tl.V * 1024, 0); + ProjectedBottomRight = new WPos(br.U * 1024 - 1, (br.V + 1) * 1024 - 1, 0); + } + + // PERF: This enumeration isn't going to change during the game + ProjectedCells = new ProjectedCellRegion(this, tl, br).ToArray(); + } + + public byte GetTerrainIndex(CPos cell) + { + // Lazily initialize a cache for terrain indexes. + if (cachedTerrainIndexes == null) + { + cachedTerrainIndexes = new CellLayer(this); + cachedTerrainIndexes.Clear(InvalidCachedTerrainIndex); + } + + var uv = cell.ToMPos(this); + var terrainIndex = cachedTerrainIndexes[uv]; + + // PERF: Cache terrain indexes per cell on demand. + if (terrainIndex == InvalidCachedTerrainIndex) + { + var custom = CustomTerrain[uv]; + terrainIndex = cachedTerrainIndexes[uv] = custom != byte.MaxValue ? custom : Rules.TerrainInfo.GetTerrainInfo(Tiles[uv]).TerrainType; + } + + return (byte)terrainIndex; + } + + public CPos ChooseRandomEdgeCell(MersenneTwister rand) + { + return AllEdgeCells.Random(rand); + } + + public WDist DistanceToEdge(WPos pos, in WVec dir) + { + var projectedPos = pos - new WVec(0, pos.Z, pos.Z); + var x = dir.X == 0 ? int.MaxValue : ((dir.X < 0 ? ProjectedTopLeft.X : ProjectedBottomRight.X) - projectedPos.X) / dir.X; + var y = dir.Y == 0 ? int.MaxValue : ((dir.Y < 0 ? ProjectedTopLeft.Y : ProjectedBottomRight.Y) - projectedPos.Y) / dir.Y; + return new WDist(Math.Min(x, y) * dir.Length); + } + + public WAngle FacingBetween(CPos cell, CPos towards, WAngle fallbackfacing) + { + var delta = CenterOfCell(towards) - CenterOfCell(cell); + if (delta.HorizontalLengthSquared == 0) + return fallbackfacing; + + return delta.Yaw; + } + + public WDist DistanceAboveTerrain(WPos pos) + { + if (Grid.Type == MapGridType.Rectangular) + return new WDist(pos.Z); + + // Apply ramp offset + var cell = CellContaining(pos); + var offset = pos - CenterOfCell(cell); + + if (Ramp.TryGetValue(cell, out var ramp) && ramp != 0) + { + var r = Grid.Ramps[ramp]; + return new WDist(offset.Z + r.CenterHeightOffset - r.HeightOffset(offset.X, offset.Y)); + } + + return new WDist(offset.Z); + } + + // Both ranges are inclusive because everything that calls it is designed for maxRange being inclusive: + // it rounds the actual distance up to the next integer so that this call + // will return any cells that intersect with the requested range circle. + // The returned positions are sorted by distance from the center. + public IEnumerable FindTilesInAnnulus(CPos center, int minRange, int maxRange, bool allowOutsideBounds = false) + { + if (maxRange < minRange) + throw new ArgumentOutOfRangeException(nameof(maxRange), "Maximum range is less than the minimum range."); + + if (maxRange >= GridTilesByDistance.Length) + throw new ArgumentOutOfRangeException(nameof(maxRange), + $"The requested range ({maxRange}) cannot exceed the value of MaximumTileSearchRange ({Grid.MaximumTileSearchRange})"); + + for (var i = minRange; i <= maxRange; i++) + { + foreach (var offset in GridTilesByDistance[i]) + { + var t = offset + center; + if (allowOutsideBounds ? Tiles.Contains(t) : Contains(t)) + yield return t; + } + } + } + + public IEnumerable FindTilesInCircle(CPos center, int maxRange, bool allowOutsideBounds = false) + { + return FindTilesInAnnulus(center, 0, maxRange, allowOutsideBounds); } public byte[] SaveBinaryData() @@ -590,5 +1438,10 @@ public byte[] SaveBinaryData() return dataStream.ToArray(); } + + public void Dispose() + { + Sequences.Dispose(); + } } } diff --git a/OpenRA.Mods.Common/Pathfinder/CellInfoLayerPool.cs b/OpenRA.Mods.Common/Pathfinder/CellInfoLayerPool.cs index 3559ed751b09..ccb386b81d4b 100644 --- a/OpenRA.Mods.Common/Pathfinder/CellInfoLayerPool.cs +++ b/OpenRA.Mods.Common/Pathfinder/CellInfoLayerPool.cs @@ -18,9 +18,9 @@ sealed class CellInfoLayerPool { const int MaxPoolSize = 4; readonly Stack> pool = new Stack>(MaxPoolSize); - readonly Map map; + readonly IMap map; - public CellInfoLayerPool(Map map) + public CellInfoLayerPool(IMap map) { this.map = map; } diff --git a/OpenRA.Mods.Common/Pathfinder/DensePathGraph.cs b/OpenRA.Mods.Common/Pathfinder/DensePathGraph.cs index 57b0234ed22a..63234c4db248 100644 --- a/OpenRA.Mods.Common/Pathfinder/DensePathGraph.cs +++ b/OpenRA.Mods.Common/Pathfinder/DensePathGraph.cs @@ -114,7 +114,7 @@ public List GetConnections(CPos position) var dy = position.Y - previousNode.Y; var index = dy * 3 + dx + 4; - var heightLayer = world.Map.Height; + var heightLayer = ((IMapElevation)world.Map).Height; var directions = (checkTerrainHeight && layer == 0 && previousNode.Layer == 0 && heightLayer[position] != heightLayer[previousNode] ? DirectedNeighborsConservative diff --git a/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs b/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs index 0502e856b6f1..cf52df685b94 100644 --- a/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs +++ b/OpenRA.Mods.Common/Pathfinder/HierarchicalPathFinder.cs @@ -307,7 +307,7 @@ public HierarchicalPathFinder(World world, Locomotor locomotor, IActorMap actorM /// void BuildGrids() { - Grid GetCPosBounds(Map map) + Grid GetCPosBounds(IMap map) { if (map.Grid.Type == MapGridType.RectangularIsometric) { @@ -937,7 +937,8 @@ public bool PathExists(CPos source, CPos target) if (costEstimator == null) return false; - if (!world.Map.Contains(source) || !world.Map.Contains(target)) + var map = world.Map; + if (!map.Contains(source) || !map.Contains(target)) return false; RebuildDomains(); @@ -1086,7 +1087,7 @@ List AbstractEdge(CPos abstractCell) /// /// Maps a local cell to a abstract node in the graph. Returns null when the local cell is unreachable. - /// The cell must have been checked to be on the map with . + /// The cell must have been checked to be on the map with . /// CPos? AbstractCellForLocalCell(CPos localCell) { diff --git a/OpenRA.Mods.Common/Projectiles/Bullet.cs b/OpenRA.Mods.Common/Projectiles/Bullet.cs index 648a969fa54d..5627868034e0 100644 --- a/OpenRA.Mods.Common/Projectiles/Bullet.cs +++ b/OpenRA.Mods.Common/Projectiles/Bullet.cs @@ -262,20 +262,22 @@ bool ShouldExplode(World world) var flightLengthReached = ticks++ >= length; var shouldBounce = remainingBounces > 0; + var map = world.Map; + if (flightLengthReached && shouldBounce) { - var cell = world.Map.CellContaining(pos); - if (!world.Map.Contains(cell)) + var cell = map.CellContaining(pos); + if (!map.Contains(cell)) return true; - if (info.InvalidBounceTerrain.Contains(world.Map.GetTerrainInfo(cell).Type)) + if (info.InvalidBounceTerrain.Contains(map.GetTerrainInfo(cell).Type)) return true; if (AnyValidTargetsInRadius(world, pos, info.Width, args.SourceActor, true)) return true; target += (pos - source) * info.BounceRangeModifier / 100; - var dat = world.Map.DistanceAboveTerrain(target); + var dat = map.DistanceAboveTerrain(target); target += new WVec(0, 0, -dat.Length); length = Math.Max((target - pos).Length / speed.Length, 1); @@ -290,7 +292,7 @@ bool ShouldExplode(World world) return true; // Driving into cell with higher height level - if (world.Map.DistanceAboveTerrain(pos).Length < 0) + if (map.DistanceAboveTerrain(pos).Length < 0) return true; // After first bounce, check for targets each tick diff --git a/OpenRA.Mods.Common/Projectiles/Missile.cs b/OpenRA.Mods.Common/Projectiles/Missile.cs index 897be9b1235d..1bb692f56bd9 100644 --- a/OpenRA.Mods.Common/Projectiles/Missile.cs +++ b/OpenRA.Mods.Common/Projectiles/Missile.cs @@ -508,11 +508,13 @@ void InclineLookahead(World world, int distCheck, out int predClfHgt, out int pr // TODO: Make sure cell on map!!! for (var tick = 0; tick <= tickLimit; tick++) { + var map = world.Map; + posProbe += step; - if (!world.Map.Contains(world.Map.CellContaining(posProbe))) + if (!map.Contains(map.CellContaining(posProbe))) break; - var ht = world.Map.Height[world.Map.CellContaining(posProbe)] * 512; + var ht = ((IMapElevation)map).Height[map.CellContaining(posProbe)] * 512; curDist += stepSize; if (ht > predClfHgt) @@ -897,13 +899,14 @@ public void Tick(World world) contrail.Update(pos); distanceCovered += new WDist(speed); - var cell = world.Map.CellContaining(pos); - var height = world.Map.DistanceAboveTerrain(pos); + var map = world.Map; + var cell = map.CellContaining(pos); + var height = map.DistanceAboveTerrain(pos); shouldExplode |= height.Length < 0 // Hit the ground || relTarDist < info.CloseEnough.Length // Within range || (info.ExplodeWhenEmpty && rangeLimit >= WDist.Zero && distanceCovered > rangeLimit) // Ran out of fuel - || !world.Map.Contains(cell) // This also avoids an IndexOutOfRangeException in GetTerrainInfo below. - || (!string.IsNullOrEmpty(info.BoundToTerrainType) && world.Map.GetTerrainInfo(cell).Type != info.BoundToTerrainType) // Hit incompatible terrain + || !map.Contains(cell) // This also avoids an IndexOutOfRangeException in GetTerrainInfo below. + || (!string.IsNullOrEmpty(info.BoundToTerrainType) && map.GetTerrainInfo(cell).Type != info.BoundToTerrainType) // Hit incompatible terrain || (height.Length < info.AirburstAltitude.Length && relTarHorDist < info.CloseEnough.Length); // Airburst if (shouldExplode) diff --git a/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs index 240a99fc3a77..1b7b29ea4dac 100644 --- a/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/MediaGlobal.cs @@ -150,7 +150,9 @@ public void Debug(string text) [Desc("Display a text message at the specified location.")] public void FloatingText(string text, WPos position, int duration = 30, Color? color = null) { - if (string.IsNullOrEmpty(text) || !world.Map.Contains(world.Map.CellContaining(position))) + var map = world.Map; + + if (string.IsNullOrEmpty(text) || !map.Contains(map.CellContaining(position))) return; var c = color ?? Color.White; diff --git a/OpenRA.Mods.Common/Scripting/Global/UserInterfaceGlobal.cs b/OpenRA.Mods.Common/Scripting/Global/UserInterfaceGlobal.cs index e9510e2b8036..1b2dbec2e2d1 100644 --- a/OpenRA.Mods.Common/Scripting/Global/UserInterfaceGlobal.cs +++ b/OpenRA.Mods.Common/Scripting/Global/UserInterfaceGlobal.cs @@ -51,10 +51,10 @@ public string Translate(string text, LuaTable table = null) } } - return Context.World.Map.Translate(text, argumentDictionary); + return ((Map)Context.World.Map).Translate(text, argumentDictionary); } - return Context.World.Map.Translate(text); + return ((Map)Context.World.Map).Translate(text); } } } diff --git a/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs b/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs index c7aa1ff0f651..b58515422d36 100644 --- a/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs +++ b/OpenRA.Mods.Common/Terrain/DefaultTerrain.cs @@ -168,19 +168,21 @@ public bool TryGetTileInfo(TerrainTile r, out TerrainTileInfo info) string[] ITemplatedTerrainInfo.EditorTemplateOrder => EditorTemplateOrder; IReadOnlyDictionary ITemplatedTerrainInfo.Templates => Templates; - void ITerrainInfoNotifyMapCreated.MapCreated(Map map) + void ITerrainInfoNotifyMapCreated.MapCreated(IMap imap) { + var mapTiles = ((IMapTiles)imap).Tiles; + // Randomize PickAny tile variants var r = new MersenneTwister(); - for (var j = map.Bounds.Top; j < map.Bounds.Bottom; j++) + for (var j = imap.Bounds.Top; j < imap.Bounds.Bottom; j++) { - for (var i = map.Bounds.Left; i < map.Bounds.Right; i++) + for (var i = imap.Bounds.Left; i < imap.Bounds.Right; i++) { - var type = map.Tiles[new MPos(i, j)].Type; + var type = mapTiles[new MPos(i, j)].Type; if (!Templates.TryGetValue(type, out var template) || !template.PickAny) continue; - map.Tiles[new MPos(i, j)] = new TerrainTile(type, (byte)r.Next(0, template.TilesCount)); + mapTiles[new MPos(i, j)] = new TerrainTile(type, (byte)r.Next(0, template.TilesCount)); } } } diff --git a/OpenRA.Mods.Common/Terrain/TerrainInfo.cs b/OpenRA.Mods.Common/Terrain/TerrainInfo.cs index 0b40346b45ca..8eba84767e84 100644 --- a/OpenRA.Mods.Common/Terrain/TerrainInfo.cs +++ b/OpenRA.Mods.Common/Terrain/TerrainInfo.cs @@ -21,7 +21,7 @@ public interface ITemplatedTerrainInfo : ITerrainInfo public interface ITerrainInfoNotifyMapCreated : ITerrainInfo { - void MapCreated(Map map); + void MapCreated(IMap map); } public class TerrainTemplateInfo diff --git a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs index b7f141b15ad0..48208abbe378 100644 --- a/OpenRA.Mods.Common/Traits/Air/Aircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/Aircraft.cs @@ -194,10 +194,12 @@ IEnumerable IActorPreviewInitInfo.ActorPreviewInits(ActorInfo ai, Act // Used to determine if an aircraft can spawn landed public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = SubCell.FullCell, Actor ignoreActor = null, BlockedByActor check = BlockedByActor.All) { - if (!world.Map.Contains(cell)) + var map = world.Map; + + if (!map.Contains(cell)) return false; - var type = world.Map.GetTerrainInfo(cell).Type; + var type = map.GetTerrainInfo(cell).Type; if (!LandableTerrainTypes.Contains(type)) return false; @@ -468,6 +470,8 @@ public void Repulse() public virtual WVec GetRepulsionForce() { + var map = self.World.Map; + if (!Info.Repulsable) return WVec.Zero; @@ -497,11 +501,11 @@ public virtual WVec GetRepulsionForce() } // Actors outside the map bounds receive an extra nudge towards the center of the map - if (!self.World.Map.Contains(self.Location)) + if (!map.Contains(self.Location)) { // The map bounds are in projected coordinates, which is technically wrong for this, // but we avoid the issues in practice by guessing the middle of the map instead of the edge - var center = WPos.Lerp(self.World.Map.ProjectedTopLeft, self.World.Map.ProjectedBottomRight, 1, 2); + var center = WPos.Lerp(map.ProjectedTopLeft, map.ProjectedBottomRight, 1, 2); repulsionForce += new WVec(0, 1024, 0).Rotate(WRot.FromYaw((self.CenterPosition - center).Yaw)); } @@ -636,18 +640,20 @@ public WVec FlyStep(int speed, WAngle facing) public CPos? FindLandingLocation(CPos targetCell, WDist maxSearchDistance) { + var map = self.World.Map; + // The easy case if (CanLand(targetCell, blockedByMobile: false)) return targetCell; var cellRange = (maxSearchDistance.Length + 1023) / 1024; - var centerPosition = self.World.Map.CenterOfCell(targetCell); - foreach (var c in self.World.Map.FindTilesInCircle(targetCell, cellRange)) + var centerPosition = map.CenterOfCell(targetCell); + foreach (var c in map.FindTilesInCircle(targetCell, cellRange)) { if (!CanLand(c, blockedByMobile: false)) continue; - var delta = self.World.Map.CenterOfCell(c) - centerPosition; + var delta = map.CenterOfCell(c) - centerPosition; if (delta.LengthSquared < maxSearchDistance.LengthSquared) return c; } @@ -666,7 +672,9 @@ public bool CanLand(IEnumerable cells, Actor dockingActor = null, bool blo public bool CanLand(CPos cell, Actor dockingActor = null, bool blockedByMobile = true) { - if (!self.World.Map.Contains(cell)) + var map = self.World.Map; + + if (!map.Contains(cell)) return false; foreach (var otherActor in self.World.ActorMap.GetActorsAt(cell)) @@ -678,7 +686,7 @@ public bool CanLand(CPos cell, Actor dockingActor = null, bool blockedByMobile = return true; var landableTerrain = overrideAircraftLanding != null ? overrideAircraftLanding.LandableTerrainTypes : Info.LandableTerrainTypes; - return landableTerrain.Contains(self.World.Map.GetTerrainInfo(cell).Type); + return landableTerrain.Contains(map.GetTerrainInfo(cell).Type); } bool IsBlockedBy(Actor self, Actor otherActor, Actor ignoreActor, bool blockedByMobile = true) @@ -738,6 +746,8 @@ void INotifyBecomingIdle.OnBecomingIdle(Actor self) protected virtual void OnBecomingIdle(Actor self) { + var map = self.World.Map; + if (Info.IdleBehavior == IdleBehaviorType.LeaveMap) { self.QueueActivity(new FlyOffMap(self)); @@ -745,7 +755,7 @@ protected virtual void OnBecomingIdle(Actor self) } else if (Info.IdleBehavior == IdleBehaviorType.LeaveMapAtClosestEdge) { - var edgeCell = self.World.Map.ChooseClosestEdgeCell(self.Location); + var edgeCell = map.ChooseClosestEdgeCell(self.Location); self.QueueActivity(new FlyOffMap(self, Target.FromCell(self.World, edgeCell))); self.QueueActivity(new RemoveSelf()); } @@ -753,7 +763,7 @@ protected virtual void OnBecomingIdle(Actor self) self.QueueActivity(new ReturnToBase(self, null, !Info.TakeOffOnResupply)); else { - var dat = self.World.Map.DistanceAboveTerrain(CenterPosition); + var dat = map.DistanceAboveTerrain(CenterPosition); if (dat == LandAltitude) { if (!CanLand(self.Location) && ReservedActor == null) @@ -795,11 +805,12 @@ public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any) public void SetPosition(Actor self, WPos pos) { CenterPosition = pos; + var map = self.World.Map; if (!self.IsInWorld) return; - var altitude = self.World.Map.DistanceAboveTerrain(CenterPosition); + var altitude = map.DistanceAboveTerrain(CenterPosition); // LandingCells define OccupiedCells, so we need to keep current position with LandindCells in sync. // Though we don't want to update LandingCells when the unit is airborne, as when non-VTOL units reserve @@ -1053,13 +1064,15 @@ public string VoicePhraseForOrder(Actor self, Order order) public void ResolveOrder(Actor self, Order order) { + var map = self.World.Map; + if (IsTraitDisabled) return; var orderString = order.OrderString; if (orderString == "Move") { - var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); + var cell = map.Clamp(map.CellContaining(order.Target.CenterPosition)); if (!Info.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) return; @@ -1074,7 +1087,7 @@ public void ResolveOrder(Actor self, Order order) } else if (orderString == "Land") { - var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); + var cell = map.Clamp(map.CellContaining(order.Target.CenterPosition)); if (!Info.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) return; diff --git a/OpenRA.Mods.Common/Traits/Air/AttackAircraft.cs b/OpenRA.Mods.Common/Traits/Air/AttackAircraft.cs index c84d5b7a2c4c..fcc2af34f17e 100644 --- a/OpenRA.Mods.Common/Traits/Air/AttackAircraft.cs +++ b/OpenRA.Mods.Common/Traits/Air/AttackAircraft.cs @@ -52,9 +52,11 @@ public override Activity GetAttackActivity(Actor self, AttackSource source, in T protected override bool CanAttack(Actor self, in Target target) { + var map = self.World.Map; + // Don't fire while landed or when outside the map. - if (self.World.Map.DistanceAboveTerrain(self.CenterPosition).Length < aircraftInfo.MinAirborneAltitude - || !self.World.Map.Contains(self.Location)) + if (map.DistanceAboveTerrain(self.CenterPosition).Length < aircraftInfo.MinAirborneAltitude + || !map.Contains(self.Location)) return false; if (!base.CanAttack(self, target)) diff --git a/OpenRA.Mods.Common/Traits/AppearsOnMapPreview.cs b/OpenRA.Mods.Common/Traits/AppearsOnMapPreview.cs index fefe0e892d65..dad77988d23f 100644 --- a/OpenRA.Mods.Common/Traits/AppearsOnMapPreview.cs +++ b/OpenRA.Mods.Common/Traits/AppearsOnMapPreview.cs @@ -26,7 +26,7 @@ public class AppearsOnMapPreviewInfo : TraitInfo, IMapPrevi "Overrides `Color` if both set.")] public readonly string Terrain = null; - void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer) + void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(IMap map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer) { Color color; if (!string.IsNullOrEmpty(Terrain)) @@ -40,7 +40,7 @@ void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInf } else { - var owner = map.PlayerDefinitions.Single(p => s.Get().InternalName == p.Value.Nodes.Last(k => k.Key == "Name").Value.Value); + var owner = ((Map)map).PlayerDefinitions.Single(p => s.Get().InternalName == p.Value.Nodes.Last(k => k.Key == "Name").Value.Value); var colorValue = owner.Value.Nodes.Where(n => n.Key == "Color"); var ownerColor = colorValue.Any() ? colorValue.First().Value.Value : "FFFFFF"; Color.TryParse(ownerColor, out color); diff --git a/OpenRA.Mods.Common/Traits/AttackMove.cs b/OpenRA.Mods.Common/Traits/AttackMove.cs index cf4d473e973a..4e86c09e9dd3 100644 --- a/OpenRA.Mods.Common/Traits/AttackMove.cs +++ b/OpenRA.Mods.Common/Traits/AttackMove.cs @@ -83,7 +83,9 @@ public void ResolveOrder(Actor self, Order order) { if (order.OrderString == "AttackMove" || order.OrderString == "AssaultMove") { - var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); + var map = self.World.Map; + + var cell = map.Clamp(map.CellContaining(order.Target.CenterPosition)); if (!Info.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) return; diff --git a/OpenRA.Mods.Common/Traits/BlocksProjectiles.cs b/OpenRA.Mods.Common/Traits/BlocksProjectiles.cs index 0720932831ee..55e6f6204286 100644 --- a/OpenRA.Mods.Common/Traits/BlocksProjectiles.cs +++ b/OpenRA.Mods.Common/Traits/BlocksProjectiles.cs @@ -36,9 +36,11 @@ public BlocksProjectiles(BlocksProjectilesInfo info) public static bool AnyBlockingActorAt(World world, WPos pos) { - var dat = world.Map.DistanceAboveTerrain(pos); + var map = world.Map; - return world.ActorMap.GetActorsAt(world.Map.CellContaining(pos)) + var dat = map.DistanceAboveTerrain(pos); + + return world.ActorMap.GetActorsAt(map.CellContaining(pos)) .Any(a => a.TraitsImplementing() .Where(t => t.BlockingHeight > dat) .Any(Exts.IsTraitEnabled)); diff --git a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs index ca0f3a2a7665..2c9bc16120a1 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/BaseBuilderQueueManager.cs @@ -383,6 +383,7 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue) { var actorInfo = world.Map.Rules.Actors[actorType]; var bi = actorInfo.TraitInfoOrDefault(); + var map = world.Map; if (bi == null) return (null, 0); @@ -395,7 +396,7 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue) var variantActorInfo = actorInfo; var vbi = bi; - var cells = world.Map.FindTilesInAnnulus(center, minRange, maxRange); + var cells = map.FindTilesInAnnulus(center, minRange, maxRange); // Sort by distance to target if we have one if (center != target) @@ -408,7 +409,7 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue) { if (buildingVariantInfo.Facings != null) { - var vector = world.Map.CenterOfCell(target) - world.Map.CenterOfCell(center); + var vector = map.CenterOfCell(target) - map.CenterOfCell(center); // The rotation Y point to upside vertically, so -Y = Y(rotation) var desireFacing = new WAngle(WAngle.ArcSin((int)((long)Math.Abs(vector.X) * 1024 / vector.Length)).Angle); @@ -469,7 +470,7 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue) // Build near the closest enemy structure var closestEnemy = world.ActorsHavingTrait().Where(a => !a.Disposed && player.RelationshipWith(a.Owner) == PlayerRelationship.Enemy) - .ClosestTo(world.Map.CenterOfCell(baseBuilder.DefenseCenter)); + .ClosestTo(map.CenterOfCell(baseBuilder.DefenseCenter)); var targetCell = closestEnemy != null ? closestEnemy.Location : baseCenter; @@ -480,7 +481,7 @@ ActorInfo ChooseBuildingToBuild(ProductionQueue queue) // Try and place the refinery near a resource field if (resourceLayer != null) { - var nearbyResources = world.Map.FindTilesInAnnulus(baseCenter, baseBuilder.Info.MinBaseRadius, baseBuilder.Info.MaxBaseRadius) + var nearbyResources = map.FindTilesInAnnulus(baseCenter, baseBuilder.Info.MinBaseRadius, baseBuilder.Info.MaxBaseRadius) .Where(a => resourceLayer.GetResource(a).Type != null) .Shuffle(world.LocalRandom).Take(baseBuilder.Info.MaxResourceCellsToCheck); diff --git a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/SupportPowerDecision.cs b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/SupportPowerDecision.cs index 7a5e5c6f6a63..c17e1b4aac5d 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/SupportPowerDecision.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/BotModuleLogic/SupportPowerDecision.cs @@ -60,9 +60,10 @@ public int GetAttractiveness(WPos pos, Player firedBy) { var answer = 0; var world = firedBy.World; - var targetTile = world.Map.CellContaining(pos); + var map = world.Map; + var targetTile = map.CellContaining(pos); - if (!world.Map.Contains(targetTile)) + if (!map.Contains(targetTile)) return 0; foreach (var consideration in Considerations) @@ -74,9 +75,9 @@ public int GetAttractiveness(WPos pos, Player firedBy) answer += consideration.GetAttractiveness(scrutinized, firedBy.RelationshipWith(scrutinized.Owner), firedBy); var delta = new WVec(radiusToUse, radiusToUse, WDist.Zero); - var tl = world.Map.CellContaining(pos - delta); - var br = world.Map.CellContaining(pos + delta); - var checkFrozen = firedBy.FrozenActorLayer.FrozenActorsInRegion(new CellRegion(world.Map.Grid.Type, tl, br)); + var tl = map.CellContaining(pos - delta); + var br = map.CellContaining(pos + delta); + var checkFrozen = firedBy.FrozenActorLayer.FrozenActorsInRegion(new CellRegion(map.Grid.Type, tl, br)); // IsValid check filters out Frozen Actors that have not initialized their Owner foreach (var scrutinized in checkFrozen) diff --git a/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs index b98d5a91a537..2b82dbd2b7f0 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/HarvesterBotModule.cs @@ -142,11 +142,12 @@ Target FindNextResource(Actor actor, HarvesterTraitWrapper harv) harv.Harvester.CanHarvestCell(cell) && claimLayer.CanClaimCell(actor, cell); + var map = world.Map; var path = harv.Mobile.PathFinder.FindPathToTargetCellByPredicate( actor, new[] { actor.Location }, IsValidResource, BlockedByActor.Stationary, - loc => world.FindActorsInCircle(world.Map.CenterOfCell(loc), Info.HarvesterEnemyAvoidanceRadius) + loc => world.FindActorsInCircle(map.CenterOfCell(loc), Info.HarvesterEnemyAvoidanceRadius) .Where(u => !u.IsDead && actor.Owner.RelationshipWith(u.Owner) == PlayerRelationship.Enemy) - .Sum(u => Math.Max(WDist.Zero.Length, Info.HarvesterEnemyAvoidanceRadius.Length - (world.Map.CenterOfCell(loc) - u.CenterPosition).Length))); + .Sum(u => Math.Max(WDist.Zero.Length, Info.HarvesterEnemyAvoidanceRadius.Length - (map.CenterOfCell(loc) - u.CenterPosition).Length))); if (path.Count == 0) return Target.Invalid; diff --git a/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs b/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs index 1851e7869a8e..657b66761f5d 100644 --- a/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs +++ b/OpenRA.Mods.Common/Traits/BotModules/SupportPowerBotModule.cs @@ -148,8 +148,8 @@ void IBotTick.BotTick(IBot bot) var region = new CellRegion(map.Grid.Type, tl, br); // HACK: The AI code should not be messing with raw coordinate transformations - var wtl = world.Map.CenterOfCell(tl.ToCPos(map)); - var wbr = world.Map.CenterOfCell(br.ToCPos(map)); + var wtl = map.CenterOfCell(tl.ToCPos(map)); + var wbr = map.CenterOfCell(br.ToCPos(map)); var targets = world.ActorMap.ActorsInBox(wtl, wbr); var frozenTargets = player.FrozenActorLayer != null ? player.FrozenActorLayer.FrozenActorsInRegion(region) : Enumerable.Empty(); diff --git a/OpenRA.Mods.Common/Traits/Buildings/Building.cs b/OpenRA.Mods.Common/Traits/Buildings/Building.cs index 84a994c13da8..9f3821d08e31 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/Building.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/Building.cs @@ -152,7 +152,8 @@ public IEnumerable TransitOnlyTiles(CPos location) public WVec CenterOffset(World w) { - var off = (w.Map.CenterOfCell(new CPos(Dimensions.X, Dimensions.Y)) - w.Map.CenterOfCell(new CPos(1, 1))) / 2; + var map = w.Map; + var off = (map.CenterOfCell(new CPos(Dimensions.X, Dimensions.Y)) - map.CenterOfCell(new CPos(1, 1))) / 2; return off - new WVec(0, 0, off.Z) + LocalCenterOffset; } @@ -214,8 +215,9 @@ public virtual bool IsCloseEnoughToBase(World world, Player p, ActorInfo ai, CPo var adjacent = requiresBuildableArea.Adjacent; var buildingMaxBounds = Dimensions; - var scanStart = world.Map.Clamp(topLeft - new CVec(adjacent, adjacent)); - var scanEnd = world.Map.Clamp(topLeft + buildingMaxBounds + new CVec(adjacent, adjacent)); + var map = world.Map; + var scanStart = map.Clamp(topLeft - new CVec(adjacent, adjacent)); + var scanEnd = map.Clamp(topLeft + buildingMaxBounds + new CVec(adjacent, adjacent)); var nearnessCandidates = new List(); var bi = world.WorldActor.Trait(); diff --git a/OpenRA.Mods.Common/Traits/Buildings/BuildingInfluence.cs b/OpenRA.Mods.Common/Traits/Buildings/BuildingInfluence.cs index 72629fcd959a..a20a77a8a7da 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/BuildingInfluence.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/BuildingInfluence.cs @@ -29,7 +29,7 @@ class InfluenceNode public Actor Actor; } - readonly Map map; + readonly IMap map; readonly CellLayer influence; public BuildingInfluence(World world) diff --git a/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs b/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs index 66c36b128eb0..414eaf681d1d 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/BuildingUtils.cs @@ -18,7 +18,9 @@ public static class BuildingUtils { public static bool IsCellBuildable(this World world, CPos cell, ActorInfo ai, BuildingInfo bi, Actor toIgnore = null) { - if (!world.Map.Contains(cell)) + var map = world.Map; + + if (!map.Contains(cell)) return false; if (!bi.AllowInvalidPlacement) @@ -77,7 +79,7 @@ public static bool IsCellBuildable(this World world, CPos cell, ActorInfo ai, Bu } // Buildings can never be placed on ramps - return world.Map.Ramp[cell] == 0 && bi.TerrainTypes.Contains(world.Map.GetTerrainInfo(cell).Type); + return ((IMapElevation)map).Ramp[cell] == 0 && bi.TerrainTypes.Contains(map.GetTerrainInfo(cell).Type); } public static bool CanPlaceBuilding(this World world, CPos cell, ActorInfo ai, BuildingInfo bi, Actor toIgnore) diff --git a/OpenRA.Mods.Common/Traits/Buildings/FootprintPlaceBuildingPreview.cs b/OpenRA.Mods.Common/Traits/Buildings/FootprintPlaceBuildingPreview.cs index f890e89b7bf0..8611273bb20d 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/FootprintPlaceBuildingPreview.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/FootprintPlaceBuildingPreview.cs @@ -90,7 +90,8 @@ public FootprintPlaceBuildingPreviewPreview(WorldRenderer wr, ActorInfo ai, Foot PlaceBuildingCellType filter = PlaceBuildingCellType.Invalid | PlaceBuildingCellType.Valid | PlaceBuildingCellType.LineBuild) { var palette = wr.Palette(info.Palette); - var topLeftPos = wr.World.Map.CenterOfCell(topLeft); + var map = wr.World.Map; + var topLeftPos = map.CenterOfCell(topLeft); foreach (var c in footprint) { if ((c.Value & filter) == 0) @@ -98,7 +99,7 @@ public FootprintPlaceBuildingPreviewPreview(WorldRenderer wr, ActorInfo ai, Foot var tile = (c.Value & PlaceBuildingCellType.Invalid) != 0 ? blockedTile : validTile; var sequenceAlpha = (c.Value & PlaceBuildingCellType.Invalid) != 0 ? blockedAlpha : validAlpha; - var pos = wr.World.Map.CenterOfCell(c.Key); + var pos = map.CenterOfCell(c.Key); var offset = new WVec(0, 0, topLeftPos.Z - pos.Z); var traitAlpha = (c.Value & PlaceBuildingCellType.LineBuild) != 0 ? info.LineBuildFootprintAlpha : info.FootprintAlpha; yield return new SpriteRenderable(tile, pos, offset, -511, palette, 1f, sequenceAlpha * traitAlpha, float3.Ones, TintModifiers.IgnoreWorldTint, true); diff --git a/OpenRA.Mods.Common/Traits/Buildings/FreeActorWithDelivery.cs b/OpenRA.Mods.Common/Traits/Buildings/FreeActorWithDelivery.cs index 7dbc1b81bff3..7a958d17c741 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/FreeActorWithDelivery.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/FreeActorWithDelivery.cs @@ -76,14 +76,16 @@ public void DoDelivery(CPos location, string actorName, string carrierActorName) void CreateActors(string actorName, string deliveringActorName, out Actor cargo, out Actor carrier) { + var map = self.World.Map; + // Get a carryall spawn location var location = info.SpawnLocation; if (location == CPos.Zero) - location = self.World.Map.ChooseClosestEdgeCell(self.Location); + location = map.ChooseClosestEdgeCell(self.Location); - var spawn = self.World.Map.CenterOfCell(location); + var spawn = map.CenterOfCell(location); - var initialFacing = self.World.Map.FacingBetween(location, self.Location, WAngle.Zero); + var initialFacing = map.FacingBetween(location, self.Location, WAngle.Zero); // If aircraft, spawn at cruise altitude var aircraftInfo = self.World.Map.Rules.Actors[deliveringActorName.ToLowerInvariant()].TraitInfoOrDefault(); diff --git a/OpenRA.Mods.Common/Traits/Buildings/RallyPoint.cs b/OpenRA.Mods.Common/Traits/Buildings/RallyPoint.cs index 130c14a55439..d2e6a0ffda16 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/RallyPoint.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/RallyPoint.cs @@ -160,8 +160,9 @@ public bool CanTarget(Actor self, in Target target, ref TargetModifiers modifier IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); - var location = self.World.Map.CellContaining(target.CenterPosition); - if (self.World.Map.Contains(location)) + var map = self.World.Map; + var location = map.CellContaining(target.CenterPosition); + if (map.Contains(location)) { cursor = info.Cursor; diff --git a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoAircraft.cs b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoAircraft.cs index 7cc9a699e0d9..ae3e6ea262da 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoAircraft.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoAircraft.cs @@ -121,7 +121,8 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) if (order.OrderString == "Move") { - var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); + var map = self.World.Map; + var cell = map.Clamp(map.CellContaining(order.Target.CenterPosition)); if (!Info.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) return; } @@ -205,9 +206,10 @@ public bool CanTarget(Actor self, in Target target, ref TargetModifiers modifier if (target.Type != TargetType.Terrain || (aircraft.Info.RequiresForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove))) return false; - var location = self.World.Map.CellContaining(target.CenterPosition); + var map = self.World.Map; + var location = map.CellContaining(target.CenterPosition); var explored = self.Owner.Shroud.IsExplored(location); - cursor = self.World.Map.Contains(location) ? aircraft.Info.Cursor : aircraft.Info.BlockedCursor; + cursor = map.Contains(location) ? aircraft.Info.Cursor : aircraft.Info.BlockedCursor; IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); diff --git a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs index bf3da0b2f16a..13489b372405 100644 --- a/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs +++ b/OpenRA.Mods.Common/Traits/Buildings/TransformsIntoMobile.cs @@ -110,7 +110,7 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) if (order.OrderString == "Move") { - var cell = self.World.Map.Clamp(this.self.World.Map.CellContaining(order.Target.CenterPosition)); + var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) return; @@ -194,16 +194,17 @@ public bool CanTarget(Actor self, in Target target, ref TargetModifiers modifier if (rejectMove || target.Type != TargetType.Terrain || (mobile.Info.RequiresForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove))) return false; - var location = self.World.Map.CellContaining(target.CenterPosition); + var map = self.World.Map; + var location = map.CellContaining(target.CenterPosition); IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); var explored = self.Owner.Shroud.IsExplored(location); - if (!self.World.Map.Contains(location) || + if (!map.Contains(location) || !(self.CurrentActivity is Transform || mobile.transforms.Any(t => !t.IsTraitDisabled && !t.IsTraitPaused)) || (!explored && !mobile.locomotor.Info.MoveIntoShroud) || (explored && !CanEnterCell(self, location))) cursor = mobile.Info.BlockedCursor; - else if (!explored || !mobile.Info.TerrainCursors.TryGetValue(self.World.Map.GetTerrainInfo(location).Type, out cursor)) + else if (!explored || !mobile.Info.TerrainCursors.TryGetValue(map.GetTerrainInfo(location).Type, out cursor)) cursor = mobile.Info.Cursor; return true; diff --git a/OpenRA.Mods.Common/Traits/Carryall.cs b/OpenRA.Mods.Common/Traits/Carryall.cs index 1d0f9cea2e94..f9195ab0d2e7 100644 --- a/OpenRA.Mods.Common/Traits/Carryall.cs +++ b/OpenRA.Mods.Common/Traits/Carryall.cs @@ -341,7 +341,8 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) { if (order.OrderString == "DeliverUnit") { - var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); + var map = self.World.Map; + var cell = map.Clamp(map.CellContaining(order.Target.CenterPosition)); if (!aircraftInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) return; @@ -440,9 +441,10 @@ public bool CanTarget(Actor self, in Target target, ref TargetModifiers modifier return true; } - var location = self.World.Map.CellContaining(target.CenterPosition); + var map = self.World.Map; + var location = map.CellContaining(target.CenterPosition); var explored = self.Owner.Shroud.IsExplored(location); - cursor = self.World.Map.Contains(location) ? info.DropOffCursor : info.DropOffBlockedCursor; + cursor = map.Contains(location) ? info.DropOffCursor : info.DropOffBlockedCursor; IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); diff --git a/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDeploy.cs b/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDeploy.cs index e657e9a7c457..930f435abfe8 100644 --- a/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDeploy.cs +++ b/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnDeploy.cs @@ -232,8 +232,8 @@ bool IsValidRampType(CPos location) if (Info.CanDeployOnRamps) return true; - var map = self.World.Map; - return !map.Ramp.Contains(location) || map.Ramp[location] == 0; + var mapRamp = ((IMapElevation)self.World.Map).Ramp; + return !mapRamp.Contains(location) || mapRamp[location] == 0; } void INotifyDeployComplete.FinishedDeploy(Actor self) diff --git a/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnTerrain.cs b/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnTerrain.cs index 3595565fc587..b2d436b37374 100644 --- a/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnTerrain.cs +++ b/OpenRA.Mods.Common/Traits/Conditions/GrantConditionOnTerrain.cs @@ -45,12 +45,14 @@ public GrantConditionOnTerrain(ActorInitializer init, GrantConditionOnTerrainInf void ITick.Tick(Actor self) { + var map = self.World.Map; + var cell = self.Location; - if (!self.World.Map.Contains(cell)) + if (!map.Contains(cell)) return; // The terrain type may change between ticks without the actor moving - var currentTerrain = cell.Layer == 0 ? self.World.Map.GetTerrainInfo(cell).Type : + var currentTerrain = cell.Layer == 0 ? map.GetTerrainInfo(cell).Type : terrainTypes[self.World.GetCustomMovementLayers()[cell.Layer].GetTerrainIndex(cell)].Type; var wantsGranted = info.TerrainTypes.Contains(currentTerrain); diff --git a/OpenRA.Mods.Common/Traits/Crates/Crate.cs b/OpenRA.Mods.Common/Traits/Crates/Crate.cs index 4f467322acdc..1c4aa7e1e57a 100644 --- a/OpenRA.Mods.Common/Traits/Crates/Crate.cs +++ b/OpenRA.Mods.Common/Traits/Crates/Crate.cs @@ -47,10 +47,12 @@ public bool CanEnterCell(World world, Actor self, CPos cell, SubCell subCell = S public bool CanExistInCell(World world, CPos cell) { - if (!world.Map.Contains(cell)) + var map = world.Map; + + if (!map.Contains(cell)) return false; - var type = world.Map.GetTerrainInfo(cell).Type; + var type = map.GetTerrainInfo(cell).Type; if (!TerrainTypes.Contains(type)) return false; @@ -182,7 +184,8 @@ void ITick.Tick(Actor self) // Sets the location (Location) and position (CenterPosition) public void SetPosition(Actor self, WPos pos) { - var cell = self.World.Map.CellContaining(pos); + var map = self.World.Map; + var cell = map.CellContaining(pos); SetLocation(self, cell); SetCenterPosition(self, self.World.Map.CenterOfCell(cell) + new WVec(WDist.Zero, WDist.Zero, self.World.Map.DistanceAboveTerrain(pos))); } diff --git a/OpenRA.Mods.Common/Traits/Husk.cs b/OpenRA.Mods.Common/Traits/Husk.cs index 4afee74f84fc..7673fcf20d8f 100644 --- a/OpenRA.Mods.Common/Traits/Husk.cs +++ b/OpenRA.Mods.Common/Traits/Husk.cs @@ -83,12 +83,13 @@ public Husk(ActorInitializer init, HuskInfo info) this.info = info; self = init.Self; + var map = init.World.Map; TopLeft = init.GetValue(); - CenterPosition = init.GetValue(init.World.Map.CenterOfCell(TopLeft)); + CenterPosition = init.GetValue(map.CenterOfCell(TopLeft)); Facing = init.GetValue(info.GetInitialFacing()); dragSpeed = init.GetValue(0); - finalPosition = init.World.Map.CenterOfCell(TopLeft); + finalPosition = map.CenterOfCell(TopLeft); effectiveOwner = init.GetValue(info, self.Owner); } @@ -104,10 +105,12 @@ void INotifyCreated.Created(Actor self) public bool CanExistInCell(CPos cell) { - if (!self.World.Map.Contains(cell)) + var map = self.World.Map; + + if (!map.Contains(cell)) return false; - if (!info.AllowedTerrain.Contains(self.World.Map.GetTerrainInfo(cell).Type)) + if (!info.AllowedTerrain.Contains(map.GetTerrainInfo(cell).Type)) return false; return true; diff --git a/OpenRA.Mods.Common/Traits/IsometricSelectable.cs b/OpenRA.Mods.Common/Traits/IsometricSelectable.cs index 7c1bb07b5c27..7c2ffda9413d 100644 --- a/OpenRA.Mods.Common/Traits/IsometricSelectable.cs +++ b/OpenRA.Mods.Common/Traits/IsometricSelectable.cs @@ -109,10 +109,11 @@ Polygon Bounds(Actor self, WorldRenderer wr, int[] bounds, int height) yMax = Math.Max(yMax, c.Y); } - left = wr.ScreenPxPosition(self.World.Map.CenterOfCell(new CPos(xMin, yMax)) - new WVec(768, 0, 0)); - right = wr.ScreenPxPosition(self.World.Map.CenterOfCell(new CPos(xMax, yMin)) + new WVec(768, 0, 0)); - top = wr.ScreenPxPosition(self.World.Map.CenterOfCell(new CPos(xMin, yMin)) - new WVec(0, 768, 0)); - bottom = wr.ScreenPxPosition(self.World.Map.CenterOfCell(new CPos(xMax, yMax)) + new WVec(0, 768, 0)); + var map = self.World.Map; + left = wr.ScreenPxPosition(map.CenterOfCell(new CPos(xMin, yMax)) - new WVec(768, 0, 0)); + right = wr.ScreenPxPosition(map.CenterOfCell(new CPos(xMax, yMin)) + new WVec(768, 0, 0)); + top = wr.ScreenPxPosition(map.CenterOfCell(new CPos(xMin, yMin)) - new WVec(0, 768, 0)); + bottom = wr.ScreenPxPosition(map.CenterOfCell(new CPos(xMax, yMax)) + new WVec(0, 768, 0)); } if (height == 0) diff --git a/OpenRA.Mods.Common/Traits/Mobile.cs b/OpenRA.Mods.Common/Traits/Mobile.cs index 31094a8677d9..5c0c8c4bf9da 100644 --- a/OpenRA.Mods.Common/Traits/Mobile.cs +++ b/OpenRA.Mods.Common/Traits/Mobile.cs @@ -478,7 +478,8 @@ public void SetPosition(Actor self, CPos cell, SubCell subCell = SubCell.Any) // Sets the location (fromCell, toCell, FromSubCell, ToSubCell) and CenterPosition public void SetPosition(Actor self, WPos pos) { - var cell = self.World.Map.CellContaining(pos); + var map = self.World.Map; + var cell = map.CellContaining(pos); SetLocation(cell, FromSubCell, cell, FromSubCell); SetCenterPosition(self, self.World.Map.CenterOfSubCell(cell, FromSubCell) + new WVec(0, 0, self.World.Map.DistanceAboveTerrain(pos).Length)); FinishedMoving(self); @@ -667,13 +668,14 @@ public override bool Tick(Actor self) pos = self.CenterPosition; cell = mobile.ToCell; subCell = mobile.ToSubCell; + var map = self.World.Map; if (recalculateSubCell) subCell = mobile.Info.LocomotorInfo.SharesCell ? self.World.ActorMap.FreeSubCell(cell, subCell, a => a != self) : SubCell.FullCell; // TODO: solve/reduce cell is full problem if (subCell == SubCell.Invalid) - subCell = self.World.Map.Grid.DefaultSubCell; + subCell = map.Grid.DefaultSubCell; // Reserve the exit cell mobile.SetPosition(self, cell, subCell); @@ -682,7 +684,7 @@ public override bool Tick(Actor self) if (delay > 0) QueueChild(new Wait(delay)); - QueueChild(mobile.LocalMove(self, pos, self.World.Map.CenterOfSubCell(cell, subCell))); + QueueChild(mobile.LocalMove(self, pos, map.CenterOfSubCell(cell, subCell))); return true; } } @@ -919,7 +921,7 @@ void IResolveOrder.ResolveOrder(Actor self, Order order) if (order.OrderString == "Move") { - var cell = self.World.Map.Clamp(this.self.World.Map.CellContaining(order.Target.CenterPosition)); + var cell = self.World.Map.Clamp(self.World.Map.CellContaining(order.Target.CenterPosition)); if (!Info.LocomotorInfo.MoveIntoShroud && !self.Owner.Shroud.IsExplored(cell)) return; @@ -990,20 +992,22 @@ public MoveOrderTargeter(Actor self, Mobile unit) public bool CanTarget(Actor self, in Target target, ref TargetModifiers modifiers, ref string cursor) { + var map = self.World.Map; + if (rejectMove || target.Type != TargetType.Terrain || (mobile.requireForceMove && !modifiers.HasModifier(TargetModifiers.ForceMove))) return false; - var location = self.World.Map.CellContaining(target.CenterPosition); + var location = map.CellContaining(target.CenterPosition); IsQueued = modifiers.HasModifier(TargetModifiers.ForceQueue); var explored = self.Owner.Shroud.IsExplored(location); if (mobile.IsTraitPaused - || !self.World.Map.Contains(location) + || !map.Contains(location) || (!explored && !locomotorInfo.MoveIntoShroud) || (explored && mobile.Locomotor.MovementCostForCell(location) == PathGraph.MovementCostForUnreachableCell)) cursor = mobile.Info.BlockedCursor; - else if (!explored || !mobile.Info.TerrainCursors.TryGetValue(self.World.Map.GetTerrainInfo(location).Type, out cursor)) + else if (!explored || !mobile.Info.TerrainCursors.TryGetValue(map.GetTerrainInfo(location).Type, out cursor)) cursor = mobile.Info.Cursor; return true; diff --git a/OpenRA.Mods.Common/Traits/Palettes/PaletteFromEmbeddedSpritePalette.cs b/OpenRA.Mods.Common/Traits/Palettes/PaletteFromEmbeddedSpritePalette.cs index 8918f672b113..3077c2dccf91 100644 --- a/OpenRA.Mods.Common/Traits/Palettes/PaletteFromEmbeddedSpritePalette.cs +++ b/OpenRA.Mods.Common/Traits/Palettes/PaletteFromEmbeddedSpritePalette.cs @@ -60,7 +60,7 @@ public class PaletteFromEmbeddedSpritePalette : ILoadsPalettes, IProvidesAssetBr public void LoadPalettes(WorldRenderer wr) { - FrameLoader.GetFrames(wr.World.Map, info.Filename, Game.ModData.SpriteLoaders, out var metadata); + FrameLoader.GetFrames((IReadOnlyFileSystem)wr.World.Map, info.Filename, Game.ModData.SpriteLoaders, out var metadata); var palettes = metadata?.GetOrDefault(); if (palettes == null || !palettes.TryGetPaletteForFrame(info.Frame, out var embeddedPalette)) throw new YamlException($"Cannot export palette from {info.Filename}: frame {info.Frame} does not define an embedded palette"); diff --git a/OpenRA.Mods.Common/Traits/Palettes/PaletteFromFile.cs b/OpenRA.Mods.Common/Traits/Palettes/PaletteFromFile.cs index e2ab35d150b8..6958a3af584c 100644 --- a/OpenRA.Mods.Common/Traits/Palettes/PaletteFromFile.cs +++ b/OpenRA.Mods.Common/Traits/Palettes/PaletteFromFile.cs @@ -68,8 +68,9 @@ public PaletteFromFile(World world, PaletteFromFileInfo info) public void LoadPalettes(WorldRenderer wr) { + // TO DO: Make this use IMap instead of Map if (info.Tileset == null || string.Equals(info.Tileset, world.Map.Tileset, StringComparison.InvariantCultureIgnoreCase)) - wr.AddPalette(info.Name, ((IProvidesCursorPaletteInfo)info).ReadPalette(world.Map), info.AllowModifiers); + wr.AddPalette(info.Name, ((IProvidesCursorPaletteInfo)info).ReadPalette((Map)world.Map), info.AllowModifiers); } public IEnumerable PaletteNames diff --git a/OpenRA.Mods.Common/Traits/Palettes/PaletteFromGimpOrJascFile.cs b/OpenRA.Mods.Common/Traits/Palettes/PaletteFromGimpOrJascFile.cs index 4a23897dc2d9..b17cd4885a7e 100644 --- a/OpenRA.Mods.Common/Traits/Palettes/PaletteFromGimpOrJascFile.cs +++ b/OpenRA.Mods.Common/Traits/Palettes/PaletteFromGimpOrJascFile.cs @@ -123,7 +123,8 @@ public PaletteFromGimpOrJascFile(World world, PaletteFromGimpOrJascFileInfo info public void LoadPalettes(WorldRenderer wr) { - wr.AddPalette(info.Name, ((IProvidesCursorPaletteInfo)info).ReadPalette(world.Map), info.AllowModifiers); + // TO DO: Make this use IMap instead of Map + wr.AddPalette(info.Name, ((IProvidesCursorPaletteInfo)info).ReadPalette((Map)world.Map), info.AllowModifiers); } public IEnumerable PaletteNames diff --git a/OpenRA.Mods.Common/Traits/Palettes/PaletteFromPng.cs b/OpenRA.Mods.Common/Traits/Palettes/PaletteFromPng.cs index 75e7ee1c7ed8..390646002bc8 100644 --- a/OpenRA.Mods.Common/Traits/Palettes/PaletteFromPng.cs +++ b/OpenRA.Mods.Common/Traits/Palettes/PaletteFromPng.cs @@ -79,7 +79,8 @@ public void LoadPalettes(WorldRenderer wr) if (info.Tileset != null && !string.Equals(info.Tileset, world.Map.Tileset, StringComparison.InvariantCultureIgnoreCase)) return; - wr.AddPalette(info.Name, ((IProvidesCursorPaletteInfo)info).ReadPalette(world.Map), info.AllowModifiers); + // TO DO: Make this use IMap instead of Map + wr.AddPalette(info.Name, ((IProvidesCursorPaletteInfo)info).ReadPalette((Map)world.Map), info.AllowModifiers); } public IEnumerable PaletteNames diff --git a/OpenRA.Mods.Common/Traits/Parachutable.cs b/OpenRA.Mods.Common/Traits/Parachutable.cs index 19635118dd52..d73b794aca97 100644 --- a/OpenRA.Mods.Common/Traits/Parachutable.cs +++ b/OpenRA.Mods.Common/Traits/Parachutable.cs @@ -87,6 +87,7 @@ void INotifyParachute.OnParachute(Actor self) void INotifyParachute.OnLanded(Actor self) { IsInAir = false; + var map = self.World.Map; if (parachutingToken != Actor.InvalidConditionToken) parachutingToken = self.RevokeCondition(parachutingToken); @@ -99,10 +100,10 @@ void INotifyParachute.OnLanded(Actor self) return; if (IgnoreActor != null && !self.World.ActorMap.GetActorsAt(cell) - .Any(a => a != IgnoreActor && a != self && self.World.Map.DistanceAboveTerrain(a.CenterPosition) == WDist.Zero)) + .Any(a => a != IgnoreActor && a != self && map.DistanceAboveTerrain(a.CenterPosition) == WDist.Zero)) return; - var onWater = info.WaterTerrainTypes.Contains(self.World.Map.GetTerrainInfo(cell).Type); + var onWater = info.WaterTerrainTypes.Contains(map.GetTerrainInfo(cell).Type); var sound = onWater ? info.WaterImpactSound : info.GroundImpactSound; Game.Sound.Play(SoundType.World, sound, self.CenterPosition); diff --git a/OpenRA.Mods.Common/Traits/Player/PlayerRadarTerrain.cs b/OpenRA.Mods.Common/Traits/Player/PlayerRadarTerrain.cs index bf72d6439a77..d0894d87a0a2 100644 --- a/OpenRA.Mods.Common/Traits/Player/PlayerRadarTerrain.cs +++ b/OpenRA.Mods.Common/Traits/Player/PlayerRadarTerrain.cs @@ -70,13 +70,15 @@ public void WorldLoaded(World w, WorldRenderer wr) w.AddFrameEndTask(_ => { + var map = world.Map; + // Set initial terrain data - foreach (var uv in world.Map.AllCells.MapCoords) + foreach (var uv in map.AllCells.MapCoords) UpdateTerrainCellColor(uv); - world.Map.Tiles.CellEntryChanged += cell => UpdateTerrainCell(cell.ToMPos(world.Map)); + ((IMapTiles)map).Tiles.CellEntryChanged += cell => UpdateTerrainCell(cell.ToMPos(map)); foreach (var rtl in radarTerrainLayers) - rtl.CellEntryChanged += cell => UpdateTerrainCell(cell.ToMPos(world.Map)); + rtl.CellEntryChanged += cell => UpdateTerrainCell(cell.ToMPos(map)); IsInitialized = true; }); @@ -84,7 +86,7 @@ public void WorldLoaded(World w, WorldRenderer wr) public (int Left, int Right) this[MPos uv] => terrainColor[uv]; - public static (int Left, int Right) GetColor(Map map, IRadarTerrainLayer[] radarTerrainLayers, MPos uv) + public static (int Left, int Right) GetColor(IMap map, IRadarTerrainLayer[] radarTerrainLayers, MPos uv) { foreach (var rtl in radarTerrainLayers) if (rtl.TryGetTerrainColorPair(uv, out var c)) diff --git a/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs b/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs index 811ab82a3802..d2efc19e7efa 100644 --- a/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs +++ b/OpenRA.Mods.Common/Traits/ProductionFromMapEdge.cs @@ -55,16 +55,17 @@ public override bool Produce(Actor self, ActorInfo producee, string productionTy var destinations = rp != null && rp.Path.Count > 0 ? rp.Path : new List { self.Location }; + var map = self.World.Map; var location = spawnLocation; if (!location.HasValue) { if (aircraftInfo != null) - location = self.World.Map.ChooseClosestEdgeCell(self.Location); + location = map.ChooseClosestEdgeCell(self.Location); if (mobileInfo != null) { var locomotor = self.World.WorldActor.TraitsImplementing().First(l => l.Info.Name == mobileInfo.Locomotor); - location = self.World.Map.ChooseClosestMatchingEdgeCell(self.Location, + location = map.ChooseClosestMatchingEdgeCell(self.Location, c => mobileInfo.CanEnterCell(self.World, null, c) && pathFinder.PathExistsForLocomotor(locomotor, c, destinations[0])); } } @@ -73,13 +74,13 @@ public override bool Produce(Actor self, ActorInfo producee, string productionTy if (!location.HasValue) return false; - var pos = self.World.Map.CenterOfCell(location.Value); + var pos = map.CenterOfCell(location.Value); // If aircraft, spawn at cruise altitude if (aircraftInfo != null) pos += new WVec(0, 0, aircraftInfo.CruiseAltitude.Length); - var initialFacing = self.World.Map.FacingBetween(location.Value, destinations[0], WAngle.Zero); + var initialFacing = map.FacingBetween(location.Value, destinations[0], WAngle.Zero); self.World.AddFrameEndTask(w => { diff --git a/OpenRA.Mods.Common/Traits/ProductionParadrop.cs b/OpenRA.Mods.Common/Traits/ProductionParadrop.cs index 1f0304f0133e..0ff18b858b0b 100644 --- a/OpenRA.Mods.Common/Traits/ProductionParadrop.cs +++ b/OpenRA.Mods.Common/Traits/ProductionParadrop.cs @@ -131,8 +131,9 @@ public override void DoProduction(Actor self, ActorInfo producee, ExitInfo exiti if (exitinfo != null) exit += exitinfo.ExitCell; - var spawn = self.World.Map.CenterOfCell(exit) + new WVec(WDist.Zero, WDist.Zero, altitude); - var to = self.World.Map.CenterOfCell(exit); + var map = self.World.Map; + var spawn = map.CenterOfCell(exit) + new WVec(WDist.Zero, WDist.Zero, altitude); + var to = map.CenterOfCell(exit); var initialFacing = (exitinfo != null && exitinfo.Facing.HasValue) ? exitinfo.Facing.Value : (to - spawn).Yaw; diff --git a/OpenRA.Mods.Common/Traits/Render/CustomTerrainDebugOverlay.cs b/OpenRA.Mods.Common/Traits/Render/CustomTerrainDebugOverlay.cs index 43c9c234085c..a662b1446695 100644 --- a/OpenRA.Mods.Common/Traits/Render/CustomTerrainDebugOverlay.cs +++ b/OpenRA.Mods.Common/Traits/Render/CustomTerrainDebugOverlay.cs @@ -70,13 +70,14 @@ IEnumerable IRenderAnnotations.RenderAnnotations(Actor self, WorldR if (self.World.ShroudObscures(uv)) continue; - var cell = uv.ToCPos(wr.World.Map); - var center = wr.World.Map.CenterOfCell(cell); + var map = wr.World.Map; + var cell = uv.ToCPos(map); + var center = map.CenterOfCell(cell); var terrainType = self.World.Map.CustomTerrain[cell]; if (terrainType == byte.MaxValue) continue; - var info = wr.World.Map.GetTerrainInfo(cell); + var info = map.GetTerrainInfo(cell); yield return new TextAnnotationRenderable(font, center, 0, info.Color, info.Type); } } diff --git a/OpenRA.Mods.Common/Traits/Render/LeavesTrails.cs b/OpenRA.Mods.Common/Traits/Render/LeavesTrails.cs index ebc9a9132cda..2f754129985d 100644 --- a/OpenRA.Mods.Common/Traits/Render/LeavesTrails.cs +++ b/OpenRA.Mods.Common/Traits/Render/LeavesTrails.cs @@ -115,11 +115,12 @@ void ITick.Tick(Actor self) if (++ticks >= cachedInterval) { - var spawnCell = Info.SpawnAtLastPosition ? self.World.Map.CellContaining(cachedPosition) : self.World.Map.CellContaining(self.CenterPosition); - if (!self.World.Map.Contains(spawnCell)) + var map = self.World.Map; + var spawnCell = Info.SpawnAtLastPosition ? map.CellContaining(cachedPosition) : map.CellContaining(self.CenterPosition); + if (!map.Contains(spawnCell)) return; - var type = self.World.Map.GetTerrainInfo(spawnCell).Type; + var type = map.GetTerrainInfo(spawnCell).Type; if (++offset >= Info.Offsets.Length) offset = 0; @@ -134,7 +135,7 @@ void ITick.Tick(Actor self) var offsetRotation = Info.Offsets[offset].Rotate(body.QuantizeOrientation(self.Orientation)); var spawnPosition = Info.SpawnAtLastPosition ? cachedPosition : self.CenterPosition; var pos = Info.Type == TrailType.CenterPosition ? spawnPosition + body.LocalToWorld(offsetRotation) : - self.World.Map.CenterOfCell(spawnCell); + map.CenterOfCell(spawnCell); self.World.AddFrameEndTask(w => w.Add(new SpriteEffect(pos, spawnFacing, self.World, Info.Image, Info.Sequences.Random(Game.CosmeticRandom), Info.Palette, Info.VisibleThroughFog))); diff --git a/OpenRA.Mods.Common/Traits/Render/WithAircraftLandingEffect.cs b/OpenRA.Mods.Common/Traits/Render/WithAircraftLandingEffect.cs index 2590a5b3a120..40134be23a88 100644 --- a/OpenRA.Mods.Common/Traits/Render/WithAircraftLandingEffect.cs +++ b/OpenRA.Mods.Common/Traits/Render/WithAircraftLandingEffect.cs @@ -54,7 +54,7 @@ void AddEffect(Actor self) Info.Sequences.Random(Game.CosmeticRandom), Info.Palette, Info.VisibleThroughFog))); } - bool ShouldAddEffect(Map map, CPos cell) + bool ShouldAddEffect(IMap map, CPos cell) { if (Info.TerrainTypes.Count == 0) return true; diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/AirstrikePower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/AirstrikePower.cs index 7c29706f5536..0a1d43fb9faa 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/AirstrikePower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/AirstrikePower.cs @@ -79,6 +79,8 @@ public override void Activate(Actor self, Order order, SupportPowerManager manag public Actor[] SendAirstrike(Actor self, WPos target, WAngle? facing = null) { + var map = self.World.Map; + var aircraft = new List(); if (!facing.HasValue) facing = new WAngle(1024 * self.World.SharedRandom.Next(info.QuantizedFacings) / info.QuantizedFacings); @@ -87,8 +89,8 @@ public Actor[] SendAirstrike(Actor self, WPos target, WAngle? facing = null) var attackRotation = WRot.FromYaw(facing.Value); var delta = new WVec(0, -1024, 0).Rotate(attackRotation); target += new WVec(0, 0, altitude); - var startEdge = target - (self.World.Map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; - var finishEdge = target + (self.World.Map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; + var startEdge = target - (map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; + var finishEdge = target + (map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; Actor camera = null; Beacon beacon = null; diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/ParatroopersPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/ParatroopersPower.cs index c1fe2469bf16..d0fc9f6fe3c7 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/ParatroopersPower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/ParatroopersPower.cs @@ -112,8 +112,10 @@ public override void Activate(Actor self, Order order, SupportPowerManager manag var dropRotation = WRot.FromYaw(facing.Value); var delta = new WVec(0, -1024, 0).Rotate(dropRotation); target += new WVec(0, 0, altitude); - var startEdge = target - (self.World.Map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; - var finishEdge = target + (self.World.Map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; + + var map = self.World.Map; + var startEdge = target - (map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; + var finishEdge = target + (map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; Actor camera = null; Beacon beacon = null; @@ -128,7 +130,7 @@ void OnEnterRange(Actor a) { camera = w.CreateActor(info.CameraActor, new TypeDictionary { - new LocationInit(self.World.Map.CellContaining(target)), + new LocationInit(map.CellContaining(target)), new OwnerInit(self.Owner), }); }); diff --git a/OpenRA.Mods.Common/Traits/SupportPowers/SpawnActorPower.cs b/OpenRA.Mods.Common/Traits/SupportPowers/SpawnActorPower.cs index a1906aa28b49..4b59db5b0e5a 100644 --- a/OpenRA.Mods.Common/Traits/SupportPowers/SpawnActorPower.cs +++ b/OpenRA.Mods.Common/Traits/SupportPowers/SpawnActorPower.cs @@ -103,13 +103,15 @@ public override void SelectTarget(Actor self, string order, SupportPowerManager public bool Validate(World world, SpawnActorPowerInfo info, CPos cell) { - if (!world.Map.Contains(cell)) + var map = world.Map; + + if (!map.Contains(cell)) return false; if (!info.AllowUnderShroud && world.ShroudObscures(cell)) return false; - if (info.Terrain != null && !info.Terrain.Contains(world.Map.GetTerrainInfo(cell).Type)) + if (info.Terrain != null && !info.Terrain.Contains(map.GetTerrainInfo(cell).Type)) return false; return true; diff --git a/OpenRA.Mods.Common/Traits/Wanders.cs b/OpenRA.Mods.Common/Traits/Wanders.cs index e93b85e2a71d..20b6ef947e4e 100644 --- a/OpenRA.Mods.Common/Traits/Wanders.cs +++ b/OpenRA.Mods.Common/Traits/Wanders.cs @@ -84,10 +84,11 @@ void INotifyIdle.TickIdle(Actor self) CPos? PickTargetLocation(Actor self) { + var map = self.World.Map; var target = self.CenterPosition + new WVec(0, -1024 * effectiveMoveRadius, 0).Rotate(WRot.FromFacing(self.World.SharedRandom.Next(255))); - var targetCell = self.World.Map.CellContaining(target); + var targetCell = map.CellContaining(target); - if (!self.World.Map.Contains(targetCell)) + if (!map.Contains(targetCell)) { // If MoveRadius is too big there might not be a valid cell to order the attack to (if actor is on a small island and can't leave) if (++ticksIdle % info.ReduceMoveRadiusDelay == 0) @@ -99,7 +100,7 @@ void INotifyIdle.TickIdle(Actor self) if (info.AvoidTerrainTypes.Count > 0) { - var terrainType = self.World.Map.GetTerrainInfo(targetCell).Type; + var terrainType = map.GetTerrainInfo(targetCell).Type; if (Info.AvoidTerrainTypes.Contains(terrainType)) return null; } diff --git a/OpenRA.Mods.Common/Traits/World/ActorMap.cs b/OpenRA.Mods.Common/Traits/World/ActorMap.cs index 2591f936a845..5fa9f9aa5bae 100644 --- a/OpenRA.Mods.Common/Traits/World/ActorMap.cs +++ b/OpenRA.Mods.Common/Traits/World/ActorMap.cs @@ -165,7 +165,7 @@ public void Dispose() } readonly ActorMapInfo info; - readonly Map map; + readonly IMap map; readonly Dictionary cellTriggers = new Dictionary(); readonly Dictionary> cellTriggerInfluence = new Dictionary>(); readonly Dictionary proximityTriggers = new Dictionary(); diff --git a/OpenRA.Mods.Common/Traits/World/BuildableTerrainOverlay.cs b/OpenRA.Mods.Common/Traits/World/BuildableTerrainOverlay.cs index f3b14f1f9a93..0feac07ea397 100644 --- a/OpenRA.Mods.Common/Traits/World/BuildableTerrainOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/BuildableTerrainOverlay.cs @@ -64,14 +64,19 @@ public BuildableTerrainOverlay(Actor self, BuildableTerrainOverlayInfo info) void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) { + var map = world.Map; + var mapTiles = ((IMapTiles)world.Map).Tiles; + var mapRamp = ((IMapElevation)world.Map).Ramp; + var m = w.Map; + render = new TerrainSpriteLayer(w, wr, disabledSprite, BlendMode.Alpha, wr.World.Type != WorldType.Editor); - world.Map.Tiles.CellEntryChanged += UpdateTerrainCell; - world.Map.CustomTerrain.CellEntryChanged += UpdateTerrainCell; + mapTiles.CellEntryChanged += UpdateTerrainCell; + map.CustomTerrain.CellEntryChanged += UpdateTerrainCell; - var cells = w.Map.AllCells.Where(c => w.Map.Contains(c) && - (!info.AllowedTerrainTypes.Contains(w.Map.GetTerrainInfo(c).Type) || - world.Map.Ramp[c] != 0)).ToHashSet(); + var cells = m.AllCells.Where(c => m.Contains(c) && + (!info.AllowedTerrainTypes.Contains(m.GetTerrainInfo(c).Type) || + mapRamp[c] != 0)).ToHashSet(); palette = wr.Palette(info.Palette); @@ -81,10 +86,12 @@ void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) void UpdateTerrainCell(CPos cell) { - if (!world.Map.Contains(cell)) + var map = world.Map; + + if (!map.Contains(cell)) return; - var buildableSprite = !info.AllowedTerrainTypes.Contains(world.Map.GetTerrainInfo(cell).Type) || world.Map.Ramp[cell] != 0 ? disabledSprite : null; + var buildableSprite = !info.AllowedTerrainTypes.Contains(map.GetTerrainInfo(cell).Type) || ((IMapElevation)map).Ramp[cell] != 0 ? disabledSprite : null; render.Update(cell, buildableSprite, palette, 1f, info.Alpha); } diff --git a/OpenRA.Mods.Common/Traits/World/CellTriggerOverlay.cs b/OpenRA.Mods.Common/Traits/World/CellTriggerOverlay.cs index 5b217fb3cbb8..396d3bd439dc 100644 --- a/OpenRA.Mods.Common/Traits/World/CellTriggerOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/CellTriggerOverlay.cs @@ -72,16 +72,18 @@ IEnumerable IRenderAnnotations.RenderAnnotations(Actor self, WorldR var triggerPositions = wr.World.ActorMap.TriggerPositions().ToHashSet(); + var map = wr.World.Map; + foreach (var uv in wr.Viewport.VisibleCellsInsideBounds.CandidateMapCoords) { if (self.World.ShroudObscures(uv)) continue; - var cell = uv.ToCPos(wr.World.Map); + var cell = uv.ToCPos(map); if (!triggerPositions.Contains(cell)) continue; - var center = wr.World.Map.CenterOfCell(cell); + var center = map.CenterOfCell(cell); yield return new TextAnnotationRenderable(font, center, 1024, color, "T"); } } diff --git a/OpenRA.Mods.Common/Traits/World/CliffBackImpassabilityLayer.cs b/OpenRA.Mods.Common/Traits/World/CliffBackImpassabilityLayer.cs index 574f1aebfc88..ada375f7c3fa 100644 --- a/OpenRA.Mods.Common/Traits/World/CliffBackImpassabilityLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/CliffBackImpassabilityLayer.cs @@ -37,22 +37,23 @@ public CliffBackImpassabilityLayer(CliffBackImpassabilityLayerInfo info) public void WorldLoaded(World w, WorldRenderer wr) { var tileType = w.Map.Rules.TerrainInfo.GetTerrainIndex(info.TerrainType); + var map = w.Map; // Units are allowed behind cliffs *only* if they are part of a tunnel portal var tunnelPortals = w.WorldActor.Info.TraitInfos() .SelectMany(mti => mti.PortalCells()) .ToHashSet(); - foreach (var uv in w.Map.AllCells.MapCoords) + foreach (var uv in map.AllCells.MapCoords) { - if (tunnelPortals.Contains(uv.ToCPos(w.Map))) + if (tunnelPortals.Contains(uv.ToCPos(map))) continue; // All the map cells that visually overlap the current cell - var testCells = w.Map.ProjectedCellsCovering(uv) - .SelectMany(puv => w.Map.Unproject(puv)); + var testCells = map.ProjectedCellsCovering(uv) + .SelectMany(puv => map.Unproject(puv)); if (testCells.Any(x => x.V >= uv.V + 4)) - w.Map.CustomTerrain[uv] = tileType; + map.CustomTerrain[uv] = tileType; } } } diff --git a/OpenRA.Mods.Common/Traits/World/CrateSpawner.cs b/OpenRA.Mods.Common/Traits/World/CrateSpawner.cs index e2ba06b897b7..d44b532cca1e 100644 --- a/OpenRA.Mods.Common/Traits/World/CrateSpawner.cs +++ b/OpenRA.Mods.Common/Traits/World/CrateSpawner.cs @@ -130,6 +130,7 @@ void SpawnCrate(Actor self) { var inWater = self.World.SharedRandom.Next(100) < info.WaterChance; var pp = ChooseDropCell(self, inWater, 100); + var map = self.World.Map; if (pp == null) return; @@ -146,9 +147,9 @@ void SpawnCrate(Actor self) var delta = new WVec(0, -1024, 0).Rotate(WRot.FromYaw(dropFacing)); var altitude = self.World.Map.Rules.Actors[info.DeliveryAircraft].TraitInfo().CruiseAltitude.Length; - var target = self.World.Map.CenterOfCell(p) + new WVec(0, 0, altitude); - var startEdge = target - (self.World.Map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; - var finishEdge = target + (self.World.Map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; + var target = map.CenterOfCell(p) + new WVec(0, 0, altitude); + var startEdge = target - (map.DistanceToEdge(target, -delta) + info.Cordon).Length * delta / 1024; + var finishEdge = target + (map.DistanceToEdge(target, delta) + info.Cordon).Length * delta / 1024; var plane = w.CreateActor(info.DeliveryAircraft, new TypeDictionary { @@ -173,10 +174,11 @@ void SpawnCrate(Actor self) { for (var n = 0; n < maxTries; n++) { - var p = self.World.Map.ChooseRandomCell(self.World.SharedRandom); + var map = self.World.Map; + var p = map.ChooseRandomCell(self.World.SharedRandom); // Is this valid terrain? - var terrainType = self.World.Map.GetTerrainInfo(p).Type; + var terrainType = map.GetTerrainInfo(p).Type; if (!(inWater ? info.ValidWater : info.ValidGround).Contains(terrainType)) continue; diff --git a/OpenRA.Mods.Common/Traits/World/CreateMapPlayers.cs b/OpenRA.Mods.Common/Traits/World/CreateMapPlayers.cs index 4c0fc0940ae3..d9fc08e2253e 100644 --- a/OpenRA.Mods.Common/Traits/World/CreateMapPlayers.cs +++ b/OpenRA.Mods.Common/Traits/World/CreateMapPlayers.cs @@ -85,7 +85,7 @@ public class CreateMapPlayers : ICreatePlayers { void ICreatePlayers.CreatePlayers(World w, MersenneTwister playerRandom) { - var players = new MapPlayers(w.Map.PlayerDefinitions).Players; + var players = new MapPlayers(((Map)w.Map).PlayerDefinitions).Players; var worldPlayers = new List(); var worldOwnerFound = false; @@ -103,7 +103,7 @@ void ICreatePlayers.CreatePlayers(World w, MersenneTwister playerRandom) } if (!worldOwnerFound) - throw new InvalidOperationException($"Map {w.Map.Title} does not define a player actor owning the world."); + throw new InvalidOperationException($"Map {((Map)w.Map).Title} does not define a player actor owning the world."); Player localPlayer = null; diff --git a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs index af3e7c9a8466..87762335ddf0 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorActorLayer.cs @@ -58,7 +58,7 @@ void ICreatePlayers.CreatePlayers(World w, MersenneTwister playerRandom) if (w.Type != WorldType.Editor) return; - Players = new MapPlayers(w.Map.PlayerDefinitions); + Players = new MapPlayers(((Map)w.Map).PlayerDefinitions); worldOwner = Players.Players.Select(kvp => kvp.Value).First(p => !p.Playable && p.OwnsWorld); w.SetWorldOwner(new Player(w, null, worldOwner, playerRandom)); @@ -79,7 +79,7 @@ public void WorldLoaded(World world, WorldRenderer wr) var height = world.Map.MapSize.Y * ts.Height; screenMap = new SpatiallyPartitioned(width, height, info.BinSize); - foreach (var kv in world.Map.ActorDefinitions) + foreach (var kv in ((Map)world.Map).ActorDefinitions) Add(kv.Key, new ActorReference(kv.Value.Value, kv.Value.ToDictionary()), true); // Update neighbours in one pass diff --git a/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs index 0233a3819e74..1253e50b8f1a 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs @@ -107,7 +107,7 @@ void ITickRender.TickRender(WorldRenderer wr, Actor self) if (Actor.RemoveInits() > 0) updated = true; - var subcell = world.Map.Tiles.Contains(cell) ? editorLayer.FreeSubCellAt(cell) : SubCell.Invalid; + var subcell = ((IMapTiles)world.Map).Tiles.Contains(cell) ? editorLayer.FreeSubCellAt(cell) : SubCell.Invalid; if (subcell != SubCell.Invalid) { Actor.AddInit(new SubCellInit(subcell)); diff --git a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs index d9f16b6ac0cb..78bb6cfeeae4 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs @@ -69,7 +69,7 @@ bool IResourceLayerInfo.TryGetResourceIndex(string resourceType, out byte index) public class EditorResourceLayer : IResourceLayer, IWorldLoaded, INotifyActorDisposing { readonly EditorResourceLayerInfo info; - protected readonly Map Map; + protected readonly IMap Map; protected readonly Dictionary ResourceTypesByIndex; protected readonly CellLayer Tiles; protected Dictionary resourceValues; @@ -106,7 +106,7 @@ public EditorResourceLayer(Actor self, EditorResourceLayerInfo info) kv => kv.Value.ResourceIndex, kv => kv.Key); - Map.Resources.CellEntryChanged += UpdateCell; + ((IMapResource)Map).Resources.CellEntryChanged += UpdateCell; } public void WorldLoaded(World w, WorldRenderer wr) @@ -123,11 +123,13 @@ public void WorldLoaded(World w, WorldRenderer wr) public void UpdateCell(CPos cell) { - var uv = cell.ToMPos(Map); - if (!Map.Resources.Contains(uv)) + var mapResource = (IMapResource)Map; + + var uv = cell.ToMPos(mapResource); + if (!mapResource.Resources.Contains(uv)) return; - var tile = Map.Resources[uv]; + var tile = mapResource.Resources[uv]; var t = Tiles[uv]; var newTile = ResourceLayerContents.Empty; @@ -144,7 +146,7 @@ public void UpdateCell(CPos cell) UpdateNetWorth(t.Type, t.Density, newTile.Type, newTile.Density); Tiles[uv] = newTile; - Map.CustomTerrain[uv] = newTerrain; + mapResource.CustomTerrain[uv] = newTerrain; CellChanged?.Invoke(cell, newTile.Type); if (!info.RecalculateResourceDensity) @@ -184,7 +186,7 @@ void UpdateNetWorth(string oldResourceType, int oldDensity, string newResourceTy protected virtual int CalculateCellDensity(ResourceLayerContents contents, CPos c) { - var resources = Map.Resources; + var resources = ((IMapResource)Map).Resources; if (contents.Type == null || !info.ResourceTypes.TryGetValue(contents.Type, out var resourceInfo) || resources[c].Type != resourceInfo.ResourceIndex) return 0; @@ -208,7 +210,10 @@ protected virtual int CalculateCellDensity(ResourceLayerContents contents, CPos protected virtual bool AllowResourceAt(string resourceType, CPos cell) { - if (!Map.Ramp.Contains(cell) || Map.Ramp[cell] != 0) + var mapRamp = ((IMapElevation)Map).Ramp; + var mapTiles = ((IMapTiles)Map).Tiles; + + if (!mapRamp.Contains(cell) || mapRamp[cell] != 0) return false; if (!info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) @@ -216,7 +221,7 @@ protected virtual bool AllowResourceAt(string resourceType, CPos cell) // Ignore custom terrain types when spawning resources in the editor var terrainInfo = Map.Rules.TerrainInfo; - var terrainType = terrainInfo.TerrainTypes[terrainInfo.GetTerrainInfo(Map.Tiles[cell]).TerrainType].Type; + var terrainType = terrainInfo.TerrainTypes[terrainInfo.GetTerrainInfo(mapTiles[cell]).TerrainType].Type; // TODO: Check against actors in the EditorActorLayer return resourceInfo.AllowedTerrainTypes.Contains(terrainType); @@ -224,7 +229,7 @@ protected virtual bool AllowResourceAt(string resourceType, CPos cell) bool CanAddResource(string resourceType, CPos cell, int amount = 1) { - var resources = Map.Resources; + var resources = ((IMapResource)Map).Resources; if (!resources.Contains(cell)) return false; @@ -242,7 +247,8 @@ bool CanAddResource(string resourceType, CPos cell, int amount = 1) protected virtual int AddResource(string resourceType, CPos cell, int amount = 1) { - var resources = Map.Resources; + var mapResource = (IMapResource)Map; + var resources = mapResource.Resources; if (!resources.Contains(cell)) return 0; @@ -253,14 +259,14 @@ protected virtual int AddResource(string resourceType, CPos cell, int amount = 1 var content = resources[cell]; var oldDensity = content.Type == resourceInfo.ResourceIndex ? content.Index : 0; var density = (byte)Math.Min(resourceInfo.MaxDensity, oldDensity + amount); - Map.Resources[cell] = new ResourceTile(resourceInfo.ResourceIndex, density); + mapResource.Resources[cell] = new ResourceTile(resourceInfo.ResourceIndex, density); return density - oldDensity; } protected virtual int RemoveResource(string resourceType, CPos cell, int amount = 1) { - var resources = Map.Resources; + var resources = ((IMapResource)Map).Resources; if (!resources.Contains(cell)) return 0; @@ -280,7 +286,7 @@ protected virtual int RemoveResource(string resourceType, CPos cell, int amount protected virtual void ClearResources(CPos cell) { - Map.Resources[cell] = default; + ((IMapResource)Map).Resources[cell] = default; } void INotifyActorDisposing.Disposing(Actor self) @@ -288,7 +294,7 @@ void INotifyActorDisposing.Disposing(Actor self) if (disposed) return; - Map.Resources.CellEntryChanged -= UpdateCell; + ((IMapResource)Map).Resources.CellEntryChanged -= UpdateCell; disposed = true; } diff --git a/OpenRA.Mods.Common/Traits/World/EditorSelectionLayer.cs b/OpenRA.Mods.Common/Traits/World/EditorSelectionLayer.cs index bc2325e0e893..f18805022c14 100644 --- a/OpenRA.Mods.Common/Traits/World/EditorSelectionLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/EditorSelectionLayer.cs @@ -43,7 +43,7 @@ public class EditorSelectionLayerInfo : TraitInfo public class EditorSelectionLayer : IWorldLoaded, IRenderAboveShroud { readonly EditorSelectionLayerInfo info; - readonly Map map; + readonly IMap map; readonly Sprite copyTile, pasteTile; readonly float copyAlpha, pasteAlpha; PaletteReference palette; @@ -96,14 +96,16 @@ IEnumerable IRenderAboveShroud.RenderAboveShroud(Actor self, WorldR if (wr.World.Type != WorldType.Editor) yield break; + var map = wr.World.Map; + if (CopyRegion != null) foreach (var c in CopyRegion) - yield return new SpriteRenderable(copyTile, wr.World.Map.CenterOfCell(c), + yield return new SpriteRenderable(copyTile, map.CenterOfCell(c), WVec.Zero, -511, palette, 1f, copyAlpha * info.FootprintAlpha, float3.Ones, TintModifiers.IgnoreWorldTint, true); if (PasteRegion != null) foreach (var c in PasteRegion) - yield return new SpriteRenderable(pasteTile, wr.World.Map.CenterOfCell(c), + yield return new SpriteRenderable(pasteTile, map.CenterOfCell(c), WVec.Zero, -511, palette, 1f, pasteAlpha * info.FootprintAlpha, float3.Ones, TintModifiers.IgnoreWorldTint, true); } diff --git a/OpenRA.Mods.Common/Traits/World/ElevatedBridgeLayer.cs b/OpenRA.Mods.Common/Traits/World/ElevatedBridgeLayer.cs index 20b00801066d..391b025b9293 100644 --- a/OpenRA.Mods.Common/Traits/World/ElevatedBridgeLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/ElevatedBridgeLayer.cs @@ -28,7 +28,7 @@ public class ElevatedBridgeLayerInfo : TraitInfo, ILobbyCustomRulesIgnore, ICust // For now this is mostly copies TerrainTunnelLayer. This will change once bridge destruction is implemented public class ElevatedBridgeLayer : ICustomMovementLayer, IWorldLoaded { - readonly Map map; + readonly IMap map; readonly CellLayer cellCenters; readonly CellLayer terrainIndices; readonly HashSet ends = new HashSet(); diff --git a/OpenRA.Mods.Common/Traits/World/HierarchicalPathFinderOverlay.cs b/OpenRA.Mods.Common/Traits/World/HierarchicalPathFinderOverlay.cs index 420941738a70..00ea205530c6 100644 --- a/OpenRA.Mods.Common/Traits/World/HierarchicalPathFinderOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/HierarchicalPathFinderOverlay.cs @@ -84,6 +84,7 @@ IEnumerable IRenderAnnotations.RenderAnnotations(Actor self, WorldR if (!Enabled) yield break; + var map = self.World.Map; var pathFinder = self.Trait(); var visibleRegion = wr.Viewport.AllVisibleCells; var locomotors = Locomotor == null @@ -104,17 +105,17 @@ IEnumerable IRenderAnnotations.RenderAnnotations(Actor self, WorldR foreach (var connectionsFromOneNode in abstractGraph) { var nodeCell = connectionsFromOneNode.Key; - var srcUv = (PPos)nodeCell.ToMPos(self.World.Map); + var srcUv = (PPos)nodeCell.ToMPos(map); foreach (var cost in connectionsFromOneNode.Value) { - var destUv = (PPos)cost.Destination.ToMPos(self.World.Map); + var destUv = (PPos)cost.Destination.ToMPos(map); if (!visibleRegion.Contains(destUv) && !visibleRegion.Contains(srcUv)) continue; var connection = new WPos[] { - self.World.Map.CenterOfSubCell(cost.Destination, SubCell.FullCell), - self.World.Map.CenterOfSubCell(nodeCell, SubCell.FullCell), + map.CenterOfSubCell(cost.Destination, SubCell.FullCell), + map.CenterOfSubCell(nodeCell, SubCell.FullCell), }; // Connections on the ground layer given in ground color. @@ -132,7 +133,7 @@ IEnumerable IRenderAnnotations.RenderAnnotations(Actor self, WorldR var centerCell = new CPos( (cost.Destination.X + nodeCell.X) / 2, (cost.Destination.Y + nodeCell.Y) / 2); - var centerPos = self.World.Map.CenterOfSubCell(centerCell, SubCell.FullCell); + var centerPos = map.CenterOfSubCell(centerCell, SubCell.FullCell); yield return new TextAnnotationRenderable(font, centerPos, 0, lineColor, cost.Cost.ToString()); } } @@ -140,7 +141,7 @@ IEnumerable IRenderAnnotations.RenderAnnotations(Actor self, WorldR foreach (var domainForCell in abstractDomains) { var nodeCell = domainForCell.Key; - var srcUv = (PPos)nodeCell.ToMPos(self.World.Map); + var srcUv = (PPos)nodeCell.ToMPos(map); if (!visibleRegion.Contains(srcUv)) continue; diff --git a/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs b/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs index 164aa8cf25b2..2a3baf5ab310 100644 --- a/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/LegacyBridgeLayer.cs @@ -46,7 +46,9 @@ public LegacyBridgeLayer(Actor self, LegacyBridgeLayerInfo info) public void WorldLoaded(World w, WorldRenderer wr) { - bridges = new CellLayer(w.Map); + var map = w.Map; + + bridges = new CellLayer(map); // Build a list of templates that should be overlaid with bridges foreach (var bridge in info.Bridges) @@ -57,7 +59,7 @@ public void WorldLoaded(World w, WorldRenderer wr) } // Take all templates to overlay from the map - foreach (var cell in w.Map.AllCells.Where(cell => bridgeTypes.ContainsKey(w.Map.Tiles[cell].Type))) + foreach (var cell in map.AllCells.Where(cell => bridgeTypes.ContainsKey(((IMapTiles)map).Tiles[cell].Type))) ConvertBridgeToActor(w, cell); // Link adjacent (long)-bridges so that artwork is updated correctly @@ -67,12 +69,14 @@ public void WorldLoaded(World w, WorldRenderer wr) void ConvertBridgeToActor(World w, CPos cell) { + var mapTiles = ((IMapTiles)w.Map).Tiles; + // This cell already has a bridge overlaying it from a previous iteration if (bridges[cell] != null) return; // Correlate the tile "image" aka subtile with its position to find the template origin - var ti = w.Map.Tiles[cell]; + var ti = mapTiles[cell]; var tile = ti.Type; var index = ti.Index; var template = terrainInfo.Templates[tile]; @@ -88,7 +92,6 @@ void ConvertBridgeToActor(World w, CPos cell) }).Trait(); var subTiles = new Dictionary(); - var mapTiles = w.Map.Tiles; // For each subtile in the template for (byte ind = 0; ind < template.Size.X * template.Size.Y; ind++) diff --git a/OpenRA.Mods.Common/Traits/World/Locomotor.cs b/OpenRA.Mods.Common/Traits/World/Locomotor.cs index 3512c6632bd7..35591fc84b1a 100644 --- a/OpenRA.Mods.Common/Traits/World/Locomotor.cs +++ b/OpenRA.Mods.Common/Traits/World/Locomotor.cs @@ -182,13 +182,14 @@ public short MovementCostForCell(CPos cell) short MovementCostForCell(CPos cell, CPos? fromCell) { - if (!world.Map.Contains(cell)) + var map = world.Map; + if (!map.Contains(cell)) return PathGraph.MovementCostForUnreachableCell; // Prevent units from jumping over height discontinuities. - if (fromCell != null && cell.Layer == 0 && fromCell.Value.Layer == 0 && world.Map.Grid.MaximumTerrainHeight > 0) + if (fromCell != null && cell.Layer == 0 && fromCell.Value.Layer == 0 && map.Grid.MaximumTerrainHeight > 0) { - var heightLayer = world.Map.Height; + var heightLayer = ((IMapElevation)map).Height; if (Math.Abs(heightLayer[cell] - heightLayer[fromCell.Value]) > 1) return PathGraph.MovementCostForUnreachableCell; } @@ -372,7 +373,7 @@ public void WorldLoaded(World w, WorldRenderer wr) var map = w.Map; actorMap = w.ActorMap; map.CustomTerrain.CellEntryChanged += UpdateCellCost; - map.Tiles.CellEntryChanged += UpdateCellCost; + ((IMapTiles)map).Tiles.CellEntryChanged += UpdateCellCost; actorMap.CellUpdated += CellUpdated; cellsCost = new[] { new CellLayer(map) }; diff --git a/OpenRA.Mods.Common/Traits/World/MapStartingLocations.cs b/OpenRA.Mods.Common/Traits/World/MapStartingLocations.cs index f4dad3d98b90..89edfad663ee 100644 --- a/OpenRA.Mods.Common/Traits/World/MapStartingLocations.cs +++ b/OpenRA.Mods.Common/Traits/World/MapStartingLocations.cs @@ -127,7 +127,7 @@ void INotifyCreated.Created(Actor self) .OptionOrDefault("separateteamspawns", info.SeparateTeamSpawnsCheckboxEnabled); var spawns = new List(); - foreach (var n in self.World.Map.ActorDefinitions) + foreach (var n in ((Map)self.World.Map).ActorDefinitions) if (n.Value.Value == "mpspawn") spawns.Add(new ActorReference(n.Key, n.Value.ToDictionary()).GetValue()); @@ -175,13 +175,15 @@ void IWorldLoaded.WorldLoaded(World world, WorldRenderer wr) { foreach (var p in world.Players) { + var map = world.Map; + if (!p.Playable) continue; if (p == world.LocalPlayer) - wr.Viewport.Center(world.Map.CenterOfCell(p.HomeLocation)); + wr.Viewport.Center(map.CenterOfCell(p.HomeLocation)); - var cells = Shroud.ProjectedCellsInRange(world.Map, p.HomeLocation, info.InitialExploreRange) + var cells = Shroud.ProjectedCellsInRange(map, p.HomeLocation, info.InitialExploreRange) .ToList(); foreach (var q in world.Players) diff --git a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs index 498eedee5fbb..80ce5e84bb46 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceLayer.cs @@ -106,7 +106,7 @@ public class ResourceLayer : IResourceLayer, IWorldLoaded { readonly ResourceLayerInfo info; readonly World world; - protected readonly Map Map; + protected readonly IMap Map; protected readonly BuildingInfluence BuildingInfluence; protected readonly CellLayer Content; protected readonly Dictionary ResourceTypesByIndex; @@ -129,9 +129,11 @@ public ResourceLayer(Actor self, ResourceLayerInfo info) protected virtual void WorldLoaded(World w, WorldRenderer wr) { - foreach (var cell in w.Map.AllCells) + var map = w.Map; + + foreach (var cell in map.AllCells) { - var resource = world.Map.Resources[cell]; + var resource = ((IMapResource)world.Map).Resources[cell]; if (!ResourceTypesByIndex.TryGetValue(resource.Type, out var resourceType)) continue; @@ -145,7 +147,7 @@ protected virtual void WorldLoaded(World w, WorldRenderer wr) return; // Set initial density based on the number of neighboring resources - foreach (var cell in w.Map.AllCells) + foreach (var cell in map.AllCells) { var resource = Content[cell]; if (resource.Type == null || !info.ResourceTypes.TryGetValue(resource.Type, out var resourceInfo)) @@ -170,7 +172,7 @@ protected virtual void WorldLoaded(World w, WorldRenderer wr) protected virtual bool AllowResourceAt(string resourceType, CPos cell) { - if (!Map.Contains(cell) || Map.Ramp[cell] != 0) + if (!Map.Contains(cell) || ((IMapElevation)Map).Ramp[cell] != 0) return false; if (resourceType == null || !info.ResourceTypes.TryGetValue(resourceType, out var resourceInfo)) diff --git a/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs b/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs index ef3a322e2825..3a0a53934033 100644 --- a/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ResourceRenderer.cs @@ -62,7 +62,7 @@ protected static object LoadResourceTypes(MiniYaml yaml) return ret; } - void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer) + void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(IMap map, ActorInfo ai, ActorReference s, List<(MPos, Color)> destinationBuffer) { var resourceLayer = ai.TraitInfoOrDefault(); if (resourceLayer == null) @@ -84,7 +84,7 @@ void IMapPreviewSignatureInfo.PopulateMapPreviewSignatureCells(Map map, ActorInf for (var j = 0; j < map.MapSize.Y; j++) { var cell = new MPos(i, j); - if (colors.TryGetValue(map.Resources[cell].Type, out var color)) + if (colors.TryGetValue(((IMapResource)map).Resources[cell].Type, out var color)) destinationBuffer.Add((cell, color)); } } diff --git a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs index 132be86c5e5e..e02b205515b4 100644 --- a/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/ShroudRenderer.cs @@ -108,7 +108,7 @@ public TileInfo(in float3 screenPosition, byte variant) readonly ShroudRendererInfo info; readonly World world; - readonly Map map; + readonly IMap map; readonly (Edges, Edges) notVisibleEdgesPair; readonly byte variantStride; readonly byte[] edgesToSpriteIndexOffset; @@ -202,11 +202,13 @@ public ShroudRenderer(World world, ShroudRendererInfo info) void IWorldLoaded.WorldLoaded(World w, WorldRenderer wr) { + var worldMap = w.Map; + // Initialize tile cache // This includes the region outside the visible area to cover any sprites peeking outside the map - foreach (var uv in w.Map.AllCells.MapCoords) + foreach (var uv in worldMap.AllCells.MapCoords) { - var pos = w.Map.CenterOfCell(uv.ToCPos(map)); + var pos = worldMap.CenterOfCell(uv.ToCPos(map)); var screen = wr.Screen3DPosition(pos - new WVec(0, 0, pos.Z)); var variant = (byte)Game.CosmeticRandom.Next(info.ShroudVariants.Length); tileInfos[uv] = new TileInfo(screen, variant); diff --git a/OpenRA.Mods.Common/Traits/World/SpawnMapActors.cs b/OpenRA.Mods.Common/Traits/World/SpawnMapActors.cs index 1ecb4dae0234..662e99e498f9 100644 --- a/OpenRA.Mods.Common/Traits/World/SpawnMapActors.cs +++ b/OpenRA.Mods.Common/Traits/World/SpawnMapActors.cs @@ -31,7 +31,7 @@ public void WorldLoaded(World world, WorldRenderer wr) .Concat(world.WorldActor.Owner.PlayerActor.TraitsImplementing()) .ToArray(); - foreach (var kv in world.Map.ActorDefinitions) + foreach (var kv in ((Map)world.Map).ActorDefinitions) { var actorReference = new ActorReference(kv.Value.Value, kv.Value.ToDictionary()); diff --git a/OpenRA.Mods.Common/Traits/World/SubterraneanActorLayer.cs b/OpenRA.Mods.Common/Traits/World/SubterraneanActorLayer.cs index 9354c178a6c5..62cbac5f9ba2 100644 --- a/OpenRA.Mods.Common/Traits/World/SubterraneanActorLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/SubterraneanActorLayer.cs @@ -31,7 +31,7 @@ public class SubterraneanActorLayerInfo : TraitInfo, ICustomMovementLayerInfo public class SubterraneanActorLayer : ICustomMovementLayer { - readonly Map map; + readonly IMap map; readonly byte terrainIndex; readonly CellLayer height; @@ -54,7 +54,7 @@ public SubterraneanActorLayer(Actor self, SubterraneanActorLayerInfo info) continue; neighbourCount++; - neighbourHeight += map.Height[neighbour]; + neighbourHeight += ((IMapElevation)map).Height[neighbour]; } } @@ -82,7 +82,7 @@ bool ValidTransitionCell(CPos cell, SubterraneanLocomotorInfo sli) if (sli.SubterraneanTransitionOnRamps) return true; - return map.Ramp[cell] == 0; + return ((IMapElevation)map).Ramp[cell] == 0; } short ICustomMovementLayer.EntryMovementCost(LocomotorInfo li, CPos cell) diff --git a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs index 738e423225fe..c712e64d6701 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainGeometryOverlay.cs @@ -55,16 +55,18 @@ IEnumerable IRenderAnnotations.RenderAnnotations(Actor self, WorldR yield break; var map = wr.World.Map; + var mapHeight = ((IMapElevation)map).Height; + var mapRamp = ((IMapElevation)map).Ramp; var colors = wr.World.Map.Rules.TerrainInfo.HeightDebugColors; - var mouseCell = wr.Viewport.ViewToWorld(Viewport.LastMousePos).ToMPos(wr.World.Map); + var mouseCell = wr.Viewport.ViewToWorld(Viewport.LastMousePos).ToMPos(map); foreach (var uv in wr.Viewport.AllVisibleCells.CandidateMapCoords) { - if (!map.Height.Contains(uv) || self.World.ShroudObscures(uv)) + if (!mapHeight.Contains(uv) || self.World.ShroudObscures(uv)) continue; - var height = (int)map.Height[uv]; - var r = map.Grid.Ramps[map.Ramp[uv]]; + var height = (int)mapHeight[uv]; + var r = map.Grid.Ramps[mapRamp[uv]]; var pos = map.CenterOfCell(uv.ToCPos(map)) - new WVec(0, 0, r.CenterHeightOffset); var width = uv == mouseCell ? 3 : 1; diff --git a/OpenRA.Mods.Common/Traits/World/TerrainLighting.cs b/OpenRA.Mods.Common/Traits/World/TerrainLighting.cs index 031975034708..58ccf4403ce9 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainLighting.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainLighting.cs @@ -54,7 +54,7 @@ public LightSource(WPos pos, CPos cell, WDist range, float intensity, in float3 } readonly TerrainLightingInfo info; - readonly Map map; + readonly IMap map; readonly Dictionary lightSources = new Dictionary(); readonly SpatiallyPartitioned partitionedLightSources; readonly float3 globalTint; @@ -111,14 +111,16 @@ public void RemoveLightSource(int token) float3 ITerrainLighting.TintAt(WPos pos) { + var mapHeight = ((IMapElevation)map).Height; + using (new PerfSample("terrain_lighting")) { var uv = map.CellContaining(pos).ToMPos(map); var tint = globalTint; - if (!map.Height.Contains(uv)) + if (!mapHeight.Contains(uv)) return tint; - var intensity = info.Intensity + info.HeightStep * map.Height[uv]; + var intensity = info.Intensity + info.HeightStep * mapHeight[uv]; if (lightSources.Count > 0) { foreach (var source in partitionedLightSources.At(new int2(pos.X, pos.Y))) diff --git a/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs b/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs index 9c4c70bf34a4..d91861c0d9a7 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainRenderer.cs @@ -61,7 +61,7 @@ void OnMissingImage(uint id, string f) public sealed class TerrainRenderer : IRenderTerrain, IWorldLoaded, INotifyActorDisposing, ITiledTerrainRenderer { - readonly Map map; + readonly IMap map; TerrainSpriteLayer spriteLayer; readonly DefaultTerrain terrainInfo; readonly DefaultTileCache tileCache; @@ -85,13 +85,13 @@ void IWorldLoaded.WorldLoaded(World world, WorldRenderer wr) foreach (var cell in map.AllCells) UpdateCell(cell); - map.Tiles.CellEntryChanged += UpdateCell; - map.Height.CellEntryChanged += UpdateCell; + ((IMapTiles)map).Tiles.CellEntryChanged += UpdateCell; + ((IMapElevation)map).Height.CellEntryChanged += UpdateCell; } public void UpdateCell(CPos cell) { - var tile = map.Tiles[cell]; + var tile = ((IMapTiles)map).Tiles[cell]; var palette = terrainInfo.Palette; if (terrainInfo.Templates.TryGetValue(tile.Type, out var template)) palette = ((DefaultTerrainTemplateInfo)template).Palette ?? palette; @@ -114,8 +114,8 @@ void INotifyActorDisposing.Disposing(Actor self) if (disposed) return; - map.Tiles.CellEntryChanged -= UpdateCell; - map.Height.CellEntryChanged -= UpdateCell; + ((IMapTiles)map).Tiles.CellEntryChanged -= UpdateCell; + ((IMapElevation)map).Height.CellEntryChanged -= UpdateCell; spriteLayer.Dispose(); diff --git a/OpenRA.Mods.Common/Traits/World/TerrainTunnelLayer.cs b/OpenRA.Mods.Common/Traits/World/TerrainTunnelLayer.cs index ce625ddb3c09..b497f126759f 100644 --- a/OpenRA.Mods.Common/Traits/World/TerrainTunnelLayer.cs +++ b/OpenRA.Mods.Common/Traits/World/TerrainTunnelLayer.cs @@ -27,7 +27,7 @@ public class TerrainTunnelLayerInfo : TraitInfo, ILobbyCustomRulesIgnore, ICusto public class TerrainTunnelLayer : ICustomMovementLayer, IWorldLoaded { - readonly Map map; + readonly IMap map; readonly CellLayer cellCenters; readonly CellLayer terrainIndices; readonly HashSet portals = new HashSet(); diff --git a/OpenRA.Mods.Common/Util.cs b/OpenRA.Mods.Common/Util.cs index fca31ffcb39a..ceff6ef87684 100644 --- a/OpenRA.Mods.Common/Util.cs +++ b/OpenRA.Mods.Common/Util.cs @@ -118,10 +118,12 @@ public static bool FacingWithinTolerance(WAngle facing, WAngle desiredFacing, WA public static WPos BetweenCells(World w, CPos from, CPos to) { - var fromPos = from.Layer == 0 ? w.Map.CenterOfCell(from) : + var map = w.Map; + + var fromPos = from.Layer == 0 ? map.CenterOfCell(from) : w.GetCustomMovementLayers()[from.Layer].CenterOfCell(from); - var toPos = to.Layer == 0 ? w.Map.CenterOfCell(to) : + var toPos = to.Layer == 0 ? map.CenterOfCell(to) : w.GetCustomMovementLayers()[to.Layer].CenterOfCell(to); return WPos.Lerp(fromPos, toPos, 1, 2); diff --git a/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs b/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs index 73b494ae3735..a96be602fa95 100644 --- a/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs +++ b/OpenRA.Mods.Common/UtilityCommands/CheckYaml.cs @@ -97,7 +97,7 @@ void IUtilityCommand.Run(Utility utility, string[] args) if (package == null) continue; - var testMap = modData.MapLoader.Load(modData, package); + using (var testMap = (IMap)modData.MapLoader.Load(modData, package)) TestMap(testMap, modData); } @@ -114,8 +114,10 @@ void IUtilityCommand.Run(Utility utility, string[] args) } } - void TestMap(Map map, ModData modData) + void TestMap(IMap imap, ModData modData) { + var map = (Map)imap; + Console.WriteLine($"Testing map: {map.Title}"); // Lint tests can't be trusted if the map rules are bogus @@ -129,9 +131,9 @@ void TestMap(Map map, ModData modData) // Run all rule checks on the map if it defines custom rules. if (map.RuleDefinitions != null || map.VoiceDefinitions != null || map.WeaponDefinitions != null) { - CheckRules(modData, map.Rules); + CheckRules(modData, ((IMap)map).Rules); if (map.SequenceDefinitions != null) - CheckSequences(modData, modData.DefaultRules, map.Sequences); + CheckSequences(modData, modData.DefaultRules, ((IMap)map).Sequences); } // Run all map-level checks here. @@ -140,7 +142,7 @@ void TestMap(Map map, ModData modData) try { var customMapPass = (ILintMapPass)modData.ObjectCreator.CreateBasic(customMapPassType); - customMapPass.Run(EmitError, EmitWarning, modData, map); + customMapPass.Run(EmitError, EmitWarning, modData, imap); } catch (Exception e) { diff --git a/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs b/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs index 47a9e7c3ba32..bce5d66d770d 100644 --- a/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/DumpSequenceSheetsCommand.cs @@ -43,7 +43,7 @@ void IUtilityCommand.Run(Utility utility, string[] args) if (mapPackage == null) throw new InvalidOperationException($"{args[2]} is not a valid tileset or map path"); - sequences = new Map(modData, mapPackage).Sequences; + sequences = ((IMap)modData.MapLoader.Load(modData, mapPackage)).Sequences; } sequences.LoadSprites(); diff --git a/OpenRA.Mods.Common/UtilityCommands/ExtractMapRules.cs b/OpenRA.Mods.Common/UtilityCommands/ExtractMapRules.cs index 24bebf887e25..93d506f5b5b8 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ExtractMapRules.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ExtractMapRules.cs @@ -20,7 +20,7 @@ public class ExtractMapRules : IUtilityCommand string IUtilityCommand.Name => "--map-rules"; bool IUtilityCommand.ValidateArguments(string[] args) { return args.Length == 2; } - void MergeAndPrint(Map map, string key, MiniYaml value) + void MergeAndPrint(IMap map, string key, MiniYaml value) { var nodes = new List(); var includes = new List(); @@ -32,9 +32,9 @@ void MergeAndPrint(Map map, string key, MiniYaml value) var files = FieldLoader.GetValue("value", value.Value); foreach (var f in files) { - include |= map.Package.Contains(f); + include |= ((Map)map).Package.Contains(f); if (include) - nodes.AddRange(MiniYaml.FromStream(map.Open(f), f, false)); + nodes.AddRange(MiniYaml.FromStream(((Map)map).Open(f), f, false)); else includes.Add(f); } @@ -52,14 +52,15 @@ void IUtilityCommand.Run(Utility utility, string[] args) { var modData = Game.ModData = utility.ModData; - var map = modData.MapLoader.Load(modData, new Folder(".").OpenPackage(args[1], modData.ModFiles)); - MergeAndPrint(map, "Rules", map.RuleDefinitions); - MergeAndPrint(map, "Sequences", map.SequenceDefinitions); - MergeAndPrint(map, "ModelSequences", map.ModelSequenceDefinitions); - MergeAndPrint(map, "Weapons", map.WeaponDefinitions); - MergeAndPrint(map, "Voices", map.VoiceDefinitions); - MergeAndPrint(map, "Music", map.MusicDefinitions); - MergeAndPrint(map, "Notifications", map.NotificationDefinitions); + var map = modData.MapLoader.Load(modData, new Folder(Platform.EngineDir).OpenPackage(args[1], modData.ModFiles)); + var imap = (IMap)map; + MergeAndPrint(imap, "Rules", map.RuleDefinitions); + MergeAndPrint(imap, "Sequences", map.SequenceDefinitions); + MergeAndPrint(imap, "ModelSequences", map.ModelSequenceDefinitions); + MergeAndPrint(imap, "Weapons", map.WeaponDefinitions); + MergeAndPrint(imap, "Voices", map.VoiceDefinitions); + MergeAndPrint(imap, "Music", map.MusicDefinitions); + MergeAndPrint(imap, "Notifications", map.NotificationDefinitions); } } } diff --git a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs index 8ef7af29e82e..9527a74ee508 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ImportLegacyMapCommand.cs @@ -77,7 +77,7 @@ protected void Run(Utility utility, string[] args) RequiresMod = ModData.Manifest.Id }; - SetBounds(Map, mapSection); + SetBounds((IMap)Map, mapSection); ReadPacks(file, filename); ReadTrees(file); @@ -93,15 +93,15 @@ protected void Run(Utility utility, string[] args) LoadWaypoints(waypoints); // Create default player definitions only if there are no players to import - MapPlayers = new MapPlayers(Map.Rules, Players.Count == 0 ? spawnCount : 0); + MapPlayers = new MapPlayers(((IMap)Map).Rules, Players.Count == 0 ? spawnCount : 0); foreach (var p in Players) LoadPlayer(file, p); Map.PlayerDefinitions = MapPlayers.ToMiniYaml(); } - if (Map.Rules.TerrainInfo is ITerrainInfoNotifyMapCreated notifyMapCreated) - notifyMapCreated.MapCreated(Map); + if (((IMap)Map).Rules.TerrainInfo is ITerrainInfoNotifyMapCreated notifyMapCreated) + notifyMapCreated.MapCreated((IMap)Map); var dest = Path.GetFileNameWithoutExtension(args[1]) + ".oramap"; @@ -159,7 +159,7 @@ void LoadBriefing(IniFile file) missionData.Value.Nodes.Add(new MiniYamlNode("Briefing", briefing.Replace("\n", " ").ToString())); } - static void SetBounds(Map map, IniSection mapSection) + static void SetBounds(IMap map, IniSection mapSection) { var offsetX = Exts.ParseIntegerInvariant(mapSection.GetValue("X", "0")); var offsetY = Exts.ParseIntegerInvariant(mapSection.GetValue("Y", "0")); @@ -223,9 +223,9 @@ void LoadVideos(IniFile file, string section) public virtual void ReadActors(IniFile file) { - LoadActors(file, "STRUCTURES", Players, Map); - LoadActors(file, "UNITS", Players, Map); - LoadActors(file, "INFANTRY", Players, Map); + LoadActors(file, "STRUCTURES", Players, (IMap)Map); + LoadActors(file, "UNITS", Players, (IMap)Map); + LoadActors(file, "INFANTRY", Players, (IMap)Map); } public abstract void LoadPlayer(IniFile file, string section); @@ -379,7 +379,7 @@ public virtual CPos ParseActorLocation(string input, int loc) return new CPos(loc % MapSize, loc / MapSize); } - public void LoadActors(IniFile file, string section, List players, Map map) + public void LoadActors(IniFile file, string section, List players, IMap map) { foreach (var s in file.GetSection(section, true)) { @@ -415,12 +415,12 @@ public void LoadActors(IniFile file, string section, List players, Map m if (section == "INFANTRY") actor.Add(new SubCellInit((SubCell)Exts.ParseByte(parts[4]))); - var actorCount = map.ActorDefinitions.Count; + var actorCount = ((Map)map).ActorDefinitions.Count; if (!map.Rules.Actors.ContainsKey(parts[1].ToLowerInvariant())) Console.WriteLine($"Ignoring unknown actor type: `{parts[1].ToLowerInvariant()}`"); else - map.ActorDefinitions.Add(new MiniYamlNode("Actor" + actorCount++, actor.Save())); + ((Map)map).ActorDefinitions.Add(new MiniYamlNode("Actor" + actorCount++, actor.Save())); } catch (Exception) { diff --git a/OpenRA.Mods.Common/UtilityCommands/LintInterfaces.cs b/OpenRA.Mods.Common/UtilityCommands/LintInterfaces.cs index b0a0f4290e3d..e823bf09d91a 100644 --- a/OpenRA.Mods.Common/UtilityCommands/LintInterfaces.cs +++ b/OpenRA.Mods.Common/UtilityCommands/LintInterfaces.cs @@ -15,7 +15,7 @@ namespace OpenRA.Mods.Common.Lint { public interface ILintPass { void Run(Action emitError, Action emitWarning, ModData modData); } - public interface ILintMapPass { void Run(Action emitError, Action emitWarning, ModData modData, Map map); } + public interface ILintMapPass { void Run(Action emitError, Action emitWarning, ModData modData, IMap map); } public interface ILintRulesPass { void Run(Action emitError, Action emitWarning, ModData modData, Ruleset rules); } public interface ILintSequencesPass { void Run(Action emitError, Action emitWarning, ModData modData, Ruleset rules, SequenceSet sequences); } } diff --git a/OpenRA.Mods.Common/UtilityCommands/ResizeMapCommand.cs b/OpenRA.Mods.Common/UtilityCommands/ResizeMapCommand.cs index 102f39ce46f9..cb256e6e1a59 100644 --- a/OpenRA.Mods.Common/UtilityCommands/ResizeMapCommand.cs +++ b/OpenRA.Mods.Common/UtilityCommands/ResizeMapCommand.cs @@ -49,8 +49,9 @@ void IUtilityCommand.Run(Utility utility, string[] args) { var modData = Game.ModData = utility.ModData; map = modData.MapLoader.Load(modData, new Folder(Platform.EngineDir).OpenPackage(args[1], modData.ModFiles)); - Console.WriteLine("Resizing map {0} from {1} to {2},{3}", map.Title, map.MapSize, width, height); - map.Resize(width, height); + var imap = (IMap)map; + Console.WriteLine("Resizing map {0} from {1} to {2},{3}", map.Title, imap.MapSize, width, height); + imap.Resize(width, height); var forRemoval = new List(); @@ -61,7 +62,7 @@ void IUtilityCommand.Run(Utility utility, string[] args) if (locationInit == null) continue; - if (!map.Contains(locationInit.Value)) + if (!((IMap)map).Contains(locationInit.Value)) { Console.WriteLine($"Removing actor {actor.Type} located at {locationInit.Value} due being outside of the new map boundaries."); forRemoval.Add(kv); diff --git a/OpenRA.Mods.Common/UtilityCommands/Utilities.cs b/OpenRA.Mods.Common/UtilityCommands/Utilities.cs index 33badcbcde0f..c56a0ed0e22d 100644 --- a/OpenRA.Mods.Common/UtilityCommands/Utilities.cs +++ b/OpenRA.Mods.Common/UtilityCommands/Utilities.cs @@ -32,6 +32,7 @@ public static class Utilities { try { + // TODO: Make this use IMap instead of Map map = modData.MapLoader.Load(modData, new Folder(Platform.EngineDir).OpenPackage(mapPath, modData.ModFiles)); } catch (InvalidDataException ex) diff --git a/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs b/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs index 7db6a5dffdc9..70c79990598d 100644 --- a/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/CreateEffectWarhead.cs @@ -138,12 +138,13 @@ public override void DoImpact(in Target target, WarheadArgs args) /// Checks if the warhead is valid against the terrain at impact position. bool IsValidAgainstTerrain(World world, WPos pos) { - var cell = world.Map.CellContaining(pos); - if (!world.Map.Contains(cell)) + var map = world.Map; + var cell = map.CellContaining(pos); + if (!map.Contains(cell)) return false; - var dat = world.Map.DistanceAboveTerrain(pos); - return IsValidTarget(dat > AirThreshold ? TargetTypeAir : world.Map.GetTerrainInfo(cell).TargetTypes); + var dat = map.DistanceAboveTerrain(pos); + return IsValidTarget(dat > AirThreshold ? TargetTypeAir : map.GetTerrainInfo(cell).TargetTypes); } } } diff --git a/OpenRA.Mods.Common/Warheads/CreateResourceWarhead.cs b/OpenRA.Mods.Common/Warheads/CreateResourceWarhead.cs index d404a25a9429..2ad67671c939 100644 --- a/OpenRA.Mods.Common/Warheads/CreateResourceWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/CreateResourceWarhead.cs @@ -34,14 +34,15 @@ public override void DoImpact(in Target target, WarheadArgs args) var firedBy = args.SourceActor; var pos = target.CenterPosition; var world = firedBy.World; - var dat = world.Map.DistanceAboveTerrain(pos); + var map = world.Map; + var dat = map.DistanceAboveTerrain(pos); if (dat > AirThreshold) return; - var targetTile = world.Map.CellContaining(pos); + var targetTile = map.CellContaining(pos); var minRange = (Size.Length > 1 && Size[1] > 0) ? Size[1] : 0; - var allCells = world.Map.FindTilesInAnnulus(targetTile, minRange, Size[0]); + var allCells = map.FindTilesInAnnulus(targetTile, minRange, Size[0]); var resourceLayer = world.WorldActor.Trait(); var maxDensity = resourceLayer.GetMaxDensity(AddsResourceType); diff --git a/OpenRA.Mods.Common/Warheads/DestroyResourceWarhead.cs b/OpenRA.Mods.Common/Warheads/DestroyResourceWarhead.cs index 4b382ea7d47e..f799ace23bcd 100644 --- a/OpenRA.Mods.Common/Warheads/DestroyResourceWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/DestroyResourceWarhead.cs @@ -36,15 +36,16 @@ public override void DoImpact(in Target target, WarheadArgs args) var firedBy = args.SourceActor; var pos = target.CenterPosition; var world = firedBy.World; - var dat = world.Map.DistanceAboveTerrain(pos); + var map = world.Map; + var dat = map.DistanceAboveTerrain(pos); if (dat > AirThreshold) return; - var targetTile = world.Map.CellContaining(pos); + var targetTile = map.CellContaining(pos); var resourceLayer = world.WorldActor.Trait(); var minRange = (Size.Length > 1 && Size[1] > 0) ? Size[1] : 0; - var allCells = world.Map.FindTilesInAnnulus(targetTile, minRange, Size[0]); + var allCells = map.FindTilesInAnnulus(targetTile, minRange, Size[0]); var removeAllTypes = ResourceTypes.Count == 0; diff --git a/OpenRA.Mods.Common/Warheads/FireClusterWarhead.cs b/OpenRA.Mods.Common/Warheads/FireClusterWarhead.cs index ad4b59178175..86abcb28f251 100644 --- a/OpenRA.Mods.Common/Warheads/FireClusterWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/FireClusterWarhead.cs @@ -68,7 +68,7 @@ public override void DoImpact(in Target target, WarheadArgs args) } } - void FireProjectileAtCell(Map map, Actor firedBy, Target target, CPos targetCell, WarheadArgs args) + void FireProjectileAtCell(IMap map, Actor firedBy, Target target, CPos targetCell, WarheadArgs args) { var tc = Target.FromCell(firedBy.World, targetCell); diff --git a/OpenRA.Mods.Common/Warheads/LeaveSmudgeWarhead.cs b/OpenRA.Mods.Common/Warheads/LeaveSmudgeWarhead.cs index 9e66e98aa435..84821078d2e1 100644 --- a/OpenRA.Mods.Common/Warheads/LeaveSmudgeWarhead.cs +++ b/OpenRA.Mods.Common/Warheads/LeaveSmudgeWarhead.cs @@ -37,26 +37,27 @@ public override void DoImpact(in Target target, WarheadArgs args) var firedBy = args.SourceActor; var world = firedBy.World; + var map = world.Map; if (Chance < world.LocalRandom.Next(100)) return; var pos = target.CenterPosition; - var dat = world.Map.DistanceAboveTerrain(pos); + var dat = map.DistanceAboveTerrain(pos); if (dat > AirThreshold) return; - var targetTile = world.Map.CellContaining(pos); + var targetTile = map.CellContaining(pos); var smudgeLayers = world.WorldActor.TraitsImplementing().ToDictionary(x => x.Info.Type); var minRange = (Size.Length > 1 && Size[1] > 0) ? Size[1] : 0; - var allCells = world.Map.FindTilesInAnnulus(targetTile, minRange, Size[0]); + var allCells = map.FindTilesInAnnulus(targetTile, minRange, Size[0]); // Draw the smudges: foreach (var sc in allCells) { - var smudgeType = world.Map.GetTerrainInfo(sc).AcceptsSmudgeType.FirstOrDefault(SmudgeType.Contains); + var smudgeType = map.GetTerrainInfo(sc).AcceptsSmudgeType.FirstOrDefault(SmudgeType.Contains); if (smudgeType == null) continue; diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs index 3d04c3a6f5b2..371da0cf64c5 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/LayerSelectorLogic.cs @@ -41,6 +41,8 @@ public LayerSelectorLogic(Widget widget, WorldRenderer worldRenderer) void IntializeLayerPreview() { layerTemplateList.RemoveChildren(); + var rules = worldRenderer.World.Map.Rules; + var tileSize = worldRenderer.World.Map.Grid.TileSize; foreach (var resourceRenderer in worldRenderer.World.WorldActor.TraitsImplementing()) { foreach (var resourceType in resourceRenderer.ResourceTypes) diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs index 3d0f4ce57218..c0f36c1b369d 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/MapEditorLogic.cs @@ -47,8 +47,9 @@ public MapEditorLogic(Widget widget, World world, WorldRenderer worldRenderer) coordinateLabel.GetText = () => { var cell = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos); - var map = worldRenderer.World.Map; - return map.Height.Contains(cell) ? $"{cell},{map.Height[cell]} ({map.Tiles[cell].Type})" : ""; + var mapHeight = ((IMapElevation)worldRenderer.World.Map).Height; + var mapTiles = ((IMapTiles)worldRenderer.World.Map).Tiles; + return mapHeight.Contains(cell) ? $"{cell},{mapHeight[cell]} ({mapTiles[cell].Type})" : ""; }; } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs index c03717c77027..c485beb7e198 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs @@ -57,20 +57,21 @@ ScrollItemWidget SetupItem(string option, ScrollItemWidget template) var maxTerrainHeight = world.Map.Grid.MaximumTerrainHeight; var tileset = modData.DefaultTerrainInfo[tilesetDropDown.Text]; - var map = Game.ModData.MapLoader.Create(Game.ModData, tileset, width + 2, height + maxTerrainHeight + 2); + var map = modData.MapLoader.Create(modData, tileset, width + 2, height + maxTerrainHeight + 2); + var imap = (IMap)map; var tl = new PPos(1, 1 + maxTerrainHeight); var br = new PPos(width, height + maxTerrainHeight); - map.SetBounds(tl, br); + imap.SetBounds(tl, br); - map.PlayerDefinitions = new MapPlayers(map.Rules, 0).ToMiniYaml(); + map.PlayerDefinitions = new MapPlayers(imap.Rules, 0).ToMiniYaml(); - if (map.Rules.TerrainInfo is ITerrainInfoNotifyMapCreated notifyMapCreated) - notifyMapCreated.MapCreated(map); + if (imap.Rules.TerrainInfo is ITerrainInfoNotifyMapCreated notifyMapCreated) + notifyMapCreated.MapCreated(imap); Action afterSave = uid => { - map.Dispose(); + ((IMap)map).Dispose(); Game.LoadEditor(uid); Ui.CloseWindow(); diff --git a/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs index 84e2a045b0ab..aeb25340f4b0 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/GameSaveBrowserLogic.cs @@ -101,14 +101,14 @@ public GameSaveBrowserLogic(Widget widget, ModData modData, Action onExit, Actio { panel.Get("SAVE_TITLE").IsVisible = () => true; - defaultSaveFilename = world.Map.Title; + defaultSaveFilename = ((Map)world.Map).Title; var filenameAttempt = 0; while (true) { if (!File.Exists(Path.Combine(baseSavePath, defaultSaveFilename + ".orasav"))) break; - defaultSaveFilename = world.Map.Title + $" ({++filenameAttempt})"; + defaultSaveFilename = ((Map)world.Map).Title + $" ({++filenameAttempt})"; } var saveButton = panel.Get("SAVE_BUTTON"); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/DebugLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/DebugLogic.cs index 05ae19001f34..67d0ad3b611b 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/DebugLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/DebugLogic.cs @@ -29,8 +29,9 @@ public DebugLogic(Widget widget, World world, WorldRenderer worldRenderer) { var cell = worldRenderer.Viewport.ViewToWorld(Viewport.LastMousePos); var map = worldRenderer.World.Map; + var mapHeight = ((IMapElevation)map).Height; var wpos = map.CenterOfCell(cell); - return map.Height.Contains(cell) ? $"({cell},{map.Height[cell]}) ({wpos})" : ""; + return mapHeight.Contains(cell) ? $"({cell},{mapHeight[cell]}) ({wpos})" : ""; }); labelWidget.GetText = () => cellPosText.Update(Viewport.LastMousePos); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoBriefingLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoBriefingLogic.cs index bcd24806494c..67cbb36589b0 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoBriefingLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoBriefingLogic.cs @@ -20,7 +20,7 @@ class GameInfoBriefingLogic : ChromeLogic public GameInfoBriefingLogic(Widget widget, ModData modData, World world) { var previewWidget = widget.Get("MAP_PREVIEW"); - previewWidget.Preview = () => modData.MapCache[world.Map.Uid]; + previewWidget.Preview = () => modData.MapCache[((Map)world.Map).Uid]; var mapDescriptionPanel = widget.Get("MAP_DESCRIPTION_PANEL"); var mapDescription = widget.Get("MAP_DESCRIPTION"); diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs index 95b42e98db47..0b9f01f0739a 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/GameInfoLogic.cs @@ -127,8 +127,8 @@ public GameInfoLogic(Widget widget, ModData modData, World world, IngameInfoPane var titleText = widget.Get("TITLE"); - var mapTitle = world.Map.Title; - var firstCategory = world.Map.Categories.FirstOrDefault(); + var mapTitle = ((Map)world.Map).Title; + var firstCategory = ((Map)world.Map).Categories.FirstOrDefault(); if (firstCategory != null) mapTitle = firstCategory + ": " + mapTitle; @@ -153,7 +153,7 @@ void SetupLobbyOptionsPanel(ButtonWidget mapTabButton, Widget optionsPanelContai { Game.LoadWidget(world, "LOBBY_OPTIONS_PANEL", optionsPanelContainer, new WidgetArgs() { - { "getMap", (Func)(() => modData.MapCache[world.Map.Uid]) }, + { "getMap", (Func)(() => modData.MapCache[((Map)world.Map).Uid]) }, { "configurationDisabled", (Func)(() => true) } }); } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/Hotkeys/EditorQuickSaveHotkeyLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/Hotkeys/EditorQuickSaveHotkeyLogic.cs index 9d5db4c83c8e..e8fe924a9088 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/Hotkeys/EditorQuickSaveHotkeyLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/Hotkeys/EditorQuickSaveHotkeyLogic.cs @@ -44,17 +44,17 @@ void SaveMap(string combinedPath) var actorDefinitions = editorActorLayer.Save(); if (actorDefinitions != null) - map.ActorDefinitions = actorDefinitions; + ((Map)map).ActorDefinitions = actorDefinitions; var playerDefinitions = editorActorLayer.Players.ToMiniYaml(); if (playerDefinitions != null) - map.PlayerDefinitions = playerDefinitions; + ((Map)map).PlayerDefinitions = playerDefinitions; - var package = (IReadWritePackage)map.Package; - SaveMapLogic.SaveMapInner(map, package, world, modData); + var package = (IReadWritePackage)((Map)map).Package; + SaveMapLogic.SaveMapInner((Map)map, package, world, modData); } - SaveMapLogic.SaveMap(modData, world, map, map.Package?.Name, SaveMap); + SaveMapLogic.SaveMap(modData, world, (Map)map, ((Map)map).Package?.Name, SaveMap); return true; } } diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs index 856abfad0ed4..8c7193eeb555 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/IngameMenuLogic.cs @@ -490,7 +490,7 @@ void CreateExitEditorButton() // Show dialog only if updated since last save button.OnClick = () => { - var map = modData.MapCache.GetUpdatedMap(world.Map.Uid); + var map = modData.MapCache.GetUpdatedMap(((Map)world.Map).Uid); var deletedOrUnavailable = map == null || modData.MapCache[map].Status != MapStatus.Available; if (actionManager.HasUnsavedItems() || deletedOrUnavailable) { diff --git a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverShroudSelectorLogic.cs b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverShroudSelectorLogic.cs index 4d7c7e9c06b2..a39e15cbc71d 100644 --- a/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverShroudSelectorLogic.cs +++ b/OpenRA.Mods.Common/Widgets/Logic/Ingame/ObserverShroudSelectorLogic.cs @@ -100,7 +100,7 @@ public ObserverShroudSelectorLogic(Widget widget, ModData modData, World world, if (logicArgs.TryGetValue("WorldViewKey", out yaml)) worldViewKey = modData.Hotkeys[yaml.Value]; - limitViews = world.Map.Visibility.HasFlag(MapVisibility.MissionSelector); + limitViews = ((Map)world.Map).Visibility.HasFlag(MapVisibility.MissionSelector); var groups = new Dictionary>(); diff --git a/OpenRA.Mods.Common/Widgets/RadarWidget.cs b/OpenRA.Mods.Common/Widgets/RadarWidget.cs index 0655a58c4b2a..da7219ad5daa 100644 --- a/OpenRA.Mods.Common/Widgets/RadarWidget.cs +++ b/OpenRA.Mods.Common/Widgets/RadarWidget.cs @@ -154,7 +154,7 @@ void SetPlayer(Player player, bool forceUpdate = false) playerRadarTerrain.CellTerrainColorChanged -= CellTerrainColorChanged; else { - world.Map.Tiles.CellEntryChanged -= CellTerrainColorChanged; + ((IMapTiles)world.Map).Tiles.CellEntryChanged -= CellTerrainColorChanged; foreach (var rtl in radarTerrainLayers) rtl.CellEntryChanged -= CellTerrainColorChanged; } @@ -163,7 +163,7 @@ void SetPlayer(Player player, bool forceUpdate = false) newPlayerRadarTerrain.CellTerrainColorChanged += CellTerrainColorChanged; else { - world.Map.Tiles.CellEntryChanged += CellTerrainColorChanged; + ((IMapTiles)world.Map).Tiles.CellEntryChanged += CellTerrainColorChanged; foreach (var rtl in radarTerrainLayers) rtl.CellEntryChanged += CellTerrainColorChanged; } @@ -361,11 +361,13 @@ public override void Draw() if (shroud != null) WidgetUtils.DrawSprite(shroudSprite, o, s); + var map = world.Map; + // Draw viewport rect if (hasRadar) { - var tl = CellToMinimapPixel(world.Map.CellContaining(worldRenderer.ProjectedPosition(worldRenderer.Viewport.TopLeft))); - var br = CellToMinimapPixel(world.Map.CellContaining(worldRenderer.ProjectedPosition(worldRenderer.Viewport.BottomRight))); + var tl = CellToMinimapPixel(map.CellContaining(worldRenderer.ProjectedPosition(worldRenderer.Viewport.TopLeft))); + var br = CellToMinimapPixel(map.CellContaining(worldRenderer.ProjectedPosition(worldRenderer.Viewport.BottomRight))); Game.Renderer.EnableScissor(mapRect); DrawRadarPings(); @@ -419,7 +421,7 @@ public override void Tick() t.Trait.PopulateRadarSignatureCells(t.Actor, cells); foreach (var cell in cells) { - if (!world.Map.Contains(cell.Cell)) + if (!((IMapTiles)world.Map).Tiles.Contains(cell.Cell)) continue; var uv = cell.Cell.ToMPos(world.Map.Grid.Type); diff --git a/OpenRA.Mods.D2k/Traits/Buildings/D2kActorPreviewPlaceBuildingPreview.cs b/OpenRA.Mods.D2k/Traits/Buildings/D2kActorPreviewPlaceBuildingPreview.cs index ed4a991e559e..abdccabbcc49 100644 --- a/OpenRA.Mods.D2k/Traits/Buildings/D2kActorPreviewPlaceBuildingPreview.cs +++ b/OpenRA.Mods.D2k/Traits/Buildings/D2kActorPreviewPlaceBuildingPreview.cs @@ -96,8 +96,9 @@ public D2kActorPreviewPlaceBuildingPreviewPreview(WorldRenderer wr, ActorInfo ai protected override IEnumerable RenderFootprint(WorldRenderer wr, CPos topLeft, Dictionary footprint, PlaceBuildingCellType filter = PlaceBuildingCellType.Invalid | PlaceBuildingCellType.Valid | PlaceBuildingCellType.LineBuild) { + var map = wr.World.Map; var palette = wr.Palette(info.Palette); - var topLeftPos = wr.World.Map.CenterOfCell(topLeft); + var topLeftPos = map.CenterOfCell(topLeft); var candidateSafeTiles = unpathableCells.Update(topLeft); foreach (var c in footprint) @@ -105,11 +106,11 @@ public D2kActorPreviewPlaceBuildingPreviewPreview(WorldRenderer wr, ActorInfo ai if ((c.Value & filter) == 0) continue; - var isUnsafe = checkUnsafeTiles && wr.World.Map.Contains(c.Key) && candidateSafeTiles.Contains(c.Key) && info.UnsafeTerrainTypes.Contains(wr.World.Map.GetTerrainInfo(c.Key).Type); + var isUnsafe = checkUnsafeTiles && map.Contains(c.Key) && candidateSafeTiles.Contains(c.Key) && info.UnsafeTerrainTypes.Contains(map.GetTerrainInfo(c.Key).Type); var tile = (c.Value & PlaceBuildingCellType.Invalid) != 0 ? blockedTile : isUnsafe ? unsafeTile : validTile; var sequenceAlpha = (c.Value & PlaceBuildingCellType.Invalid) != 0 ? blockedAlpha : isUnsafe ? unsafeAlpha : validAlpha; - var pos = wr.World.Map.CenterOfCell(c.Key); + var pos = map.CenterOfCell(c.Key); var offset = new WVec(0, 0, topLeftPos.Z - pos.Z); var traitAlpha = (c.Value & PlaceBuildingCellType.LineBuild) != 0 ? info.LineBuildFootprintAlpha : info.FootprintAlpha; yield return new SpriteRenderable(tile, pos, offset, -511, palette, 1f, sequenceAlpha * traitAlpha, float3.Ones, TintModifiers.IgnoreWorldTint, true); diff --git a/OpenRA.Mods.D2k/Traits/Buildings/D2kBuilding.cs b/OpenRA.Mods.D2k/Traits/Buildings/D2kBuilding.cs index c33839bfd81b..f1a9f4ff84c3 100644 --- a/OpenRA.Mods.D2k/Traits/Buildings/D2kBuilding.cs +++ b/OpenRA.Mods.D2k/Traits/Buildings/D2kBuilding.cs @@ -79,11 +79,10 @@ void INotifyCreated.Created(Actor self) protected override void AddedToWorld(Actor self) { base.AddedToWorld(self); + var map = self.World.Map; if (layer != null && (info.ConcretePrerequisites.Length == 0 || techTree == null || techTree.HasPrerequisites(info.ConcretePrerequisites))) { - var map = self.World.Map; - if (!(self.World.Map.Rules.TerrainInfo is ITemplatedTerrainInfo terrainInfo)) throw new InvalidDataException("D2kBuilding requires a template-based tileset."); @@ -130,7 +129,7 @@ protected override void AddedToWorld(Actor self) foreach (var kv in self.OccupiesSpace.OccupiedCells()) { totalTiles++; - if (!info.DamageTerrainTypes.Contains(self.World.Map.GetTerrainInfo(kv.Cell).Type)) + if (!info.DamageTerrainTypes.Contains(map.GetTerrainInfo(kv.Cell).Type)) safeTiles++; } diff --git a/OpenRA.Mods.D2k/Traits/Sandworm.cs b/OpenRA.Mods.D2k/Traits/Sandworm.cs index 1a927cce98bb..f8519486814a 100644 --- a/OpenRA.Mods.D2k/Traits/Sandworm.cs +++ b/OpenRA.Mods.D2k/Traits/Sandworm.cs @@ -109,9 +109,10 @@ bool IsValidTarget(Actor a) if (noiseDirection == WVec.Zero) return; - var moveTo = self.World.Map.CellContaining(self.CenterPosition + noiseDirection); + var map = self.World.Map; + var moveTo = map.CellContaining(self.CenterPosition + noiseDirection); - while (!self.World.Map.Contains(moveTo) || !mobile.CanEnterCell(moveTo, null, BlockedByActor.None)) + while (!map.Contains(moveTo) || !mobile.CanEnterCell(moveTo, null, BlockedByActor.None)) { // without this check, this while can be infinity loop if (moveTo == self.Location) @@ -121,7 +122,7 @@ bool IsValidTarget(Actor a) } noiseDirection /= 2; - moveTo = self.World.Map.CellContaining(self.CenterPosition + noiseDirection); + moveTo = map.CellContaining(self.CenterPosition + noiseDirection); } // Don't get stuck when the noise is distributed evenly! This will make the worm wander instead of trying to move to where it already is diff --git a/OpenRA.Mods.D2k/Traits/SpiceBloom.cs b/OpenRA.Mods.D2k/Traits/SpiceBloom.cs index 113049c9c537..2be6828aa1c8 100644 --- a/OpenRA.Mods.D2k/Traits/SpiceBloom.cs +++ b/OpenRA.Mods.D2k/Traits/SpiceBloom.cs @@ -90,10 +90,12 @@ public SpiceBloom(Actor self, SpiceBloomInfo info) void ITick.Tick(Actor self) { - if (!self.World.Map.Contains(self.Location)) + var map = self.World.Map; + + if (!map.Contains(self.Location)) return; - if (info.GrowthTerrainTypes.Count > 0 && !info.GrowthTerrainTypes.Contains(self.World.Map.GetTerrainInfo(self.Location).Type)) + if (info.GrowthTerrainTypes.Count > 0 && !info.GrowthTerrainTypes.Contains(map.GetTerrainInfo(self.Location).Type)) return; ticks++; @@ -116,11 +118,12 @@ void ITick.Tick(Actor self) void SeedResources(Actor self) { + var map = self.World.Map; var pieces = self.World.SharedRandom.Next(info.Pieces[0], info.Pieces[1]) * ticks / growTicks; if (pieces < info.Pieces[0]) pieces = info.Pieces[0]; - var cells = self.World.Map.FindTilesInAnnulus(self.Location, 1, info.Range); + var cells = map.FindTilesInAnnulus(self.Location, 1, info.Range); for (var i = 0; i < pieces; i++) { @@ -150,7 +153,7 @@ void SeedResources(Actor self) Source = self.CenterPosition, CurrentSource = () => self.CenterPosition, SourceActor = self, - PassiveTarget = self.World.Map.CenterOfCell(cell.Value) + PassiveTarget = map.CenterOfCell(cell.Value) }; self.World.AddFrameEndTask(_ => diff --git a/OpenRA.Mods.D2k/Traits/World/BuildableTerrainLayer.cs b/OpenRA.Mods.D2k/Traits/World/BuildableTerrainLayer.cs index 656cc78c1ce0..01416c17f0b3 100644 --- a/OpenRA.Mods.D2k/Traits/World/BuildableTerrainLayer.cs +++ b/OpenRA.Mods.D2k/Traits/World/BuildableTerrainLayer.cs @@ -49,8 +49,9 @@ public BuildableTerrainLayer(Actor self, BuildableTerrainLayerInfo info) { this.info = info; world = self.World; - strength = new CellLayer(world.Map); - radarColor = new CellLayer<(Color, Color)>(world.Map); + var map = world.Map; + strength = new CellLayer(map); + radarColor = new CellLayer<(Color, Color)>(map); terrainRenderer = self.Trait(); } diff --git a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs index 9830600bd1f2..7cb217063b04 100644 --- a/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs +++ b/OpenRA.Mods.D2k/UtilityCommands/D2kMapImporter.cs @@ -335,7 +335,7 @@ void Initialize(string mapFile) var tl = new PPos(MapCordonWidth, MapCordonWidth); var br = new PPos(MapCordonWidth + mapSize.Width - 1, MapCordonWidth + mapSize.Height - 1); - map.SetBounds(tl, br); + ((IMap)map).SetBounds(tl, br); // Get all templates from the tileset YAML file that have at least one frame and an Image property corresponding to the requested tileset // Each frame is a tile from the Dune 2000 tileset files, with the Frame ID being the index of the tile in the original file @@ -345,7 +345,7 @@ void Initialize(string mapFile) return templateInfo.Frames != null && string.Equals(templateInfo.Images[0], tilesetName, StringComparison.InvariantCultureIgnoreCase); }).Select(ts => ts.Value).ToList(); - var players = new MapPlayers(map.Rules, playerCount); + var players = new MapPlayers(((IMap)map).Rules, playerCount); map.PlayerDefinitions = players.ToMiniYaml(); } @@ -356,16 +356,17 @@ void FillMap() var tileInfo = stream.ReadUInt16(); var tileSpecialInfo = stream.ReadUInt16(); var tile = GetTile(tileInfo); + var mapResources = ((IMapResource)map).Resources; var locationOnMap = GetCurrentTilePositionOnMap(); - map.Tiles[locationOnMap] = tile; + ((IMapTiles)map).Tiles[locationOnMap] = tile; // Spice if (tileSpecialInfo == 1) - map.Resources[locationOnMap] = new ResourceTile(1, 1); + mapResources[locationOnMap] = new ResourceTile(1, 1); if (tileSpecialInfo == 2) - map.Resources[locationOnMap] = new ResourceTile(1, 2); + mapResources[locationOnMap] = new ResourceTile(1, 2); // Actors if (ActorDataByActorCode.ContainsKey(tileSpecialInfo)) diff --git a/mods/modcontent/mod.yaml b/mods/modcontent/mod.yaml index 0d90cf9a0ba9..dc92f7a423c6 100644 --- a/mods/modcontent/mod.yaml +++ b/mods/modcontent/mod.yaml @@ -73,3 +73,5 @@ TerrainFormat: DefaultTerrain SpriteSequenceFormat: DefaultSpriteSequence ModelSequenceFormat: PlaceholderModelSequence + +DefaultMapLoader: