Browse Source

Minimap as HUD element with API control

Features:
 * Define Minimap available modes (surface/radar, scale) from Lua, using player:set_minimap_modes()
 * New HUD elements for displaying minimap with custom size and placing
 * New minimap mode for displaying a texture instead of the map
Pierre-Yves Rollo 3 years ago
parent
commit
81c66d6efb

+ 32 - 1
doc/lua_api.txt

@@ -1484,6 +1484,15 @@ Displays an image oriented or translated according to current heading direction.
 
 
 If translation is chosen, texture is repeated horizontally to fill the whole element.
 If translation is chosen, texture is repeated horizontally to fill the whole element.
 
 
+### `minimap`
+
+Displays a minimap on the HUD.
+
+* `size`: Size of the minimap to display. Minimap should be a square to avoid
+  distortion.
+* `alignment`: The alignment of the minimap.
+* `offset`: offset in pixels from position.
+
 Representations of simple things
 Representations of simple things
 ================================
 ================================
 
 
@@ -6366,6 +6375,27 @@ object you are working with still exists.
 * `hud_set_hotbar_selected_image(texturename)`
 * `hud_set_hotbar_selected_image(texturename)`
     * sets image for selected item of hotbar
     * sets image for selected item of hotbar
 * `hud_get_hotbar_selected_image`: returns texturename
 * `hud_get_hotbar_selected_image`: returns texturename
+* `set_minimap_modes({mode, mode, ...}, selected_mode)`
+    * Overrides the available minimap modes (and toggle order), and changes the
+    selected mode.
+    * `mode` is a table consisting of up to four fields:
+        * `type`: Available type:
+            * `off`: Minimap off
+            * `surface`: Minimap in surface mode
+            * `radar`: Minimap in radar mode
+            * `texture`: Texture to be displayed instead of terrain map
+              (texture is centered around 0,0 and can be scaled).
+              Texture size is limited to 512 x 512 pixel.
+        * `label`: Optional label to display on minimap mode toggle
+          The translation must be handled within the mod.
+        * `size`: Sidelength or diameter, in number of nodes, of the terrain
+          displayed in minimap
+        * `texture`: Only for texture type, name of the texture to display
+        * `scale`: Only for texture type, scale of the texture map in nodes per
+          pixel (for example a `scale` of 2 means each pixel represents a 2x2
+          nodes square)
+    * `selected_mode` is the mode index to be selected after modes have been changed
+    (0 is the first mode).
 * `set_sky(parameters)`
 * `set_sky(parameters)`
     * `parameters` is a table with the following optional fields:
     * `parameters` is a table with the following optional fields:
         * `base_color`: ColorSpec, changes fog in "skybox" and "plain".
         * `base_color`: ColorSpec, changes fog in "skybox" and "plain".
@@ -8047,7 +8077,8 @@ Used by `Player:hud_add`. Returned by `Player:hud_get`.
 
 
     {
     {
         hud_elem_type = "image",  -- See HUD element types
         hud_elem_type = "image",  -- See HUD element types
-        -- Type of element, can be "image", "text", "statbar", "inventory" or "compass"
+        -- Type of element, can be "image", "text", "statbar", "inventory",
+        -- "compass" or "minimap"
 
 
         position = {x=0.5, y=0.5},
         position = {x=0.5, y=0.5},
         -- Left corner position of element
         -- Left corner position of element

+ 1 - 0
src/client/client.cpp

@@ -129,6 +129,7 @@ Client::Client(
 	if (g_settings->getBool("enable_minimap")) {
 	if (g_settings->getBool("enable_minimap")) {
 		m_minimap = new Minimap(this);
 		m_minimap = new Minimap(this);
 	}
 	}
+
 	m_cache_save_interval = g_settings->getU16("server_map_save_interval");
 	m_cache_save_interval = g_settings->getU16("server_map_save_interval");
 }
 }
 
 

+ 1 - 0
src/client/client.h

@@ -223,6 +223,7 @@ public:
 	void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt);
 	void handleCommand_CSMRestrictionFlags(NetworkPacket *pkt);
 	void handleCommand_PlayerSpeed(NetworkPacket *pkt);
 	void handleCommand_PlayerSpeed(NetworkPacket *pkt);
 	void handleCommand_MediaPush(NetworkPacket *pkt);
 	void handleCommand_MediaPush(NetworkPacket *pkt);
+	void handleCommand_MinimapModes(NetworkPacket *pkt);
 
 
 	void ProcessData(NetworkPacket *pkt);
 	void ProcessData(NetworkPacket *pkt);
 
 

+ 29 - 46
src/client/game.cpp

@@ -1416,11 +1416,9 @@ bool Game::createClient(const GameStartData &start_data)
 	}
 	}
 
 
 	mapper = client->getMinimap();
 	mapper = client->getMinimap();
-	if (mapper) {
-		mapper->setMinimapMode(MINIMAP_MODE_OFF);
-		if (client->modsLoaded())
-			client->getScript()->on_minimap_ready(mapper);
-	}
+
+	if (mapper && client->modsLoaded())
+		client->getScript()->on_minimap_ready(mapper);
 
 
 	return true;
 	return true;
 }
 }
@@ -2222,52 +2220,37 @@ void Game::toggleMinimap(bool shift_pressed)
 	if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
 	if (!mapper || !m_game_ui->m_flags.show_hud || !g_settings->getBool("enable_minimap"))
 		return;
 		return;
 
 
-	if (shift_pressed) {
+	if (shift_pressed)
 		mapper->toggleMinimapShape();
 		mapper->toggleMinimapShape();
-		return;
-	}
+	else
+		mapper->nextMode();
+
+	// TODO: When legacy minimap is deprecated, keep only HUD minimap stuff here
 
 
+	// Not so satisying code to keep compatibility with old fixed mode system
+	// -->
 	u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
 	u32 hud_flags = client->getEnv().getLocalPlayer()->hud_flags;
 
 
