A minetest mod for mapmaking
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

482 lines
15KB

  1. -- Arguments
  2. -- chunk: The chunk coordinate conversion API
  3. -- gui: The GUI API
  4. -- skin: The GUI skin
  5. -- audio: The audio playback API
  6. -- maps: The map API
  7. -- markers: The marker API
  8. -- map_formspec: The map display API
  9. -- settings: The mod settings
  10. local chunk, gui, skin, audio, maps, markers, map_formspec, settings = ...;
  11. -- The list of players looking at maps, and the map IDs that they're looking at
  12. local player_maps = {};
  13. -- Generate formspec data for the map marker editor
  14. --
  15. -- selected_id: The id of the currently selected marker, or nil if no marker is
  16. -- selected
  17. -- detail: The map's detail level
  18. -- page: The current page
  19. --
  20. -- Returns a formspec string for use in containers
  21. local function marker_formspec(selected_id, detail, page)
  22. local marker_lookup = markers.get_all();
  23. local formspec = {
  24. gui.button {
  25. x = 0.125,
  26. y = 0.125,
  27. w = 1.125,
  28. h = 0.5,
  29. id = "clear_marker",
  30. text = "Erase",
  31. tooltip = "Remove the selected marker",
  32. },
  33. gui.label {
  34. x = 1.375,
  35. y = 3.5,
  36. text = string.format("%d / %d", page, math.ceil(#marker_lookup / 20)),
  37. },
  38. };
  39. if selected_id then
  40. table.insert(formspec, gui.style {
  41. selector = "marker-" .. selected_id,
  42. properties = {
  43. bgimg = skin.marker_button.selected_texture .. ".png",
  44. bgimg_hovered = skin.marker_button.selected_texture .. ".png",
  45. bgimg_pressed = skin.marker_button.selected_texture .. ".png",
  46. }
  47. });
  48. end
  49. local starting_id = ((page - 1) * 20) + 1;
  50. for i = starting_id,math.min(#marker_lookup,starting_id + 19),1 do
  51. local marker = marker_lookup[i];
  52. table.insert(formspec, gui.image_button {
  53. x = (i - starting_id) % 5 * 0.625 + 0.125,
  54. y = math.floor((i - starting_id) / 5) * 0.625 + 0.75,
  55. w = 0.5,
  56. h = 0.5,
  57. image = marker.textures[math.min(detail, #marker.textures)] .. ".png",
  58. id = "marker-" .. marker.id,
  59. tooltip = marker.name,
  60. });
  61. end
  62. if page > 1 then
  63. table.insert(formspec, gui.button {
  64. x = 0.125,
  65. y = 3.25,
  66. w = 0.5,
  67. h = 0.5,
  68. id = "prev_button",
  69. text = "<"
  70. });
  71. end
  72. if starting_id + 19 < #marker_lookup then
  73. table.insert(formspec, gui.button {
  74. x = 2.625,
  75. y = 3.25,
  76. w = 0.5,
  77. h = 0.5,
  78. id = "next_button",
  79. text = ">"
  80. });
  81. end
  82. return table.concat(formspec);
  83. end
  84. -- Show a map to a player
  85. --
  86. -- map: The map to display
  87. -- player_x: The X position (in world coordinates)
  88. -- player_z: The Z position (in world coordinates)
  89. -- player_name: The name of the player to show to
  90. -- height_mode: Whether or not to display the map in height mode
  91. -- (Optional) marker_page: The current page that the marker editor is on
  92. local function show_map_formspec(map, player_x, player_z, player_name, height_mode, marker_page)
  93. map:fill_local(player_x, player_z);
  94. player_maps[player_name] = {
  95. id = map.id,
  96. page = marker_page or 1,
  97. height_mode = height_mode,
  98. };
  99. player_x, player_z = map:to_coordinates(player_x, player_z, true);
  100. local formspec, formspec_width, _ = map_formspec.from_map(map, player_x, player_z, height_mode);
  101. local height_button_texture;
  102. if height_mode then
  103. height_button_texture = skin.height_button_texture .. ".png";
  104. else
  105. height_button_texture = skin.flat_button_texture .. ".png";
  106. end
  107. local data = {
  108. gui.style_type {
  109. selector = "button,image_button,label",
  110. properties = {
  111. noclip = true,
  112. }
  113. },
  114. gui.style_type {
  115. selector = "button,image_button",
  116. properties = {
  117. border = false,
  118. bgimg = skin.marker_button.texture .. ".png",
  119. bgimg_hovered = skin.marker_button.hovered_texture .. ".png",
  120. bgimg_pressed = skin.marker_button.pressed_texture .. ".png",
  121. bgimg_middle = skin.marker_button.radius,
  122. textcolor = skin.marker_button.font_color,
  123. },
  124. },
  125. gui.container {
  126. x = formspec_width - 0.01,
  127. y = 0.125,
  128. w = 0.75,
  129. h = 0.75,
  130. bg = skin.marker_bg,
  131. gui.image_button {
  132. x = 0.125,
  133. y = 0.125,
  134. w = 0.5,
  135. h = 0.5,
  136. id = "height_button",
  137. image = height_button_texture,
  138. tooltip = "Toggle height view",
  139. }
  140. },
  141. };
  142. if markers.count() > 0 then
  143. table.insert(data, gui.container {
  144. x = formspec_width - 0.01,
  145. y = 1,
  146. w = 3.25,
  147. h = 3.875,
  148. bg = skin.marker_bg,
  149. marker_formspec(map:get_marker(player_x, player_z), map.detail, marker_page or 1)});
  150. end
  151. formspec = formspec .. table.concat(data);
  152. minetest.show_formspec(player_name, "cartographer:map", formspec);
  153. end
  154. -- Get the description text for a map ID and dimensions
  155. --
  156. -- id: The map ID
  157. -- from_x: The x coordinate of the top-left corner of the map, in map coordinates
  158. -- from_z: The z coordinate of the top-left corner of the map, in map coordinates
  159. -- w: The width, in world coordinates
  160. -- h: The height, in world coordinates
  161. --
  162. -- returns a string containing the description
  163. local function map_description(id, from_x, from_z, w, h)
  164. return string.format("Map #%d\n[%d,%d] - [%d,%d]",
  165. id,
  166. chunk.from(from_x), chunk.from(from_z),
  167. chunk.from(from_x + w + 1), chunk.from(from_z + h + 1));
  168. end
  169. -- Create a map from metadata, and assign the ID to the metadata
  170. --
  171. -- meta: A metadata object containing the map ID
  172. -- player_x: The X position (in map coordinates)
  173. -- player_z: The Z position (in map coordinates)
  174. --
  175. -- Returns the id of the new map
  176. local function map_from_meta(meta, player_x, player_z)
  177. local size = meta:get_int("cartographer:size");
  178. if size == 0 then
  179. size = settings.default_size;
  180. end
  181. local detail = meta:get_int("cartographer:detail");
  182. if detail == 0 then
  183. detail = 1;
  184. end
  185. local scale = meta:get_int("cartographer:scale");
  186. if scale == 0 then
  187. scale = 1;
  188. end
  189. local total_size = size * scale;
  190. local map_x = math.floor(player_x / total_size) * total_size;
  191. local map_y = math.floor(player_z / total_size) * total_size;
  192. local id = maps.create(map_x, map_y, size, size, false, detail, scale);
  193. meta:set_int("cartographer:map_id", id);
  194. meta:set_string("description", map_description(id, map_x, map_y, total_size, total_size));
  195. return id;
  196. end
  197. -- Show a map to a player from metadata, creating it if necessary
  198. --
  199. -- meta: A metadata object containing the map ID
  200. -- player: The player to show the map to
  201. local function show_map_meta(meta, player)
  202. local pos = player:get_pos();
  203. local player_x = chunk.to(pos.x);
  204. local player_z = chunk.to(pos.z);
  205. local id = meta:get_int("cartographer:map_id");
  206. if id == 0 then
  207. id = map_from_meta(meta, player_x, player_z);
  208. end
  209. local map = maps.get(id);
  210. if map then
  211. show_map_formspec(map, pos.x, pos.z, player:get_player_name(), true);
  212. end
  213. end
  214. -- Called when a player sends input to the server from a formspec
  215. -- This callback handles player input in the map formspec, for editing markers
  216. --
  217. -- player: The player who sent the input
  218. -- name: The formspec name
  219. -- fields: A table containing the input
  220. minetest.register_on_player_receive_fields(function(player, name, fields)
  221. if name == "cartographer:map" then
  222. local data = player_maps[player:get_player_name()];
  223. if not data then
  224. return;
  225. end
  226. local map = maps.get(data.id);
  227. if not map then
  228. return;
  229. end
  230. for k,_ in pairs(fields) do
  231. local marker = k:match("marker%-(.+)");
  232. local pos = player:get_pos();
  233. if marker or k == "clear_marker" then
  234. local player_x, player_z = map:to_coordinates(pos.x, pos.z, true);
  235. map:set_marker(player_x, player_z, marker);
  236. audio.play_feedback("cartographer_write", player);
  237. show_map_formspec(map, pos.x, pos.z, player:get_player_name(), data.page);
  238. elseif k == "prev_button" then
  239. local new_page = math.max(data.page - 1, 1);
  240. show_map_formspec(map, pos.x, pos.z, player:get_player_name(), data.height_mode, new_page);
  241. elseif k == "next_button" then
  242. local new_page = math.min(data.page + 1, math.ceil(markers.count() / 20));
  243. show_map_formspec(map, pos.x, pos.z, player:get_player_name(), data.height_mode, new_page);
  244. elseif k == "height_button" then
  245. show_map_formspec(map, pos.x, pos.z, player:get_player_name(), not data.height_mode, data.page);
  246. elseif k == "quit" then
  247. player_maps[player:get_player_name()] = nil;
  248. end
  249. end
  250. end
  251. end);
  252. -- The map item/node
  253. minetest.register_node("cartographer:map", {
  254. description = "Map",
  255. inventory_image = "cartographer_map.png",
  256. wield_image = "cartographer_map.png",
  257. tiles = { "cartographer_map.png" },
  258. drawtype = "signlike",
  259. paramtype = "light",
  260. paramtype2 = "wallmounted",
  261. stack_max = 1,
  262. sunlight_propagates = true,
  263. walkable = false,
  264. selection_box = {
  265. type = "fixed",
  266. fixed = {
  267. {-0.5, -0.5, -0.5, 0.5, -7 / 16, 0.5},
  268. },
  269. },
  270. groups = {
  271. attached_node = 1,
  272. dig_immediate = 3,
  273. },
  274. -- Called when this node is placed in the world. Copies map data from the
  275. -- item to the node.
  276. -- pos: The position of the node
  277. -- stack: The itemstack that was placed
  278. after_place_node = function(pos, _, stack, _)
  279. local meta = stack:get_meta():to_table();
  280. local node_meta = minetest.get_meta(pos);
  281. node_meta:from_table(meta);
  282. -- Consume the item after placing
  283. return false;
  284. end,
  285. -- Called when this node is dug. Turns the node into an item.
  286. -- pos: The position of the node
  287. on_dig = function(pos, _, _)
  288. local node_meta = minetest.get_meta(pos):to_table();
  289. local item = ItemStack("cartographer:map");
  290. item:get_meta():from_table(node_meta);
  291. if minetest.add_item(pos, item) then
  292. minetest.remove_node(pos);
  293. end
  294. end,
  295. -- Called when a player right-clicks this node. Display's the map's
  296. -- content, creating it if it doesn't exist.
  297. -- pos: The position of the node
  298. -- player: The player that right-clicked the node
  299. on_rightclick = function(pos, _, player)
  300. audio.play_feedback("cartographer_open_map", player);
  301. show_map_meta(minetest.get_meta(pos), player);
  302. end,
  303. -- Called when a player uses this item. Displays the map's content,
  304. -- creating it if it doesn't exist.
  305. -- stack: The itemstack
  306. -- player: The player that used the item
  307. on_use = function(stack, player)
  308. audio.play_feedback("cartographer_open_map", player);
  309. show_map_meta(stack:get_meta(), player);
  310. return stack;
  311. end,
  312. -- Called when a node is about to be turned into an item. Copies all
  313. -- metadata into any items matching this node's name.
  314. -- oldnode: The old node's data
  315. -- oldmeta: A table containing the old node's metadata
  316. -- drops: A table containing the new items
  317. preserve_metadata = function(_, oldnode, oldmeta, drops)
  318. for _,item in ipairs(drops) do
  319. if item:get_name() == oldnode.name then
  320. item:get_meta():from_table({fields=oldmeta});
  321. end
  322. end
  323. end,
  324. });
  325. -- Create an empty map item with the given parameters
  326. --
  327. -- size: The size of the map
  328. -- detail: The detail level of the map
  329. -- scale: The scaling factor of the map
  330. --
  331. -- Returns an ItemStack
  332. local function create_map_item(size, detail, scale)
  333. local map = ItemStack("cartographer:map");
  334. local meta = map:get_meta();
  335. meta:set_int("cartographer:size", size);
  336. meta:set_int("cartographer:detail", detail);
  337. meta:set_int("cartographer:scale", scale);
  338. meta:set_string("description", "Empty Map\nUse to set the initial location");
  339. return map;
  340. end
  341. -- Create a copy of the given map
  342. --
  343. -- stack: An itemstack containing a map
  344. --
  345. -- Returns a new ItemStack with the copied map
  346. local function copy_map_item(stack)
  347. local meta = stack:get_meta();
  348. local size = meta:get_int("cartographer:size");
  349. local detail = meta:get_int("cartographer:detail");
  350. local scale = meta:get_int("cartographer:scale");
  351. local copy = create_map_item(size, detail, scale);
  352. local copy_meta = copy:get_meta();
  353. local id = meta:get_int("cartographer:map_id");
  354. if id > 0 then
  355. local src = maps.get(id);
  356. local new_id = maps.create(src.x, src.z, src.w, src.h, false, src.detail, src.scale);
  357. local dest = maps.get(new_id);
  358. for k,v in pairs(src.fill) do
  359. dest.fill[k] = table.copy(v);
  360. end
  361. for k,v in pairs(src.markers) do
  362. dest.markers[k] = table.copy(v);
  363. end
  364. copy_meta:set_int("cartographer:map_id", new_id);
  365. copy_meta:set_string("description", map_description(new_id,
  366. dest.x, dest.z,
  367. dest.w * dest.scale, dest.h * dest.scale));
  368. end
  369. return copy;
  370. end
  371. -- Resize the given map item
  372. --
  373. -- meta: A metadata object containing the map data
  374. -- size: The new size
  375. local function resize_map_item(meta, size)
  376. local old_size = meta:get_int("cartographer:size");
  377. if old_size >= size then
  378. return;
  379. end
  380. meta:set_int("cartographer:size", size);
  381. local id = meta:get_int("cartographer:map_id");
  382. if id > 0 then
  383. local map = maps.get(id);
  384. map:resize(size, size);
  385. meta:set_string("description", map_description(id, map.x, map.z, map.w * map.scale, map.h * map.scale));
  386. end
  387. end
  388. -- Change the scale of the given map item
  389. --
  390. -- meta: A metadata object containing the map data
  391. -- scale: The new scale
  392. local function rescale_map_item(meta, scale)
  393. local old_scale = meta:get_int("cartographer:scale");
  394. if old_scale >= scale then
  395. return;
  396. end
  397. meta:set_int("cartographer:scale", scale);
  398. local id = meta:get_int("cartographer:map_id");
  399. if id > 0 then
  400. local map = maps.get(id);
  401. map:rescale(scale);
  402. meta:set_string("description", map_description(id, map.x, map.z, map.w * map.scale, map.h * map.scale));
  403. end
  404. end
  405. return {
  406. create = create_map_item,
  407. copy = copy_map_item,
  408. resize = resize_map_item,
  409. rescale = rescale_map_item,
  410. };