Pipeworks
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.
 
 

473 lines
18 KiB

  1. local S = minetest.get_translator("pipeworks")
  2. local assumed_eye_pos = vector.new(0, 1.5, 0)
  3. local function vector_copy(v)
  4. return { x = v.x, y = v.y, z = v.z }
  5. end
  6. local function delay(x)
  7. return (function() return x end)
  8. end
  9. local function set_wielder_formspec(data, meta)
  10. meta:set_string("formspec",
  11. "invsize[8,"..(6+data.wield_inv_height)..";]"..
  12. "item_image[0,0;1,1;"..data.name_base.."_off]"..
  13. "label[1,0;"..minetest.formspec_escape(data.description).."]"..
  14. "list[current_name;"..minetest.formspec_escape(data.wield_inv_name)..";"..((8-data.wield_inv_width)*0.5)..",1;"..data.wield_inv_width..","..data.wield_inv_height..";]"..
  15. "list[current_player;main;0,"..(2+data.wield_inv_height)..";8,4;]" ..
  16. "listring[]")
  17. meta:set_string("infotext", data.description)
  18. end
  19. local can_tool_dig_node = function(nodename, toolcaps, toolname)
  20. --pipeworks.logger("can_tool_dig_node() STUB nodename="..tostring(nodename).." toolname="..tostring(toolname).." toolcaps: "..dump(toolcaps))
  21. -- brief documentation of minetest.get_dig_params() as it's not yet documented in lua_api.txt:
  22. -- takes two arguments, a node's block groups and a tool's capabilities,
  23. -- both as they appear in their respective definitions.
  24. -- returns a table with the following fields:
  25. -- diggable: boolean, can this tool dig this node at all
  26. -- time: float, time needed to dig with this tool
  27. -- wear: int, number of wear points to inflict on the item
  28. local nodedef = minetest.registered_nodes[nodename]
  29. -- don't explode due to nil def in event of unknown node!
  30. if (nodedef == nil) then return false end
  31. local nodegroups = nodedef.groups
  32. local diggable = minetest.get_dig_params(nodegroups, toolcaps).diggable
  33. if not diggable then
  34. -- a pickaxe can't actually dig leaves based on it's groups alone,
  35. -- but a player holding one can - the game seems to fall back to the hand.
  36. -- fall back to checking the hand's properties if the tool isn't the correct one.
  37. local hand_caps = minetest.registered_items[""].tool_capabilities
  38. diggable = minetest.get_dig_params(nodegroups, hand_caps).diggable
  39. end
  40. return diggable
  41. end
  42. local function wielder_on(data, wielder_pos, wielder_node)
  43. data.fixup_node(wielder_pos, wielder_node)
  44. if wielder_node.name ~= data.name_base.."_off" then return end
  45. wielder_node.name = data.name_base.."_on"
  46. minetest.swap_node(wielder_pos, wielder_node)
  47. minetest.check_for_falling(wielder_pos)
  48. local wielder_meta = minetest.get_meta(wielder_pos)
  49. local inv = wielder_meta:get_inventory()
  50. local wield_inv_name = data.wield_inv_name
  51. local wieldindex
  52. for i, stack in ipairs(inv:get_list(wield_inv_name)) do
  53. if not stack:is_empty() then
  54. wieldindex = i
  55. break
  56. end
  57. end
  58. if not wieldindex then
  59. if not data.ghost_inv_name then return end
  60. wield_inv_name = data.ghost_inv_name
  61. inv:set_stack(wield_inv_name, 1, ItemStack(data.ghost_tool))
  62. wieldindex = 1
  63. end
  64. local dir = minetest.facedir_to_dir(wielder_node.param2)
  65. -- under/above is currently intentionally left switched
  66. -- even though this causes some problems with deployers and e.g. seeds
  67. -- as there are some issues related to nodebreakers otherwise breaking 2 nodes afar.
  68. -- solidity would have to be checked as well,
  69. -- but would open a whole can of worms related to difference in nodebreaker/deployer behavior
  70. -- and the problems of wielders acting on themselves if below is solid
  71. local under_pos = vector.subtract(wielder_pos, dir)
  72. local above_pos = vector.subtract(under_pos, dir)
  73. local pitch
  74. local yaw
  75. if dir.z < 0 then
  76. yaw = 0
  77. pitch = 0
  78. elseif dir.z > 0 then
  79. yaw = math.pi
  80. pitch = 0
  81. elseif dir.x < 0 then
  82. yaw = 3*math.pi/2
  83. pitch = 0
  84. elseif dir.x > 0 then
  85. yaw = math.pi/2
  86. pitch = 0
  87. elseif dir.y > 0 then
  88. yaw = 0
  89. pitch = -math.pi/2
  90. else
  91. yaw = 0
  92. pitch = math.pi/2
  93. end
  94. local virtplayer = pipeworks.create_fake_player({
  95. name = data.masquerade_as_owner and wielder_meta:get_string("owner")
  96. or ":pipeworks:" .. minetest.pos_to_string(wielder_pos),
  97. formspec = wielder_meta:get_string("formspec"),
  98. look_dir = vector.multiply(dir, -1),
  99. look_pitch = pitch,
  100. look_yaw = yaw,
  101. sneak = data.sneak,
  102. position = vector.subtract(wielder_pos, assumed_eye_pos),
  103. inventory = inv,
  104. wield_index = wieldindex,
  105. wield_list = wield_inv_name
  106. })
  107. local pointed_thing = { type="node", under=under_pos, above=above_pos }
  108. data.act(virtplayer, pointed_thing)
  109. if data.eject_drops then
  110. for i, stack in ipairs(inv:get_list("main")) do
  111. if not stack:is_empty() then
  112. pipeworks.tube_inject_item(wielder_pos, wielder_pos, dir, stack)
  113. inv:set_stack("main", i, ItemStack(""))
  114. end
  115. end
  116. end
  117. end
  118. local function wielder_off(data, pos, node)
  119. if node.name == data.name_base.."_on" then
  120. node.name = data.name_base.."_off"
  121. minetest.swap_node(pos, node)
  122. minetest.check_for_falling(pos)
  123. end
  124. end
  125. local function register_wielder(data)
  126. data.fixup_node = data.fixup_node or function (pos, node) end
  127. data.fixup_oldmetadata = data.fixup_oldmetadata or function (m) return m end
  128. for _, state in ipairs({ "off", "on" }) do
  129. local groups = { snappy=2, choppy=2, oddly_breakable_by_hand=2, mesecon=2, tubedevice=1, tubedevice_receiver=1 }
  130. if state == "on" then groups.not_in_creative_inventory = 1 end
  131. local tile_images = {}
  132. for _, face in ipairs({ "top", "bottom", "side2", "side1", "back", "front" }) do
  133. table.insert(tile_images, data.texture_base.."_"..face..(data.texture_stateful[face] and "_"..state or "")..".png")
  134. end
  135. minetest.register_node(data.name_base.."_"..state, {
  136. description = data.description,
  137. tiles = tile_images,
  138. mesecons = {
  139. effector = {
  140. rules = pipeworks.rules_all,
  141. action_on = function (pos, node)
  142. wielder_on(data, pos, node)
  143. end,
  144. action_off = function (pos, node)
  145. wielder_off(data, pos, node)
  146. end,
  147. },
  148. },
  149. tube = {
  150. can_insert = function(pos, node, stack, tubedir)
  151. if not data.tube_permit_anteroposterior_insert then
  152. local nodedir = minetest.facedir_to_dir(node.param2)
  153. if vector.equals(tubedir, nodedir) or vector.equals(tubedir, vector.multiply(nodedir, -1)) then
  154. return false
  155. end
  156. end
  157. local meta = minetest.get_meta(pos)
  158. local inv = meta:get_inventory()
  159. return inv:room_for_item(data.wield_inv_name, stack)
  160. end,
  161. insert_object = function(pos, node, stack, tubedir)
  162. if not data.tube_permit_anteroposterior_insert then
  163. local nodedir = minetest.facedir_to_dir(node.param2)
  164. if vector.equals(tubedir, nodedir) or vector.equals(tubedir, vector.multiply(nodedir, -1)) then
  165. return stack
  166. end
  167. end
  168. local meta = minetest.get_meta(pos)
  169. local inv = meta:get_inventory()
  170. return inv:add_item(data.wield_inv_name, stack)
  171. end,
  172. input_inventory = data.wield_inv_name,
  173. connect_sides = data.tube_connect_sides,
  174. can_remove = function(pos, node, stack, tubedir)
  175. return stack:get_count()
  176. end,
  177. },
  178. is_ground_content = true,
  179. paramtype2 = "facedir",
  180. tubelike = 1,
  181. groups = groups,
  182. sounds = default.node_sound_stone_defaults(),
  183. drop = data.name_base.."_off",
  184. on_construct = function(pos)
  185. local meta = minetest.get_meta(pos)
  186. set_wielder_formspec(data, meta)
  187. local inv = meta:get_inventory()
  188. inv:set_size(data.wield_inv_name, data.wield_inv_width*data.wield_inv_height)
  189. if data.ghost_inv_name then
  190. inv:set_size(data.ghost_inv_name, 1)
  191. end
  192. if data.eject_drops then
  193. inv:set_size("main", 100)
  194. end
  195. end,
  196. after_place_node = function (pos, placer)
  197. pipeworks.scan_for_tube_objects(pos)
  198. local placer_pos = placer:get_pos()
  199. if placer_pos and placer:is_player() then placer_pos = vector.add(placer_pos, assumed_eye_pos) end
  200. if placer_pos then
  201. local dir = vector.subtract(pos, placer_pos)
  202. local node = minetest.get_node(pos)
  203. node.param2 = minetest.dir_to_facedir(dir, true)
  204. minetest.set_node(pos, node)
  205. minetest.log("action", "real (6d) facedir: " .. node.param2)
  206. end
  207. minetest.get_meta(pos):set_string("owner", placer:get_player_name())
  208. end,
  209. can_dig = (data.can_dig_nonempty_wield_inv and delay(true) or function(pos, player)
  210. local meta = minetest.get_meta(pos)
  211. local inv = meta:get_inventory()
  212. return inv:is_empty(data.wield_inv_name)
  213. end),
  214. after_dig_node = function(pos, oldnode, oldmetadata, digger)
  215. -- The legacy-node fixup is done here in a
  216. -- different form from the standard fixup,
  217. -- rather than relying on a standard fixup
  218. -- in an on_dig callback, because some
  219. -- non-standard diggers (such as technic's
  220. -- mining drill) don't respect on_dig.
  221. oldmetadata = data.fixup_oldmetadata(oldmetadata)
  222. for _, stack in ipairs(oldmetadata.inventory[data.wield_inv_name] or {}) do
  223. if not stack:is_empty() then
  224. minetest.add_item(pos, stack)
  225. end
  226. end
  227. pipeworks.scan_for_tube_objects(pos)
  228. end,
  229. on_rotate = pipeworks.on_rotate,
  230. on_punch = data.fixup_node,
  231. allow_metadata_inventory_put = function(pos, listname, index, stack, player)
  232. if not pipeworks.may_configure(pos, player) then return 0 end
  233. return stack:get_count()
  234. end,
  235. allow_metadata_inventory_take = function(pos, listname, index, stack, player)
  236. if not pipeworks.may_configure(pos, player) then return 0 end
  237. return stack:get_count()
  238. end,
  239. allow_metadata_inventory_move = function(pos, from_list, from_index, to_list, to_index, count, player)
  240. if not pipeworks.may_configure(pos, player) then return 0 end
  241. return count
  242. end
  243. })
  244. end
  245. end
  246. if pipeworks.enable_node_breaker then
  247. local data
  248. -- see after end of data table for other use of these variables
  249. local name_base = "pipeworks:nodebreaker"
  250. local wield_inv_name = "pick"
  251. data = {
  252. name_base = name_base,
  253. description = S("Node Breaker"),
  254. texture_base = "pipeworks_nodebreaker",
  255. texture_stateful = { top = true, bottom = true, side2 = true, side1 = true, front = true },
  256. tube_connect_sides = { top=1, bottom=1, left=1, right=1, back=1 },
  257. tube_permit_anteroposterior_insert = false,
  258. wield_inv_name = wield_inv_name,
  259. wield_inv_width = 1,
  260. wield_inv_height = 1,
  261. can_dig_nonempty_wield_inv = true,
  262. ghost_inv_name = "ghost_pick",
  263. ghost_tool = ":", -- hand by default
  264. fixup_node = function (pos, node)
  265. local meta = minetest.get_meta(pos)
  266. local inv = meta:get_inventory()
  267. -- Node breakers predating the visible pick slot
  268. -- may have been partially updated. This code
  269. -- fully updates them. Some have been observed
  270. -- to have no pick slot at all; first add one.
  271. if inv:get_size("pick") ~= 1 then
  272. inv:set_size("pick", 1)
  273. end
  274. -- Originally, they had a ghost pick in a "pick"
  275. -- inventory, no other inventory, and no form.
  276. -- The partial update of early with-form node
  277. -- breaker code gives them "ghost_pick" and "main"
  278. -- inventories, but leaves the old ghost pick in
  279. -- the "pick" inventory, and doesn't add a form.
  280. -- First perform that partial update.
  281. if inv:get_size("ghost_pick") ~= 1 then
  282. inv:set_size("ghost_pick", 1)
  283. inv:set_size("main", 100)
  284. end
  285. -- If the node breaker predates the visible pick
  286. -- slot, which we can detect by it not having a
  287. -- form, then the pick slot needs to be cleared
  288. -- of the old ghost pick.
  289. if (meta:get_string("formspec") or "") == "" then
  290. inv:set_stack("pick", 1, ItemStack(""))
  291. end
  292. -- Finally, unconditionally set the formspec
  293. -- and infotext. This not only makes the
  294. -- pick slot visible for node breakers where
  295. -- it wasn't before; it also updates the form
  296. -- for node breakers that had an older version
  297. -- of the form, and sets infotext where it was
  298. -- missing for early with-form node breakers.
  299. set_wielder_formspec(data, meta)
  300. end,
  301. fixup_oldmetadata = function (oldmetadata)
  302. -- Node breakers predating the visible pick slot,
  303. -- with node form, kept their ghost pick in an
  304. -- inventory named "pick", the same name as the
  305. -- later visible pick slot. The pick must be
  306. -- removed to avoid spilling it.
  307. if not oldmetadata.fields.formspec then
  308. return { inventory = { pick = {} }, fields = oldmetadata.fields }
  309. else
  310. return oldmetadata
  311. end
  312. end,
  313. masquerade_as_owner = true,
  314. sneak = false,
  315. act = function(virtplayer, pointed_thing)
  316. --local dname = "nodebreaker.act() "
  317. local wieldstack = virtplayer:get_wielded_item()
  318. local oldwieldstack = ItemStack(wieldstack)
  319. local on_use = (minetest.registered_items[wieldstack:get_name()] or {}).on_use
  320. if on_use then
  321. --pipeworks.logger(dname.."invoking on_use "..tostring(on_use))
  322. wieldstack = on_use(wieldstack, virtplayer, pointed_thing) or wieldstack
  323. virtplayer:set_wielded_item(wieldstack)
  324. else
  325. local under_node = minetest.get_node(pointed_thing.under)
  326. local def = minetest.registered_nodes[under_node.name]
  327. if not def then
  328. -- do not dig an unknown node
  329. return
  330. end
  331. -- check that the current tool is capable of destroying the
  332. -- target node.
  333. -- if we can't, don't dig, and leave the wield stack unchanged.
  334. -- note that wieldstack:get_tool_capabilities() returns hand
  335. -- properties if the item has none of it's own.
  336. if can_tool_dig_node(under_node.name,
  337. wieldstack:get_tool_capabilities(),
  338. wieldstack:get_name()) then
  339. def.on_dig(pointed_thing.under, under_node, virtplayer)
  340. local sound = def.sounds and def.sounds.dug
  341. if sound then
  342. minetest.sound_play(sound.name,
  343. {pos=pointed_thing.under, gain=sound.gain})
  344. end
  345. wieldstack = virtplayer:get_wielded_item()
  346. else
  347. --pipeworks.logger(dname.."couldn't dig node!")
  348. end
  349. end
  350. local wieldname = wieldstack:get_name()
  351. if wieldname == oldwieldstack:get_name() then
  352. -- don't mechanically wear out tool
  353. if wieldstack:get_count() == oldwieldstack:get_count() and
  354. wieldstack:get_metadata() == oldwieldstack:get_metadata() and
  355. ((minetest.registered_items[wieldstack:get_name()] or {}).wear_represents or "mechanical_wear") == "mechanical_wear" then
  356. virtplayer:set_wielded_item(oldwieldstack)
  357. end
  358. elseif wieldname ~= "" then
  359. -- tool got replaced by something else:
  360. -- treat it as a drop
  361. virtplayer:get_inventory():add_item("main", wieldstack)
  362. virtplayer:set_wielded_item(ItemStack(""))
  363. end
  364. end,
  365. eject_drops = true,
  366. }
  367. register_wielder(data)
  368. minetest.register_craft({
  369. output = "pipeworks:nodebreaker_off",
  370. recipe = {
  371. { "pipeworks:gear", "pipeworks:gear", "pipeworks:gear" },
  372. { "default:stone", "mesecons:piston", "default:stone" },
  373. { "group:wood", "mesecons:mesecon", "group:wood" },
  374. }
  375. })
  376. -- aliases for when someone had technic installed, but then uninstalled it but not pipeworks
  377. minetest.register_alias("technic:nodebreaker_off", "pipeworks:nodebreaker_off")
  378. minetest.register_alias("technic:nodebreaker_on", "pipeworks:nodebreaker_on")
  379. minetest.register_alias("technic:node_breaker_off", "pipeworks:nodebreaker_off")
  380. minetest.register_alias("technic:node_breaker_on", "pipeworks:nodebreaker_on")
  381. -- turn legacy auto-tree-taps into node breakers
  382. dofile(pipeworks.modpath.."/legacy.lua")
  383. -- register LBM for transition to cheaper node breakers
  384. local lbm_id = "pipeworks:refund_node_breaker_pick"
  385. minetest.register_lbm({
  386. name = lbm_id,
  387. label = "Give back mese pick for pre-transition node breakers",
  388. run_at_every_load = false,
  389. nodenames = { name_base.."_on", name_base.."_off" },
  390. action = function(pos, node)
  391. pipeworks.logger(lbm_id.." entry, nodename="..node.name)
  392. local invref = minetest.get_meta(pos):get_inventory()
  393. invref:add_item(wield_inv_name, ItemStack("default:pick_mese"))
  394. end
  395. })
  396. end
  397. if pipeworks.enable_deployer then
  398. register_wielder({
  399. name_base = "pipeworks:deployer",
  400. description = S("Deployer"),
  401. texture_base = "pipeworks_deployer",
  402. texture_stateful = { front = true },
  403. tube_connect_sides = { back=1 },
  404. tube_permit_anteroposterior_insert = true,
  405. wield_inv_name = "main",
  406. wield_inv_width = 3,
  407. wield_inv_height = 3,
  408. can_dig_nonempty_wield_inv = false,
  409. masquerade_as_owner = true,
  410. sneak = false,
  411. act = function(virtplayer, pointed_thing)
  412. local wieldstack = virtplayer:get_wielded_item()
  413. virtplayer:set_wielded_item((minetest.registered_items[wieldstack:get_name()] or {on_place=minetest.item_place}).on_place(wieldstack, virtplayer, pointed_thing) or wieldstack)
  414. end,
  415. eject_drops = false,
  416. })
  417. minetest.register_craft({
  418. output = "pipeworks:deployer_off",
  419. recipe = {
  420. { "group:wood", "default:chest", "group:wood" },
  421. { "default:stone", "mesecons:piston", "default:stone" },
  422. { "default:stone", "mesecons:mesecon", "default:stone" },
  423. }
  424. })
  425. -- aliases for when someone had technic installed, but then uninstalled it but not pipeworks
  426. minetest.register_alias("technic:deployer_off", "pipeworks:deployer_off")
  427. minetest.register_alias("technic:deployer_on", "pipeworks:deployer_on")
  428. end
  429. if pipeworks.enable_dispenser then
  430. register_wielder({
  431. name_base = "pipeworks:dispenser",
  432. description = S("Dispenser"),
  433. texture_base = "pipeworks_dispenser",
  434. texture_stateful = { front = true },
  435. tube_connect_sides = { back=1 },
  436. tube_permit_anteroposterior_insert = true,
  437. wield_inv_name = "main",
  438. wield_inv_width = 3,
  439. wield_inv_height = 3,
  440. can_dig_nonempty_wield_inv = false,
  441. masquerade_as_owner = false,
  442. sneak = true,
  443. act = function(virtplayer, pointed_thing)
  444. local wieldstack = virtplayer:get_wielded_item()
  445. virtplayer:set_wielded_item((minetest.registered_items[wieldstack:get_name()] or
  446. {on_drop=minetest.item_drop}).on_drop(wieldstack, virtplayer, virtplayer:get_pos()) or
  447. wieldstack)
  448. end,
  449. eject_drops = false,
  450. })
  451. minetest.register_craft({
  452. output = "pipeworks:dispenser_off",
  453. recipe = {
  454. { "default:desert_sand", "default:chest", "default:desert_sand" },
  455. { "default:stone", "mesecons:piston", "default:stone" },
  456. { "default:stone", "mesecons:mesecon", "default:stone" },
  457. }
  458. })
  459. end