init.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431
  1. -- Mod global namespace
  2. if not minetest.global_exists("map") then map = {} end
  3. map.modpath = minetest.get_modpath("map")
  4. map.players = map.players or {}
  5. map.time_step = 10
  6. map.charge_time = 60*60 -- 1 hour of continuous use.
  7. -- Localize for performance.
  8. local math_floor = math.floor
  9. -- Shall be called shortly after player joins game, to create the initial cache.
  10. -- Shall also be called whenever inventory is modified in such a way that a mapping kit is moved/added/removed.
  11. -- The cache shall be cleared whenever the player dies.
  12. -- Shall also be called whenever the player modifies their inventory in a way that a mapping kit is changed.
  13. function map.update_inventory_info(pname)
  14. local player = minetest.get_player_by_name(pname)
  15. if not player or not player:is_player() then
  16. return
  17. end
  18. if not map.players[pname] then
  19. map.players[pname] = {has_kit = false, indices={}}
  20. end
  21. local inv = player:get_inventory()
  22. if not inv then
  23. return
  24. end
  25. -- Reset list ID indices array.
  26. map.players[pname].indices = {}
  27. if inv:contains_item("main", "map:mapping_kit") or inv:contains_item("main", "map:mapping_tool") then
  28. local list = inv:get_list("main")
  29. for k, v in ipairs(list) do
  30. if not v:is_empty() then
  31. if map.is_mapping_kit(v:get_name()) then
  32. local wear = v:get_wear()
  33. -- If wear is 0, the tool is not charged, and has never been charged.
  34. -- Ignore discharged mapping kits/tools.
  35. if wear > 0 and wear < 65534 then
  36. table.insert(map.players[pname].indices, k)
  37. elseif wear == 0 and v:get_name() == "map:mapping_kit" then
  38. -- Treat mapping kit as if it is a tool with full charge.
  39. -- The item will be upgraded by the function that actually handles charge draining.
  40. table.insert(map.players[pname].indices, k)
  41. end
  42. end
  43. end
  44. end
  45. end
  46. if #(map.players[pname].indices) > 0 then
  47. -- At least one inventory slot has a mapping kit.
  48. -- Need to check if there's one in the hotbar.
  49. local has_kit = false
  50. local barmax = 8
  51. if minetest.check_player_privs(pname, "big_hotbar") then
  52. barmax = 16
  53. end
  54. for k, v in ipairs(map.players[pname].indices) do
  55. if v >= 1 and v <= barmax then
  56. has_kit = true
  57. break
  58. end
  59. end
  60. map.players[pname].has_kit = has_kit
  61. else
  62. map.players[pname].has_kit = false
  63. end
  64. -- Finally, update the HUD flags on the client.
  65. map.update_hud_flags(player)
  66. end
  67. -- Called from bones code, mainly.
  68. function map.clear_inventory_info(pname)
  69. map.players[pname] = {has_kit = false, indices={}}
  70. end
  71. function map.consume_charge()
  72. for name, data in pairs(map.players) do
  73. if data.has_kit and #data.indices > 0 then
  74. local player = minetest.get_player_by_name(name)
  75. if player and player:is_player() then
  76. local inv = player:get_inventory()
  77. if inv then
  78. local idx = inv:get_size("main") + 1
  79. -- Find first mapping kit.
  80. for _, index in ipairs(data.indices) do
  81. if index < idx then
  82. idx = index
  83. end
  84. end
  85. if idx ~= 0 then
  86. local stack = inv:get_stack("main", idx)
  87. local sn = stack:get_name()
  88. if map.is_mapping_kit(sn) then
  89. local depleted = false
  90. -- Convert nodes to tools.
  91. if sn == "map:mapping_kit" then
  92. stack = ItemStack("map:mapping_tool")
  93. stack:set_wear(1) -- Give full charge.
  94. end
  95. -- Use up charge.
  96. -- Note: we assume the tool has charge, if not, it should not have been in the cache!
  97. local wear = stack:get_wear()
  98. local increment = (65535 / map.charge_time)
  99. wear = wear + (increment * map.time_step)
  100. -- Don't let wear reach max or tool will be destroyed.
  101. if wear >= 65534 then
  102. wear = 65534
  103. depleted = true
  104. end
  105. stack:set_wear(math_floor(wear))
  106. inv:set_stack("main", idx, stack)
  107. -- If this mapping tool has no charge left, update the cache info.
  108. if depleted then
  109. map.update_inventory_info(name)
  110. end
  111. end
  112. end
  113. end
  114. end
  115. end
  116. end
  117. -- Call recursively.
  118. minetest.after(map.time_step, function() map.consume_charge() end)
  119. end
  120. -- Not called when player digs or places node, or if player picks up a dropped item.
  121. -- Is called when an item is dropped/taken from ground, or is moved/taken from chest, etc.
  122. -- Specifically, NOT called when inventory is modified by a process the player did not initiate.
  123. function map.on_player_inventory_action(player, action, inventory, info)
  124. if action == "put" or action == "take" then
  125. local name = info.stack:get_name()
  126. if map.is_mapping_kit(name) then
  127. local pname = player:get_player_name()
  128. map.update_inventory_info(pname)
  129. end
  130. elseif action == "move" then
  131. local pname = player:get_player_name()
  132. if not map.players[pname] then
  133. map.update_inventory_info(pname)
  134. end
  135. if info.from_list == "main" then
  136. local from = info.from_index
  137. -- If the moved from slot was listed as holding a mapping kit, need to refresh the cache.
  138. for k, v in ipairs(map.players[pname].indices) do
  139. if from == v then
  140. map.update_inventory_info(pname)
  141. break
  142. end
  143. end
  144. end
  145. if info.to_list == "main" then
  146. -- This is only called when player moves from player-inv to another player-inv.
  147. -- We have to check what item was added.
  148. local stack = inventory:get_stack("main", info.to_index)
  149. if map.is_mapping_kit(stack:get_name()) then
  150. map.update_inventory_info(pname)
  151. end
  152. end
  153. end
  154. end
  155. -- May be called with player object or player name.
  156. -- Return 'true' if the minimap is ENABLED.
  157. function map.update_hud_flags(player)
  158. if type(player) == "string" then
  159. player = minetest.get_player_by_name(player)
  160. end
  161. if not player or not player:is_player() then
  162. return
  163. end
  164. local has_kit = map.has_mapping_kit(player)
  165. local minimap_enabled = has_kit
  166. local radar_enabled = false
  167. -- Map & radar combined into same device.
  168. player:hud_set_flags({
  169. minimap = minimap_enabled,
  170. minimap_radar = minimap_enabled,
  171. })
  172. if minimap_enabled then
  173. return true
  174. end
  175. end
  176. -- May be called with either a player object or a player name.
  177. function map.has_mapping_kit(pname_or_pref)
  178. local pname = pname_or_pref
  179. if type(pname) ~= "string" then
  180. pname = pname_or_pref:get_player_name()
  181. end
  182. -- If data doesn't exist yet, create the cache.
  183. if not map.players[pname] then
  184. map.update_inventory_info(pname)
  185. end
  186. return map.players[pname].has_kit
  187. end
  188. -- Use from /lua command, mainly.
  189. function map.query(pname)
  190. if map.has_mapping_kit(pname) then
  191. minetest.chat_send_player("MustTest", "# Server: Player <" .. rename.gpn(pname) .. "> has a mapping kit!")
  192. else
  193. minetest.chat_send_player("MustTest", "# Server: Player <" .. rename.gpn(pname) .. "> does not have a mapping kit.")
  194. end
  195. end
  196. function map.on_use(itemstack, user, pointed_thing)
  197. map.update_inventory_info(user:get_player_name())
  198. end
  199. function map.on_joinplayer(player)
  200. local pname = player:get_player_name()
  201. minetest.after(3, function()
  202. map.update_inventory_info(pname)
  203. end)
  204. end
  205. function map.on_leaveplayer(player, timeout)
  206. -- Cleanup.
  207. map.players[player:get_player_name()] = nil
  208. end
  209. function map.is_mapping_kit(name)
  210. if name == "map:mapping_kit" or name == "map:mapping_tool" then
  211. return true
  212. end
  213. return false
  214. end
  215. function map.on_place(itemstack, placer, pt)
  216. if not placer or not placer:is_player() then
  217. return
  218. end
  219. if pt.type ~= "node" then
  220. return
  221. end
  222. local pname = placer:get_player_name()
  223. local node = minetest.get_node(pt.under)
  224. local ndef = minetest.registered_nodes[node.name]
  225. if not ndef then
  226. return
  227. end
  228. if ndef.on_rightclick and not placer:get_player_control().sneak then
  229. return ndef.on_rightclick(pt.under, node, placer, itemstack, pt) or itemstack
  230. end
  231. local fakestack = ItemStack("map:mapping_kit")
  232. local retstack, success, position = minetest.item_place(fakestack, placer, pt)
  233. if success then
  234. -- Store wear level in the node.
  235. local meta = minetest.get_meta(position)
  236. meta:set_int("wear", itemstack:get_wear())
  237. itemstack:take_item()
  238. if itemstack:get_count() == 0 then
  239. -- Must take action *after* 'on_place' completes.
  240. minetest.after(0, function() map.update_inventory_info(pname) end)
  241. end
  242. return itemstack
  243. end
  244. end
  245. function map.on_dig(pos, node, digger)
  246. if not digger or not digger:is_player() then
  247. return minetest.node_dig(pos, node, digger)
  248. end
  249. if minetest.is_protected(pos, digger:get_player_name()) then
  250. return false
  251. end
  252. local meta = minetest.get_meta(pos)
  253. local wear = meta:get_int("wear")
  254. local inv = digger:get_inventory()
  255. if inv then
  256. local stack = ItemStack("map:mapping_tool")
  257. stack:set_wear(wear)
  258. local leftover = inv:add_item("main", stack)
  259. minetest.item_drop(leftover, nil, pos)
  260. minetest.remove_node(pos)
  261. return true
  262. end
  263. -- In case of any failure, just dig normally.
  264. return minetest.node_dig(pos, node, digger)
  265. end
  266. -- Set HUD flags 'on joinplayer'
  267. if not map.run_once then
  268. local desc = "Stone Mapper Uplink\n\nAllows viewing a map of your surroundings.\nKeep in your hotbar and use with the 'minimap' key (default F9).\nMust be charged to operate."
  269. -- Mapping kit item.
  270. minetest.register_node("map:mapping_kit", {
  271. tiles = {"map_mapping_kit_tile.png"},
  272. wield_image = "map_mapping_kit.png",
  273. description = desc,
  274. inventory_image = "map_mapping_kit.png",
  275. paramtype = 'light',
  276. paramtype2 = "wallmounted",
  277. drawtype = "nodebox",
  278. sunlight_propagates = true,
  279. walkable = false,
  280. node_box = {
  281. type = "wallmounted",
  282. wall_top = {-0.375, 0.4375, -0.5, 0.375, 0.5, 0.5},
  283. wall_bottom = {-0.375, -0.5, -0.5, 0.375, -0.4375, 0.5},
  284. wall_side = {-0.5, -0.5, -0.375, -0.4375, 0.5, 0.375},
  285. },
  286. selection_box = {type = "wallmounted"},
  287. stack_max = 1,
  288. groups = utility.dig_groups("bigitem", {flammable = 3, attached_node = 1}),
  289. sounds = default.node_sound_leaves_defaults(),
  290. drop = "map:mapping_tool",
  291. on_use = function(...)
  292. return map.on_use(...)
  293. end,
  294. on_dig = function(...)
  295. return map.on_dig(...)
  296. end,
  297. })
  298. -- Tool item is required in order for wear-bar to work.
  299. minetest.register_tool("map:mapping_tool", {
  300. description = desc,
  301. inventory_image = "map_mapping_kit.png",
  302. wear_represents = "eu_charge",
  303. groups = {not_repaired_by_anvil = 1, disable_repair = 1},
  304. on_use = function(...)
  305. return map.on_use(...)
  306. end,
  307. on_place = function(...)
  308. return map.on_place(...)
  309. end,
  310. })
  311. -- Crafting.
  312. minetest.register_craft({
  313. output = "map:mapping_tool",
  314. recipe = {
  315. {"default:glass", "plastic:plastic_sheeting", "default:obsidian_shard"},
  316. {"default:steel_ingot", "techcrafts:control_logic_unit", "default:steel_ingot"},
  317. {"fine_wire:silver", "battery:battery", "dusts:diamond"},
  318. }
  319. })
  320. -- Fuel.
  321. minetest.register_craft({
  322. type = "fuel",
  323. recipe = "map:mapping_tool",
  324. burntime = 5,
  325. })
  326. minetest.register_on_player_inventory_action(function(...)
  327. return map.on_player_inventory_action(...) end)
  328. minetest.register_on_joinplayer(function(...)
  329. return map.on_joinplayer(...) end)
  330. minetest.register_on_leaveplayer(function(...)
  331. return map.on_leaveplayer(...) end)
  332. minetest.after(map.time_step, function() map.consume_charge() end)
  333. local c = "map:core"
  334. local f = map.modpath .. "/init.lua"
  335. reload.register_file(c, f, false)
  336. map.run_once = true
  337. end