-	MinimapMode mode = MINIMAP_MODE_OFF;
-	if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE) {
-		mode = mapper->getMinimapMode();
-		mode = (MinimapMode)((int)mode + 1);
-		// If radar is disabled and in, or switching to, radar mode
-		if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE) && mode > 3)
-			mode = MINIMAP_MODE_OFF;
-	}
+	if (!(hud_flags & HUD_FLAG_MINIMAP_VISIBLE)) {
+		m_game_ui->m_flags.show_minimap = false;
+	} else {
 
 
-	m_game_ui->m_flags.show_minimap = true;
-	switch (mode) {
-		case MINIMAP_MODE_SURFACEx1:
-			m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x1");
-			break;
-		case MINIMAP_MODE_SURFACEx2:
-			m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x2");
-			break;
-		case MINIMAP_MODE_SURFACEx4:
-			m_game_ui->showTranslatedStatusText("Minimap in surface mode, Zoom x4");
-			break;
-		case MINIMAP_MODE_RADARx1:
-			m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x1");
-			break;
-		case MINIMAP_MODE_RADARx2:
-			m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x2");
-			break;
-		case MINIMAP_MODE_RADARx4:
-			m_game_ui->showTranslatedStatusText("Minimap in radar mode, Zoom x4");
-			break;
-		default:
-			mode = MINIMAP_MODE_OFF;
-			m_game_ui->m_flags.show_minimap = false;
-			if (hud_flags & HUD_FLAG_MINIMAP_VISIBLE)
-				m_game_ui->showTranslatedStatusText("Minimap hidden");
-			else
-				m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
-	}
+	// If radar is disabled, try to find a non radar mode or fall back to 0
+		if (!(hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE))
+			while (mapper->getModeIndex() &&
+					mapper->getModeDef().type == MINIMAP_TYPE_RADAR)
+				mapper->nextMode();
 
 
-	mapper->setMinimapMode(mode);
+		m_game_ui->m_flags.show_minimap = mapper->getModeDef().type !=
+				MINIMAP_TYPE_OFF;
+	}
+	// <--
+	// End of 'not so satifying code'
+	if ((hud_flags & HUD_FLAG_MINIMAP_VISIBLE) ||
+			(hud && hud->hasElementOfType(HUD_ELEM_MINIMAP)))
+		m_game_ui->showStatusText(utf8_to_wide(mapper->getModeDef().label));
+	else
+		m_game_ui->showTranslatedStatusText("Minimap currently disabled by game or mod");
 }
 }
 
 
 void Game::toggleFog()
 void Game::toggleFog()
@@ -3954,7 +3937,7 @@ void Game::updateFrame(ProfilerGraph *graph, RunStats *stats, f32 dtime,
 	/*
 	/*
 		Update minimap pos and rotation
 		Update minimap pos and rotation
 	*/
 	*/
-	if (mapper && m_game_ui->m_flags.show_minimap && m_game_ui->m_flags.show_hud) {
+	if (mapper && m_game_ui->m_flags.show_hud) {
 		mapper->setPos(floatToInt(player->getPosition(), BS));
 		mapper->setPos(floatToInt(player->getPosition(), BS));
 		mapper->setAngle(player->getYaw());
 		mapper->setAngle(player->getYaw());
 	}
 	}

+ 29 - 1
src/client/hud.cpp

@@ -36,6 +36,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "mesh.h"
 #include "mesh.h"
 #include "wieldmesh.h"
 #include "wieldmesh.h"
 #include "client/renderingengine.h"
 #include "client/renderingengine.h"
+#include "client/minimap.h"
 
 
 #ifdef HAVE_TOUCHSCREENGUI
 #ifdef HAVE_TOUCHSCREENGUI
 #include "gui/touchscreengui.h"
 #include "gui/touchscreengui.h"
@@ -297,6 +298,18 @@ void Hud::drawItems(v2s32 upperleftpos, v2s32 screen_offset, s32 itemcount,
 	}
 	}
 }
 }
 
 
+bool Hud::hasElementOfType(HudElementType type)
+{
+	for (size_t i = 0; i != player->maxHudId(); i++) {
+		HudElement *e = player->getHud(i);
+		if (!e)
+			continue;
+		if (e->type == type)
+			return true;
+	}
+	return false;
+}
+
 // Calculates screen position of waypoint. Returns true if waypoint is visible (in front of the player), else false.
 // Calculates screen position of waypoint. Returns true if waypoint is visible (in front of the player), else false.
 bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos)
 bool Hud::calculateScreenPos(const v3s16 &camera_offset, HudElement *e, v2s32 *pos)
 {
 {
@@ -491,7 +504,22 @@ void Hud::drawLuaElements(const v3s16 &camera_offset)
 				default:
 				default:
 					break;
 					break;
 				}
 				}
-
+				break; }
+			case HUD_ELEM_MINIMAP: {
+				if (e->size.X <= 0 || e->size.Y <= 0)
+					break;
+				if (!client->getMinimap())
+					break;
+				// Draw a minimap of size "size"
+				v2s32 dstsize(e->size.X * m_scale_factor,
+				              e->size.Y * m_scale_factor);
+				// (no percent size as minimap would likely be anamorphosed)
+				v2s32 offset((e->align.X - 1.0) * dstsize.X / 2,
+				             (e->align.Y - 1.0) * dstsize.Y / 2);
+				core::rect<s32> rect(0, 0, dstsize.X, dstsize.Y);
+				rect += pos + offset + v2s32(e->offset.X * m_scale_factor,
+				                             e->offset.Y * m_scale_factor);
+				client->getMinimap()->drawMinimap(rect);
 				break; }
 				break; }
 			default:
 			default:
 				infostream << "Hud::drawLuaElements: ignoring drawform " << e->type <<
 				infostream << "Hud::drawLuaElements: ignoring drawform " << e->type <<

+ 2 - 0
src/client/hud.h

@@ -81,6 +81,8 @@ public:
 		m_selected_face_normal = face_normal;
 		m_selected_face_normal = face_normal;
 	}
 	}
 
 
+	bool hasElementOfType(HudElementType type);
+
 	void drawLuaElements(const v3s16 &camera_offset);
 	void drawLuaElements(const v3s16 &camera_offset);
 
 
 private:
 private:

+ 1 - 1
src/client/mapblock_mesh.cpp

@@ -1044,7 +1044,7 @@ MapBlockMesh::MapBlockMesh(MeshMakeData *data, v3s16 camera_offset):
 	m_use_tangent_vertices = data->m_use_tangent_vertices;
 	m_use_tangent_vertices = data->m_use_tangent_vertices;
 	m_enable_vbo = g_settings->getBool("enable_vbo");
 	m_enable_vbo = g_settings->getBool("enable_vbo");
 
 
-	if (g_settings->getBool("enable_minimap")) {
+	if (data->m_client->getMinimap()) {
 		m_minimap_mapblock = new MinimapMapblock;
 		m_minimap_mapblock = new MinimapMapblock;
 		m_minimap_mapblock->getMinimapNodes(
 		m_minimap_mapblock->getMinimapNodes(
 			&data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE);
 			&data->m_vmanip, data->m_blockpos * MAP_BLOCKSIZE);

+ 178 - 59
src/client/minimap.cpp

@@ -25,7 +25,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #include "shader.h"
 #include "shader.h"
 #include "mapblock.h"
 #include "mapblock.h"
 #include "client/renderingengine.h"
 #include "client/renderingengine.h"
-
+#include "gettext.h"
 
 
 ////
 ////
 //// MinimapUpdateThread
 //// MinimapUpdateThread
@@ -108,8 +108,11 @@ void MinimapUpdateThread::doUpdate()
 		}
 		}
 	}
 	}
 
 
-	if (data->map_invalidated && data->mode != MINIMAP_MODE_OFF) {
-		getMap(data->pos, data->map_size, data->scan_height);
+
+	if (data->map_invalidated && (
+				data->mode.type == MINIMAP_TYPE_RADAR ||
+				data->mode.type == MINIMAP_TYPE_SURFACE)) {
+		getMap(data->pos, data->mode.map_size, data->mode.scan_height);
 		data->map_invalidated = false;
 		data->map_invalidated = false;
 	}
 	}
 }
 }
