Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring Map.cs to Enable Mod-defined Map Formats #20582

Open
wants to merge 2 commits into
base: bleed
Choose a base branch
from

Conversation

AspectInteractive2
Copy link
Contributor

@AspectInteractive2 AspectInteractive2 commented Dec 24, 2022

This is a rebased and working version of Ice Reaper's PR #18378, with the addition of splitting out the functionality of Map.cs into optional plug-and-play interfaces. Together this allows the creation of Map formats, which are custom versions of Map.cs that may or may not implement all the existing Map.cs functions (depending on whether they are needed by the mod), and additionally can decide on how these functions are implemented. The most notable example of this currently is isometric vs rectangular cells, these can be defined as two different map formats implementing the same interface, removing the need for numerous 'if else' clauses within the existing Map.cs to check for which C&C mod is currently being used. Additionally this also means that most of the existing functionality in Map.cs can now belong to OpenRA.Mods.Common, which enables users of the mod SDK to modify this behaviour without having to fork the OpenRA repo.

Below are the changes:

  • A new interface IMap now incorporates all the base functionality of Map.cs
  • All core functions within OpenRA now point to IMap, rather than the Map class directly
  • All height/ramp related functions now point to a cast of Map to IMapElevation rather than to Map directly
  • All Tiles and Resources related functions now point to casts of Map to IMapTiles and IMapResources respectively
  • DefaultMap and DefaultMapLoader have been created as the initial Map format, and map format loader, however these will eventually be replaced in future PRs with more specialised map formats (e.g. Gen1Map, Gen1MapLoader for TD/RA and Gen2Map, Gen2MapLoader for TS). This implementation sits in OpenRA.Mods.Common rather than OpenRA.Game.

Also please see this comment from pchote for the motivation behind most of the above. The majority of these changes have been implemented in this PR.

NOTE: I have kept my commits unsquashed for now, to make it easier to split out the PR in the future if this becomes necessary.

@AspectInteractive2 AspectInteractive2 force-pushed the mod-mapformats-rebased branch 2 times, most recently from d22ecab to 18fdf40 Compare December 25, 2022 07:38
OpenRA.Game/ModData.cs Outdated Show resolved Hide resolved
OpenRA.Mods.Cnc/Traits/ConyardChronoReturn.cs Outdated Show resolved Hide resolved
OpenRA.Mods.Common/MapFormats/DefaultMap.cs Outdated Show resolved Hide resolved
OpenRA.Game/Map/MapPreview.cs Show resolved Hide resolved
OpenRA.Game/Map/Map.cs Show resolved Hide resolved
@@ -273,16 +273,17 @@ public MapPreview(Map map, ModData modData)

innerData = new InnerData
{
MapFormat = map.MapFormat,
// TO DO: Check if this MapFormat retrieval is correct and working
MapFormat = Map.GetMapFormat(Package),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GetMapFormat is only used to get the format before yaml parsing. You are using it after

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed, this now uses a cast of IMap as with the other attributes.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still unresolved. Keep the original implementation

OpenRA.Game/Map/MapGrid.cs Outdated Show resolved Hide resolved
OpenRA.Game/Map/Map.cs Show resolved Hide resolved
OpenRA.Game/Map/MapPreview.cs Outdated Show resolved Hide resolved
mods/modcontent/mod.yaml Outdated Show resolved Hide resolved
OpenRA.Mods.Common/UtilityCommands/Utilities.cs Outdated Show resolved Hide resolved
OpenRA.Mods.Common/Lint/CheckMapMetadata.cs Outdated Show resolved Hide resolved
OpenRA.Game/Map/Map.cs Show resolved Hide resolved
Copy link
Member

@PunkPun PunkPun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Creating a new map crashes

OpenRA.Mods.Common/UtilityCommands/ExtractMapRules.cs Outdated Show resolved Hide resolved
OpenRA.Mods.Common/UtilityCommands/ExtractMapRules.cs Outdated Show resolved Hide resolved
OpenRA.Mods.Common/Traits/World/EditorResourceLayer.cs Outdated Show resolved Hide resolved
OpenRA.Mods.Common/Traits/World/EditorCursorLayer.cs Outdated Show resolved Hide resolved
OpenRA.Mods.Common/Lint/CheckMapMetadata.cs Outdated Show resolved Hide resolved
OpenRA.Mods.Common/EditorBrushes/EditorCopyPasteBrush.cs Outdated Show resolved Hide resolved
OpenRA.Game/World.cs Outdated Show resolved Hide resolved
OpenRA.Game/World.cs Show resolved Hide resolved
OpenRA.Game/World.cs Outdated Show resolved Hide resolved
OpenRA.Game/ModData.cs Outdated Show resolved Hide resolved
@AspectInteractive2
Copy link
Contributor Author

Creating a new map crashes

This was due to a typo in the If statement for Grid.MaximumTerrainHeight > 0 in DefaultMap.cs, as well as MapSize not being initialised. It now works.

Copy link
Contributor

@anvilvapre anvilvapre left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change makes sense.

Don't find it a logical choice to keep map implementing all the logic of all interfaces. Better would be to isolate the logic in other objects i.e. Map.GetTileManager().Tiles. But i understand that makes the rewrite even bigger.

Casts are now done without checks. Because the default mods are known to implement them. But to support mods, better would be to try the cast and skip the functionality if not implemented by the mod.

OpenRA.Game/Graphics/Viewport.cs Outdated Show resolved Hide resolved
OpenRA.Game/Graphics/WorldRenderer.cs Show resolved Hide resolved
byte[] SavePreview();
}

