init.lua 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394
  1. -- Mod global namespace
  2. map = map or {}
  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. --[[
  125. if action == "take" or action == "put" then
  126. minetest.chat_send_player("MustTest",
  127. "# Server: " .. action .. " in " .. info.index .. ", " .. info.stack:get_name() .. " " .. info.stack:get_count())
  128. end
  129. --]]
  130. if action == "put" or action == "take" then
  131. local name = info.stack:get_name()
  132. if map.is_mapping_kit(name) then
  133. local pname = player:get_player_name()
  134. map.update_inventory_info(pname)
  135. end
  136. elseif action == "move" then
  137. local pname = player:get_player_name()
  138. if not map.players[pname] then
  139. map.update_inventory_info(pname)
  140. end
  141. if info.from_list == "main" then
  142. local from = info.from_index
  143. -- If the moved from slot was listed as holding a mapping kit, need to refresh the cache.
  144. for k, v in ipairs(map.players[pname].indices) do
  145. if from == v then
  146. map.update_inventory_info(pname)
  147. break
  148. end
  149. end
  150. end
  151. if info.to_list == "main" then
  152. -- This is only called when player moves from player-inv to another player-inv.
  153. -- We have to check what item was added.
  154. local stack = inventory:get_stack("main", info.to_index)
  155. if map.is_mapping_kit(stack:get_name()) then
  156. map.update_inventory_info(pname)
  157. end
  158. end
  159. end
  160. end
  161. -- May be called with player object or player name.
  162. -- Return 'true' if the minimap is ENABLED.
  163. function map.update_hud_flags(player)
  164. if type(player) == "string" then
  165. player = minetest.get_player_by_name(player)
  166. end
  167. if not player or not player:is_player() then
  168. return
  169. end
  170. local has_kit = map.has_mapping_kit(player)
  171. local minimap_enabled = has_kit
  172. local radar_enabled = false
  173. -- Map & radar combined into same device.
  174. player:hud_set_flags({
  175. minimap = minimap_enabled,
  176. minimap_radar = minimap_enabled,
  177. })
  178. if minimap_enabled then
  179. return true
  180. end
  181. end
  182. -- May be called with either a player object or a player name.
  183. function map.has_mapping_kit(pname_or_pref)
  184. local pname = pname_or_pref
  185. if type(pname) ~= "string" then
  186. pname = pname_or_pref:get_player_name()
  187. end
  188. -- If data doesn't exist yet, create the cache.
  189. if not map.players[pname] then
  190. map.update_inventory_info(pname)
  191. end
  192. return map.players[pname].has_kit
  193. end
  194. -- Use from /lua command, mainly.
  195. function map.query(pname)
  196. if map.has_mapping_kit(pname) then
  197. minetest.chat_send_player("MustTest", "# Server: Player <" .. rename.gpn(pname) .. "> has a mapping kit!")
  198. else
  199. minetest.chat_send_player("MustTest", "# Server: Player <" .. rename.gpn(pname) .. "> does not have a mapping kit.")
  200. end
  201. end
  202. function map.on_use(itemstack, user, pointed_thing)
  203. map.update_inventory_info(user:get_player_name())
  204. end
  205. function map.on_joinplayer(player)
  206. local pname = player:get_player_name()
  207. minetest.after(3, function()
  208. map.update_inventory_info(pname)
  209. end)
  210. end
  211. function map.on_leaveplayer(player, timeout)
  212. -- Cleanup.
  213. map.players[player:get_player_name()] = nil
  214. end
  215. function map.is_mapping_kit(name)
  216. if name == "map:mapping_kit" or name == "map:mapping_tool" then
  217. return true
  218. end
  219. return false
  220. end
  221. function map.on_place(itemstack, placer, pt)
  222. if not placer or not placer:is_player() then
  223. return
  224. end
  225. if pt.type ~= "node" then
  226. return
  227. end
  228. local node = minetest.get_node(pt.under)
  229. local ndef = minetest.registered_nodes[node.name]
  230. if not ndef then
  231. return
  232. end
  233. if ndef.on_rightclick and not placer:get_player_control().sneak then
  234. return ndef.on_rightclick(pt.under, node, placer, itemstack, pt) or itemstack
  235. end
  236. local fakestack = ItemStack("map:mapping_kit")
  237. local retstack, success = minetest.item_place(fakestack, placer, pt)
  238. if success then
  239. itemstack:take_item()
  240. return itemstack
  241. end
  242. end
  243. -- Set HUD flags 'on joinplayer'
  244. if not map.run_once then
  245. 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."
  246. -- Mapping kit item.
  247. minetest.register_node("map:mapping_kit", {
  248. tiles = {"map_mapping_kit_tile.png"},
  249. wield_image = "map_mapping_kit.png",
  250. description = desc,
  251. inventory_image = "map_mapping_kit.png",
  252. paramtype = 'light',
  253. paramtype2 = "wallmounted",
  254. drawtype = "nodebox",
  255. sunlight_propagates = true,
  256. walkable = false,
  257. node_box = {
  258. type = "wallmounted",
  259. wall_top = {-0.375, 0.4375, -0.5, 0.375, 0.5, 0.5},
  260. wall_bottom = {-0.375, -0.5, -0.5, 0.375, -0.4375, 0.5},
  261. wall_side = {-0.5, -0.5, -0.375, -0.4375, 0.5, 0.375},
  262. },
  263. selection_box = {type = "wallmounted"},
  264. stack_max = 1,
  265. groups = utility.dig_groups("bigitem", {flammable = 3, attached_node = 1}),
  266. sounds = default.node_sound_leaves_defaults(),
  267. drop = "map:mapping_tool",
  268. on_use = function(...)
  269. return map.on_use(...)
  270. end,
  271. })
  272. -- Tool item is required in order for wear-bar to work.
  273. minetest.register_tool("map:mapping_tool", {
  274. description = desc,
  275. inventory_image = "map_mapping_kit.png",
  276. wear_represents = "eu_charge",
  277. groups = {not_repaired_by_anvil = 1, disable_repair = 1},
  278. on_use = function(...)
  279. return map.on_use(...)
  280. end,
  281. on_place = function(...)
  282. return map.on_place(...)
  283. end,
  284. })
  285. -- Crafting.
  286. minetest.register_craft({
  287. output = "map:mapping_tool",
  288. recipe = {
  289. {"default:glass", "plastic:plastic_sheeting", "default:obsidian_shard"},
  290. {"default:steel_ingot", "techcrafts:control_logic_unit", "default:steel_ingot"},
  291. {"fine_wire:silver", "battery:battery", "dusts:diamond"},
  292. }
  293. })
  294. -- Fuel.
  295. minetest.register_craft({
  296. type = "fuel",
  297. recipe = "map:mapping_tool",
  298. burntime = 5,
  299. })
  300. minetest.register_on_player_inventory_action(function(...)
  301. return map.on_player_inventory_action(...) end)
  302. minetest.register_on_joinplayer(function(...)
  303. return map.on_joinplayer(...) end)
  304. minetest.register_on_leaveplayer(function(...)
  305. return map.on_leaveplayer(...) end)
  306. minetest.after(map.time_step, function() map.consume_charge() end)
  307. local c = "map:core"
  308. local f = map.modpath .. "/init.lua"
  309. reload.register_file(c, f, false)
  310. map.run_once = true
  311. end