@@ -181,19 +184,26 @@ Minimap::Minimap(Client *client)
 	this->m_ndef    = client->getNodeDefManager();
 	this->m_ndef    = client->getNodeDefManager();
 
 
 	m_angle = 0.f;
 	m_angle = 0.f;
+	m_current_mode_index = 0;
 
 
 	// Initialize static settings
 	// Initialize static settings
 	m_enable_shaders = g_settings->getBool("enable_shaders");
 	m_enable_shaders = g_settings->getBool("enable_shaders");
 	m_surface_mode_scan_height =
 	m_surface_mode_scan_height =
 		g_settings->getBool("minimap_double_scan_height") ? 256 : 128;
 		g_settings->getBool("minimap_double_scan_height") ? 256 : 128;
 
 
+	// Initialize minimap modes
+	addMode(MINIMAP_TYPE_OFF);
+	addMode(MINIMAP_TYPE_SURFACE, 256);
+	addMode(MINIMAP_TYPE_SURFACE, 128);
+	addMode(MINIMAP_TYPE_SURFACE, 64);
+	addMode(MINIMAP_TYPE_RADAR,   512);
+	addMode(MINIMAP_TYPE_RADAR,   256);
+	addMode(MINIMAP_TYPE_RADAR,   128);
+
 	// Initialize minimap data
 	// Initialize minimap data
 	data = new MinimapData;
 	data = new MinimapData;
-	data->mode              = MINIMAP_MODE_OFF;
-	data->is_radar          = false;
-	data->map_invalidated   = true;
-	data->texture           = NULL;
-	data->heightmap_texture = NULL;
+	data->map_invalidated = true;
+
 	data->minimap_shape_round = g_settings->getBool("minimap_shape_round");
 	data->minimap_shape_round = g_settings->getBool("minimap_shape_round");
 
 
 	// Get round minimap textures
 	// Get round minimap textures
@@ -215,6 +225,8 @@ Minimap::Minimap(Client *client)
 	// Create object marker texture
 	// Create object marker texture
 	data->object_marker_red = m_tsrc->getTexture("object_marker_red.png");
 	data->object_marker_red = m_tsrc->getTexture("object_marker_red.png");
 
 
+	setModeIndex(0);
+
 	// Create mesh buffer for minimap
 	// Create mesh buffer for minimap
 	m_meshbuffer = getMinimapMeshBuffer();
 	m_meshbuffer = getMinimapMeshBuffer();
 
 
@@ -280,29 +292,101 @@ MinimapShape Minimap::getMinimapShape()
 	return MINIMAP_SHAPE_SQUARE;
 	return MINIMAP_SHAPE_SQUARE;
 }
 }
 
 
-void Minimap::setMinimapMode(MinimapMode mode)
+void Minimap::setModeIndex(size_t index)
 {
 {
-	static const MinimapModeDef modedefs[MINIMAP_MODE_COUNT] = {
-		{false, 0, 0},
-		{false, m_surface_mode_scan_height, 256},
-		{false, m_surface_mode_scan_height, 128},
-		{false, m_surface_mode_scan_height, 64},
-		{true, 32, 128},
-		{true, 32, 64},
-		{true, 32, 32}
-	};
-
-	if (mode >= MINIMAP_MODE_COUNT)
-		return;
-
 	MutexAutoLock lock(m_mutex);
 	MutexAutoLock lock(m_mutex);
 
 
-	data->is_radar    = modedefs[mode].is_radar;
-	data->scan_height = modedefs[mode].scan_height;
-	data->map_size    = modedefs[mode].map_size;
-	data->mode        = mode;
+	if (index < m_modes.size()) {
+		data->mode = m_modes[index];
+		m_current_mode_index = index;
+	} else {
+		data->mode = MinimapModeDef{MINIMAP_TYPE_OFF, N_("Minimap hidden"), 0, 0, ""};
+		m_current_mode_index = 0;
+	}
+
+	data->map_invalidated = true;
 
 
-	m_minimap_update_thread->deferUpdate();
+	if (m_minimap_update_thread)
+		m_minimap_update_thread->deferUpdate();
+}
+
+void Minimap::addMode(MinimapModeDef mode)
+{
+	// Check validity
+	if (mode.type == MINIMAP_TYPE_TEXTURE) {
+		if (mode.texture.empty())
+			return;
+		if (mode.scale < 1)
+			mode.scale = 1;
+	}
+
+	int zoom = -1;
+
+	// Build a default standard label
+	if (mode.label == "") {
+		switch (mode.type) {
+			case MINIMAP_TYPE_OFF:
+				mode.label = N_("Minimap hidden");
+				break;
+			case MINIMAP_TYPE_SURFACE:
+				mode.label = N_("Minimap in surface mode, Zoom x%d");
+				if (mode.map_size > 0)
+					zoom = 256 / mode.map_size;
+				break;
+			case MINIMAP_TYPE_RADAR:
+				mode.label = N_("Minimap in radar mode, Zoom x%d");
+				if (mode.map_size > 0)
+					zoom = 512 / mode.map_size;
+				break;
+			case MINIMAP_TYPE_TEXTURE:
+				mode.label = N_("Minimap in texture mode");
+				break;
+			default:
+				break;
+		}
+	}
+
+	if (zoom >= 0) {
+		char label_buf[1024];
+		porting::mt_snprintf(label_buf, sizeof(label_buf),
+			mode.label.c_str(), zoom);
+		mode.label = label_buf;
+	}
+
+	m_modes.push_back(mode);
+}
+
+void Minimap::addMode(MinimapType type, u16 size, std::string label,
+		std::string texture, u16 scale)
+{
+	MinimapModeDef mode;
+	mode.type = type;
+	mode.label = label;
+	mode.map_size = size;
+	mode.texture = texture;
+	mode.scale = scale;
+	switch (type) {
+		case MINIMAP_TYPE_SURFACE:
+			mode.scan_height = m_surface_mode_scan_height;
+			break;
+		case MINIMAP_TYPE_RADAR:
+			mode.scan_height = 32;
+			break;
+		default:
+			mode.scan_height = 0;
+	}
+	addMode(mode);
+}
+
+void Minimap::nextMode()
+{
+	if (m_modes.empty())
+		return;
+	m_current_mode_index++;
+	if (m_current_mode_index >= m_modes.size())
+		m_current_mode_index = 0;
+
+	setModeIndex(m_current_mode_index);
 }
 }
 
 
 void Minimap::setPos(v3s16 pos)
 void Minimap::setPos(v3s16 pos)
@@ -331,16 +415,16 @@ void Minimap::setAngle(f32 angle)
 void Minimap::blitMinimapPixelsToImageRadar(video::IImage *map_image)
 void Minimap::blitMinimapPixelsToImageRadar(video::IImage *map_image)
 {
 {
 	video::SColor c(240, 0, 0, 0);
 	video::SColor c(240, 0, 0, 0);
-	for (s16 x = 0; x < data->map_size; x++)
-	for (s16 z = 0; z < data->map_size; z++) {
-		MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size];
+	for (s16 x = 0; x < data->mode.map_size; x++)
+	for (s16 z = 0; z < data->mode.map_size; z++) {
+		MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.map_size];
 
 
 		if (mmpixel->air_count > 0)
 		if (mmpixel->air_count > 0)
 			c.setGreen(core::clamp(core::round32(32 + mmpixel->air_count * 8), 0, 255));
 			c.setGreen(core::clamp(core::round32(32 + mmpixel->air_count * 8), 0, 255));
 		else
 		else
 			c.setGreen(0);
 			c.setGreen(0);
 
 
-		map_image->setPixel(x, data->map_size - z - 1, c);
+		map_image->setPixel(x, data->mode.map_size - z - 1, c);
 	}
 	}
 }
 }
 
 
