init.lua 33 KB

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