functions.lua 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391
  1. -- Function tables, per tier.
  2. battery_lv = battery_lv or {}
  3. battery_mv = battery_mv or {}
  4. battery_hv = battery_hv or {}
  5. -- Localize for performance.
  6. local math_floor = math.floor
  7. for k, v in ipairs({
  8. {tier="lv", up="LV", name="Low-Voltage"},
  9. {tier="mv", up="MV", name="Medium-Voltage"},
  10. {tier="hv", up="HV", name="High-Voltage"},
  11. }) do
  12. -- Which function table are we operating on?
  13. local functable = _G["battery_" .. v.tier]
  14. -- Typedata is used when traversing the network, without touching the node.
  15. -- It must contain as much data as needed to get the node even if unloaded.
  16. -- This must be done after node construction.
  17. -- This should also be done when punched, to allow old nodes to be upgraded.
  18. functable.initialize_typedata =
  19. function(pos)
  20. local meta = minetest.get_meta(pos)
  21. meta:set_string("technic_machine", "yes")
  22. meta:set_string("technic_type", "battery")
  23. meta:set_string("technic_tier", v.tier)
  24. -- This will technically be the wrong name, most of the time, but we should
  25. -- only ever use it to look up the node definition in order to get functions.
  26. -- And the functions for arrays 0 through 12 should be all the same.
  27. meta:set_string("technic_name", "battery:array0_" .. v.tier)
  28. end
  29. functable.compose_infotext =
  30. function(pos)
  31. local meta = minetest.get_meta(pos)
  32. local inv = meta:get_inventory()
  33. local chg = meta:get_int("energy")
  34. local max = meta:get_int("max")
  35. local size = inv:get_size("batteries")
  36. local cnt = functable.get_battery_count(inv)
  37. local infotext = v.up .. " Battery Array\n" ..
  38. "Internal Battery Units: " .. cnt .. "/" .. size .. "\n" ..
  39. "Energy: " .. chg .. "/" .. max .. " EUs\n"
  40. if max > 0 then
  41. local percent = math_floor(chg / max * 100)
  42. infotext = infotext .. "Charge: " .. percent .. "%"
  43. else
  44. infotext = infotext .. "Charge: 0%"
  45. end
  46. meta:set_string("infotext", infotext)
  47. end
  48. functable.compose_formspec =
  49. function(pos)
  50. local meta = minetest.get_meta(pos)
  51. local chg, max = functable.get_energy_status(meta)
  52. local charge_desc = v.name .. " Charge Status: " ..
  53. chg .. "/" .. max .. " EUs"
  54. local formspec =
  55. "size[8,8.5]" ..
  56. default.formspec.get_form_colors() ..
  57. default.formspec.get_form_image() ..
  58. default.formspec.get_slot_colors() ..
  59. "label[0,0;" .. minetest.formspec_escape(charge_desc) .. "]" ..
  60. "label[0,0.5;NRAIB (Non-Redundant Array of Independant Batteries)]" ..
  61. "list[context;batteries;0,1;6,2;]" ..
  62. "label[7,0.5;Config]" ..
  63. "list[context;cfg;7,1;1,2;]" ..
  64. "list[current_player;main;0,4.25;8,1;]" ..
  65. "list[current_player;main;0,5.5;8,3;8]" ..
  66. default.get_hotbar_bg(0, 4.25)
  67. meta:set_string("formspec", formspec)
  68. end
  69. functable.update_charge_visual =
  70. function(pos)
  71. local meta = minetest.get_meta(pos)
  72. local chg, max = functable.get_energy_status(meta)
  73. local name = "battery:array0_" .. v.tier
  74. if max > 0 then -- Avoid divide-by-zero.
  75. local percent = math_floor((chg / max) * 100)
  76. local sz = math.ceil(100 / 12)
  77. for i = 0, 12, 1 do
  78. if percent <= sz*i then
  79. name = "battery:array" .. i .. "_" .. v.tier
  80. break
  81. end
  82. end
  83. end
  84. machines.swap_node(pos, name)
  85. end
  86. functable.on_punch =
  87. function(pos, node, puncher, pointed_thing)
  88. functable.initialize_typedata(pos)
  89. functable.trigger_update(pos)
  90. end
  91. functable.can_dig =
  92. function(pos, player)
  93. local meta = minetest.get_meta(pos)
  94. local inv = meta:get_inventory()
  95. return inv:is_empty("batteries") and
  96. inv:is_empty("cfg")
  97. end
  98. functable.allow_metadata_inventory_put =
  99. function(pos, listname, index, stack, player)
  100. local NCONF = "cfg:dev"
  101. local NBATT = "battery:battery"
  102. local pname = player:get_player_name()
  103. if minetest.test_protection(pos, pname) then
  104. return 0
  105. end
  106. if listname == "cfg" and stack:get_name() == NCONF then
  107. return stack:get_count()
  108. elseif listname == "batteries" and stack:get_name() == NBATT then
  109. return stack:get_count()
  110. end
  111. return 0
  112. end
  113. functable.allow_metadata_inventory_move =
  114. function(pos, from_list, from_index, to_list, to_index, count, player)
  115. local meta = minetest.get_meta(pos)
  116. local inv = meta:get_inventory()
  117. local stack = inv:get_stack(from_list, from_index)
  118. return functable.allow_metadata_inventory_put(pos, to_list, to_index, stack, player)
  119. end
  120. functable.allow_metadata_inventory_take =
  121. function(pos, listname, index, stack, player)
  122. local pname = player:get_player_name()
  123. if minetest.test_protection(pos, pname) then
  124. return 0
  125. end
  126. return stack:get_count()
  127. end
  128. functable.get_battery_count =
  129. function(inv)
  130. local batteries = inv:get_list("batteries")
  131. local count = 0
  132. for k, v in ipairs(batteries) do
  133. if v:get_name() == "battery:battery" then
  134. -- Only 1 battery allowed per stack.
  135. count = count + 1
  136. end
  137. end
  138. return count
  139. end
  140. functable.update_maximum_charge =
  141. function(meta)
  142. local count = functable.get_battery_count(meta:get_inventory())
  143. local max
  144. if v.tier == "lv" then
  145. max = count * 10000
  146. elseif v.tier == "mv" then
  147. max = count * 50000
  148. elseif v.tier == "hv" then
  149. max = count * 120000
  150. end
  151. local chg = meta:get_int("energy")
  152. -- Ensure charge isn't over max. This can happen if user removed a battery.
  153. if chg > max then
  154. meta:set_int("energy", max)
  155. end
  156. meta:set_int("max", max)
  157. end
  158. functable.on_timer =
  159. function(pos, elapsed)
  160. machines.log_update(pos, "Battery Array")
  161. local meta = minetest.get_meta(pos)
  162. local current_amount = meta:get_int("energy")
  163. local old_eu_amount = meta:get_int("old_eu")
  164. -- Todo: here, we can respond to changes in EU amount since last update.
  165. meta:set_int("old_eu", current_amount)
  166. -- Needed in case the operator removes or adds a battery.
  167. -- Also, EUs can be added/drained from batteries without going through a distributer.
  168. functable.update_maximum_charge(meta)
  169. functable.update_listeners(pos)
  170. functable.update_charge_visual(pos)
  171. functable.compose_infotext(pos)
  172. functable.compose_formspec(pos)
  173. end
  174. functable.update_listeners =
  175. function(pos)
  176. local table_in = {
  177. purpose = "refresh_eu_status",
  178. }
  179. local table_out = {}
  180. local traversal = {}
  181. -- Do not process self.
  182. local hash = minetest.hash_node_position(pos)
  183. traversal[hash] = 0
  184. -- Update any listening EU observers on the same network tier.
  185. local hubs = machines.get_adjacent_network_hubs(pos, {v.tier})
  186. if hubs then
  187. for k, v in ipairs(hubs) do
  188. local node = minetest.get_node(v)
  189. local def = minetest.reg_ns_nodes[node.name]
  190. if def and def.on_machine_execute then
  191. def.on_machine_execute(v, table_in, table_out, traversal)
  192. end
  193. end
  194. end
  195. end
  196. functable.on_blast =
  197. function(pos)
  198. local drops = {}
  199. default.get_inventory_drops(pos, "batteries", drops)
  200. default.get_inventory_drops(pos, "cfg", drops)
  201. drops[#drops+1] = "battery:array0_" .. v.tier
  202. minetest.remove_node(pos)
  203. return drops
  204. end
  205. functable.on_construct =
  206. function(pos)
  207. functable.initialize_typedata(pos)
  208. local meta = minetest.get_meta(pos)
  209. local inv = meta:get_inventory()
  210. inv:set_size("batteries", 6*2)
  211. inv:set_size("cfg", 2)
  212. functable.update_maximum_charge(meta)
  213. functable.compose_infotext(pos)
  214. functable.compose_formspec(pos)
  215. end
  216. functable.after_place_node =
  217. function(pos, placer, itemstack, pointed_thing)
  218. end
  219. functable.on_metadata_inventory_move =
  220. function(pos)
  221. functable.trigger_update(pos)
  222. end
  223. functable.on_metadata_inventory_put =
  224. function(pos)
  225. functable.trigger_update(pos)
  226. end
  227. functable.on_metadata_inventory_take =
  228. function(pos)
  229. functable.trigger_update(pos)
  230. end
  231. functable.trigger_update =
  232. function(pos)
  233. local timer = minetest.get_node_timer(pos)
  234. if not timer:is_started() then
  235. timer:start(1.0)
  236. end
  237. end
  238. -- Read the current & max charge of the battery, but do not trigger any update.
  239. -- Function shall be used internally ONLY.
  240. functable.get_energy_status =
  241. function(meta)
  242. local chg = meta:get_int("energy")
  243. local max = meta:get_int("max")
  244. return chg, max
  245. end
  246. functable.on_machine_execute =
  247. function(pos, table_in, table_out, traversal)
  248. -- We do not check for recursion depth for battery array boxes because
  249. -- these cannot be chains, so there is no problem with network size.
  250. -- Do not process this node more than once.
  251. local hash = minetest.hash_node_position(pos)
  252. if traversal[hash] then return end
  253. traversal[hash] = 0
  254. local meta = minetest.get_meta(pos)
  255. local purpose = table_in.purpose
  256. if purpose == "get_eu_status" then
  257. local chg, max = functable.get_energy_status(meta)
  258. table_out.eu_chg = (table_out.eu_chg or 0) + chg
  259. table_out.eu_max = (table_out.eu_max or 0) + max
  260. table_out.num_batteries = (table_out.num_batteries or 0) + 1
  261. elseif purpose == "store_eu" then
  262. if table_out.amount_eu <= 0 then return end
  263. local chg = meta:get_int("energy")
  264. local max = meta:get_int("max")
  265. -- Don't trigger an update cascade unless something changed.
  266. if chg < max then
  267. local amount = (table_out.amount_eu or 0)
  268. -- Clamp the amount the battery receives to its max capacity.
  269. -- This allows us to correctly calculate the remaining charge,
  270. -- after putting as much as possible in the battery.
  271. if amount + chg > max then
  272. amount = max - chg
  273. end
  274. if amount >= 1 then
  275. local chg = chg + amount
  276. if chg > max then
  277. chg = max
  278. end
  279. meta:set_int("energy", chg)
  280. functable.trigger_update(pos)
  281. end
  282. -- Calculate remaining charge.
  283. table_out.amount_eu = (table_out.amount_eu or 0) - amount
  284. end
  285. elseif purpose == "retrieve_eu" then
  286. if (table_out.wanted_eu or 0) <= 0 then return end
  287. local wanted_eu = (table_out.wanted_eu or 0)
  288. local current_eu = meta:get_int("energy")
  289. local eu_retrieved = 0
  290. -- Do *not* drain battery if we don't have enough EU. Just return 0.
  291. if current_eu >= wanted_eu then
  292. eu_retrieved = wanted_eu
  293. current_eu = current_eu - wanted_eu
  294. table_out.gotten_eu = (table_out.gotten_eu or 0) + eu_retrieved
  295. table_out.wanted_eu = (table_out.wanted_eu or 0) - eu_retrieved
  296. meta:set_int("energy", current_eu)
  297. end
  298. -- Don't trigger an update cascade unless something changed.
  299. if eu_retrieved > 0 then -- Energy was drained; need update.
  300. functable.trigger_update(pos)
  301. end
  302. end
  303. end
  304. if not battery.functions_loaded then
  305. local c = "battery:core"
  306. local f = battery.modpath .. "/functions.lua"
  307. reload.register_file(c, f, false)
  308. battery.functions_loaded = true
  309. end
  310. end