@@ -349,9 +433,9 @@ void Minimap::blitMinimapPixelsToImageSurface(
 {
 {
 	// This variable creation/destruction has a 1% cost on rendering minimap
 	// This variable creation/destruction has a 1% cost on rendering minimap
 	video::SColor tilecolor;
 	video::SColor tilecolor;
-	for (s16 x = 0; x < data->map_size; x++)
-	for (s16 z = 0; z < data->map_size; z++) {
-		MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->map_size];
+	for (s16 x = 0; x < data->mode.map_size; x++)
+	for (s16 z = 0; z < data->mode.map_size; z++) {
+		MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.map_size];
 
 
 		const ContentFeatures &f = m_ndef->get(mmpixel->n);
 		const ContentFeatures &f = m_ndef->get(mmpixel->n);
 		const TileDef *tile = &f.tiledef[0];
 		const TileDef *tile = &f.tiledef[0];
@@ -367,10 +451,10 @@ void Minimap::blitMinimapPixelsToImageSurface(
 		tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255);
 		tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255);
 		tilecolor.setAlpha(240);
 		tilecolor.setAlpha(240);
 
 
-		map_image->setPixel(x, data->map_size - z - 1, tilecolor);
+		map_image->setPixel(x, data->mode.map_size - z - 1, tilecolor);
 
 
 		u32 h = mmpixel->height;
 		u32 h = mmpixel->height;
-		heightmap_image->setPixel(x,data->map_size - z - 1,
+		heightmap_image->setPixel(x,data->mode.map_size - z - 1,
 			video::SColor(255, h, h, h));
 			video::SColor(255, h, h, h));
 	}
 	}
 }
 }
