init.lua 33 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933
  1. -- Initial speed of a box.
  2. local SPEED = 7
  3. -- Acceleration of a box.
  4. local ACCEL = 0.1
  5. -- Elevator interface/database version.
  6. local VERSION = 8
  7. -- Maximum time a box can go without players nearby.
  8. local PTIMEOUT = 120
  9. -- Localize for performance.
  10. local vector_distance = vector.distance
  11. local vector_round = vector.round
  12. local math_floor = math.floor
  13. local math_random = math.random
  14. local math_min = math.min
  15. local math_max = math.max
  16. -- Detect optional mods.
  17. local technic_path = minetest.get_modpath("technic")
  18. local chains_path = minetest.get_modpath("chains")
  19. local homedecor_path = minetest.get_modpath("homedecor")
  20. local armor_path = minetest.get_modpath("3d_armor")
  21. -- Central "network" table.
  22. local elevator = {
  23. motors = {},
  24. }
  25. local str = minetest.get_mod_storage and minetest.get_mod_storage()
  26. local elevator_file = minetest.get_worldpath() .. "/elevator"
  27. local function load_elevator()
  28. if str and ((str.contains and str:contains("data")) or (str:get_string("data") and str:get_string("data") ~= "")) then
  29. elevator = minetest.deserialize(str:get_string("data"))
  30. return
  31. end
  32. local file = io.open(elevator_file)
  33. if file then
  34. elevator = minetest.deserialize(file:read("*all")) or {}
  35. file:close()
  36. end
  37. end
  38. local function save_elevator()
  39. if str then
  40. str:set_string("data", minetest.serialize(elevator))
  41. return
  42. end
  43. local f = io.open(elevator_file, "w")
  44. f:write(minetest.serialize(elevator))
  45. f:close()
  46. end
  47. load_elevator()
  48. -- Elevator boxes in action.
  49. local boxes = {}
  50. -- Player formspecs.
  51. local formspecs = {}
  52. -- Player near box timeout.
  53. local lastboxes = {}
  54. -- Players riding boxes.
  55. local riding = {}
  56. -- Globalstep timer.
  57. local time = 0
  58. -- Helper function to read unloaded nodes.
  59. local function get_node(pos)
  60. local node = minetest.get_node_or_nil(pos)
  61. if node then return node end
  62. local _,_ = VoxelManip():read_from_map(pos, pos)
  63. return minetest.get_node_or_nil(pos)
  64. end
  65. -- Use homedecor's placeholder if possible.
  66. local placeholder = homedecor_path and "homedecor:expansion_placeholder" or "elevator:placeholder"
  67. if homedecor_path then
  68. minetest.register_alias("elevator:placeholder", "homedecor:expansion_placeholder")
  69. else
  70. -- Placeholder node, in the style of homedecor.
  71. minetest.register_node(placeholder, {
  72. description = "Expansion Placeholder",
  73. selection_box = {
  74. type = "fixed",
  75. fixed = {0, 0, 0, 0, 0, 0},
  76. },
  77. groups = {
  78. not_in_creative_inventory=1
  79. },
  80. drawtype = "airlike",
  81. paramtype = "light",
  82. sunlight_propagates = true,
  83. walkable = false,
  84. buildable_to = false,
  85. is_ground_content = false,
  86. on_dig = function(pos, node, player)
  87. minetest.remove_node(pos)
  88. minetest.set_node(pos, {name=placeholder})
  89. end
  90. })
  91. end
  92. local VISUAL_INCREASE = 1.75
  93. -- Cause <sender> to ride <motorhash> beginning at <pos> and targetting <target>.
  94. local function create_box(motorhash, pos, target, sender)
  95. -- First create the box.
  96. local obj = minetest.add_entity(pos, "elevator:box")
  97. obj:setpos(pos)
  98. -- Attach the player.
  99. sender:setpos(pos)
  100. sender:set_attach(obj, "", {x=0, y=9, z=0}, {x=0, y=0, z=0})
  101. sender:set_eye_offset({x=0, y=-9, z=0},{x=0, y=-9, z=0})
  102. sender:set_properties({visual_size = {x=VISUAL_INCREASE, y=VISUAL_INCREASE}})
  103. if armor_path then
  104. armor:update_player_visuals(sender)
  105. end
  106. -- Set the box properties.
  107. obj:get_luaentity().motor = motorhash
  108. obj:get_luaentity().uid = math_floor(math_random() * 1000000)
  109. obj:get_luaentity().attached = sender:get_player_name()
  110. obj:get_luaentity().start = pos
  111. obj:get_luaentity().target = target
  112. obj:get_luaentity().halfway = {x=pos.x, y=(pos.y+target.y)/2, z=pos.z}
  113. obj:get_luaentity().vmult = (target.y < pos.y) and -1 or 1
  114. -- Set the speed.
  115. obj:setvelocity({x=0, y=SPEED*obj:get_luaentity().vmult, z=0})
  116. obj:setacceleration({x=0, y=ACCEL*obj:get_luaentity().vmult, z=0})
  117. -- Set the tables.
  118. boxes[motorhash] = obj
  119. riding[sender:get_player_name()] = {
  120. motor = motorhash,
  121. pos = pos,
  122. target = target,
  123. box = obj,
  124. }
  125. return obj
  126. end
  127. -- Try to teleport player away from any closed (on) elevator node.
  128. local function teleport_player_from_elevator(player)
  129. local function solid(pos)
  130. if not minetest.registered_nodes[minetest.get_node(pos).name] then
  131. return true
  132. end
  133. return minetest.registered_nodes[minetest.get_node(pos).name].walkable
  134. end
  135. local pos = vector_round(player:getpos())
  136. local node = minetest.get_node(pos)
  137. -- elevator_off is like a shaft, so the player would already be falling.
  138. if node.name == "elevator:elevator_on" then
  139. local front = vector.subtract(pos, minetest.facedir_to_dir(node.param2))
  140. local front_above = vector.add(front, {x=0, y=1, z=0})
  141. local front_below = vector.subtract(front, {x=0, y=1, z=0})
  142. -- If the front isn't solid, it's ok to teleport the player.
  143. if not solid(front) and not solid(front_above) then
  144. player:setpos(front)
  145. end
  146. end
  147. end
  148. minetest.register_globalstep(function(dtime)
  149. -- Don't want to run this too often.
  150. time = time + dtime
  151. if time < 0.5 then
  152. return
  153. end
  154. time = 0
  155. -- Only count riders who are still logged in.
  156. local newriding = {}
  157. for _,p in ipairs(minetest.get_connected_players()) do
  158. local pos = p:getpos()
  159. local name = p:get_player_name()
  160. newriding[name] = riding[name]
  161. -- If the player is indeed riding, update their position.
  162. if newriding[name] then
  163. newriding[name].pos = pos
  164. end
  165. end
  166. riding = newriding
  167. for name,r in pairs(riding) do
  168. -- If the box is no longer loaded or existent, create another.
  169. local ok = r.box and r.box.getpos and r.box:getpos() and r.box:get_luaentity() and r.box:get_luaentity().attached == name
  170. if not ok then
  171. minetest.log("action", "[elevator] "..minetest.pos_to_string(r.pos).." created due to lost rider.")
  172. minetest.after(0, create_box, r.motor, r.pos, r.target, minetest.get_player_by_name(name))
  173. end
  174. end
  175. -- Ensure boxes are deleted after <PTIMEOUT> seconds if there are no players nearby.
  176. for motor,obj in pairs(boxes) do
  177. if type(obj) ~= "table" then
  178. return
  179. end
  180. lastboxes[motor] = lastboxes[motor] and math_min(lastboxes[motor], PTIMEOUT) or PTIMEOUT
  181. lastboxes[motor] = math_max(lastboxes[motor] - 1, 0)
  182. local pos = obj:getpos()
  183. if pos then
  184. for _,object in ipairs(minetest.get_objects_inside_radius(pos, 5)) do
  185. if object.is_player and object:is_player() then
  186. lastboxes[motor] = PTIMEOUT
  187. break
  188. end
  189. end
  190. if lastboxes[motor] < 1 then
  191. minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to lack of players.")
  192. boxes[motor] = false
  193. end
  194. else
  195. minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to lack of position during player check.")
  196. boxes[motor] = false
  197. end
  198. end
  199. end)
  200. minetest.register_on_leaveplayer(function(player)
  201. -- We don't want players potentially logging into open elevators.
  202. teleport_player_from_elevator(player)
  203. end)
  204. local function phash(pos)
  205. return minetest.pos_to_string(pos)
  206. end
  207. local function punhash(pos)
  208. return minetest.string_to_pos(pos)
  209. end
  210. -- Starting from <pos>, locate a motor hash.
  211. local function locate_motor(pos)
  212. local p = vector.new(pos)
  213. while true do
  214. local node = get_node(p)
  215. if node.name == "elevator:elevator_on" or node.name == "elevator:elevator_off" then
  216. p.y = p.y + 2
  217. elseif node.name == "elevator:shaft" then
  218. p.y = p.y + 1
  219. elseif node.name == "elevator:motor" then
  220. return phash(p)
  221. else
  222. return nil
  223. end
  224. end
  225. end
  226. local function build_motor(hash)
  227. local need_saving = false
  228. local motor = elevator.motors[hash]
  229. -- Just ignore motors that don't exist.
  230. if not motor then
  231. return
  232. end
  233. local p = punhash(hash)
  234. local node = get_node(p)
  235. -- And ignore motors that aren't motors.
  236. if node.name ~= "elevator:motor" then
  237. return
  238. end
  239. p.y = p.y - 1
  240. motor.elevators = {}
  241. motor.pnames = {}
  242. motor.labels = {}
  243. -- Run down through the shaft, storing information about elevators.
  244. while true do
  245. local node = get_node(p)
  246. if node.name == "elevator:shaft" then
  247. p.y = p.y - 1
  248. else
  249. p.y = p.y - 1
  250. local node = get_node(p)
  251. if node.name == "elevator:elevator_on" or node.name == "elevator:elevator_off" then
  252. table.insert(motor.elevators, phash(p))
  253. table.insert(motor.pnames, tostring(p.y))
  254. table.insert(motor.labels, "")
  255. p.y = p.y - 1
  256. need_saving = true
  257. else
  258. break
  259. end
  260. end
  261. end
  262. -- Set the elevators fully.
  263. for i,m in ipairs(motor.elevators) do
  264. local pos = punhash(m)
  265. local meta = minetest.get_meta(pos)
  266. meta:set_int("version", VERSION)
  267. if meta:get_string("motor") ~= hash then
  268. build_motor(meta:get_string("motor"))
  269. end
  270. motor.labels[i] = meta:get_string("label")
  271. meta:set_string("motor", hash)
  272. if motor.labels[i] ~= meta:get_string("infotext") then
  273. meta:set_string("infotext", motor.labels[i])
  274. end
  275. end
  276. if need_saving then
  277. save_elevator()
  278. end
  279. end
  280. local function unbuild(pos, add)
  281. local need_saving = false
  282. local p = table.copy(pos)
  283. p.y = p.y - 1
  284. -- Loop down through the network, set any elevators below this to the off position.
  285. while true do
  286. local node = get_node(p)
  287. if node.name == "elevator:shaft" then
  288. p.y = p.y - 1
  289. else
  290. p.y = p.y - 1
  291. local node = get_node(p)
  292. if node.name == "elevator:elevator_on" or node.name == "elevator:elevator_off" then
  293. local meta = minetest.get_meta(p)
  294. meta:set_string("motor", "")
  295. p.y = p.y - 1
  296. else
  297. break
  298. end
  299. end
  300. end
  301. -- After a short delay, build the motor and handle box removal.
  302. minetest.after(0.01, function(p2, add)
  303. if not p2 or not add then
  304. return
  305. end
  306. p2.y = p2.y + add
  307. local motorhash = locate_motor(p2)
  308. build_motor(motorhash)
  309. -- If there's a box below this point, break it.
  310. if boxes[motorhash] and boxes[motorhash]:getpos() and p2.y >= boxes[motorhash]:getpos().y then
  311. boxes[motorhash] = nil
  312. end
  313. -- If the box does not exist, just clear it.
  314. if boxes[motorhash] and not boxes[motorhash]:getpos() then
  315. boxes[motorhash] = nil
  316. end
  317. end, table.copy(pos), add)
  318. end
  319. minetest.register_node("elevator:motor", {
  320. description = "Elevator Motor",
  321. tiles = {
  322. "default_steel_block.png",
  323. "default_steel_block.png",
  324. "elevator_motor.png",
  325. "elevator_motor.png",
  326. "elevator_motor.png",
  327. "elevator_motor.png",
  328. },
  329. groups = {cracky=1},
  330. sounds = default.node_sound_stone_defaults(),
  331. after_place_node = function(pos, placer, itemstack)
  332. -- Set up the motor table.
  333. elevator.motors[phash(pos)] = {
  334. elevators = {},
  335. pnames = {},
  336. labels = {},
  337. }
  338. save_elevator()
  339. build_motor(phash(pos))
  340. end,
  341. on_destruct = function(pos)
  342. -- Destroy everything related to this motor.
  343. boxes[phash(pos)] = nil
  344. elevator.motors[phash(pos)] = nil
  345. save_elevator()
  346. end,
  347. })
  348. for _,mode in ipairs({"on", "off"}) do
  349. local nodename = "elevator:elevator_"..mode
  350. local on = (mode == "on")
  351. local box
  352. local cbox
  353. if on then
  354. -- Active elevators have a ceiling and floor.
  355. box = {
  356. { 0.48, -0.5,-0.5, 0.5, 1.5, 0.5},
  357. {-0.5 , -0.5, 0.48, 0.48, 1.5, 0.5},
  358. {-0.5, -0.5,-0.5 ,-0.48, 1.5, 0.5},
  359. { -0.5,-0.5,-0.5,0.5,-0.48, 0.5},
  360. { -0.5, 1.45,-0.5,0.5, 1.5, 0.5},
  361. }
  362. cbox = table.copy(box)
  363. -- But you can enter them from the top.
  364. cbox[5] = nil
  365. else
  366. -- Inactive elevators are almost like shafts.
  367. box = {
  368. { 0.48, -0.5,-0.5, 0.5, 1.5, 0.5},
  369. {-0.5 , -0.5, 0.48, 0.48, 1.5, 0.5},
  370. {-0.5, -0.5,-0.5 ,-0.48, 1.5, 0.5},
  371. {-0.5 , -0.5, -0.48, 0.5, 1.5, -0.5},
  372. }
  373. cbox = box
  374. end
  375. minetest.register_node(nodename, {
  376. description = "Elevator",
  377. drawtype = "nodebox",
  378. sunlight_propagates = false,
  379. paramtype = "light",
  380. paramtype2 = "facedir",
  381. on_rotate = screwdriver.disallow,
  382. selection_box = {
  383. type = "fixed",
  384. fixed = box,
  385. },
  386. collision_box = {
  387. type = "fixed",
  388. fixed = cbox,
  389. },
  390. node_box = {
  391. type = "fixed",
  392. fixed = box,
  393. },
  394. tiles = on and {
  395. "default_steel_block.png",
  396. "default_steel_block.png",
  397. "elevator_box.png",
  398. "elevator_box.png",
  399. "elevator_box.png",
  400. "elevator_box.png",
  401. } or {
  402. "elevator_box.png",
  403. "elevator_box.png",
  404. "elevator_box.png",
  405. "elevator_box.png",
  406. "elevator_box.png",
  407. "elevator_box.png",
  408. },
  409. groups = {cracky=1, choppy=1, snappy=1},
  410. drop = "elevator:elevator_off",
  411. -- Emit a bit of light when active.
  412. light_source = (on and 4 or nil),
  413. after_place_node = function(pos, placer, itemstack)
  414. local meta = minetest.get_meta(pos)
  415. meta:set_int("version", VERSION)
  416. -- Add a placeholder to avoid nodes being placed in the top.
  417. local p = vector.add(pos, {x=0, y=1, z=0})
  418. local p2 = minetest.dir_to_facedir(placer:get_look_dir())
  419. minetest.set_node(p, {name=placeholder, paramtype2="facedir", param2=p2})
  420. -- Try to build a motor above.
  421. local motor = locate_motor(pos)
  422. if motor then
  423. build_motor(motor)
  424. end
  425. end,
  426. after_dig_node = function(pos, node, meta, digger)
  427. unbuild(pos, 2)
  428. end,
  429. on_place = function(itemstack, placer, pointed_thing)
  430. local pos = pointed_thing.above
  431. local node = minetest.get_node(vector.add(pos, {x=0, y=1, z=0}))
  432. if (node ~= nil and node.name ~= "air" and node.name ~= placeholder) then
  433. return
  434. end
  435. return minetest.item_place(itemstack, placer, pointed_thing)
  436. end,
  437. on_rightclick = function(pos, node, sender)
  438. if not sender or not sender:is_player() then
  439. return
  440. end
  441. local formspec
  442. local meta = minetest.get_meta(pos)
  443. formspecs[sender:get_player_name()] = {pos}
  444. if on then
  445. if vector_distance(sender:getpos(), pos) > 1 or minetest.get_node(sender:getpos()).name ~= nodename then
  446. minetest.chat_send_player(sender:get_player_name(), "# Server: You are not inside the booth.")
  447. return
  448. end
  449. -- Build the formspec from the motor table.
  450. local tpnames = {}
  451. local tpnames_l = {}
  452. local motorhash = meta:get_string("motor")
  453. local motor = elevator.motors[motorhash]
  454. for ji,jv in ipairs(motor.pnames) do
  455. if tonumber(jv) ~= pos.y then
  456. table.insert(tpnames, jv)
  457. local jy = jv -- Is a string.
  458. local rp = rc.pos_to_realmpos({x=pos.x, y=tonumber(jv), z=pos.z})
  459. if rp then
  460. jy = tostring(rp.y)
  461. end
  462. local ename = (motor.labels[ji] and motor.labels[ji] ~= "") and (jy .. " - " .. minetest.formspec_escape(motor.labels[ji])) or jy
  463. if tonumber(jv) > pos.y then
  464. ename = ename .. " - Up"
  465. else
  466. ename = ename .. " - Down"
  467. end
  468. table.insert(tpnames_l, ename)
  469. end
  470. end
  471. formspecs[sender:get_player_name()] = {pos, tpnames}
  472. if #tpnames > 0 then
  473. if not minetest.test_protection(pos, sender:get_player_name()) then
  474. formspec = "size[4,6]" .. default.gui_bg .. default.gui_bg_img .. default.gui_slots
  475. .."label[0,0;Click once to travel.]"
  476. .."textlist[-0.1,0.5;4,4;target;"..table.concat(tpnames_l, ",").."]"
  477. .."field[0.25,5.25;4,0;label;;"..minetest.formspec_escape(meta:get_string("label")).."]"
  478. .."button_exit[-0.05,5.5;4,1;setlabel;Set label]"
  479. else
  480. formspec = "size[4,4.4]" .. default.gui_bg .. default.gui_bg_img .. default.gui_slots
  481. .."label[0,0;Click once to travel.]"
  482. .."textlist[-0.1,0.5;4,4;target;"..table.concat(tpnames_l, ",").."]"
  483. end
  484. else
  485. if not minetest.test_protection(pos, sender:get_player_name()) then
  486. formspec = "size[4,2]" .. default.gui_bg .. default.gui_bg_img .. default.gui_slots
  487. .."label[0,0;No targets available.]"
  488. .."field[0.25,1.25;4,0;label;;"..minetest.formspec_escape(meta:get_string("label")).."]"
  489. .."button_exit[-0.05,1.5;4,1;setlabel;Set label]"
  490. else
  491. formspec = "size[4,0.4]" .. default.gui_bg .. default.gui_bg_img .. default.gui_slots
  492. .."label[0,0;No targets available.]"
  493. end
  494. end
  495. minetest.show_formspec(sender:get_player_name(), "elevator:elevator", formspec)
  496. elseif not elevator.motors[meta:get_string("motor")] then
  497. if not minetest.test_protection(pos, sender:get_player_name()) then
  498. formspec = "size[4,2]" .. default.gui_bg .. default.gui_bg_img .. default.gui_slots
  499. .."label[0,0;This elevator is inactive.]"
  500. .."field[0.25,1.25;4,0;label;;"..minetest.formspec_escape(meta:get_string("label")).."]"
  501. .."button_exit[-0.05,1.5;4,1;setlabel;Set label]"
  502. else
  503. formspec = "size[4,0.4]" .. default.gui_bg .. default.gui_bg_img .. default.gui_slots
  504. .."label[0,0;This elevator is inactive.]"
  505. end
  506. minetest.show_formspec(sender:get_player_name(), "elevator:elevator", formspec)
  507. elseif boxes[meta:get_string("motor")] then
  508. if not minetest.test_protection(pos, sender:get_player_name()) then
  509. formspec = "size[4,2]" .. default.gui_bg .. default.gui_bg_img .. default.gui_slots
  510. .."label[0,0;This elevator is in use.]"
  511. .."field[0.25,1.25;4,0;label;;"..minetest.formspec_escape(meta:get_string("label")).."]"
  512. .."button_exit[-0.05,1.5;4,1;setlabel;Set label]"
  513. else
  514. formspec = "size[4,0.4]" .. default.gui_bg .. default.gui_bg_img .. default.gui_slots
  515. .."label[0,0;This elevator is in use.]"
  516. end
  517. minetest.show_formspec(sender:get_player_name(), "elevator:elevator", formspec)
  518. end
  519. end,
  520. on_destruct = function(pos)
  521. local p = vector.add(pos, {x=0, y=1, z=0})
  522. if get_node(p).name == placeholder then
  523. minetest.remove_node(p)
  524. end
  525. end,
  526. })
  527. end
  528. minetest.register_on_player_receive_fields(function(sender, formname, fields)
  529. if formname ~= "elevator:elevator" then
  530. return
  531. end
  532. local pos = formspecs[sender:get_player_name()] and formspecs[sender:get_player_name()][1] or nil
  533. if not pos then
  534. return true
  535. end
  536. local meta = minetest.get_meta(pos)
  537. if fields.setlabel then
  538. if minetest.test_protection(pos, sender:get_player_name()) then
  539. return true
  540. end
  541. meta:set_string("label", fields.label)
  542. meta:set_string("infotext", fields.label)
  543. -- Rebuild the elevator shaft so the other elevators can read this label.
  544. local motorhash = meta:get_string("motor")
  545. build_motor(elevator.motors[motorhash] and motorhash or locate_motor(pos))
  546. return true
  547. end
  548. -- Double check if it's ok to go.
  549. if vector_distance(sender:getpos(), pos) > 1 then
  550. return true
  551. end
  552. if fields.target then
  553. local closeformspec = ""
  554. -- HACK: With player information extensions enabled, we can check if closing formspecs are now allowed. This is specifically used on Survival in Ethereal.
  555. local pi = minetest.get_player_information(sender:get_player_name())
  556. if (not (pi.major == 0 and pi.minor == 4 and pi.patch == 15)) and (pi.protocol_version or 29) < 29 then
  557. closeformspec = "size[4,2] label[0,0;You are now using the elevator.\nUpgrade Minetest to avoid this dialog.] button_exit[0,1;4,1;close;Close]"
  558. end
  559. -- End hacky HACK.
  560. minetest.after(0.2, minetest.show_formspec, sender:get_player_name(), "elevator:elevator", closeformspec)
  561. -- Ensure we're connected to a motor.
  562. local motorhash = meta:get_string("motor")
  563. local motor = elevator.motors[motorhash]
  564. if not motor then
  565. motorhash = locate_motor(pos)
  566. motor = elevator.motors[motorhash]
  567. if motor then
  568. meta:set_string("motor", "")
  569. build_motor(motorhash)
  570. minetest.chat_send_player(sender:get_player_name(), "# Server: Recalibrated to a new motor, please try again.")
  571. return true
  572. end
  573. end
  574. if not motor then
  575. minetest.chat_send_player(sender:get_player_name(), "# Server: This elevator is not attached to a motor.")
  576. return true
  577. end
  578. if not formspecs[sender:get_player_name()][2] or not formspecs[sender:get_player_name()][2][minetest.explode_textlist_event(fields.target).index] then
  579. return true
  580. end
  581. -- Locate our target elevator.
  582. local target = nil
  583. local selected_target = formspecs[sender:get_player_name()][2][minetest.explode_textlist_event(fields.target).index]
  584. for i,v in ipairs(motor.pnames) do
  585. if v == selected_target then
  586. target = punhash(motor.elevators[i])
  587. end
  588. end
  589. -- Found the elevator? Then go!
  590. if target then
  591. -- Final check.
  592. if boxes[motorhash] then
  593. minetest.chat_send_player(sender:get_player_name(), "# Server: This elevator is in use.")
  594. return true
  595. end
  596. local obj = create_box(motorhash, pos, target, sender)
  597. -- Teleport anyone standing within an on elevator out, or they'd fall through the off elevators.
  598. for _,p in ipairs(motor.elevators) do
  599. local p = punhash(p)
  600. for _,object in ipairs(minetest.get_objects_inside_radius(p, 0.6)) do
  601. if object.is_player and object:is_player() then
  602. if object:get_player_name() ~= obj:get_luaentity().attached then
  603. teleport_player_from_elevator(object)
  604. end
  605. end
  606. end
  607. end
  608. else
  609. minetest.chat_send_player(sender:get_player_name(), "# Server: This target is invalid.")
  610. return true
  611. end
  612. return true
  613. end
  614. return true
  615. end)
  616. -- Compatability with an older version.
  617. minetest.register_alias("elevator:elevator", "elevator:elevator_off")
  618. -- Ensure an elevator is up to the latest version.
  619. local function upgrade_elevator(pos, meta)
  620. if meta:get_int("version") ~= VERSION then
  621. minetest.log("action", "[elevator] Updating elevator with old version at "..minetest.pos_to_string(pos))
  622. minetest.after(0, function(pos) build_motor(locate_motor(pos)) end, pos)
  623. meta:set_int("version", VERSION)
  624. meta:set_string("formspec", "")
  625. meta:set_string("infotext", meta:get_string("label"))
  626. end
  627. end
  628. -- Convert off to on when applicable.
  629. local offabm = function(pos, node)
  630. local meta = minetest.get_meta(pos)
  631. upgrade_elevator(pos, meta)
  632. if not boxes[meta:get_string("motor")] and elevator.motors[meta:get_string("motor")] then
  633. node.name = "elevator:elevator_on"
  634. minetest.swap_node(pos, node)
  635. end
  636. end
  637. minetest.register_abm({
  638. nodenames = {"elevator:elevator_off"},
  639. interval = 1,
  640. chance = 1,
  641. action = offabm,
  642. label = "Elevator (Off)",
  643. })
  644. -- Convert on to off when applicable.
  645. minetest.register_abm({
  646. nodenames = {"elevator:elevator_on"},
  647. interval = 1,
  648. chance = 1,
  649. action = function(pos, node)
  650. local meta = minetest.get_meta(pos)
  651. upgrade_elevator(pos, meta)
  652. if boxes[meta:get_string("motor")] or not elevator.motors[meta:get_string("motor")] then
  653. node.name = "elevator:elevator_off"
  654. minetest.swap_node(pos, node)
  655. end
  656. end,
  657. label = "Elevator (On)",
  658. })
  659. minetest.register_node("elevator:shaft", {
  660. description = "Elevator Shaft",
  661. tiles = { "elevator_shaft.png" },
  662. drawtype = "nodebox",
  663. paramtype = "light",
  664. on_rotate = screwdriver.disallow,
  665. sunlight_propagates = true,
  666. groups = {cracky=2, oddly_breakable_by_hand=1},
  667. sounds = default.node_sound_stone_defaults(),
  668. node_box = {
  669. type = "fixed",
  670. fixed = {
  671. {-8/16,-8/16,-8/16,-7/16,8/16,8/16},
  672. {7/16,-8/16,-8/16,8/16,8/16,8/16},
  673. {-7/16,-8/16,-8/16,7/16,8/16,-7/16},
  674. {-7/16,-8/16,8/16,7/16,8/16,7/16},
  675. },
  676. },
  677. collisionbox = {
  678. type = "fixed",
  679. fixed = {
  680. {-8/16,-8/16,-8/16,-7/16,8/16,8/16},
  681. {7/16,-8/16,-8/16,8/16,8/16,8/16},
  682. {-7/16,-8/16,-8/16,7/16,8/16,-7/16},
  683. {-7/16,-8/16,8/16,7/16,8/16,7/16},
  684. },
  685. },
  686. after_place_node = function(pos)
  687. -- We might have connected a motor above to an elevator below.
  688. build_motor(locate_motor(pos))
  689. end,
  690. on_destruct = function(pos)
  691. -- Remove boxes and deactivate elevators below us.
  692. unbuild(pos, 1)
  693. end,
  694. })
  695. local box = {
  696. { 0.48, -0.5,-0.5, 0.5, 1.5, 0.5},
  697. {-0.5 , -0.5, 0.48, 0.48, 1.5, 0.5},
  698. {-0.5, -0.5,-0.5 ,-0.48, 1.5, 0.5},
  699. {-0.5 , -0.5, -0.48, 0.5, 1.5, -0.5},
  700. { -0.5,-0.5,-0.5,0.5,-0.48, 0.5},
  701. { -0.5, 1.45,-0.5,0.5, 1.5, 0.5},
  702. }
  703. -- Elevator box node. Not intended to be placeable.
  704. minetest.register_node("elevator:elevator_box", {
  705. description = "Elevator",
  706. drawtype = "nodebox",
  707. paramtype = 'light',
  708. paramtype2 = "facedir",
  709. wield_scale = {x=0.6, y=0.6, z=0.6},
  710. selection_box = {
  711. type = "fixed",
  712. fixed = { -0.5, -0.5, -0.5, 0.5, 1.5, 0.5 }
  713. },
  714. collision_box = {
  715. type = "fixed",
  716. fixed = box,
  717. },
  718. node_box = {
  719. type = "fixed",
  720. fixed = box,
  721. },
  722. tiles = {
  723. "default_steel_block.png",
  724. "default_steel_block.png",
  725. "elevator_box.png",
  726. "elevator_box.png",
  727. "elevator_box.png",
  728. "elevator_box.png",
  729. },
  730. groups = {not_in_creative_inventory = 1},
  731. light_source = 4,
  732. })
  733. -- Remove the player from self, and teleport them to pos if specified.
  734. local function detach(self, pos)
  735. local player = minetest.get_player_by_name(self.attached)
  736. local attached = player:get_attach()
  737. if not attached or attached:get_luaentity().uid ~= self.uid then
  738. return
  739. end
  740. player:set_detach()
  741. player:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0})
  742. player:set_properties({visual_size = {x=1, y=1}})
  743. if armor_path then
  744. armor:update_player_visuals(player)
  745. end
  746. if pos then
  747. player:setpos(pos)
  748. minetest.after(0.1, function(pl, p)
  749. pl:setpos(p)
  750. end, player, pos)
  751. end
  752. riding[self.attached] = nil
  753. end
  754. local box_entity = {
  755. physical = false,
  756. collisionbox = {0,0,0,0,0,0},
  757. visual = "wielditem",
  758. visual_size = {x=1, y=1},
  759. textures = {"elevator:elevator_box"},
  760. attached = "",
  761. motor = false,
  762. target = false,
  763. start = false,
  764. lastpos = false,
  765. halfway = false,
  766. vmult = 0,
  767. on_activate = function(self, staticdata)
  768. -- Don't want the box being destroyed by anything except the elevator system.
  769. self.object:set_armor_groups({immortal=1})
  770. end,
  771. on_step = function(self, dtime)
  772. local pos = self.object:getpos()
  773. -- First, check if this box needs removed.
  774. -- If the motor has a box and it isn't this box.
  775. if boxes[self.motor] and boxes[self.motor] ~= self.object then
  776. minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to duplication.")
  777. self.object:remove()
  778. return
  779. end
  780. -- If our attached player can't be found.
  781. if not minetest.get_player_by_name(self.attached) then
  782. minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to lack of attachee logged in.")
  783. self.object:remove()
  784. boxes[self.motor] = nil
  785. return
  786. end
  787. -- If our attached player is no longer with us.
  788. if not minetest.get_player_by_name(self.attached):get_attach() or minetest.get_player_by_name(self.attached):get_attach():get_luaentity().uid ~= self.uid then
  789. minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to lack of attachee.")
  790. self.object:remove()
  791. boxes[self.motor] = nil
  792. return
  793. end
  794. -- If our motor's box is nil, we should self-destruct.
  795. if not boxes[self.motor] then
  796. minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to nil entry in boxes.")
  797. detach(self)
  798. self.object:remove()
  799. boxes[self.motor] = nil
  800. return
  801. end
  802. minetest.get_player_by_name(self.attached):setpos(pos)
  803. -- Ensure lastpos is set to something.
  804. self.lastpos = self.lastpos or pos
  805. -- Loop through all travelled nodes.
  806. for y=self.lastpos.y,pos.y,((self.lastpos.y > pos.y) and -0.3 or 0.3) do
  807. local p = vector_round({x=pos.x, y=y, z=pos.z})
  808. local node = get_node(p)
  809. if node.name == "elevator:shaft" then
  810. -- Nothing, just continue on our way.
  811. elseif node.name == "elevator:elevator_on" or node.name == "elevator:elevator_off" then
  812. -- If this is our target, detach the player here, destroy this box, and update the target elevator without waiting for the abm.
  813. if vector_distance(p, self.target) < 1 then
  814. minetest.log("action", "[elevator] "..minetest.pos_to_string(p).." broke due to arrival.")
  815. detach(self, vector.add(self.target, {x=0, y=-0.4, z=0}))
  816. self.object:remove()
  817. boxes[self.motor] = nil
  818. offabm(self.target, node)
  819. return
  820. end
  821. else
  822. -- Check if we're in the top part of an elevator, if so it's fine.
  823. local below = vector.add(p, {x=0,y=-1,z=0})
  824. local belownode = get_node(below)
  825. if belownode.name ~= "elevator:elevator_on" and belownode.name ~= "elevator:elevator_off" then
  826. -- If we aren't, then break the box.
  827. minetest.log("action", "[elevator] "..minetest.pos_to_string(p).." broke on "..node.name)
  828. boxes[self.motor] = nil
  829. detach(self, p)
  830. self.object:remove()
  831. return
  832. end
  833. end
  834. end
  835. self.lastpos = pos
  836. end,
  837. }
  838. minetest.register_entity("elevator:box", box_entity)
  839. -- Register recipes!
  840. minetest.register_craft({
  841. output = "elevator:elevator",
  842. recipe = {
  843. {"cast_iron:ingot", "chains:iron_chain", "cast_iron:ingot"},
  844. {"cast_iron:ingot", "default:mese_crystal", "cast_iron:ingot"},
  845. {"stainless_steel:ingot", "default:glass", "stainless_steel:ingot"},
  846. },
  847. })
  848. minetest.register_craft({
  849. output = "elevator:shaft",
  850. recipe = {
  851. {"titanium:crystal", "", "titanium:crystal"},
  852. {"xpanes:chainlink_flat", "default:obsidian_glass", "xpanes:chainlink_flat"},
  853. {"cast_iron:ingot", "", "cast_iron:ingot"},
  854. },
  855. })
  856. minetest.register_craft({
  857. output = "elevator:motor",
  858. recipe = {
  859. {"default:diamond", "techcrafts:control_logic_unit", "default:diamond"},
  860. {"stainless_steel:block", "techcrafts:electric_motor", "stainless_steel:block"},
  861. {"chains:iron_chain", "default:diamond", "chains:iron_chain"}
  862. },
  863. })