From 7ad3935dc60397e2042bd9b94abb5b4146d52f2f Mon Sep 17 00:00:00 2001
From: Tim Felgentreff <timfelgentreff@gmail.com>
Date: Fri, 3 Jul 2020 14:58:28 +0200
Subject: [PATCH] allow adding purely decorative tiles in the editor without
 requiring mixing, leaving that up to the user

---
 src/editor/edmap.cpp       | 63 +++++++++++++++++++++++---------------
 src/include/tile.h         |  5 ++-
 src/include/tileset.h      |  6 +++-
 src/map/mapfield.cpp       |  6 ++++
 src/map/script_map.cpp     |  5 ++-
 src/map/script_tileset.cpp | 30 ++++++++++++++----
 src/map/tileset.cpp        | 11 +++++--
 7 files changed, 90 insertions(+), 36 deletions(-)

diff --git a/src/editor/edmap.cpp b/src/editor/edmap.cpp
index a2afd8333..210d23fc4 100644
--- a/src/editor/edmap.cpp
+++ b/src/editor/edmap.cpp
@@ -100,6 +100,9 @@ void EditorChangeTile(const Vec2i &pos, int tileIndex, const Vec2i &lock_pos)
 
 	// Change the flags
 	CMapField &mf = *Map.Field(pos);
+	if (mf.isDecorative()) {
+		return;
+	}
 	int tile = tileIndex;
 	if (TileToolRandom) {
 		int n = 0;
@@ -143,6 +146,10 @@ static void EditorChangeSurrounding(const Vec2i &pos, const Vec2i &lock_pos)
 		return;
 	}
 
+	if (mf.isDecorative()) {
+		return;
+	}
+
 	const unsigned int quad = QuadFromTile(pos);
 	const unsigned int TH_QUAD_M = 0xFFFF0000; // Top half quad mask
 	const unsigned int BH_QUAD_M = 0x0000FFFF; // Bottom half quad mask