@@ -378,21 +462,46 @@ void Minimap::blitMinimapPixelsToImageSurface(
 video::ITexture *Minimap::getMinimapTexture()
 video::ITexture *Minimap::getMinimapTexture()
 {
 {
 	// update minimap textures when new scan is ready
 	// update minimap textures when new scan is ready
-	if (data->map_invalidated)
+	if (data->map_invalidated && data->mode.type != MINIMAP_TYPE_TEXTURE)
 		return data->texture;
 		return data->texture;
 
 
 	// create minimap and heightmap images in memory
 	// create minimap and heightmap images in memory
-	core::dimension2d<u32> dim(data->map_size, data->map_size);
+	core::dimension2d<u32> dim(data->mode.map_size, data->mode.map_size);
 	video::IImage *map_image       = driver->createImage(video::ECF_A8R8G8B8, dim);
 	video::IImage *map_image       = driver->createImage(video::ECF_A8R8G8B8, dim);
 	video::IImage *heightmap_image = driver->createImage(video::ECF_A8R8G8B8, dim);
 	video::IImage *heightmap_image = driver->createImage(video::ECF_A8R8G8B8, dim);
 	video::IImage *minimap_image   = driver->createImage(video::ECF_A8R8G8B8,
 	video::IImage *minimap_image   = driver->createImage(video::ECF_A8R8G8B8,
 		core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
 		core::dimension2d<u32>(MINIMAP_MAX_SX, MINIMAP_MAX_SY));
 
 
 	// Blit MinimapPixels to images
 	// Blit MinimapPixels to images
-	if (data->is_radar)
-		blitMinimapPixelsToImageRadar(map_image);
-	else
+	switch(data->mode.type) {
+	case MINIMAP_TYPE_OFF:
+		break;
+	case MINIMAP_TYPE_SURFACE:
 		blitMinimapPixelsToImageSurface(map_image, heightmap_image);
 		blitMinimapPixelsToImageSurface(map_image, heightmap_image);
+		break;
+	case MINIMAP_TYPE_RADAR:
+		blitMinimapPixelsToImageRadar(map_image);
+		break;
+	case MINIMAP_TYPE_TEXTURE:
+		// Want to use texture source, to : 1 find texture, 2 cache it
+		video::ITexture* texture = m_tsrc->getTexture(data->mode.texture);
+		video::IImage* image = driver->createImageFromData(
+			 texture->getColorFormat(), texture->getSize(), texture->lock(), true, false);
+		texture->unlock();
+
+		auto dim = image->getDimension();
+
+		map_image->fill(video::SColor(255, 0, 0, 0));
+
+		image->copyTo(map_image,
+			irr::core::vector2d<int> {
+				((data->mode.map_size - (static_cast<int>(dim.Width))) >> 1)
+					- data->pos.X / data->mode.scale,
+				((data->mode.map_size - (static_cast<int>(dim.Height))) >> 1)
+					+ data->pos.Z / data->mode.scale
+			});
+		image->drop();
+	}
 
 
 	map_image->copyToScaling(minimap_image);
 	map_image->copyToScaling(minimap_image);
 	map_image->drop();
 	map_image->drop();
@@ -461,21 +570,31 @@ scene::SMeshBuffer *Minimap::getMinimapMeshBuffer()
 
 
 void Minimap::drawMinimap()
 void Minimap::drawMinimap()
 {
 {
+	// Non hud managed minimap drawing (legacy minimap)
+	v2u32 screensize = RenderingEngine::get_instance()->getWindowSize();
+	const u32 size = 0.25 * screensize.Y;
+
+	drawMinimap(core::rect<s32>(
+		screensize.X - size - 10, 10,
+		screensize.X - 10, size + 10));
+}
+
+void Minimap::drawMinimap(core::rect<s32> rect) {
+
 	video::ITexture *minimap_texture = getMinimapTexture();
 	video::ITexture *minimap_texture = getMinimapTexture();
 	if (!minimap_texture)
 	if (!minimap_texture)
 		return;
 		return;
 
 
+	if (data->mode.type == MINIMAP_TYPE_OFF)
+		return;
+
 	updateActiveMarkers();
 	updateActiveMarkers();
-	v2u32 screensize = RenderingEngine::get_instance()->getWindowSize();
-	const u32 size = 0.25 * screensize.Y;
 
 
 	core::rect<s32> oldViewPort = driver->getViewPort();
 	core::rect<s32> oldViewPort = driver->getViewPort();
 	core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
 	core::matrix4 oldProjMat = driver->getTransform(video::ETS_PROJECTION);
 	core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
 	core::matrix4 oldViewMat = driver->getTransform(video::ETS_VIEW);
 
 
-	driver->setViewPort(core::rect<s32>(
-		screensize.X - size - 10, 10,
-		screensize.X - 10, size + 10));
+	driver->setViewPort(rect);
 	driver->setTransform(video::ETS_PROJECTION, core::matrix4());
 	driver->setTransform(video::ETS_PROJECTION, core::matrix4());
 	driver->setTransform(video::ETS_VIEW, core::matrix4());
 	driver->setTransform(video::ETS_VIEW, core::matrix4());
 
 
@@ -488,7 +607,7 @@ void Minimap::drawMinimap()
 	material.TextureLayer[0].Texture = minimap_texture;
 	material.TextureLayer[0].Texture = minimap_texture;
 	material.TextureLayer[1].Texture = data->heightmap_texture;
 	material.TextureLayer[1].Texture = data->heightmap_texture;
 
 
-	if (m_enable_shaders && !data->is_radar) {
+	if (m_enable_shaders && data->mode.type == MINIMAP_TYPE_SURFACE) {
 		u16 sid = m_shdrsrc->getShader("minimap_shader", 1, 1);
 		u16 sid = m_shdrsrc->getShader("minimap_shader", 1, 1);
 		material.MaterialType = m_shdrsrc->getShaderInfo(sid).material;
 		material.MaterialType = m_shdrsrc->getShaderInfo(sid).material;
 	} else {
 	} else {
@@ -529,14 +648,14 @@ void Minimap::drawMinimap()
 	driver->setViewPort(oldViewPort);
 	driver->setViewPort(oldViewPort);
 
 
 	// Draw player markers
 	// Draw player markers
-	v2s32 s_pos(screensize.X - size - 10, 10);
+	v2s32 s_pos(rect.UpperLeftCorner.X, rect.UpperLeftCorner.Y);
 	core::dimension2di imgsize(data->object_marker_red->getOriginalSize());
 	core::dimension2di imgsize(data->object_marker_red->getOriginalSize());
 	core::rect<s32> img_rect(0, 0, imgsize.Width, imgsize.Height);
 	core::rect<s32> img_rect(0, 0, imgsize.Width, imgsize.Height);
 	static const video::SColor col(255, 255, 255, 255);
 	static const video::SColor col(255, 255, 255, 255);
 	static const video::SColor c[4] = {col, col, col, col};
 	static const video::SColor c[4] = {col, col, col, col};
 	f32 sin_angle = std::sin(m_angle * core::DEGTORAD);
 	f32 sin_angle = std::sin(m_angle * core::DEGTORAD);
 	f32 cos_angle = std::cos(m_angle * core::DEGTORAD);
 	f32 cos_angle = std::cos(m_angle * core::DEGTORAD);
-	s32 marker_size2 =  0.025 * (float)size;
+	s32 marker_size2 =  0.025 * (float)rect.getWidth();;
 	for (std::list<v2f>::const_iterator
 	for (std::list<v2f>::const_iterator
 			i = m_active_markers.begin();
 			i = m_active_markers.begin();
 			i != m_active_markers.end(); ++i) {
 			i != m_active_markers.end(); ++i) {
@@ -547,8 +666,8 @@ void Minimap::drawMinimap()
 			posf.X = t1;
 			posf.X = t1;
 			posf.Y = t2;
 			posf.Y = t2;
 		}
 		}
-		posf.X = (posf.X + 0.5) * (float)size;
-		posf.Y = (posf.Y + 0.5) * (float)size;
+		posf.X = (posf.X + 0.5) * (float)rect.getWidth();
+		posf.Y = (posf.Y + 0.5) * (float)rect.getHeight();
 		core::rect<s32> dest_rect(
 		core::rect<s32> dest_rect(
 			s_pos.X + posf.X - marker_size2,
 			s_pos.X + posf.X - marker_size2,
 			s_pos.Y + posf.Y - marker_size2,
 			s_pos.Y + posf.Y - marker_size2,
@@ -571,16 +690,16 @@ void Minimap::updateActiveMarkers()
 	for (Nametag *nametag : nametags) {
 	for (Nametag *nametag : nametags) {
 		v3s16 pos = floatToInt(nametag->parent_node->getAbsolutePosition() +
 		v3s16 pos = floatToInt(nametag->parent_node->getAbsolutePosition() +
 			intToFloat(client->getCamera()->getOffset(), BS), BS);
 			intToFloat(client->getCamera()->getOffset(), BS), BS);
-		pos -= data->pos - v3s16(data->map_size / 2,
-				data->scan_height / 2,
-				data->map_size / 2);
-		if (pos.X < 0 || pos.X > data->map_size ||
-				pos.Y < 0 || pos.Y > data->scan_height ||
-				pos.Z < 0 || pos.Z > data->map_size) {
+		pos -= data->pos - v3s16(data->mode.map_size / 2,
+				data->mode.scan_height / 2,
+				data->mode.map_size / 2);
+		if (pos.X < 0 || pos.X > data->mode.map_size ||
+				pos.Y < 0 || pos.Y > data->mode.scan_height ||
+				pos.Z < 0 || pos.Z > data->mode.map_size) {
 			continue;
 			continue;
 		}
 		}
-		pos.X = ((float)pos.X / data->map_size) * MINIMAP_MAX_SX;
-		pos.Z = ((float)pos.Z / data->map_size) * MINIMAP_MAX_SY;
+		pos.X = ((float)pos.X / data->mode.map_size) * MINIMAP_MAX_SX;
+		pos.Z = ((float)pos.Z / data->mode.map_size) * MINIMAP_MAX_SY;
 		const video::SColor &mask_col = minimap_mask->getPixel(pos.X, pos.Z);
 		const video::SColor &mask_col = minimap_mask->getPixel(pos.X, pos.Z);
 		if (!mask_col.getAlpha()) {
 		if (!mask_col.getAlpha()) {
 			continue;
 			continue;

+ 22 - 19
src/client/minimap.h

@@ -19,6 +19,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 
 
 #pragma once
 #pragma once
 
 
+#include "../hud.h"
 #include "irrlichttypes_extrabloated.h"
 #include "irrlichttypes_extrabloated.h"
 #include "util/thread.h"
 #include "util/thread.h"
 #include "voxel.h"
 #include "voxel.h"
@@ -33,26 +34,18 @@ class IShaderSource;
 #define MINIMAP_MAX_SX 512
 #define MINIMAP_MAX_SX 512
 #define MINIMAP_MAX_SY 512
 #define MINIMAP_MAX_SY 512
 
 
-enum MinimapMode {
-	MINIMAP_MODE_OFF,
-	MINIMAP_MODE_SURFACEx1,
-	MINIMAP_MODE_SURFACEx2,
-	MINIMAP_MODE_SURFACEx4,
-	MINIMAP_MODE_RADARx1,
-	MINIMAP_MODE_RADARx2,
-	MINIMAP_MODE_RADARx4,
-	MINIMAP_MODE_COUNT,
-};
-
 enum MinimapShape {
 enum MinimapShape {
 	MINIMAP_SHAPE_SQUARE,
 	MINIMAP_SHAPE_SQUARE,
 	MINIMAP_SHAPE_ROUND,
 	MINIMAP_SHAPE_ROUND,
 };
 };
 
 
 struct MinimapModeDef {
 struct MinimapModeDef {
-	bool is_radar;
+	MinimapType type;
+	std::string label;
 	u16 scan_height;
 	u16 scan_height;
 	u16 map_size;
 	u16 map_size;
+	std::string texture;
+	u16 scale;
 };
 };
 
 
 struct MinimapPixel {
 struct MinimapPixel {
@@ -69,12 +62,9 @@ struct MinimapMapblock {
 };
 };
 
 
 struct MinimapData {
 struct MinimapData {
-	bool is_radar;
-	MinimapMode mode;
+	MinimapModeDef mode;
 	v3s16 pos;
 	v3s16 pos;
 	v3s16 old_pos;
 	v3s16 old_pos;
-	u16 scan_height;
-	u16 map_size;
 	MinimapPixel minimap_scan[MINIMAP_MAX_SX * MINIMAP_MAX_SY];
 	MinimapPixel minimap_scan[MINIMAP_MAX_SX * MINIMAP_MAX_SY];
 	bool map_invalidated;
 	bool map_invalidated;
 	bool minimap_shape_round;
 	bool minimap_shape_round;
@@ -127,12 +117,22 @@ public:
 	v3s16 getPos() const { return data->pos; }
 	v3s16 getPos() const { return data->pos; }
 	void setAngle(f32 angle);
 	void setAngle(f32 angle);
 	f32 getAngle() const { return m_angle; }
 	f32 getAngle() const { return m_angle; }
-	void setMinimapMode(MinimapMode mode);
-	MinimapMode getMinimapMode() const { return data->mode; }
 	void toggleMinimapShape();
 	void toggleMinimapShape();
 	void setMinimapShape(MinimapShape shape);
 	void setMinimapShape(MinimapShape shape);
 	MinimapShape getMinimapShape();
 	MinimapShape getMinimapShape();
 
 
+	void clearModes() { m_modes.clear(); };
+	void addMode(MinimapModeDef mode);
+	void addMode(MinimapType type, u16 size = 0, std::string label = "",
+			std::string texture = "", u16 scale = 1);
+
+	void setModeIndex(size_t index);
+	size_t getModeIndex() const { return m_current_mode_index; };
+	size_t getMaxModeIndex() const { return m_modes.size() - 1; };
+	void nextMode();
+
+	void setModesFromString(std::string modes_string);
+	MinimapModeDef getModeDef() const { return data->mode; }
 
 
 	video::ITexture *getMinimapTexture();
 	video::ITexture *getMinimapTexture();
 
 
@@ -144,6 +144,7 @@ public:
 
 
 	void updateActiveMarkers();
 	void updateActiveMarkers();
 	void drawMinimap();
 	void drawMinimap();
+	void drawMinimap(core::rect<s32> rect);
 
 
 	video::IVideoDriver *driver;
 	video::IVideoDriver *driver;
 	Client* client;
 	Client* client;
@@ -153,9 +154,11 @@ private:
 	ITextureSource *m_tsrc;
 	ITextureSource *m_tsrc;
 	IShaderSource *m_shdrsrc;
 	IShaderSource *m_shdrsrc;
 	const NodeDefManager *m_ndef;
 	const NodeDefManager *m_ndef;
-	MinimapUpdateThread *m_minimap_update_thread;
+	MinimapUpdateThread *m_minimap_update_thread = nullptr;
 	scene::SMeshBuffer *m_meshbuffer;
 	scene::SMeshBuffer *m_meshbuffer;
 	bool m_enable_shaders;
 	bool m_enable_shaders;
+	std::vector<MinimapModeDef> m_modes;
+	size_t m_current_mode_index;
 	u16 m_surface_mode_scan_height;
 	u16 m_surface_mode_scan_height;
 	f32 m_angle;
 	f32 m_angle;
 	std::mutex m_mutex;
 	std::mutex m_mutex;

+ 1 - 0
src/hud.cpp

@@ -29,6 +29,7 @@ const struct EnumString es_HudElementType[] =
 	{HUD_ELEM_WAYPOINT,  "waypoint"},
 	{HUD_ELEM_WAYPOINT,  "waypoint"},
 	{HUD_ELEM_IMAGE_WAYPOINT, "image_waypoint"},
 	{HUD_ELEM_IMAGE_WAYPOINT, "image_waypoint"},
 	{HUD_ELEM_COMPASS,   "compass"},
 	{HUD_ELEM_COMPASS,   "compass"},
+	{HUD_ELEM_MINIMAP,   "minimap"},
 	{0, NULL},
 	{0, NULL},
 };
 };
 
 

+ 11 - 2
src/hud.h

@@ -51,7 +51,6 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 #define HUD_HOTBAR_ITEMCOUNT_DEFAULT 8
 #define HUD_HOTBAR_ITEMCOUNT_DEFAULT 8
 #define HUD_HOTBAR_ITEMCOUNT_MAX     32
 #define HUD_HOTBAR_ITEMCOUNT_MAX     32
 
 
-
 #define HOTBAR_IMAGE_SIZE 48
 #define HOTBAR_IMAGE_SIZE 48
 
 
 enum HudElementType {
 enum HudElementType {
@@ -61,7 +60,8 @@ enum HudElementType {
 	HUD_ELEM_INVENTORY = 3,
 	HUD_ELEM_INVENTORY = 3,
 	HUD_ELEM_WAYPOINT  = 4,
 	HUD_ELEM_WAYPOINT  = 4,
 	HUD_ELEM_IMAGE_WAYPOINT = 5,
 	HUD_ELEM_IMAGE_WAYPOINT = 5,
-	HUD_ELEM_COMPASS   = 6
+	HUD_ELEM_COMPASS   = 6,
+	HUD_ELEM_MINIMAP   = 7
 };
 };
 
 
 enum HudElementStat {
 enum HudElementStat {
@@ -108,3 +108,12 @@ extern const EnumString es_HudElementType[];
 extern const EnumString es_HudElementStat[];
 extern const EnumString es_HudElementStat[];
 extern const EnumString es_HudBuiltinElement[];
 extern const EnumString es_HudBuiltinElement[];
 
 
+// Minimap stuff
+
+enum MinimapType {
+	MINIMAP_TYPE_OFF,
+	MINIMAP_TYPE_SURFACE,
+	MINIMAP_TYPE_RADAR,
+	MINIMAP_TYPE_TEXTURE,
+};
+

+ 1 - 0
src/network/clientopcodes.cpp

@@ -122,6 +122,7 @@ const ToClientCommandHandler toClientCommandTable[TOCLIENT_NUM_MSG_TYPES] =
 	null_command_handler,
 	null_command_handler,
 	{ "TOCLIENT_SRP_BYTES_S_B",            TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_SrpBytesSandB }, // 0x60
 	{ "TOCLIENT_SRP_BYTES_S_B",            TOCLIENT_STATE_NOT_CONNECTED, &Client::handleCommand_SrpBytesSandB }, // 0x60
 	{ "TOCLIENT_FORMSPEC_PREPEND",         TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FormspecPrepend }, // 0x61,
 	{ "TOCLIENT_FORMSPEC_PREPEND",         TOCLIENT_STATE_CONNECTED, &Client::handleCommand_FormspecPrepend }, // 0x61,
+	{ "TOCLIENT_MINIMAP_MODES",            TOCLIENT_STATE_CONNECTED, &Client::handleCommand_MinimapModes }, // 0x62,
 };
 };
 
 
 const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, false };
 const static ServerCommandFactory null_command_factory = { "TOSERVER_NULL", 0, false };

+ 41 - 5
src/network/clientpackethandler.cpp

@@ -1164,15 +1164,24 @@ void Client::handleCommand_HudSetFlags(NetworkPacket* pkt)
 	m_minimap_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE);
 	m_minimap_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_VISIBLE);
 	bool m_minimap_radar_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE);
 	bool m_minimap_radar_disabled_by_server = !(player->hud_flags & HUD_FLAG_MINIMAP_RADAR_VISIBLE);
 
 
+	// Not so satisying code to keep compatibility with old fixed mode system
+	// -->
+
 	// Hide minimap if it has been disabled by the server
 	// Hide minimap if it has been disabled by the server
 	if (m_minimap && m_minimap_disabled_by_server && was_minimap_visible)
 	if (m_minimap && m_minimap_disabled_by_server && was_minimap_visible)
 		// defers a minimap update, therefore only call it if really
 		// defers a minimap update, therefore only call it if really
 		// needed, by checking that minimap was visible before
 		// needed, by checking that minimap was visible before
-		m_minimap->setMinimapMode(MINIMAP_MODE_OFF);
-
-	// Switch to surface mode if radar disabled by server
-	if (m_minimap && m_minimap_radar_disabled_by_server && was_minimap_radar_visible)
-		m_minimap->setMinimapMode(MINIMAP_MODE_SURFACEx1);
+		m_minimap->setModeIndex(0);
+
+	// If radar has been disabled, try to find a non radar mode or fall back to 0
+	if (m_minimap && m_minimap_radar_disabled_by_server
+			&& was_minimap_radar_visible) {
+		while (m_minimap->getModeIndex() > 0 &&
+				m_minimap->getModeDef().type == MINIMAP_TYPE_RADAR)
+			m_minimap->nextMode();
+	}
+	// <--
+	// End of 'not so satifying code'
 }
 }
 
 
 void Client::handleCommand_HudSetParam(NetworkPacket* pkt)
 void Client::handleCommand_HudSetParam(NetworkPacket* pkt)
@@ -1611,3 +1620,30 @@ void Client::handleCommand_ModChannelSignal(NetworkPacket *pkt)
 	if (valid_signal)
 	if (valid_signal)
 		m_script->on_modchannel_signal(channel, signal);
 		m_script->on_modchannel_signal(channel, signal);
 }
 }
+
+void Client::handleCommand_MinimapModes(NetworkPacket *pkt)
+{
+	u16 count; // modes
+	u16 mode;  // wanted current mode index after change
+
+	*pkt >> count >> mode;
+
+	if (m_minimap)
+		m_minimap->clearModes();
+
+	for (size_t index = 0; index < count; index++) {
+		u16 type;
+		std::string label;
+		u16 size;
+		std::string texture;
+		u16 scale;
+
+		*pkt >> type >> label >> size >> texture >> scale;
+
+		if (m_minimap)
+			m_minimap->addMode(MinimapType(type), size, label, texture, scale);
+	}
+
+	if (m_minimap)
+		m_minimap->setModeIndex(mode);
+}

+ 13 - 1
src/network/networkprotocol.h

@@ -204,6 +204,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
 	PROTOCOL VERSION 39:
 	PROTOCOL VERSION 39:
 		Updated set_sky packet
 		Updated set_sky packet
 		Adds new sun, moon and stars packets
 		Adds new sun, moon and stars packets
+		Minimap modes
 */
 */
 
 
 #define LATEST_PROTOCOL_VERSION 39
 #define LATEST_PROTOCOL_VERSION 39
@@ -764,7 +765,18 @@ enum ToClientCommand
 		u8[len] formspec
 		u8[len] formspec
 	*/
 	*/
 
 
-	TOCLIENT_NUM_MSG_TYPES = 0x62,
+	TOCLIENT_MINIMAP_MODES = 0x62,
+	/*
+		u16 count // modes
+		u16 mode  // wanted current mode index after change
+		for each mode
+			u16 type
+			std::string label
+			u16 size
+			std::string extra
+	*/
+
+	TOCLIENT_NUM_MSG_TYPES = 0x63,
 };
 };
 
 
 enum ToServerCommand
 enum ToServerCommand

+ 1 - 0
src/network/serveropcodes.cpp

@@ -221,4 +221,5 @@ const ClientCommandFactory clientCommandFactoryTable[TOCLIENT_NUM_MSG_TYPES] =
 	null_command_factory, // 0x5f
 	null_command_factory, // 0x5f
 	{ "TOSERVER_SRP_BYTES_S_B",            0, true }, // 0x60
 	{ "TOSERVER_SRP_BYTES_S_B",            0, true }, // 0x60
 	{ "TOCLIENT_FORMSPEC_PREPEND",         0, true }, // 0x61
 	{ "TOCLIENT_FORMSPEC_PREPEND",         0, true }, // 0x61
+	{ "TOCLIENT_MINIMAP_MODES",            0, true }, // 0x62
 };
 };

+ 14 - 10
src/script/lua_api/l_minimap.cpp

@@ -89,7 +89,7 @@ int LuaMinimap::l_get_mode(lua_State *L)
 	LuaMinimap *ref = checkobject(L, 1);
 	LuaMinimap *ref = checkobject(L, 1);
 	Minimap *m = getobject(ref);
 	Minimap *m = getobject(ref);
 
 
-	lua_pushinteger(L, m->getMinimapMode());
+	lua_pushinteger(L, m->getModeIndex());
 	return 1;
 	return 1;
 }
 }
 
 
@@ -98,13 +98,11 @@ int LuaMinimap::l_set_mode(lua_State *L)
 	LuaMinimap *ref = checkobject(L, 1);
 	LuaMinimap *ref = checkobject(L, 1);
 	Minimap *m = getobject(ref);
 	Minimap *m = getobject(ref);
 
 
-	s32 mode = lua_tointeger(L, 2);
-	if (mode < MINIMAP_MODE_OFF ||
-		mode >= MINIMAP_MODE_COUNT) {
+	u32 mode = lua_tointeger(L, 2);
+	if (mode >= m->getMaxModeIndex())
 		return 0;
 		return 0;
-	}
 
 
-	m->setMinimapMode((MinimapMode) mode);
+	m->setModeIndex(mode);
 	return 1;
 	return 1;
 }
 }
 
 
