init.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  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. function map.on_use(itemstack, user, pointed_thing)
  189. map.update_inventory_info(user:get_player_name())
  190. end
  191. function map.on_joinplayer(player)
  192. local pname = player:get_player_name()
  193. minetest.after(3, function()
  194. map.update_inventory_info(pname)
  195. end)
  196. end
  197. function map.on_leaveplayer(player, timeout)
  198. -- Cleanup.
  199. map.players[player:get_player_name()] = nil
  200. end
  201. function map.is_mapping_kit(name)
  202. if name == "map:mapping_kit" or name == "map:mapping_tool" then
  203. return true
  204. end
  205. return false
  206. end
  207. function map.on_place(itemstack, placer, pt)
  208. if not placer or not placer:is_player() then
  209. return
  210. end
  211. if pt.type ~= "node" then
  212. return
  213. end
  214. local pname = placer:get_player_name()
  215. local node = minetest.get_node(pt.under)
  216. local ndef = minetest.registered_nodes[node.name]
  217. if not ndef then
  218. return
  219. end
  220. if ndef.on_rightclick and not placer:get_player_control().sneak then
  221. return ndef.on_rightclick(pt.under, node, placer, itemstack, pt) or itemstack
  222. end
  223. local fakestack = ItemStack("map:mapping_kit")
  224. local retstack, success, position = minetest.item_place(fakestack, placer, pt)
  225. if success then
  226. -- Store wear level in the node.
  227. local meta = minetest.get_meta(position)
  228. meta:set_int("wear", itemstack:get_wear())
  229. itemstack:take_item()
  230. if itemstack:get_count() == 0 then
  231. -- Must take action *after* 'on_place' completes.
  232. minetest.after(0, function() map.update_inventory_info(pname) end)
  233. end
  234. return itemstack
  235. end
  236. end
  237. function map.on_dig(pos, node, digger)
  238. if not digger or not digger:is_player() then
  239. return minetest.node_dig(pos, node, digger)
  240. end
  241. if minetest.is_protected(pos, digger:get_player_name()) then
  242. return false
  243. end
  244. local meta = minetest.get_meta(pos)
  245. local wear = meta:get_int("wear")
  246. local inv = digger:get_inventory()
  247. if inv then
  248. local stack = ItemStack("map:mapping_tool")
  249. stack:set_wear(wear)
  250. local leftover = inv:add_item("main", stack)
  251. minetest.item_drop(leftover, nil, pos)
  252. minetest.remove_node(pos)
  253. return true
  254. end
  255. -- In case of any failure, just dig normally.
  256. return minetest.node_dig(pos, node, digger)
  257. end
  258. -- Set HUD flags 'on joinplayer'
  259. if not map.run_once then
  260. 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."
  261. -- Mapping kit item.
  262. minetest.register_node("map:mapping_kit", {
  263. tiles = {"map_mapping_kit_tile.png"},
  264. wield_image = "map_mapping_kit.png",
  265. description = desc,
  266. inventory_image = "map_mapping_kit.png",
  267. paramtype = 'light',
  268. paramtype2 = "wallmounted",
  269. drawtype = "nodebox",
  270. sunlight_propagates = true,
  271. walkable = false,
  272. node_box = {
  273. type = "wallmounted",
  274. wall_top = {-0.375, 0.4375, -0.5, 0.375, 0.5, 0.5},
  275. wall_bottom = {-0.375, -0.5, -0.5, 0.375, -0.4375, 0.5},
  276. wall_side = {-0.5, -0.5, -0.375, -0.4375, 0.5, 0.375},
  277. },
  278. selection_box = {type = "wallmounted"},
  279. stack_max = 1,
  280. groups = utility.dig_groups("bigitem", {flammable = 3, attached_node = 1}),
  281. sounds = default.node_sound_leaves_defaults(),
  282. drop = "map:mapping_tool",
  283. on_use = function(...)
  284. return map.on_use(...)
  285. end,
  286. on_dig = function(...)
  287. return map.on_dig(...)
  288. end,
  289. })
  290. -- Tool item is required in order for wear-bar to work.
  291. minetest.register_tool("map:mapping_tool", {
  292. description = desc,
  293. inventory_image = "map_mapping_kit.png",
  294. wear_represents = "eu_charge",
  295. groups = {not_repaired_by_anvil = 1, disable_repair = 1},
  296. on_use = function(...)
  297. return map.on_use(...)
  298. end,
  299. on_place = function(...)
  300. return map.on_place(...)
  301. end,
  302. })
  303. -- Crafting.
  304. minetest.register_craft({
  305. output = "map:mapping_tool",
  306. recipe = {
  307. {"default:glass", "plastic:plastic_sheeting", "default:obsidian_shard"},
  308. {"default:steel_ingot", "techcrafts:control_logic_unit", "default:steel_ingot"},
  309. {"fine_wire:silver", "battery:battery", "dusts:diamond"},
  310. }
  311. })
  312. -- Fuel.
  313. minetest.register_craft({
  314. type = "fuel",
  315. recipe = "map:mapping_tool",
  316. burntime = 5,
  317. })
  318. minetest.register_on_player_inventory_action(function(...)
  319. return map.on_player_inventory_action(...) end)
  320. minetest.register_on_joinplayer(function(...)
  321. return map.on_joinplayer(...) end)
  322. minetest.register_on_leaveplayer(function(...)
  323. return map.on_leaveplayer(...) end)
  324. minetest.after(map.time_step, function() map.consume_charge() end)
  325. local c = "map:core"
  326. local f = map.modpath .. "/init.lua"
  327. reload.register_file(c, f, false)
  328. map.run_once = true
  329. end