stat2.lua 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618
  1. stat2 = stat2 or {}
  2. stat2_hv = stat2_hv or {}
  3. stat2_mv = stat2_mv or {}
  4. stat2_lv = stat2_lv or {}
  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. --minetest.chat_send_player("MustTest", "# Server: Cable box @ " .. minetest.pos_to_string(pos) .. " punched!")
  265. functable.trigger_update(pos)
  266. functable.update_infotext_and_formspec(pos)
  267. local meta = minetest.get_meta(pos)
  268. local owner = meta:get_string("owner")
  269. if owner == "" then
  270. meta:set_string("owner", puncher:get_player_name())
  271. end
  272. functable.privatize(meta)
  273. end
  274. functable.can_dig =
  275. function(pos, player)
  276. return true
  277. end
  278. functable.on_metadata_inventory_move =
  279. function(pos, from_list, from_index, to_list, to_index, count, player)
  280. functable.trigger_update(pos)
  281. end
  282. functable.on_metadata_inventory_put =
  283. function(pos, listname, index, stack, player)
  284. functable.trigger_update(pos)
  285. end
  286. functable.on_metadata_inventory_take =
  287. function(pos, listname, index, stack, player)
  288. functable.trigger_update(pos)
  289. end
  290. functable.allow_metadata_inventory_put =
  291. function(pos, listname, index, stack, player)
  292. return 0
  293. end
  294. functable.allow_metadata_inventory_move =
  295. function(pos, from_list, from_index, to_list, to_index, count, player)
  296. return 0
  297. end
  298. functable.allow_metadata_inventory_take =
  299. function(pos, listname, index, stack, player)
  300. return 0
  301. end
  302. functable.on_construct =
  303. function(pos)
  304. local meta = minetest.get_meta(pos)
  305. local inv = meta:get_inventory()
  306. inv:set_size("buffer", 1)
  307. meta:set_string("np", "DUMMY")
  308. meta:set_string("sp", "DUMMY")
  309. meta:set_string("ep", "DUMMY")
  310. meta:set_string("wp", "DUMMY")
  311. meta:set_string("up", "DUMMY")
  312. meta:set_string("dp", "DUMMY")
  313. meta:set_string("ne", "DUMMY")
  314. meta:set_string("se", "DUMMY")
  315. meta:set_string("ee", "DUMMY")
  316. meta:set_string("we", "DUMMY")
  317. meta:set_string("ue", "DUMMY")
  318. meta:set_string("de", "DUMMY")
  319. meta:set_string("owner", "DUMMY")
  320. meta:set_string("nodename", "DUMMY")
  321. meta:set_int("needupdate", 0)
  322. functable.privatize(meta)
  323. end
  324. functable.privatize = function(meta)
  325. --minetest.chat_send_player("MustTest", "# Server: Privatizing!")
  326. meta:mark_as_private({
  327. "needupdate",
  328. "owner",
  329. "nodename",
  330. "np", "sp", "wp", "ep", "up", "dp",
  331. "ne", "se", "we", "ee", "ue", "de",
  332. })
  333. end
  334. functable.on_destruct =
  335. function(pos)
  336. local meta = minetest.get_meta(pos)
  337. local owner = meta:get_string("owner")
  338. stat2.invalidate_hubs(pos, v.tier)
  339. net2.clear_caches(pos, owner, v.tier)
  340. nodestore.del_node(pos)
  341. end
  342. functable.on_blast =
  343. function(pos, intensity)
  344. local drops = {}
  345. drops[#drops+1] = "stat2:" .. v.tier
  346. minetest.remove_node(pos)
  347. return drops
  348. end
  349. functable.on_timer =
  350. function(pos, elapsed)
  351. local meta = minetest.get_meta(pos)
  352. if meta:get_int("needupdate") == 1 then
  353. stat2.refresh_hubs(pos, v.tier)
  354. functable.update_infotext_and_formspec(pos)
  355. meta:set_int("needupdate", 0)
  356. end
  357. end
  358. functable.after_place_node =
  359. function(pos, placer, itemstack, pointed_thing)
  360. local meta = minetest.get_meta(pos)
  361. meta:set_string("owner", placer:get_player_name())
  362. meta:set_string("nodename", minetest.get_node(pos).name)
  363. -- All routing allowed by default.
  364. -- But player can still manually configure.
  365. -- This gives most options with least hassle.
  366. for k, v in ipairs({
  367. {m="ne"},
  368. {m="se"},
  369. {m="ee"},
  370. {m="we"},
  371. {m="ue"},
  372. {m="de"},
  373. }) do
  374. meta:set_int(v.m, 1)
  375. end
  376. local owner = meta:get_string("owner")
  377. stat2.invalidate_hubs(pos, v.tier)
  378. stat2.refresh_hubs(pos, v.tier)
  379. functable.update_infotext_and_formspec(pos)
  380. net2.clear_caches(pos, owner, v.tier)
  381. nodestore.add_node(pos)
  382. end
  383. functable.on_receive_fields =
  384. function(pos, formname, fields, sender)
  385. local pname = sender:get_player_name()
  386. local meta = minetest.get_meta(pos)
  387. local owner = meta:get_string("owner")
  388. if fields.quit then
  389. return
  390. end
  391. -- Check authorization.
  392. if pname ~= owner and not gdac.player_is_admin(pname) then
  393. minetest.chat_send_player(pname, "# Server: No authorization to modify cable box.")
  394. easyvend.sound_error(pname)
  395. return
  396. end
  397. for k, v in ipairs({
  398. {d="n", m="np", e="ne"},
  399. {d="s", m="sp", e="se"},
  400. {d="e", m="ep", e="ee"},
  401. {d="w", m="wp", e="we"},
  402. {d="u", m="up", e="ue"},
  403. {d="d", m="dp", e="de"},
  404. }) do
  405. if fields[v.d] then
  406. local enabled = meta:get_int(v.e)
  407. if enabled == 0 then
  408. enabled = 1
  409. else
  410. enabled = 0
  411. end
  412. meta:set_int(v.e, enabled)
  413. end
  414. end
  415. if fields.autoconf then
  416. for k, v in ipairs({
  417. {n="ne", m="np"},
  418. {n="se", m="sp"},
  419. {n="ee", m="ep"},
  420. {n="we", m="wp"},
  421. {n="ue", m="up"},
  422. {n="de", m="dp"},
  423. }) do
  424. local p = minetest.string_to_pos(meta:get_string(v.m))
  425. if p then
  426. meta:set_int(v.n, 1)
  427. else
  428. meta:set_int(v.n, 0)
  429. end
  430. end
  431. meta:set_int("needupdate", 1)
  432. end
  433. functable.update_infotext_and_formspec(pos)
  434. functable.trigger_update(pos)
  435. end
  436. end
  437. -- One-time initialization.
  438. if not stat2.run_once then
  439. for k, v in ipairs({
  440. {tier="hv", name="HV"},
  441. {tier="mv", name="MV"},
  442. {tier="lv", name="LV"},
  443. }) do
  444. -- Which function table are we operating on?
  445. local functable = _G["stat2_" .. v.tier]
  446. local nodebox = {
  447. {2, 2, 2, 14, 14, 14}, -- Inner box.
  448. -- Front face.
  449. {0, 0, 0, 4, 16, 4},
  450. {12, 0, 0, 16, 16, 4},
  451. {0, 12, 0, 16, 16, 4},
  452. {0, 0, 0, 16, 4, 4},
  453. -- Back face.
  454. {0, 0, 12, 4, 16, 16},
  455. {12, 0, 12, 16, 16, 16},
  456. {0, 12, 12, 16, 16, 16},
  457. {0, 0, 12, 16, 4, 16},
  458. -- Left face.
  459. {0, 12, 0, 4, 16, 16},
  460. {0, 0, 0, 4, 4, 16},
  461. -- Right face.
  462. {12, 12, 0, 16, 16, 16},
  463. {12, 0, 0, 16, 4, 16},
  464. }
  465. for k, v in ipairs(nodebox) do
  466. for m, n in ipairs(v) do
  467. local p = nodebox[k][m]
  468. p = p / 16
  469. p = p - 0.5
  470. nodebox[k][m] = p
  471. end
  472. end
  473. minetest.register_node(":stat2:" .. v.tier, {
  474. drawtype = "nodebox",
  475. 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.",
  476. tiles = {"switching_station_" .. v.tier .. ".png"},
  477. groups = utility.dig_groups("machine"),
  478. node_box = {
  479. type = "fixed",
  480. fixed = nodebox,
  481. },
  482. selection_box = {
  483. type = "fixed",
  484. fixed = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
  485. },
  486. paramtype = "light",
  487. paramtype2 = "facedir",
  488. is_ground_content = false,
  489. sounds = default.node_sound_metal_defaults(),
  490. on_rotate = function(...)
  491. return screwdriver.rotate_simple(...) end,
  492. on_punch = function(...)
  493. return functable.on_punch(...) end,
  494. can_dig = function(...)
  495. return functable.can_dig(...) end,
  496. on_timer = function(...)
  497. return functable.on_timer(...) end,
  498. on_construct = function(...)
  499. return functable.on_construct(...) end,
  500. after_place_node = function(...)
  501. return functable.after_place_node(...) end,
  502. on_blast = function(...)
  503. return functable.on_blast(...) end,
  504. on_destruct = function(...)
  505. return functable.on_destruct(...) end,
  506. on_metadata_inventory_move = function(...)
  507. return functable.on_metadata_inventory_move(...) end,
  508. on_metadata_inventory_put = function(...)
  509. return functable.on_metadata_inventory_put(...) end,
  510. on_metadata_inventory_take = function(...)
  511. return functable.on_metadata_inventory_take(...) end,
  512. allow_metadata_inventory_put = function(...)
  513. return functable.allow_metadata_inventory_put(...) end,
  514. allow_metadata_inventory_move = function(...)
  515. return functable.allow_metadata_inventory_move(...) end,
  516. allow_metadata_inventory_take = function(...)
  517. return functable.allow_metadata_inventory_take(...) end,
  518. on_receive_fields = function(...)
  519. return functable.on_receive_fields(...) end,
  520. })
  521. end
  522. local c = "stat2:core"
  523. local f = switching_station.modpath .. "/stat2.lua"
  524. reload.register_file(c, f, false)
  525. stat2.run_once = true
  526. end