@@ -140,8 +138,11 @@ int LuaMinimap::l_show(lua_State *L)
 	LuaMinimap *ref = checkobject(L, 1);
 	LuaMinimap *ref = checkobject(L, 1);
 	Minimap *m = getobject(ref);
 	Minimap *m = getobject(ref);
 
 
-	if (m->getMinimapMode() == MINIMAP_MODE_OFF)
-		m->setMinimapMode(MINIMAP_MODE_SURFACEx1);
+	// This is not very adapted to new minimap mode management. Btw, tried
+	// to do something compatible.
+
+	if (m->getModeIndex() == 0 && m->getMaxModeIndex() > 0)
+		m->setModeIndex(1);
 
 
 	client->showMinimap(true);
 	client->showMinimap(true);
 	return 1;
 	return 1;
@@ -155,8 +156,11 @@ int LuaMinimap::l_hide(lua_State *L)
 	LuaMinimap *ref = checkobject(L, 1);
 	LuaMinimap *ref = checkobject(L, 1);
 	Minimap *m = getobject(ref);
 	Minimap *m = getobject(ref);
 
 
-	if (m->getMinimapMode() != MINIMAP_MODE_OFF)
-		m->setMinimapMode(MINIMAP_MODE_OFF);
+	// This is not very adapted to new minimap mode management. Btw, tried
+	// to do something compatible.
+
+	if (m->getModeIndex() != 0)
+		m->setModeIndex(0);
 
 
 	client->showMinimap(false);
 	client->showMinimap(false);
 	return 1;
 	return 1;