@@ -158,45 +165,53 @@ static void EditorChangeSurrounding(const Vec2i &pos, const Vec2i &lock_pos)
 	if (pos.y) {
 		const Vec2i offset(0, -1);
 		// Insert into the bottom the new tile.
-		unsigned q2 = QuadFromTile(pos + offset);
-		unsigned u = (q2 & TH_QUAD_M) | ((quad >> 16) & BH_QUAD_M);
-		if (u != q2 && (pos + offset) != lock_pos) {
-			did_change = true;
-			int tile = Map.Tileset->tileFromQuad(u & BH_QUAD_M, u);
-			EditorChangeTile(pos + offset, tile, lock_pos);
+		if (!(Map.Field(pos + offset)->isDecorative())) {
+			unsigned q2 = QuadFromTile(pos + offset);
+			unsigned u = (q2 & TH_QUAD_M) | ((quad >> 16) & BH_QUAD_M);
+			if (u != q2 && (pos + offset) != lock_pos) {
+				did_change = true;
+				int tile = Map.Tileset->tileFromQuad(u & BH_QUAD_M, u);
+				EditorChangeTile(pos + offset, tile, lock_pos);
+			}
 		}
 	}
 	if (pos.y < Map.Info.MapHeight - 1) {
 		const Vec2i offset(0, 1);
 		// Insert into the top the new tile.
-		unsigned q2 = QuadFromTile(pos + offset);
-		unsigned u = (q2 & BH_QUAD_M) | ((quad << 16) & TH_QUAD_M);
-		if (u != q2 && (pos + offset) != lock_pos) {
-			did_change = true;
-			int tile = Map.Tileset->tileFromQuad(u & TH_QUAD_M, u);
-			EditorChangeTile(pos + offset, tile, lock_pos);
+		if (!(Map.Field(pos + offset)->isDecorative())) {
+			unsigned q2 = QuadFromTile(pos + offset);
+			unsigned u = (q2 & BH_QUAD_M) | ((quad << 16) & TH_QUAD_M);
+			if (u != q2 && (pos + offset) != lock_pos) {
+				did_change = true;
+				int tile = Map.Tileset->tileFromQuad(u & TH_QUAD_M, u);
+				EditorChangeTile(pos + offset, tile, lock_pos);
+			}
 		}
 	}
 	if (pos.x) {
 		const Vec2i offset(-1, 0);
 		// Insert into the left the new tile.
-		unsigned q2 = QuadFromTile(pos + offset);
-		unsigned u = (q2 & LH_QUAD_M) | ((quad >> 8) & RH_QUAD_M);
-		if (u != q2 && (pos + offset) != lock_pos) {
-			did_change = true;
-			int tile = Map.Tileset->tileFromQuad(u & RH_QUAD_M, u);
-			EditorChangeTile(pos + offset, tile, lock_pos);
+		if (!(Map.Field(pos + offset)->isDecorative())) {
+			unsigned q2 = QuadFromTile(pos + offset);
+			unsigned u = (q2 & LH_QUAD_M) | ((quad >> 8) & RH_QUAD_M);
+			if (u != q2 && (pos + offset) != lock_pos) {
+				did_change = true;
+				int tile = Map.Tileset->tileFromQuad(u & RH_QUAD_M, u);
+				EditorChangeTile(pos + offset, tile, lock_pos);
+			}
 		}
 	}
 	if (pos.x < Map.Info.MapWidth - 1) {
 		const Vec2i offset(1, 0);
 		// Insert into the right the new tile.
-		unsigned q2 = QuadFromTile(pos + offset);
-		unsigned u = (q2 & RH_QUAD_M) | ((quad << 8) & LH_QUAD_M);
-		if (u != q2 && (pos + offset) != lock_pos) {
-			did_change = true;
-			int tile = Map.Tileset->tileFromQuad(u & LH_QUAD_M, u);
-			EditorChangeTile(pos + offset, tile, lock_pos);
+		if (!(Map.Field(pos + offset)->isDecorative())) {
+			unsigned q2 = QuadFromTile(pos + offset);
+			unsigned u = (q2 & RH_QUAD_M) | ((quad << 8) & LH_QUAD_M);
+			if (u != q2 && (pos + offset) != lock_pos) {
+				did_change = true;
+				int tile = Map.Tileset->tileFromQuad(u & LH_QUAD_M, u);
+				EditorChangeTile(pos + offset, tile, lock_pos);
+			}
 		}
 	}
 
diff --git a/src/include/tile.h b/src/include/tile.h
index 001a6707c..b8226a535 100644
--- a/src/include/tile.h
+++ b/src/include/tile.h
@@ -210,6 +210,9 @@ public:
 	/// Returns true, if coast on the map tile field
 	bool RockOnMap() const;
 
+	/// Returns true if the field should not need mixing with the surroundings
+	bool isDecorative() const;
+
 	bool isAWall() const;
 	bool isHuman() const;
 	bool isAHumanWall() const;
@@ -227,7 +230,7 @@ private:
 #endif
 	unsigned short tile;       /// graphic tile number
 public:
-	unsigned short Flags;      /// field flags
+	unsigned int Flags;        /// field flags
 private:
 	unsigned char cost;        /// unit cost to move in this tile
 public:
diff --git a/src/include/tileset.h b/src/include/tileset.h
index 501df6ba0..39dd93853 100644
--- a/src/include/tileset.h
+++ b/src/include/tileset.h
@@ -37,6 +37,7 @@
 ----------------------------------------------------------------------------*/
 
 #include "vec2i.h"
+#include <string>
 #include <vector>
 
 struct lua_State;
@@ -61,6 +62,8 @@ struct lua_State;
 #define MapFieldSeaUnit  0x4000  /// Water unit on field
 #define MapFieldBuilding 0x8000  /// Building on field
 
+#define MapFieldDecorative 0x10000  /// A field that needs no mixing with the surroundings, for the editor
+
 /**
 **  These are used for lookup tiles types
 **  mainly used for the FOW implementation of the seen woods/rocks
@@ -110,7 +113,7 @@ public:
 
 public:
 	unsigned short tile;  /// graphical pos
-	unsigned short flag;  /// Flag
+	unsigned int flag;    /// Flag
 	CTileInfo tileinfo;   /// Tile descriptions
 };
 
@@ -164,6 +167,7 @@ public:
 
 	void parse(lua_State *l);
 	void buildTable(lua_State *l);
+	int parseTilesetTileFlags(lua_State *l, int *back, int *j);
 
 private:
 	unsigned int getOrAddSolidTileIndexByName(const std::string &name);
diff --git a/src/map/mapfield.cpp b/src/map/mapfield.cpp
index 2a0d2f576..01788bcb1 100644
--- a/src/map/mapfield.cpp
+++ b/src/map/mapfield.cpp
@@ -233,6 +233,12 @@ bool CMapField::RockOnMap() const
 	return CheckMask(MapFieldRocks);
 }
 
+/// Returns true if the field should not need mixing with the surroundings
+bool CMapField::isDecorative() const
+{
+	return CheckMask(MapFieldDecorative);
+}
+
 bool CMapField::isAWall() const
 {
 	return Flags & MapFieldWall;
diff --git a/src/map/script_map.cpp b/src/map/script_map.cpp
index 969d57191..b4f0c2787 100644
--- a/src/map/script_map.cpp
+++ b/src/map/script_map.cpp
@@ -468,8 +468,11 @@ static int CclSetTileFlags(lua_State *l)
 	int j = 0;
 	int flags = 0;
 
-	ParseTilesetTileFlags(l, &flags, &j);
+	unsigned char newBase = Map.Tileset->parseTilesetTileFlags(l, &flags, &j);
 	Map.Tileset->tiles[tilenumber].flag = flags;
+	if (newBase) {
+		Map.Tileset->tiles[tilenumber].tileinfo.BaseTerrain = newBase;
+	}
 	return 0;
 }
 
diff --git a/src/map/script_tileset.cpp b/src/map/script_tileset.cpp
index ddef899e2..a95f14559 100644
--- a/src/map/script_tileset.cpp
+++ b/src/map/script_tileset.cpp
@@ -38,6 +38,7 @@
 #include "tileset.h"
 
 #include "script.h"
+#include <cstring>
 
 /*----------------------------------------------------------------------------
 --  Functions
@@ -61,7 +62,8 @@ static bool ModifyFlag(const char *flagName, unsigned int *flag)
 		{"air-unit", MapFieldAirUnit},
 		{"sea-unit", MapFieldSeaUnit},
 		{"building", MapFieldBuilding},
-		{"human", MapFieldHuman}
+		{"human", MapFieldHuman},
+		{"decorative", MapFieldDecorative}
 	};
 
 	for (unsigned int i = 0; i != sizeof(flags) / sizeof(*flags); ++i) {
@@ -98,8 +100,10 @@ static bool ModifyFlag(const char *flagName, unsigned int *flag)
 **  @param back  pointer for the flags (return).
 **  @param j     pointer for the location in the array. in and out
 **
+**  @return      index for basename, if the name this tile should be available as a different basename, or 0
+**
 */
-void ParseTilesetTileFlags(lua_State *l, int *back, int *j)
+int CTileset::parseTilesetTileFlags(lua_State *l, int *back, int *j)
 {
 	unsigned int flags = 3;
 
@@ -114,12 +118,18 @@ void ParseTilesetTileFlags(lua_State *l, int *back, int *j)
 		const char *value = LuaToString(l, -1);
 		lua_pop(l, 1);
 
-		//  Flags are only needed for the editor
+		//  Flags are mostly needed for the editor
 		if (ModifyFlag(value, &flags) == false) {
 			LuaError(l, "solid: unsupported tag: %s" _C_ value);
 		}
 	}
 	*back = flags;
+
+	if (flags & MapFieldDecorative) {
+		return getOrAddSolidTileIndexByName(std::to_string(solidTerrainTypes.size()));
+	} else {
+		return 0;
+	}
 }
 
 /**
@@ -190,7 +200,10 @@ void CTileset::parseSolid(lua_State *l)
 	++j;
 
 	int f = 0;
-	ParseTilesetTileFlags(l, &f, &j);
+	if (parseTilesetTileFlags(l, &f, &j)) {
+		LuaError(l, "cannot set a custom basename in the main set of flags");
+	}
+
 	//  Vector: the tiles.
 	lua_rawgeti(l, -1, j + 1);
 	if (!lua_istable(l, -1)) {
@@ -204,10 +217,13 @@ void CTileset::parseSolid(lua_State *l)
 		if (lua_istable(l, -1)) {
 			int k = 0;
 			int tile_flag = 0;
-			ParseTilesetTileFlags(l, &tile_flag, &k);
+			unsigned char new_basename = parseTilesetTileFlags(l, &tile_flag, &k);
 			--j;
 			lua_pop(l, 1);
 			tiles[index + j].flag = tile_flag;
+			if (new_basename) {
+				tiles[index + j].tileinfo.BaseTerrain = new_basename;
+			}
 			continue;
 		}
 		const int pud = LuaToNumber(l, -1);
@@ -248,7 +264,9 @@ void CTileset::parseMixed(lua_State *l)
 	++j;
 
 	int f = 0;
-	ParseTilesetTileFlags(l, &f, &j);
+	if (parseTilesetTileFlags(l, &f, &j)) {
+		LuaError(l, "cannot set a custom basename in the main set of flags");
+	}
 
 	for (; j < args; ++j) {
 		lua_rawgeti(l, -1, j + 1);
diff --git a/src/map/tileset.cpp b/src/map/tileset.cpp
index f9f581f39..43bc8d0a3 100644
--- a/src/map/tileset.cpp
+++ b/src/map/tileset.cpp
@@ -656,11 +656,16 @@ unsigned CTileset::getQuadFromTile(unsigned int tile) const
 
 void CTileset::fillSolidTiles(std::vector<unsigned int> *solidTiles) const
 {
-	for (size_t i = 16; i < tiles.size(); i += 16) {
-		const CTileInfo &info = tiles[i].tileinfo;
+	std::vector<int> seen_types;
+	seen_types.resize(solidTerrainTypes.size(), 0);
 
+	for (size_t i = 16; i < tiles.size(); i++) {
+		const CTileInfo &info = tiles[i].tileinfo;
 		if (info.BaseTerrain && info.MixTerrain == 0) {
-			solidTiles->push_back(tiles[i].tile);
+			if (seen_types[info.BaseTerrain] == 0) {
+				seen_types[info.BaseTerrain] = 1;
+				solidTiles->push_back(tiles[i].tile);
+			}
 		}
 	}
 }