init.lua 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485
  1. -- This is a mod where functions shared by most machines (tools) are located.
  2. -- This avoides excessive code duplication. Machine tools include centrifuge,
  3. -- electric furnace, extractor, grinder, and others. These machines behave
  4. -- generally in similar ways.
  5. machines = machines or {}
  6. machines.modpath = minetest.get_modpath("machines")
  7. local NEED_LOG = true
  8. local SINGLEPLAYER = minetest.is_singleplayer()
  9. -- API function, for logging machine updates. Needed during debugging.
  10. machines.log_update =
  11. function(pos, name)
  12. if SINGLEPLAYER == true and NEED_LOG == true then
  13. minetest.chat_send_all("# Server: " .. name .. " updates @ " .. rc.pos_to_namestr(pos) .. ".")
  14. end
  15. end
  16. -- Typedata is used when traversing the network, without touching the node.
  17. -- It must contain as much data as needed to get the node even if unloaded.
  18. -- This must be done after node construction.
  19. -- This should also be done when punched, to allow old nodes to be upgraded.
  20. machines.initialize_typedata =
  21. function(pos, name, tier)
  22. local meta = minetest.get_meta(pos)
  23. meta:set_string("technic_machine", "yes")
  24. meta:set_string("technic_type", "tool")
  25. meta:set_string("technic_tier", tier)
  26. meta:set_string("technic_name", name)
  27. end
  28. machines.get_energy =
  29. function(from, to, wanted_charge)
  30. local meta = minetest.get_meta(from)
  31. local node = minetest.get_node(to)
  32. local def = minetest.reg_ns_nodes[node.name]
  33. -- Make sure energy can actually be extracted from the node.
  34. if def and def.on_machine_execute then
  35. local table_in = {
  36. purpose = "retrieve_eu",
  37. }
  38. local table_out = {
  39. wanted_eu = wanted_charge,
  40. }
  41. local traversal = {}
  42. -- Do not process self.
  43. local hash = minetest.hash_node_position(from)
  44. traversal[hash] = 0
  45. def.on_machine_execute(to, table_in, table_out, traversal)
  46. return (table_out.gotten_eu or 0)
  47. end
  48. return 0
  49. end
  50. -- API function. Try to get cooktime for a machine by draining power off the network.
  51. machines.get_cooktime =
  52. function(pos, name_cfg, name_ugp, wanted_time, eu_demand)
  53. local meta = minetest.get_meta(pos)
  54. local inv = meta:get_inventory()
  55. -- By default, machine wants this many EUs per/sec.
  56. local eups = eu_demand
  57. local upglist = inv:get_list(name_ugp) -- Name configurable.
  58. -- Old machines might not have the upgrade inventory.
  59. if upglist then
  60. for k, v in ipairs(upglist) do
  61. if v:get_name() == "battery:battery" and v:get_count() == 1 then
  62. -- Each battery upgrade reduces energy needed by 1/3 of previous.
  63. eups = math.floor((eups / 3) * 2)
  64. end
  65. end
  66. end
  67. local wanted_eu = wanted_time * eups
  68. local tried_once = false
  69. ::try_again::
  70. -- If enough energy is buffered we can grab it & run.
  71. local eu_buffered = meta:get_int("eu_buffered")
  72. if wanted_eu <= eu_buffered then
  73. eu_buffered = eu_buffered - wanted_eu
  74. meta:set_int("eu_buffered", eu_buffered)
  75. return wanted_time
  76. end
  77. if tried_once == false then
  78. -- Otherwise, we have to refill the buffer.
  79. -- Get location of network access.
  80. local netpos = machines.get_adjacent_network_hubs(pos, {"mv"})
  81. -- Energy source(s) found.
  82. for k, v in ipairs(netpos) do
  83. -- Should be enough for at least 1 of even the longest cooking item.
  84. -- If not, how could we fix this? Maybe just reduce the cooktime for the
  85. -- offending item ....
  86. local charge = machines.get_energy(pos, v, 10000)
  87. eu_buffered = eu_buffered + charge
  88. meta:set_int("eu_buffered", eu_buffered)
  89. tried_once = true
  90. goto try_again
  91. end
  92. end
  93. -- No energy obtained. (Machine should fallback to fuel, if any.)
  94. return 0
  95. end
  96. -- API function. Most machines share this function in common.
  97. machines.allow_metadata_inventory_put =
  98. function(pos, listname, index, stack, player, fueltype, fuelname, srcname, dstname, cfgname, upgname)
  99. local pname = player:get_player_name()
  100. if minetest.test_protection(pos, pname) then return 0 end
  101. if listname == fuelname then
  102. if type(fueltype) == "string" then
  103. local cresult = minetest.get_craft_result({
  104. method = fueltype,
  105. width = 1,
  106. items = {stack},
  107. })
  108. if cresult.time ~= 0 then -- Valid fuel.
  109. return stack:get_count()
  110. end
  111. elseif type(fueltype) == "table" then
  112. for k, v in ipairs(fueltype) do
  113. local cresult = minetest.get_craft_result({
  114. method = v,
  115. width = 1,
  116. items = {stack},
  117. })
  118. if cresult.time ~= 0 then -- Valid fuel.
  119. return stack:get_count()
  120. end
  121. end
  122. end
  123. elseif listname == srcname then
  124. return stack:get_count()
  125. elseif dstname and listname == dstname then
  126. return 0
  127. elseif listname == cfgname and stack:get_name() == "cfg:dev" then
  128. return stack:get_count()
  129. elseif listname == upgname and stack:get_name() == "battery:battery" then
  130. return stack:get_count()
  131. end
  132. return 0
  133. end
  134. machines.on_machine_execute =
  135. function(pos, table_in, table_out, traversal)
  136. -- We do not check for recursion depth for crafting machines because
  137. -- these machines cannot be chains, so there is no problem with network size.
  138. -- Do not process this node more than once.
  139. local hash = minetest.hash_node_position(pos)
  140. if traversal[hash] then return end
  141. traversal[hash] = 0
  142. local purpose = table_in.purpose
  143. if purpose == "autostart_trigger" then
  144. local timer = minetest.get_node_timer(pos)
  145. if not timer:is_started() then
  146. timer:start(1.0)
  147. end
  148. end
  149. end
  150. -- API function to get locations of network hubs adjacent to a machine.
  151. -- This function must return a table of all adjacent hubs.
  152. machines.get_adjacent_network_hubs =
  153. function(pos, tiers)
  154. -- If `tiers` is omitted or nil, then all tiers are allowed.
  155. if not tiers then tiers = {"lv", "mv", "hv"} end
  156. -- Return list of discovered network hubs.
  157. local hubs = {}
  158. -- List of valid adjacent locations.
  159. local targets = {
  160. {x=pos.x+1, y=pos.y, z=pos.z},
  161. {x=pos.x-1, y=pos.y, z=pos.z},
  162. {x=pos.x, y=pos.y, z=pos.z+1},
  163. {x=pos.x, y=pos.y, z=pos.z-1},
  164. {x=pos.x, y=pos.y-1, z=pos.z},
  165. {x=pos.x, y=pos.y+1, z=pos.z},
  166. }
  167. -- Get all adjacent nodes once.
  168. local nodes = {}
  169. for k, v in ipairs(targets) do
  170. local node = minetest.get_node(v)
  171. nodes[#nodes+1] = {node=node, pos=v}
  172. end
  173. -- Scan through adjacent nodes and find valid ones.
  174. for j, t in ipairs(tiers) do
  175. for k, v in ipairs(nodes) do
  176. if v.node.name == "switching_station:" .. t then
  177. hubs[#hubs+1] = v.pos
  178. end
  179. end
  180. end
  181. return hubs
  182. end
  183. -- Public API function.
  184. -- Swap node only if current node is not the wanted node.
  185. machines.swap_node = function(pos, name)
  186. local node = minetest.get_node(pos)
  187. if node.name == name then
  188. -- Node not swapped.
  189. return false
  190. end
  191. node.name = name
  192. minetest.swap_node(pos, node)
  193. -- Node swapped.
  194. return true
  195. end
  196. -- Public API function.
  197. -- This function implements unified operation for all machine tools.
  198. -- It is based on furnace cooking code, but concept applies broadly.
  199. machines.on_machine_timer =
  200. function(pos, elapsed, data)
  201. -- Get or inizialize metadata.
  202. local meta = minetest.get_meta(pos)
  203. local fuel_time = (meta:get_float("fuel_time") or 0)
  204. local src_time = (meta:get_float("src_time") or 0)
  205. local fuel_totaltime = (meta:get_float("fuel_totaltime") or 0)
  206. local inv = meta:get_inventory()
  207. local srclist = inv:get_list("src")
  208. local fuellist = inv:get_list("fuel")
  209. -- Begin cooking logic.
  210. -- Check if we have cookable content.
  211. local cooked, aftercooked = minetest.get_craft_result({
  212. method = data.method,
  213. width = 1,
  214. items = srclist,
  215. })
  216. local cookable = true
  217. if cooked.time == 0 then
  218. cookable = false
  219. end
  220. -- Check if we have enough fuel to burn.
  221. if fuel_time < fuel_totaltime then
  222. -- The furnace is currently active and has enough fuel.
  223. fuel_time = fuel_time + 1
  224. -- If there is a cookable item then check if it is ready yet.
  225. if cookable then
  226. src_time = src_time + 1
  227. if src_time >= cooked.time then
  228. -- Place result in dst list if possible.
  229. -- Note that separating recipes have 2 outputs, not 1.
  230. -- Alloying recipes have 2 inputs and 1 output.
  231. if data.method == "separating" then
  232. if inv:room_for_item("dst", cooked.item[1]) and
  233. inv:room_for_item("dst", cooked.item[2]) then
  234. inv:add_item("dst", cooked.item[1])
  235. inv:add_item("dst", cooked.item[2])
  236. inv:set_stack("src", 1, aftercooked.items[1])
  237. end
  238. elseif data.method == "alloying" then
  239. if inv:room_for_item("dst", cooked.item) then
  240. inv:add_item("dst", cooked.item)
  241. inv:set_stack("src", 1, aftercooked.items[1])
  242. inv:set_stack("src", 2, aftercooked.items[2])
  243. end
  244. else
  245. if inv:room_for_item("dst", cooked.item) then
  246. inv:add_item("dst", cooked.item)
  247. inv:set_stack("src", 1, aftercooked.items[1])
  248. end
  249. end
  250. src_time = 0
  251. end
  252. end
  253. else
  254. -- Machine ran out of fuel/energy.
  255. if cookable then
  256. -- Try to get cooktime from energy.
  257. local gotten_cooktime = machines.get_cooktime(pos, 'cfg', 'upg', cooked.time, data.demand)
  258. if gotten_cooktime >= cooked.time then
  259. fuel_totaltime = gotten_cooktime
  260. fuel_time = 0
  261. else
  262. -- We need to get new fuel.
  263. local fuel, afterfuel = minetest.get_craft_result({
  264. method = data.fuel,
  265. width = 1,
  266. items = fuellist,
  267. })
  268. if fuel.time == 0 then
  269. -- No valid fuel in fuel list.
  270. fuel_totaltime = 0
  271. fuel_time = 0
  272. src_time = 0
  273. else
  274. -- Take fuel from fuel list.
  275. inv:set_stack("fuel", 1, afterfuel.items[1])
  276. fuel_totaltime = fuel.time
  277. fuel_time = 0
  278. end
  279. end
  280. else
  281. -- We don't need to get new fuel since there is no cookable item.
  282. fuel_totaltime = 0
  283. fuel_time = 0
  284. src_time = 0
  285. end
  286. end
  287. -- Update formspec, infotext and node.
  288. local formspec = data.form.inactive()
  289. local item_state
  290. local item_percent = 0
  291. if cookable then
  292. item_percent = math.floor(src_time / cooked.time * 100)
  293. if item_percent > 100 then
  294. item_state = "100% (Output Full)"
  295. else
  296. item_state = item_percent .. "%"
  297. end
  298. else
  299. if srclist[1]:is_empty() then
  300. item_state = "Empty"
  301. else
  302. item_state = "Not " .. data.processable
  303. end
  304. end
  305. local fuel_state = "Empty"
  306. local active = "Standby"
  307. local result = false
  308. if fuel_time <= fuel_totaltime and fuel_totaltime ~= 0 then
  309. active = "Active"
  310. local fuel_percent = math.floor(fuel_time / fuel_totaltime * 100)
  311. fuel_state = fuel_percent .. "%"
  312. formspec = data.form.active(fuel_percent, item_percent)
  313. machines.swap_node(pos, data.swap.active)
  314. -- Make sure timer restarts automatically.
  315. result = true
  316. else
  317. if not fuellist[1]:is_empty() then
  318. fuel_state = "0%"
  319. end
  320. machines.swap_node(pos, data.swap.inactive)
  321. -- Stop timer on the inactive furnace.
  322. local timer = minetest.get_node_timer(pos)
  323. timer:stop()
  324. end
  325. -- Compose infotext.
  326. local infotext = data.name .. " (" .. active .. ")\n" ..
  327. "Item: " .. item_state .. "\n" .. "Fuel Burn: " .. fuel_state .. "\n" ..
  328. "Buffered: " .. meta:get_int("eu_buffered") .. " EUs"
  329. -- Set meta values.
  330. meta:set_float("fuel_totaltime", fuel_totaltime)
  331. meta:set_float("fuel_time", fuel_time)
  332. meta:set_float("src_time", src_time)
  333. meta:set_string("formspec", formspec)
  334. meta:set_string("infotext", infotext)
  335. return result
  336. end
  337. -- API constructor for standard cooking/grinding/extracting/etc. machines.
  338. machines.after_place_machine =
  339. function(pos, placer, name, size, form)
  340. local pname = placer:get_player_name()
  341. local meta = minetest.get_meta(pos)
  342. local inv = meta:get_inventory()
  343. inv:set_size('src', size)
  344. inv:set_size('fuel', 1)
  345. inv:set_size('dst', 4)
  346. inv:set_size('cfg', 2)
  347. inv:set_size('upg', 2)
  348. -- Compose infotext.
  349. local infotext = name .. " (Standby)\n" ..
  350. "Item: Empty\nFuel Burn: Empty\n" ..
  351. "Buffered: 0 EUs"
  352. meta:set_string("formspec", form())
  353. meta:set_string("infotext", infotext)
  354. end
  355. -- Deliver EUs to batteries on the network. Return EUs left over.
  356. -- This would be called by machines that produce EUs.
  357. machines.deliver_charge_to_network =
  358. function(from, to, charge)
  359. local node = minetest.get_node(to)
  360. local def = minetest.reg_ns_nodes[node.name]
  361. if def and def.on_machine_execute then
  362. local table_in = {
  363. purpose = "store_eu",
  364. }
  365. local table_out = {
  366. amount_eu = charge,
  367. }
  368. local traversal = {}
  369. -- Do not process self.
  370. local hash = minetest.hash_node_position(from)
  371. traversal[hash] = 0
  372. def.on_machine_execute(to, table_in, table_out, traversal)
  373. return (table_out.amount_eu or 0)
  374. end
  375. -- No network, so all EUs are left over.
  376. return charge
  377. end
  378. if not machines.run_once then
  379. local c = "machines:core"
  380. local f = machines.modpath .. "/init.lua"
  381. reload.register_file(c, f, false)
  382. -- A 'dummy' item that represents 1 unit of atomic energy.
  383. -- This item is never supposed to appear in a player's inventory.
  384. -- It cannot be crafted and should never be loose in the world.
  385. -- We register it so we can use it in machine inventory slots.
  386. minetest.register_craftitem(":atomic:energy", {
  387. description = "Energy",
  388. inventory_image = "electric_ball.png",
  389. -- Engine limit.
  390. stack_max = 65535,
  391. })
  392. dofile(machines.modpath .. "/common.lua")
  393. dofile(machines.modpath .. "/solar.lua")
  394. dofile(machines.modpath .. "/reactor.lua")
  395. dofile(machines.modpath .. "/windy.lua")
  396. dofile(machines.modpath .. "/tide.lua")
  397. dofile(machines.modpath .. "/panel.lua")
  398. dofile(machines.modpath .. "/leecher.lua")
  399. dofile(machines.modpath .. "/charger.lua")
  400. dofile(machines.modpath .. "/workshop.lua")
  401. machines.run_once = true
  402. end