+ 62 - 0
src/script/lua_api/l_object.cpp

@@ -2199,6 +2199,67 @@ int ObjectRef::l_get_day_night_ratio(lua_State *L)
 	return 1;
 	return 1;
 }
 }
 
 
+// set_minimap_modes(self, modes, modeindex)
+int ObjectRef::l_set_minimap_modes(lua_State *L)
+{
+	NO_MAP_LOCK_REQUIRED;
+
+	// Arg 1 should be a player
+	ObjectRef *ref = checkobject(L, 1);
+	RemotePlayer *player = getplayer(ref);
+	if (player == NULL)
+		return 0;
+
+	// Arg 2 should be a table (of tables)
+	if (!lua_istable(L, 2)) {
+		return 0;
+	}
+
+	// Arg 3 should be a number
+	s16 wantedmode = lua_tonumber(L, 3);
+
+	std::vector<MinimapMode> modes;
+
+	lua_pushnil(L);
+	while (lua_next(L, 2) != 0) {
+		/* key is at index -2, value is at index -1 */
+		if (lua_istable(L, -1)) {
+			bool ok = true;
+			MinimapMode mode;
+			std::string type = getstringfield_default(L, -1, "type", "");
+			if (type == "off")
+				mode.type = MINIMAP_TYPE_OFF;
+			else if (type == "surface")
+				mode.type = MINIMAP_TYPE_SURFACE;
+			else if (type == "radar")
+				mode.type = MINIMAP_TYPE_RADAR;
+			else if (type == "texture") {
+				mode.type = MINIMAP_TYPE_TEXTURE;
+				mode.texture = getstringfield_default(L, -1, "texture", "");
+				mode.scale = getintfield_default(L, -1, "scale", 1);
+			} else {
+				warningstream << "Minimap mode of unknown type \"" << type.c_str()
+					<< "\" ignored.\n" << std::endl;
+				ok = false;
+			}
+
+			if (ok) {
+				mode.label = getstringfield_default(L, -1, "label", "");
+				// Size is limited to 512. Performance gets poor if size too large, and
+				// segfaults have been experienced.
+				mode.size = rangelim(getintfield_default(L, -1, "size", 0), 1, 512);
+				modes.push_back(mode);
+			}
+		}
+		/* removes 'value'; keeps 'key' for next iteration */
+		lua_pop(L, 1);
+	}
+	lua_pop(L, 1); // Remove key
+
+	getServer(L)->SendMinimapModes(player->getPeerId(), modes, wantedmode);
+	return 0;
+}
+
 ObjectRef::ObjectRef(ServerActiveObject *object):
 ObjectRef::ObjectRef(ServerActiveObject *object):
 	m_object(object)
 	m_object(object)
 {
 {
@@ -2359,5 +2420,6 @@ luaL_Reg ObjectRef::methods[] = {
 	luamethod(ObjectRef, set_eye_offset),
 	luamethod(ObjectRef, set_eye_offset),
 	luamethod(ObjectRef, get_eye_offset),
 	luamethod(ObjectRef, get_eye_offset),
 	luamethod(ObjectRef, send_mapblock),
 	luamethod(ObjectRef, send_mapblock),
+	luamethod(ObjectRef, set_minimap_modes),
 	{0,0}
 	{0,0}
 };
 };

+ 3 - 0
src/script/lua_api/l_object.h

@@ -377,4 +377,7 @@ private:
 
 
 	// send_mapblock(pos)
 	// send_mapblock(pos)
 	static int l_send_mapblock(lua_State *L);
 	static int l_send_mapblock(lua_State *L);
+
+	// set_minimap_modes(self, modes, wanted_mode)
+	static int l_set_minimap_modes(lua_State *L);
 };
 };