public interface IMapElevation : IMap
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

code above does not check if a map implements this interface. does a direct cast. likely because also maps without elevation implement it(?).

may as well add this to IMap.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if you referenced the right code above as I don't see the cast. However the idea is that functions that are not implemented would not be called in the first place, because the mod would not call them. E.g.: Elevation isn't used in Red Alert, so it would never call functions that have casts to IMapElevation.

@@ -273,16 +273,16 @@ public MapPreview(Map map, ModData modData)

innerData = new InnerData
{
MapFormat = map.MapFormat,
MapFormat = ((IMap)map).MapFormat,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

unnessary cast? i assume map implements IMap.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map does not implement IMap, DefaultMap does. Map now only has the core functionality central to all maps, which is basically loading and checking the map, and getting its map format (see the new Map.cs for the remaining functionality of Map, which is now only 164 lines of code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase fail. The first commit makes a change that is then unmade

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is correct because the IMap interface is first introduced in the second commit. First commit only deals with abstracting the map loader.

OpenRA.Game/Map/MapPreview.cs Show resolved Hide resolved
OpenRA.Game/Traits/World/ScreenMap.cs Outdated Show resolved Hide resolved
@@ -189,7 +189,7 @@ internal World(ModData modData, Map map, OrderManager orderManager, WorldType ty
this.modData = modData;
Type = type;
OrderManager = orderManager;
Map = map;
Map = (IMap)map;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not pass an IMap in constructor.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two reasons. First World.cs is part of OpenRA.Game, so it is not mod code and therefore may need to use Map.cs without the interface. Secondly it does call some functions that rely on Map not IMap, such as CacheModels, which is out of scope to refactor at this stage. However since World.cs will likely always need to be in OpenRA.Game, there will always be core map functions that it can and will need to access that are not part of IMap, so a cast to IMap while retaining the Map parameter makes the most sense here.

@@ -56,7 +56,7 @@ public IEnumerable<IActorPreview> RenderPreviewSprites(ActorPreviewInitializer i

// Some mods may define terrain-specific bibs
var sequence = Sequence;
if (map.Tiles.Contains(cell))
if (((IMapTiles)map).Tiles.Contains(cell))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to me it seems more logical to let a map own a TileManager. i.e. map.GetTileManager(). promoting splitting logic.

i.e. otherwise you could have a map implement endless interfaces.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Map no longer has Tiles, this functionality now belongs to IMapTiles and the current implementation sits in DefaultMap rather than Map. And Tiles is a CellLayer class which seems to work the same as your proposed TileManager class. In any case changing how Tiles are managed is not the goal of this PR. All I am doing here is moving the functionality out of Map.cs and into DefaultMap, behind an interface IMapTiles.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i understand. but then still, it does not make sense to have IMapTiles inherit from IMap. Completely different things. Like letting ITrait inherit from IWidget. just because some class will implement both.

but i understand how you came to this solution.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, but this is how it was done with the Tilesets refactoring as well (see below link), with ITemplatedTerrainInfo and ITerrainInfoNotifyMapCreated both inheriting from ITerrainInfo. The downside is it adds a dependency to IMap where it may not need to be there, but on the plus side it means less casting is necessary, since you can cast to IMapTiles and still use the functionality from IMap, rather than having to cast twice.

https://github.com/OpenRA/OpenRA/pull/18728/files#diff-b08312ffb1592016a1b6797006dca462f3eb0f22a38ca2956e55e1e9c4f49ee1R14-R23

@@ -42,6 +42,8 @@ public TSEditorResourceLayer(Actor self, TSEditorResourceLayerInfo info)

bool IsValidVeinNeighbour(CPos cell, CPos neighbour)
{
var mapRamp = ((IMapElevation)Map).Ramp;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

try the cast, to allows mods to keep using this logic, even if disabled.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure what you mean here. Mods can keep using this logic if they want and it is still part of the default functionality found in DefaultMap.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise here.

Copy link
Member

@PunkPun PunkPun left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed the first commit. Running make throws tons of errors, and the commit should be renamed to something like: "Abstracted map loading to DefaultMapLoader : IMapLoader"

OpenRA.Mods.Common/Widgets/Logic/Editor/NewMapLogic.cs Outdated Show resolved Hide resolved
OpenRA.Mods.Common/UtilityCommands/ExtractMapRules.cs Outdated Show resolved Hide resolved
OpenRA.Mods.Common/MapFormats/DefaultMap.cs Show resolved Hide resolved
@@ -273,16 +273,16 @@ public MapPreview(Map map, ModData modData)

innerData = new InnerData
{
MapFormat = map.MapFormat,
MapFormat = ((IMap)map).MapFormat,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase fail. The first commit makes a change that is then unmade

// 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<TerrainTile>(Grid.Type, size);
Copy link
Member

@PunkPun PunkPun Feb 1, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase fail. First commit comments this out

@PunkPun
Copy link
Member

PunkPun commented Feb 2, 2023

It's weird how you added a massive interface of IMapCell.cs, even adding a new file in third commit and then removed it in 6th commit. There are a bunch of errors in the first commits that then get fixed in following commits, even when they couldn't have not been introduced in the first place

@AspectInteractive2
Copy link
Contributor Author

AspectInteractive2 commented Feb 4, 2023

It's weird how you added a massive interface of IMapCell.cs, even adding a new file in third commit and then removed it in 6th commit. There are a bunch of errors in the first commits that then get fixed in following commits, even when they couldn't have not been introduced in the first place

The purpose of keeping the commit is purely to show the genesis of the project, since it went through a revision of separating out the IMapCell interface, before merging it back into IMap after discussing it and finding that the cell logic is too closely coupled to the engine to be isolated at this stage. The first and last commits are the most meaningful, since the first shows the initial loader and map format design, while the latter shows the new interface that is being implemented to support the new map format. All of the commits in between are purely revisions to show the evolution, in case you are wondering how the various interfaces came to be.

@PunkPun
Copy link
Member

PunkPun commented Feb 4, 2023

How this PR came to be or it's evolution is irrelevant and just noise making it harder to find regressions

@AspectInteractive2 AspectInteractive2 force-pushed the mod-mapformats-rebased branch 2 times, most recently from 68f7759 to 2b39b3d Compare February 4, 2023 07:41
@AspectInteractive2
Copy link
Contributor Author

How this PR came to be or it's evolution is irrelevant and just noise making it harder to find regressions

Sure, okay, I've squashed the commits and renamed the first commit.

@@ -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}).");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

casts could be moved higher up

OpenRA.Mods.Cnc/UtilityCommands/ImportTSMapCommand.cs Outdated Show resolved Hide resolved
var mapResources = worldRenderer.World.Map.Resources;
var map = worldRenderer.World.Map;
var gridType = map.Grid.Type;
var mapResource = (IMapResource)worldRenderer.World.Map;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
var mapResource = (IMapResource)worldRenderer.World.Map;
var mapResource = (IMapResource)map;

OpenRA.Mods.Common/MapFormats/DefaultMap.cs Show resolved Hide resolved
OpenRA.Mods.Common/MapFormats/DefaultMap.cs Show resolved Hide resolved
OpenRA.Mods.Common/Traits/World/ElevatedBridgeLayer.cs Outdated Show resolved Hide resolved
@@ -67,12 +69,15 @@ public void WorldLoaded(World w, WorldRenderer wr)

void ConvertBridgeToActor(World w, CPos cell)
{
var map = w.Map;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this variable isn't needed

OpenRA.Mods.Common/UtilityCommands/ExtractMapRules.cs Outdated Show resolved Hide resolved
mods/modcontent/mod.yaml Show resolved Hide resolved
@AspectInteractive2 AspectInteractive2 force-pushed the mod-mapformats-rebased branch 2 times, most recently from 97f83a1 to 642df58 Compare March 11, 2023 14:11
@AspectInteractive2 AspectInteractive2 force-pushed the mod-mapformats-rebased branch 7 times, most recently from 9c1d13a to 65621cd Compare March 18, 2023 05:18
@AspectInteractive2 AspectInteractive2 force-pushed the mod-mapformats-rebased branch 7 times, most recently from 70bc065 to 65103d9 Compare April 6, 2023 13:03
Map Load(ModData modData, IReadOnlyPackage package);
Map Create(ModData modData, ITerrainInfo terrainInfo, int width, int height);
string ComputeUID(ModData modData, IReadOnlyPackage package);
void UpdatePreview(ModData modData, MapPreview mapPreview, IReadOnlyPackage p, IReadOnlyPackage parent, MapClassification classification, string[] mapCompatibility, MapGridType gridType);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

couldn't we just pass ModData in the IMapLoader constructor and avoid passing it in every function? MapPreview also contains a reference to MapData so it's unnecessary to pass

#pragma warning disable IDE1006 // Naming Styles
protected readonly ModData modData;
protected Translation Translation;
#pragma warning restore IDE1006 // Naming Styles
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just follow the rule. There are only a few uses of these variables anyway

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From memory following this rule actually isn't possible because modData must be distinct from ModData, yet the naming rule does not allow lowercase 'm'. So the only way to follow the rule is to do something like ModDataInstance, which would be nastier than disabling the rule for those two lines imo.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps rename file to MapInterfaces and move IMapLoader here

}

static int GetMapFormat(IReadOnlyPackage p)
public static int GetMapFormat(IReadOnlyPackage p)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's weird to have this defined in Map.cs when it is only used in DefaultMap for uid extraction

@@ -273,16 +273,17 @@ public MapPreview(Map map, ModData modData)

innerData = new InnerData
{
MapFormat = map.MapFormat,
// TO DO: Check if this MapFormat retrieval is correct and working
MapFormat = Map.GetMapFormat(Package),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Still unresolved. Keep the original implementation

@@ -63,8 +63,8 @@ public class RemoteMapData

public class MapPreview : IDisposable, IReadOnlyFileSystem
{
/// <summary>Wrapper that enables map data to be replaced in an atomic fashion.</summary>
class InnerData
/// <summary>Wrapper that enables map data to be replaced in an atomic fashion</summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

?

new MapField("Voices", "VoiceDefinitions", required: false),
new MapField("Music", "MusicDefinitions", required: false),
new MapField("Notifications", "NotificationDefinitions", required: false),
//new MapField("Translations", "TranslationDefinitions", required: false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rebase error


if (yaml.TryGetValue("MapFormat", out var temp))
{
var format = FieldLoader.GetValue<int>("MapFormat", temp.Value);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

newData.MapFormat is never set

SequenceSet Sequences { get; }
CellRegion AllCells { get; }
List<CPos> AllEdgeCells { get; }
WDist CellHeightStep { get; }
Copy link
Member

@penev92 penev92 Apr 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why isn't this on the Elevation interface?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@@ -97,7 +97,7 @@ void IUtilityCommand.Run(Utility utility, string[] args)
if (package == null)
continue;

using (var testMap = new Map(modData, package))
var testMap = modData.MapLoader.Load(modData, package);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Keep the using

else
throw new InvalidDataException($"Unknown binary map format '{Format}'");
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"An opening brace should not be followed by a blank line"

@@ -11,25 +11,28 @@

using System;
using OpenRA.Server;
using OpenRA.Mods.Common.MapFormats;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Using directives should be ordered alphabetically by the namespaces"

{
public const int SupportedMapFormat = 12;

/// <summary>Defines the order of the fields in map.yaml</summary>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Documentation text should end with a period"

catch (Exception e)
{
Log.Write("debug", "Failed to load rules for {0} with error {1}", Title, e);
rules = Ruleset.LoadDefaultsForTileSet(modData, Tileset);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"Unnecessary assignment of a value to 'rules'"

Something went really wrong here. rules is set but not used

@@ -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;
Copy link
Member

@penev92 penev92 Apr 6, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A few lines above don't do Map = self.World.Map;. You have no idea whether that would give you a map implementation you can use. Check the type and only use if it implements IMapResource. Otherwise you are free to throw here so the modder knows not to use this trait with an incompatible map format.
Then remove all the casts like this one.
Repeat that for every affected trait in this commit.

@PunkPun
Copy link
Member

PunkPun commented Apr 6, 2023

The extraction of UpdateFromMap from MapPreview to DefaultMap doesn't make any sense. If you wanted customisability you'd need to extract way way more. Atm it's just a redundant change. Ping @IceReaper

@PunkPun
Copy link
Member

PunkPun commented Apr 6, 2023

I'd say MapPreview is completely out of scope of this PR

Copy link
Member

@pchote pchote left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In general (and i'm pretty sure I have already explained this more than once!) this is very difficult to review and has a high risk of regressions because it is attempting to do things backwards and touching a lot of things twice.

Like I showed with the tileset generalisation, the simpler approach is to start by introducing the interfaces, changing the way that world.Map is accessed without changing anything about how that object is created. This separates out concerns allowing each interface to be done with its own commit.

Only at the end do we try to replace Map with DefaultMap etc - none of the users of world.Map need to change at this point because they are all already using the interfaces.

A very partial specific review below.

@@ -32,7 +32,7 @@ public sealed class TerrainSpriteLayer : IDisposable
readonly bool restrictToBounds;

readonly WorldRenderer worldRenderer;
readonly Map map;
readonly IMap map;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The TerrainSpriteLayer constructor should be checking that world.Map implements IMapTiles and throw an exception if it doesn't (see how the tileset code does this for examples).

@@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

var cellOrigin = map.CenterOfCell(cell);
if (map is IMapElevation mapElevation)
    cellOrigin -= new WVec(0, 0, map.Grid.Ramps[mapElevation.Ramp[cell]].CenterHeightOffset);

@@ -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;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise here, Viewport should be able to handle maps that don't implement IMapElevation (this is one of the main usecases in the upstream mods for these interfaces!)

@@ -126,7 +126,7 @@ public class MapGrid : IGlobalModData

public CellRamp[] Ramps { get; }

internal readonly CVec[][] TilesByDistance;
public readonly CVec[][] TilesByDistance;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is only used within DefaultMap. Move it there instead of making it public.

@@ -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));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should support maps that don't implement IMapElevation.

@@ -56,7 +56,7 @@ public JumpjetActorLayer(Actor self, JumpjetActorLayerInfo info)
continue;

neighbourCount++;
neighbourHeight += map.Height[neighbour];
neighbourHeight += ((IMapElevation)map).Height[neighbour];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Check that map implements the interface first and throw if it doesn't (see tileset code).

@@ -42,6 +42,8 @@ public TSEditorResourceLayer(Actor self, TSEditorResourceLayerInfo info)

bool IsValidVeinNeighbour(CPos cell, CPos neighbour)
{
var mapRamp = ((IMapElevation)Map).Ramp;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise here.

@abcdefg30
Copy link
Member

Sorry, but I have to agree with @pchote here. I don't think this PR can be merged in it's current state, but should be split into more digestible parts.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants