stat2.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615
  1. if not minetest.global_exists("stat2") then stat2 = {} end
  2. if not minetest.global_exists("stat2_hv") then stat2_hv = {} end
  3. if not minetest.global_exists("stat2_mv") then stat2_mv = {} end
  4. if not minetest.global_exists("stat2_lv") then stat2_lv = {} end
  5. -- It shall not be possible for a network to span more than 2000
  6. -- meters (2 kilometers) in any of the 3 dimensions. This allows us
  7. -- to optimize caches by their location in the world. Max is 64!
  8. function stat2.chain_limit(tier)
  9. if tier == "lv" then
  10. return 16
  11. elseif tier == "mv" then
  12. return 32
  13. elseif tier == "hv" then
  14. return 64
  15. end
  16. return 0
  17. end
  18. local direction_rules = {
  19. ["n"] = "z",
  20. ["s"] = "z",
  21. ["w"] = "x",
  22. ["e"] = "x",
  23. ["u"] = "y",
  24. ["d"] = "y",
  25. }
  26. -- Invalidate all nearby hubs from a position: NSEW, UD.
  27. -- This should cause them to re-update their routing.
  28. -- This would need to be done if a hub or cable is removed.
  29. stat2.invalidate_hubs =
  30. function(pos, tier)
  31. for m, n in ipairs({
  32. {d="n"},
  33. {d="s"},
  34. {d="e"},
  35. {d="w"},
  36. {d="u"},
  37. {d="d"},
  38. }) do
  39. local p = stat2.find_hub(pos, n.d, tier)
  40. if p then
  41. local meta = minetest.get_meta(p)
  42. for i, j in ipairs({
  43. {m="np"},
  44. {m="sp"},
  45. {m="ep"},
  46. {m="wp"},
  47. {m="up"},
  48. {m="dp"},
  49. }) do
  50. meta:set_string(j.m, "DUMMY")
  51. local owner = meta:get_string("owner")
  52. net2.clear_caches(p, owner, tier)
  53. end
  54. -- Trigger node update.
  55. _G["stat2_" .. tier].trigger_update(p)
  56. end
  57. end
  58. end
  59. -- Find a network hub, starting from a position and continuing in a direction.
  60. -- Cable nodes (with the right axis) may intervene between hubs.
  61. -- This function must return the position of the next hub, if possible, or nil.
  62. stat2.find_hub =
  63. function(pos, dir, tier)
  64. local meta = minetest.get_meta(pos)
  65. local p = vector.new(pos.x, pos.y, pos.z)
  66. local d = stat2.direction_to_vector(dir)
  67. local station_name = "stat2:" .. tier
  68. local cable_name = "cb2:" .. tier
  69. -- Max cable length. +1 because a switching station takes up 1 meter of length.
  70. local cable_length = cable.get_max_length(tier)+1
  71. -- Seek a limited number of meters in a direction.
  72. for i = 1, cable_length, 1 do
  73. p = vector.add(p, d)
  74. local node = minetest.get_node(p)
  75. if node.name == station_name then
  76. -- Compatible switching station found!
  77. return p
  78. elseif node.name == cable_name then
  79. -- It's a cable node. We need to check its rotation.
  80. local paxis = stat2.cable_rotation_to_axis(node.param2)
  81. if paxis then
  82. local daxis = direction_rules[dir]
  83. if not daxis or paxis ~= daxis then
  84. -- Cable has bad axis. Stop scanning.
  85. return nil
  86. end
  87. else
  88. -- Invalid param2. We stop scanning.
  89. return nil
  90. end
  91. -- Unless these items can automatically update switching stations when removed, we can't allow this.
  92. --elseif minetest.get_item_group(node.name, "conductor") > 0 and minetest.get_item_group(node.name, "block") > 0 then
  93. -- Anything that is both in group `conductor` and `block` is treated as a cable node.
  94. -- This allows cables to pass through walls without requiring ugly holes.
  95. else
  96. -- Anything other than a cable node or switching station blocks search.
  97. return nil
  98. end
  99. end
  100. return nil
  101. end
  102. -- Get a unit vector from a cardinal direction, or up/down.
  103. stat2.direction_to_vector =
  104. function(dir)
  105. local d = vector.new(0, 0, 0)
  106. if dir == "n" then
  107. d.z = 1
  108. elseif dir == "s" then
  109. d.z = -1
  110. elseif dir == "w" then
  111. d.x = -1
  112. elseif dir == "e" then
  113. d.x = 1
  114. elseif dir == "u" then
  115. d.y = 1
  116. elseif dir == "d" then
  117. d.y = -1
  118. else
  119. return nil
  120. end
  121. return d
  122. end
  123. local param2_rules = {
  124. [0] = "x",
  125. [1] = "z",
  126. [2] = "x",
  127. [3] = "z",
  128. [4] = "x",
  129. [5] = "y",
  130. [6] = "x",
  131. [7] = "y",
  132. [8] = "x",
  133. [9] = "y",
  134. [10] = "x",
  135. [11] = "y",
  136. [12] = "y",
  137. [13] = "z",
  138. [14] = "y",
  139. [15] = "z",
  140. [16] = "y",
  141. [17] = "z",
  142. [18] = "y",
  143. [19] = "z",
  144. [20] = "x",
  145. [21] = "z",
  146. [22] = "x",
  147. [23] = "z",
  148. }
  149. -- Get cable axis alignment from a param2 value.
  150. -- Cable nodes must have nodeboxes that match this.
  151. -- All cable nodes must align to the same axis.
  152. stat2.cable_rotation_to_axis =
  153. function(param2)
  154. if param2 >= 0 and param2 <= 23 then
  155. return param2_rules[param2]
  156. end
  157. end
  158. stat2.refresh_hubs =
  159. function(pos, tier)
  160. local meta = minetest.get_meta(pos)
  161. local owner = meta:get_string("owner")
  162. local changed = false
  163. for k, v in ipairs({
  164. {n="n", m="np"},
  165. {n="s", m="sp"},
  166. {n="e", m="ep"},
  167. {n="w", m="wp"},
  168. {n="u", m="up"},
  169. {n="d", m="dp"},
  170. }) do
  171. local p = stat2.find_hub(pos, v.n, tier)
  172. local p2 = meta:get_string(v.m)
  173. local p3 = ""
  174. if p then
  175. p3 = minetest.pos_to_string(p)
  176. end
  177. if p2 ~= p3 then
  178. changed = true
  179. -- Something changed, so we'll need to clear the caches.
  180. end
  181. if p then
  182. local m = minetest.get_meta(p)
  183. if m:get_string("owner") == owner then
  184. meta:set_string(v.m, minetest.pos_to_string(p))
  185. else
  186. meta:set_string(v.m, "DUMMY")
  187. end
  188. else
  189. meta:set_string(v.m, "DUMMY")
  190. end
  191. end
  192. if changed then
  193. net2.clear_caches(pos, owner, tier)
  194. nodestore.update_hub_info(pos)
  195. end
  196. end
  197. -- Create copies of these functions by tier.
  198. for k, v in ipairs({
  199. {tier="hv", up="HV"},
  200. {tier="mv", up="MV"},
  201. {tier="lv", up="LV"},
  202. }) do
  203. -- Which function table are we operating on?
  204. local functable = _G["stat2_" .. v.tier]
  205. functable.update_infotext_and_formspec =
  206. function(pos)
  207. local meta = minetest.get_meta(pos)
  208. local formspec =
  209. "size[6,4.5]" ..
  210. default.formspec.get_form_colors() ..
  211. default.formspec.get_form_image() ..
  212. default.formspec.get_slot_colors()
  213. formspec = formspec ..
  214. "label[0,0.5;Routing Controls]"
  215. local routing = "Routing: ["
  216. for m, n in ipairs({
  217. {d="n", n="N", x=0, y=1, m="np", e="ne"},
  218. {d="s", n="S", x=1, y=1, m="sp", e="se"},
  219. {d="e", n="E", x=2, y=1, m="ep", e="ee"},
  220. {d="w", n="W", x=3, y=1, m="wp", e="we"},
  221. {d="u", n="U", x=4, y=1, m="up", e="ue"},
  222. {d="d", n="D", x=5, y=1, m="dp", e="de"},
  223. }) do
  224. -- Determine valid routing values.
  225. local p = minetest.string_to_pos(meta:get_string(n.m))
  226. local c = meta:get_int(n.e)
  227. local e = ""
  228. local r = ""
  229. local x = "x"
  230. if not p then
  231. e = "!"
  232. end
  233. if c == 1 then
  234. x = "o"
  235. r = n.n
  236. if not p then
  237. r = "!"
  238. end
  239. end
  240. routing = routing .. r
  241. formspec = formspec ..
  242. "button[" .. n.x .. "," .. n.y .. ";1,1;" ..
  243. n.d .. ";" .. n.n .. " (" .. x .. e .. ")]"
  244. end
  245. routing = routing .. "]"
  246. formspec = formspec ..
  247. "label[0,2.5;" .. minetest.formspec_escape(routing) .. "]" ..
  248. "button[0,3;3,1;autoconf;Auto Route & Prune]"
  249. local infotext = v.up .. " Cable Box\n" .. routing
  250. meta:set_string("formspec", formspec)
  251. meta:set_string("infotext", infotext)
  252. end
  253. functable.trigger_update =
  254. function(pos)
  255. local timer = minetest.get_node_timer(pos)
  256. if not timer:is_started() then
  257. timer:start(1.0)
  258. end
  259. local meta = minetest.get_meta(pos)
  260. meta:set_int("needupdate", 1)
  261. end
  262. functable.on_punch =
  263. function(pos, node, puncher, pointed_thing)
  264. functable.trigger_update(pos)
  265. functable.update_infotext_and_formspec(pos)
  266. local meta = minetest.get_meta(pos)
  267. local owner = meta:get_string("owner")
  268. if owner == "" then
  269. meta:set_string("owner", puncher:get_player_name())
  270. end
  271. functable.privatize(meta)
  272. end
  273. functable.can_dig =
  274. function(pos, player)
  275. return true
  276. end
  277. functable.on_metadata_inventory_move =
  278. function(pos, from_list, from_index, to_list, to_index, count, player)
  279. functable.trigger_update(pos)
  280. end
  281. functable.on_metadata_inventory_put =
  282. function(pos, listname, index, stack, player)
  283. functable.trigger_update(pos)
  284. end
  285. functable.on_metadata_inventory_take =
  286. function(pos, listname, index, stack, player)
  287. functable.trigger_update(pos)
  288. end
  289. functable.allow_metadata_inventory_put =
  290. function(pos, listname, index, stack, player)
  291. return 0
  292. end
  293. functable.allow_metadata_inventory_move =
  294. function(pos, from_list, from_index, to_list, to_index, count, player)
  295. return 0
  296. end
  297. functable.allow_metadata_inventory_take =
  298. function(pos, listname, index, stack, player)
  299. return 0
  300. end
  301. functable.on_construct =
  302. function(pos)
  303. local meta = minetest.get_meta(pos)
  304. local inv = meta:get_inventory()
  305. inv:set_size("buffer", 1)
  306. meta:set_string("np", "DUMMY")
  307. meta:set_string("sp", "DUMMY")
  308. meta:set_string("ep", "DUMMY")
  309. meta:set_string("wp", "DUMMY")
  310. meta:set_string("up", "DUMMY")
  311. meta:set_string("dp", "DUMMY")
  312. meta:set_string("ne", "DUMMY")
  313. meta:set_string("se", "DUMMY")
  314. meta:set_string("ee", "DUMMY")
  315. meta:set_string("we", "DUMMY")
  316. meta:set_string("ue", "DUMMY")
  317. meta:set_string("de", "DUMMY")
  318. meta:set_string("owner", "DUMMY")
  319. meta:set_string("nodename", "DUMMY")
  320. meta:set_int("needupdate", 0)
  321. functable.privatize(meta)
  322. end
  323. functable.privatize = function(meta)
  324. meta:mark_as_private({
  325. "needupdate",
  326. "owner",
  327. "nodename",
  328. "np", "sp", "wp", "ep", "up", "dp",
  329. "ne", "se", "we", "ee", "ue", "de",
  330. })
  331. end
  332. functable.on_destruct =
  333. function(pos)
  334. local meta = minetest.get_meta(pos)
  335. local owner = meta:get_string("owner")
  336. stat2.invalidate_hubs(pos, v.tier)
  337. net2.clear_caches(pos, owner, v.tier)
  338. nodestore.del_node(pos)
  339. end
  340. functable.on_blast =
  341. function(pos, intensity)
  342. local drops = {}
  343. drops[#drops+1] = "stat2:" .. v.tier
  344. minetest.remove_node(pos)
  345. return drops
  346. end
  347. functable.on_timer =
  348. function(pos, elapsed)
  349. local meta = minetest.get_meta(pos)
  350. if meta:get_int("needupdate") == 1 then
  351. stat2.refresh_hubs(pos, v.tier)
  352. functable.update_infotext_and_formspec(pos)
  353. meta:set_int("needupdate", 0)
  354. end
  355. end
  356. functable.after_place_node =
  357. function(pos, placer, itemstack, pointed_thing)
  358. local meta = minetest.get_meta(pos)
  359. meta:set_string("owner", placer:get_player_name())
  360. meta:set_string("nodename", minetest.get_node(pos).name)
  361. -- All routing allowed by default.
  362. -- But player can still manually configure.
  363. -- This gives most options with least hassle.
  364. for k, v in ipairs({
  365. {m="ne"},
  366. {m="se"},
  367. {m="ee"},
  368. {m="we"},
  369. {m="ue"},
  370. {m="de"},
  371. }) do
  372. meta:set_int(v.m, 1)
  373. end
  374. local owner = meta:get_string("owner")
  375. stat2.invalidate_hubs(pos, v.tier)
  376. stat2.refresh_hubs(pos, v.tier)
  377. functable.update_infotext_and_formspec(pos)
  378. net2.clear_caches(pos, owner, v.tier)
  379. nodestore.add_node(pos)
  380. end
  381. functable.on_receive_fields =
  382. function(pos, formname, fields, sender)
  383. local pname = sender:get_player_name()
  384. local meta = minetest.get_meta(pos)
  385. local owner = meta:get_string("owner")
  386. if fields.quit then
  387. return
  388. end
  389. -- Check authorization.
  390. if pname ~= owner and not gdac.player_is_admin(pname) then
  391. minetest.chat_send_player(pname, "# Server: No authorization to modify cable box.")
  392. easyvend.sound_error(pname)
  393. return
  394. end
  395. for k, v in ipairs({
  396. {d="n", m="np", e="ne"},
  397. {d="s", m="sp", e="se"},
  398. {d="e", m="ep", e="ee"},
  399. {d="w", m="wp", e="we"},
  400. {d="u", m="up", e="ue"},
  401. {d="d", m="dp", e="de"},
  402. }) do
  403. if fields[v.d] then
  404. local enabled = meta:get_int(v.e)
  405. if enabled == 0 then
  406. enabled = 1
  407. else
  408. enabled = 0
  409. end
  410. meta:set_int(v.e, enabled)
  411. end
  412. end
  413. if fields.autoconf then
  414. for k, v in ipairs({
  415. {n="ne", m="np"},
  416. {n="se", m="sp"},
  417. {n="ee", m="ep"},
  418. {n="we", m="wp"},
  419. {n="ue", m="up"},
  420. {n="de", m="dp"},
  421. }) do
  422. local p = minetest.string_to_pos(meta:get_string(v.m))
  423. if p then
  424. meta:set_int(v.n, 1)
  425. else
  426. meta:set_int(v.n, 0)
  427. end
  428. end
  429. meta:set_int("needupdate", 1)
  430. end
  431. functable.update_infotext_and_formspec(pos)
  432. functable.trigger_update(pos)
  433. end
  434. end
  435. -- One-time initialization.
  436. if not stat2.run_once then
  437. for k, v in ipairs({
  438. {tier="hv", name="HV"},
  439. {tier="mv", name="MV"},
  440. {tier="lv", name="LV"},
  441. }) do
  442. -- Which function table are we operating on?
  443. local functable = _G["stat2_" .. v.tier]
  444. local nodebox = {
  445. {2, 2, 2, 14, 14, 14}, -- Inner box.
  446. -- Front face.
  447. {0, 0, 0, 4, 16, 4},
  448. {12, 0, 0, 16, 16, 4},
  449. {0, 12, 0, 16, 16, 4},
  450. {0, 0, 0, 16, 4, 4},
  451. -- Back face.
  452. {0, 0, 12, 4, 16, 16},
  453. {12, 0, 12, 16, 16, 16},
  454. {0, 12, 12, 16, 16, 16},
  455. {0, 0, 12, 16, 4, 16},
  456. -- Left face.
  457. {0, 12, 0, 4, 16, 16},
  458. {0, 0, 0, 4, 4, 16},
  459. -- Right face.
  460. {12, 12, 0, 16, 16, 16},
  461. {12, 0, 0, 16, 4, 16},
  462. }
  463. for k, v in ipairs(nodebox) do
  464. for m, n in ipairs(v) do
  465. local p = nodebox[k][m]
  466. p = p / 16
  467. p = p - 0.5
  468. nodebox[k][m] = p
  469. end
  470. end
  471. minetest.register_node(":stat2:" .. v.tier, {
  472. drawtype = "nodebox",
  473. description = v.name .. " Cable Box\n\nBox resistance limit: " .. stat2.chain_limit(v.tier) .. ".\nCables do not turn corners by themselves.\nCable boxes allow them to do so.",
  474. tiles = {"switching_station_" .. v.tier .. ".png"},
  475. groups = utility.dig_groups("machine"),
  476. node_box = {
  477. type = "fixed",
  478. fixed = nodebox,
  479. },
  480. selection_box = {
  481. type = "fixed",
  482. fixed = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
  483. },
  484. paramtype = "light",
  485. paramtype2 = "facedir",
  486. is_ground_content = false,
  487. sounds = default.node_sound_metal_defaults(),
  488. on_rotate = function(...)
  489. return screwdriver.rotate_simple(...) end,
  490. on_punch = function(...)
  491. return functable.on_punch(...) end,
  492. can_dig = function(...)
  493. return functable.can_dig(...) end,
  494. on_timer = function(...)
  495. return functable.on_timer(...) end,
  496. on_construct = function(...)
  497. return functable.on_construct(...) end,
  498. after_place_node = function(...)
  499. return functable.after_place_node(...) end,
  500. on_blast = function(...)
  501. return functable.on_blast(...) end,
  502. on_destruct = function(...)
  503. return functable.on_destruct(...) end,
  504. on_metadata_inventory_move = function(...)
  505. return functable.on_metadata_inventory_move(...) end,
  506. on_metadata_inventory_put = function(...)
  507. return functable.on_metadata_inventory_put(...) end,
  508. on_metadata_inventory_take = function(...)
  509. return functable.on_metadata_inventory_take(...) end,
  510. allow_metadata_inventory_put = function(...)
  511. return functable.allow_metadata_inventory_put(...) end,
  512. allow_metadata_inventory_move = function(...)
  513. return functable.allow_metadata_inventory_move(...) end,
  514. allow_metadata_inventory_take = function(...)
  515. return functable.allow_metadata_inventory_take(...) end,
  516. on_receive_fields = function(...)
  517. return functable.on_receive_fields(...) end,
  518. })
  519. end
  520. local c = "stat2:core"
  521. local f = switching_station.modpath .. "/stat2.lua"
  522. reload.register_file(c, f, false)
  523. stat2.run_once = true
  524. end