+ 17 - 0
src/server.cpp

@@ -2662,6 +2662,23 @@ void Server::sendRequestedMedia(session_t peer_id,
 	}
 	}
 }
 }
 
 
+void Server::SendMinimapModes(session_t peer_id,
+		std::vector<MinimapMode> &modes, size_t wanted_mode)
+{
+	RemotePlayer *player = m_env->getPlayer(peer_id);
+	assert(player);
+	if (player->getPeerId() == PEER_ID_INEXISTENT)
+		return;
+
+	NetworkPacket pkt(TOCLIENT_MINIMAP_MODES, 0, peer_id);
+	pkt << (u16)modes.size() << (u16)wanted_mode;
+
+	for (auto &mode : modes)
+		pkt << (u16)mode.type << mode.label << mode.size << mode.texture << mode.scale;
+
+	Send(&pkt);
+}
+
 void Server::sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id)
 void Server::sendDetachedInventory(Inventory *inventory, const std::string &name, session_t peer_id)
 {
 {
 	NetworkPacket pkt(TOCLIENT_DETACHED_INVENTORY, 0, peer_id);
 	NetworkPacket pkt(TOCLIENT_DETACHED_INVENTORY, 0, peer_id);

+ 12 - 0
src/server.h

@@ -118,6 +118,14 @@ struct ServerPlayingSound
 	std::unordered_set<session_t> clients; // peer ids
 	std::unordered_set<session_t> clients; // peer ids
 };
 };
 
 
+struct MinimapMode {
+	MinimapType type = MINIMAP_TYPE_OFF;
+	std::string label;
+	u16 size = 0;
+	std::string texture;
+	u16 scale = 1;
+};
+
 class Server : public con::PeerHandler, public MapEventReceiver,
 class Server : public con::PeerHandler, public MapEventReceiver,
 		public IGameDef
 		public IGameDef
 {
 {
@@ -331,6 +339,10 @@ public:
 	void SendPlayerSpeed(session_t peer_id, const v3f &added_vel);
 	void SendPlayerSpeed(session_t peer_id, const v3f &added_vel);
 	void SendPlayerFov(session_t peer_id);
 	void SendPlayerFov(session_t peer_id);
 
 
+	void SendMinimapModes(session_t peer_id,
+			std::vector<MinimapMode> &modes,
+			size_t wanted_mode);
+
 	void sendDetachedInventories(session_t peer_id, bool incremental);
 	void sendDetachedInventories(session_t peer_id, bool incremental);
 
 
 	virtual bool registerModStorage(ModMetadata *storage);
 	virtual bool registerModStorage(ModMetadata *storage);