diff --git a/elevator/LICENSE.txt b/elevator/LICENSE.txt new file mode 100644 index 0000000..2a0794c --- /dev/null +++ b/elevator/LICENSE.txt @@ -0,0 +1,15 @@ +ISC License + +Copyright (c) 2017 Beha + +Permission to use, copy, modify, and/or distribute this software for any +purpose with or without fee is hereby granted, provided that the above +copyright notice and this permission notice appear in all copies. + +THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH +REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY +AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, +INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM +LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE +OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR +PERFORMANCE OF THIS SOFTWARE. diff --git a/elevator/README.md b/elevator/README.md new file mode 100644 index 0000000..999e65c --- /dev/null +++ b/elevator/README.md @@ -0,0 +1,7 @@ +# Realtime Elevators for Minetest + +## Dependencies +This mod has no dependencies other than default, but has recipes designed for use with technic and either homedecor/chains (for chains) or farming redo (for rope) and will detect if those are installed. + +## Usage +Each shaft begins with a motor, and extends down through shaft and elevator nodes. Each elevator node can be used to travel to any other elevator node below the motor and through an unbroken shaft. Only one player can use each shaft at once. diff --git a/elevator/depends.txt b/elevator/depends.txt new file mode 100644 index 0000000..6796bf4 --- /dev/null +++ b/elevator/depends.txt @@ -0,0 +1,5 @@ +default +technic? +homedecor? +chains? +farming? diff --git a/elevator/init.lua b/elevator/init.lua new file mode 100644 index 0000000..84ea482 --- /dev/null +++ b/elevator/init.lua @@ -0,0 +1,966 @@ +-- Initial speed of a box. +local SPEED = 10 +-- Acceleration of a box. +local ACCEL = 0.1 +-- Elevator interface/database version. +local VERSION = 8 +-- Maximum time a box can go without players nearby. +local PTIMEOUT = 120 + +-- Detect optional mods. +local technic_path = minetest.get_modpath("technic") +local chains_path = minetest.get_modpath("chains") +local homedecor_path = minetest.get_modpath("homedecor") +local armor_path = minetest.get_modpath("3d_armor") + +-- Central "network" table. +local elevator = { + motors = {}, +} + +local str = minetest.get_mod_storage and minetest.get_mod_storage() + +local elevator_file = minetest.get_worldpath() .. "/elevator" + +local function load_elevator() + if str and ((str.contains and str:contains("data")) or (str:get_string("data") and str:get_string("data") ~= "")) then + elevator = minetest.deserialize(str:get_string("data")) + return + end + local file = io.open(elevator_file) + if file then + elevator = minetest.deserialize(file:read("*all")) or {} + file:close() + end +end + +local function save_elevator() + if str then + str:set_string("data", minetest.serialize(elevator)) + return + end + local f = io.open(elevator_file, "w") + f:write(minetest.serialize(elevator)) + f:close() +end + +load_elevator() + +-- Elevator boxes in action. +local boxes = {} +-- Player formspecs. +local formspecs = {} +-- Player near box timeout. +local lastboxes = {} +-- Players riding boxes. +local riding = {} +-- Globalstep timer. +local time = 0 + +-- Helper function to read unloaded nodes. +local function get_node(pos) + local node = minetest.get_node_or_nil(pos) + if node then return node end + local _,_ = VoxelManip():read_from_map(pos, pos) + return minetest.get_node_or_nil(pos) +end + +-- Use homedecor's placeholder if possible. +local placeholder = homedecor_path and "homedecor:expansion_placeholder" or "elevator:placeholder" +if homedecor_path then + minetest.register_alias("elevator:placeholder", "homedecor:expansion_placeholder") +else + -- Placeholder node, in the style of homedecor. + minetest.register_node(placeholder, { + description = "Expansion Placeholder", + selection_box = { + type = "fixed", + fixed = {0, 0, 0, 0, 0, 0}, + }, + groups = { + not_in_creative_inventory=1 + }, + drawtype = "airlike", + paramtype = "light", + sunlight_propagates = true, + + walkable = false, + buildable_to = false, + is_ground_content = false, + + on_dig = function(pos, node, player) + minetest.remove_node(pos) + minetest.set_node(pos, {name=placeholder}) + end + }) +end + +local VISUAL_INCREASE = 1.75 + +-- Cause to ride beginning at and targetting . +local function create_box(motorhash, pos, target, sender) + -- First create the box. + local obj = minetest.add_entity(pos, "elevator:box") + obj:setpos(pos) + -- Attach the player. + sender:setpos(pos) + sender:set_attach(obj, "", {x=0, y=9, z=0}, {x=0, y=0, z=0}) + sender:set_eye_offset({x=0, y=-9, z=0},{x=0, y=-9, z=0}) + sender:set_properties({visual_size = {x=VISUAL_INCREASE, y=VISUAL_INCREASE}}) + if armor_path then + armor:update_player_visuals(sender) + end + -- Set the box properties. + obj:get_luaentity().motor = motorhash + obj:get_luaentity().uid = math.floor(math.random() * 1000000) + obj:get_luaentity().attached = sender:get_player_name() + obj:get_luaentity().start = pos + obj:get_luaentity().target = target + obj:get_luaentity().halfway = {x=pos.x, y=(pos.y+target.y)/2, z=pos.z} + obj:get_luaentity().vmult = (target.y < pos.y) and -1 or 1 + -- Set the speed. + obj:setvelocity({x=0, y=SPEED*obj:get_luaentity().vmult, z=0}) + obj:setacceleration({x=0, y=ACCEL*obj:get_luaentity().vmult, z=0}) + -- Set the tables. + boxes[motorhash] = obj + riding[sender:get_player_name()] = { + motor = motorhash, + pos = pos, + target = target, + box = obj, + } + return obj +end + +-- Try to teleport player away from any closed (on) elevator node. +local function teleport_player_from_elevator(player) + local function solid(pos) + if not minetest.registered_nodes[minetest.get_node(pos).name] then + return true + end + return minetest.registered_nodes[minetest.get_node(pos).name].walkable + end + local pos = vector.round(player:getpos()) + local node = minetest.get_node(pos) + -- elevator_off is like a shaft, so the player would already be falling. + if node.name == "elevator:elevator_on" then + local front = vector.subtract(pos, minetest.facedir_to_dir(node.param2)) + local front_above = vector.add(front, {x=0, y=1, z=0}) + local front_below = vector.subtract(front, {x=0, y=1, z=0}) + -- If the front isn't solid, it's ok to teleport the player. + if not solid(front) and not solid(front_above) then + player:setpos(front) + end + end +end + +minetest.register_globalstep(function(dtime) + -- Don't want to run this too often. + time = time + dtime + if time < 0.5 then + return + end + time = 0 + -- Only count riders who are still logged in. + local newriding = {} + for _,p in ipairs(minetest.get_connected_players()) do + local pos = p:getpos() + local name = p:get_player_name() + newriding[name] = riding[name] + -- If the player is indeed riding, update their position. + if newriding[name] then + newriding[name].pos = pos + end + end + riding = newriding + for name,r in pairs(riding) do + -- If the box is no longer loaded or existent, create another. + local ok = r.box and r.box.getpos and r.box:getpos() and r.box:get_luaentity() and r.box:get_luaentity().attached == name + if not ok then + minetest.log("action", "[elevator] "..minetest.pos_to_string(r.pos).." created due to lost rider.") + minetest.after(0, create_box, r.motor, r.pos, r.target, minetest.get_player_by_name(name)) + end + end + -- Ensure boxes are deleted after seconds if there are no players nearby. + for motor,obj in pairs(boxes) do + if type(obj) ~= "table" then + return + end + lastboxes[motor] = lastboxes[motor] and math.min(lastboxes[motor], PTIMEOUT) or PTIMEOUT + lastboxes[motor] = math.max(lastboxes[motor] - 1, 0) + local pos = obj:getpos() + if pos then + for _,object in ipairs(minetest.get_objects_inside_radius(pos, 5)) do + if object.is_player and object:is_player() then + lastboxes[motor] = PTIMEOUT + break + end + end + if lastboxes[motor] < 1 then + minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to lack of players.") + boxes[motor] = false + end + else + minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to lack of position during player check.") + boxes[motor] = false + end + end +end) + +minetest.register_on_leaveplayer(function(player) + -- We don't want players potentially logging into open elevators. + teleport_player_from_elevator(player) +end) + +local function phash(pos) + return minetest.pos_to_string(pos) +end + +local function punhash(pos) + return minetest.string_to_pos(pos) +end + +-- Starting from , locate a motor hash. +local function locate_motor(pos) + local p = vector.new(pos) + while true do + local node = get_node(p) + if node.name == "elevator:elevator_on" or node.name == "elevator:elevator_off" then + p.y = p.y + 2 + elseif node.name == "elevator:shaft" then + p.y = p.y + 1 + elseif node.name == "elevator:motor" then + return phash(p) + else + return nil + end + end +end + +local function build_motor(hash) + local need_saving = false + local motor = elevator.motors[hash] + -- Just ignore motors that don't exist. + if not motor then + return + end + local p = punhash(hash) + local node = get_node(p) + -- And ignore motors that aren't motors. + if node.name ~= "elevator:motor" then + return + end + p.y = p.y - 1 + motor.elevators = {} + motor.pnames = {} + motor.labels = {} + -- Run down through the shaft, storing information about elevators. + while true do + local node = get_node(p) + if node.name == "elevator:shaft" then + p.y = p.y - 1 + else + p.y = p.y - 1 + local node = get_node(p) + if node.name == "elevator:elevator_on" or node.name == "elevator:elevator_off" then + table.insert(motor.elevators, phash(p)) + table.insert(motor.pnames, tostring(p.y)) + table.insert(motor.labels, "") + p.y = p.y - 1 + need_saving = true + else + break + end + end + end + -- Set the elevators fully. + for i,m in ipairs(motor.elevators) do + local pos = punhash(m) + local meta = minetest.get_meta(pos) + meta:set_int("version", VERSION) + if meta:get_string("motor") ~= hash then + build_motor(meta:get_string("motor")) + end + motor.labels[i] = meta:get_string("label") + meta:set_string("motor", hash) + if motor.labels[i] ~= meta:get_string("infotext") then + meta:set_string("infotext", motor.labels[i]) + end + end + if need_saving then + save_elevator() + end +end + +local function unbuild(pos, add) + local need_saving = false + local p = table.copy(pos) + p.y = p.y - 1 + -- Loop down through the network, set any elevators below this to the off position. + while true do + local node = get_node(p) + if node.name == "elevator:shaft" then + p.y = p.y - 1 + else + p.y = p.y - 1 + local node = get_node(p) + if node.name == "elevator:elevator_on" or node.name == "elevator:elevator_off" then + local meta = minetest.get_meta(p) + meta:set_string("motor", "") + p.y = p.y - 1 + else + break + end + end + end + -- After a short delay, build the motor and handle box removal. + minetest.after(0.01, function(p2, add) + if not p2 or not add then + return + end + p2.y = p2.y + add + local motorhash = locate_motor(p2) + build_motor(motorhash) + -- If there's a box below this point, break it. + if boxes[motorhash] and boxes[motorhash]:getpos() and p2.y >= boxes[motorhash]:getpos().y then + boxes[motorhash] = nil + end + -- If the box does not exist, just clear it. + if boxes[motorhash] and not boxes[motorhash]:getpos() then + boxes[motorhash] = nil + end + end, table.copy(pos), add) +end + +minetest.register_node("elevator:motor", { + description = "Elevator Motor", + tiles = { + "default_steel_block.png", + "default_steel_block.png", + "elevator_motor.png", + "elevator_motor.png", + "elevator_motor.png", + "elevator_motor.png", + }, + groups = {cracky=1}, + sounds = default.node_sound_stone_defaults(), + after_place_node = function(pos, placer, itemstack) + -- Set up the motor table. + elevator.motors[phash(pos)] = { + elevators = {}, + pnames = {}, + labels = {}, + } + save_elevator() + build_motor(phash(pos)) + end, + on_destruct = function(pos) + -- Destroy everything related to this motor. + boxes[phash(pos)] = nil + elevator.motors[phash(pos)] = nil + save_elevator() + end, +}) + +for _,mode in ipairs({"on", "off"}) do + local nodename = "elevator:elevator_"..mode + local on = (mode == "on") + local box + local cbox + if on then + -- Active elevators have a ceiling and floor. + box = { + + { 0.48, -0.5,-0.5, 0.5, 1.5, 0.5}, + {-0.5 , -0.5, 0.48, 0.48, 1.5, 0.5}, + {-0.5, -0.5,-0.5 ,-0.48, 1.5, 0.5}, + + { -0.5,-0.5,-0.5,0.5,-0.48, 0.5}, + { -0.5, 1.45,-0.5,0.5, 1.5, 0.5}, + } + cbox = table.copy(box) + -- But you can enter them from the top. + cbox[5] = nil + else + -- Inactive elevators are almost like shafts. + box = { + + { 0.48, -0.5,-0.5, 0.5, 1.5, 0.5}, + {-0.5 , -0.5, 0.48, 0.48, 1.5, 0.5}, + {-0.5, -0.5,-0.5 ,-0.48, 1.5, 0.5}, + {-0.5 , -0.5, -0.48, 0.5, 1.5, -0.5}, + } + cbox = box + end + minetest.register_node(nodename, { + description = "Elevator", + drawtype = "nodebox", + sunlight_propagates = false, + paramtype = "light", + paramtype2 = "facedir", + on_rotate = screwdriver.disallow, + + selection_box = { + type = "fixed", + fixed = box, + }, + + collision_box = { + type = "fixed", + fixed = cbox, + }, + + node_box = { + type = "fixed", + fixed = box, + }, + + tiles = on and { + "default_steel_block.png", + "default_steel_block.png", + "elevator_box.png", + "elevator_box.png", + "elevator_box.png", + "elevator_box.png", + } or { + "elevator_box.png", + "elevator_box.png", + "elevator_box.png", + "elevator_box.png", + "elevator_box.png", + "elevator_box.png", + }, + groups = {cracky=1, choppy=1, snappy=1}, + drop = "elevator:elevator_off", + + -- Emit a bit of light when active. + light_source = (on and 4 or nil), + + after_place_node = function(pos, placer, itemstack) + local meta = minetest.get_meta(pos) + meta:set_int("version", VERSION) + + -- Add a placeholder to avoid nodes being placed in the top. + local p = vector.add(pos, {x=0, y=1, z=0}) + local p2 = minetest.dir_to_facedir(placer:get_look_dir()) + minetest.set_node(p, {name=placeholder, paramtype2="facedir", param2=p2}) + + -- Try to build a motor above. + local motor = locate_motor(pos) + if motor then + build_motor(motor) + end + end, + + after_dig_node = function(pos, node, meta, digger) + unbuild(pos, 2) + end, + + on_place = function(itemstack, placer, pointed_thing) + local pos = pointed_thing.above + local node = minetest.get_node(vector.add(pos, {x=0, y=1, z=0})) + if (node ~= nil and node.name ~= "air" and node.name ~= placeholder) then + return + end + return minetest.item_place(itemstack, placer, pointed_thing) + end, + + on_rightclick = function(pos, node, sender) + if not sender or not sender:is_player() then + return + end + local formspec + local meta = minetest.get_meta(pos) + formspecs[sender:get_player_name()] = {pos} + if on then + if vector.distance(sender:getpos(), pos) > 1 or minetest.get_node(sender:getpos()).name ~= nodename then + minetest.chat_send_player(sender:get_player_name(), "You are not inside the booth.") + return + end + -- Build the formspec from the motor table. + local tpnames = {} + local tpnames_l = {} + local motorhash = meta:get_string("motor") + local motor = elevator.motors[motorhash] + for ji,jv in ipairs(motor.pnames) do + if tonumber(jv) ~= pos.y then + table.insert(tpnames, jv) + table.insert(tpnames_l, (motor.labels[ji] and motor.labels[ji] ~= "") and (jv.." - "..minetest.formspec_escape(motor.labels[ji])) or jv) + end + end + formspecs[sender:get_player_name()] = {pos, tpnames} + if #tpnames > 0 then + if not minetest.is_protected(pos, sender:get_player_name()) then + formspec = "size[4,6]" + .."label[0,0;Click once to travel.]" + .."textlist[-0.1,0.5;4,4;target;"..table.concat(tpnames_l, ",").."]" + .."field[0.25,5.25;4,0;label;;"..minetest.formspec_escape(meta:get_string("label")).."]" + .."button_exit[-0.05,5.5;4,1;setlabel;Set label]" + else + formspec = "size[4,4.4]" + .."label[0,0;Click once to travel.]" + .."textlist[-0.1,0.5;4,4;target;"..table.concat(tpnames_l, ",").."]" + end + else + if not minetest.is_protected(pos, sender:get_player_name()) then + formspec = "size[4,2]" + .."label[0,0;No targets available.]" + .."field[0.25,1.25;4,0;label;;"..minetest.formspec_escape(meta:get_string("label")).."]" + .."button_exit[-0.05,1.5;4,1;setlabel;Set label]" + else + formspec = "size[4,0.4]" + .."label[0,0;No targets available.]" + end + end + minetest.show_formspec(sender:get_player_name(), "elevator:elevator", formspec) + elseif not elevator.motors[meta:get_string("motor")] then + if not minetest.is_protected(pos, sender:get_player_name()) then + formspec = "size[4,2]" + .."label[0,0;This elevator is inactive.]" + .."field[0.25,1.25;4,0;label;;"..minetest.formspec_escape(meta:get_string("label")).."]" + .."button_exit[-0.05,1.5;4,1;setlabel;Set label]" + else + formspec = "size[4,0.4]" + .."label[0,0;This elevator is inactive.]" + end + minetest.show_formspec(sender:get_player_name(), "elevator:elevator", formspec) + elseif boxes[meta:get_string("motor")] then + if not minetest.is_protected(pos, sender:get_player_name()) then + formspec = "size[4,2]" + .."label[0,0;This elevator is in use.]" + .."field[0.25,1.25;4,0;label;;"..minetest.formspec_escape(meta:get_string("label")).."]" + .."button_exit[-0.05,1.5;4,1;setlabel;Set label]" + else + formspec = "size[4,0.4]" + .."label[0,0;This elevator is in use.]" + end + minetest.show_formspec(sender:get_player_name(), "elevator:elevator", formspec) + end + end, + + on_destruct = function(pos) + local p = vector.add(pos, {x=0, y=1, z=0}) + if get_node(p).name == placeholder then + minetest.remove_node(p) + end + end, + }) +end + +minetest.register_on_player_receive_fields(function(sender, formname, fields) + if formname ~= "elevator:elevator" then + return + end + local pos = formspecs[sender:get_player_name()] and formspecs[sender:get_player_name()][1] or nil + if not pos then + return true + end + local meta = minetest.get_meta(pos) + if fields.setlabel then + if minetest.is_protected(pos, sender:get_player_name()) then + return true + end + meta:set_string("label", fields.label) + meta:set_string("infotext", fields.label) + -- Rebuild the elevator shaft so the other elevators can read this label. + local motorhash = meta:get_string("motor") + build_motor(elevator.motors[motorhash] and motorhash or locate_motor(pos)) + return true + end + -- Double check if it's ok to go. + if vector.distance(sender:getpos(), pos) > 1 then + return true + end + if fields.target then + local closeformspec = "" + -- HACK: With player information extensions enabled, we can check if closing formspecs are now allowed. This is specifically used on Survival in Ethereal. + local pi = minetest.get_player_information(sender:get_player_name()) + if (not (pi.major == 0 and pi.minor == 4 and pi.patch == 15)) and (pi.protocol_version or 29) < 29 then + 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]" + end + -- End hacky HACK. + minetest.after(0.2, minetest.show_formspec, sender:get_player_name(), "elevator:elevator", closeformspec) + -- Ensure we're connected to a motor. + local motorhash = meta:get_string("motor") + local motor = elevator.motors[motorhash] + if not motor then + motorhash = locate_motor(pos) + motor = elevator.motors[motorhash] + if motor then + meta:set_string("motor", "") + build_motor(motorhash) + minetest.chat_send_player(sender:get_player_name(), "Recalibrated to a new motor, please try again.") + return true + end + end + if not motor then + minetest.chat_send_player(sender:get_player_name(), "This elevator is not attached to a motor.") + return true + end + if not formspecs[sender:get_player_name()][2] or not formspecs[sender:get_player_name()][2][minetest.explode_textlist_event(fields.target).index] then + return true + end + -- Locate our target elevator. + local target = nil + local selected_target = formspecs[sender:get_player_name()][2][minetest.explode_textlist_event(fields.target).index] + for i,v in ipairs(motor.pnames) do + if v == selected_target then + target = punhash(motor.elevators[i]) + end + end + -- Found the elevator? Then go! + if target then + -- Final check. + if boxes[motorhash] then + minetest.chat_send_player(sender:get_player_name(), "This elevator is in use.") + return true + end + local obj = create_box(motorhash, pos, target, sender) + -- Teleport anyone standing within an on elevator out, or they'd fall through the off elevators. + for _,p in ipairs(motor.elevators) do + local p = punhash(p) + for _,object in ipairs(minetest.get_objects_inside_radius(p, 0.6)) do + if object.is_player and object:is_player() then + if object:get_player_name() ~= obj:get_luaentity().attached then + teleport_player_from_elevator(object) + end + end + end + end + else + minetest.chat_send_player(sender:get_player_name(), "This target is invalid.") + return true + end + return true + end + return true +end) + +-- Compatability with an older version. +minetest.register_alias("elevator:elevator", "elevator:elevator_off") + +-- Ensure an elevator is up to the latest version. +local function upgrade_elevator(pos, meta) + if meta:get_int("version") ~= VERSION then + minetest.log("action", "[elevator] Updating elevator with old version at "..minetest.pos_to_string(pos)) + minetest.after(0, function(pos) build_motor(locate_motor(pos)) end, pos) + meta:set_int("version", VERSION) + meta:set_string("formspec", "") + meta:set_string("infotext", meta:get_string("label")) + end +end + +-- Convert off to on when applicable. +local offabm = function(pos, node) + local meta = minetest.get_meta(pos) + upgrade_elevator(pos, meta) + if not boxes[meta:get_string("motor")] and elevator.motors[meta:get_string("motor")] then + node.name = "elevator:elevator_on" + minetest.swap_node(pos, node) + end +end + +minetest.register_abm({ + nodenames = {"elevator:elevator_off"}, + interval = 1, + chance = 1, + action = offabm, + label = "Elevator (Off)", +}) + +-- Convert on to off when applicable. +minetest.register_abm({ + nodenames = {"elevator:elevator_on"}, + interval = 1, + chance = 1, + action = function(pos, node) + local meta = minetest.get_meta(pos) + upgrade_elevator(pos, meta) + if boxes[meta:get_string("motor")] or not elevator.motors[meta:get_string("motor")] then + node.name = "elevator:elevator_off" + minetest.swap_node(pos, node) + end + end, + label = "Elevator (On)", +}) + +minetest.register_node("elevator:shaft", { + description = "Elevator Shaft", + tiles = { "elevator_shaft.png" }, + drawtype = "nodebox", + paramtype = "light", + on_rotate = screwdriver.disallow, + sunlight_propagates = true, + groups = {cracky=2, oddly_breakable_by_hand=1}, + sounds = default.node_sound_stone_defaults(), + node_box = { + type = "fixed", + fixed = { + {-8/16,-8/16,-8/16,-7/16,8/16,8/16}, + {7/16,-8/16,-8/16,8/16,8/16,8/16}, + {-7/16,-8/16,-8/16,7/16,8/16,-7/16}, + {-7/16,-8/16,8/16,7/16,8/16,7/16}, + }, + }, + collisionbox = { + type = "fixed", + fixed = { + {-8/16,-8/16,-8/16,-7/16,8/16,8/16}, + {7/16,-8/16,-8/16,8/16,8/16,8/16}, + {-7/16,-8/16,-8/16,7/16,8/16,-7/16}, + {-7/16,-8/16,8/16,7/16,8/16,7/16}, + }, + }, + after_place_node = function(pos) + -- We might have connected a motor above to an elevator below. + build_motor(locate_motor(pos)) + end, + on_destruct = function(pos) + -- Remove boxes and deactivate elevators below us. + unbuild(pos, 1) + end, +}) + +local box = { + { 0.48, -0.5,-0.5, 0.5, 1.5, 0.5}, + {-0.5 , -0.5, 0.48, 0.48, 1.5, 0.5}, + {-0.5, -0.5,-0.5 ,-0.48, 1.5, 0.5}, + {-0.5 , -0.5, -0.48, 0.5, 1.5, -0.5}, + { -0.5,-0.5,-0.5,0.5,-0.48, 0.5}, + { -0.5, 1.45,-0.5,0.5, 1.5, 0.5}, +} + +-- Elevator box node. Not intended to be placeable. +minetest.register_node("elevator:elevator_box", { + description = "Elevator", + drawtype = "nodebox", + paramtype = 'light', + paramtype2 = "facedir", + wield_scale = {x=0.6, y=0.6, z=0.6}, + + selection_box = { + type = "fixed", + fixed = { -0.5, -0.5, -0.5, 0.5, 1.5, 0.5 } + }, + + collision_box = { + type = "fixed", + fixed = box, + }, + + node_box = { + type = "fixed", + fixed = box, + }, + + tiles = { + "default_steel_block.png", + "default_steel_block.png", + "elevator_box.png", + "elevator_box.png", + "elevator_box.png", + "elevator_box.png", + }, + groups = {not_in_creative_inventory = 1}, + + light_source = 4, +}) + +-- Remove the player from self, and teleport them to pos if specified. +local function detach(self, pos) + local player = minetest.get_player_by_name(self.attached) + local attached = player:get_attach() + if not attached or attached:get_luaentity().uid ~= self.uid then + return + end + player:set_detach() + player:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0}) + player:set_properties({visual_size = {x=1, y=1}}) + if armor_path then + armor:update_player_visuals(player) + end + if pos then + player:setpos(pos) + minetest.after(0.1, function(pl, p) + pl:setpos(p) + end, player, pos) + end + riding[self.attached] = nil +end + +local box_entity = { + physical = false, + collisionbox = {0,0,0,0,0,0}, + visual = "wielditem", + visual_size = {x=1, y=1}, + textures = {"elevator:elevator_box"}, + + attached = "", + motor = false, + target = false, + + start = false, + lastpos = false, + halfway = false, + vmult = 0, + + on_activate = function(self, staticdata) + -- Don't want the box being destroyed by anything except the elevator system. + self.object:set_armor_groups({immortal=1}) + end, + + on_step = function(self, dtime) + local pos = self.object:getpos() + -- First, check if this box needs removed. + -- If the motor has a box and it isn't this box. + if boxes[self.motor] and boxes[self.motor] ~= self.object then + minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to duplication.") + self.object:remove() + return + end + -- If our attached player can't be found. + if not minetest.get_player_by_name(self.attached) then + minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to lack of attachee logged in.") + self.object:remove() + boxes[self.motor] = nil + return + end + -- If our attached player is no longer with us. + 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 + minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to lack of attachee.") + self.object:remove() + boxes[self.motor] = nil + return + end + -- If our motor's box is nil, we should self-destruct. + if not boxes[self.motor] then + minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to nil entry in boxes.") + detach(self) + self.object:remove() + boxes[self.motor] = nil + return + end + + minetest.get_player_by_name(self.attached):setpos(pos) + -- Ensure lastpos is set to something. + self.lastpos = self.lastpos or pos + + -- Loop through all travelled nodes. + for y=self.lastpos.y,pos.y,((self.lastpos.y > pos.y) and -0.3 or 0.3) do + local p = vector.round({x=pos.x, y=y, z=pos.z}) + local node = get_node(p) + if node.name == "elevator:shaft" then + -- Nothing, just continue on our way. + elseif node.name == "elevator:elevator_on" or node.name == "elevator:elevator_off" then + -- If this is our target, detach the player here, destroy this box, and update the target elevator without waiting for the abm. + if vector.distance(p, self.target) < 1 then + minetest.log("action", "[elevator] "..minetest.pos_to_string(p).." broke due to arrival.") + detach(self, vector.add(self.target, {x=0, y=-0.4, z=0})) + self.object:remove() + boxes[self.motor] = nil + offabm(self.target, node) + return + end + else + -- Check if we're in the top part of an elevator, if so it's fine. + local below = vector.add(p, {x=0,y=-1,z=0}) + local belownode = get_node(below) + if belownode.name ~= "elevator:elevator_on" and belownode.name ~= "elevator:elevator_off" then + -- If we aren't, then break the box. + minetest.log("action", "[elevator] "..minetest.pos_to_string(p).." broke on "..node.name) + boxes[self.motor] = nil + detach(self, p) + self.object:remove() + return + end + end + end + self.lastpos = pos + end, +} + +minetest.register_entity("elevator:box", box_entity) + +if technic_path and chains_path then + minetest.register_craft({ + output = "elevator:elevator", + recipe = { + {"technic:cast_iron_ingot", "chains:chain", "technic:cast_iron_ingot"}, + {"technic:cast_iron_ingot", "default:mese_crystal", "technic:cast_iron_ingot"}, + {"technic:stainless_steel_ingot", "default:glass", "technic:stainless_steel_ingot"}, + }, + }) + + minetest.register_craft({ + output = "elevator:shaft", + recipe = { + {"technic:cast_iron_ingot", "default:glass"}, + {"default:glass", "glooptest:chainlink"}, + }, + }) + + minetest.register_craft({ + output = "elevator:motor", + recipe = { + {"default:diamond", "technic:control_logic_unit", "default:diamond"}, + {"default:steelblock", "technic:motor", "default:steelblock"}, + {"chains:chain", "default:diamond", "chains:chain"} + }, + }) +elseif technic_path and farming and farming.mod and farming.mod == "redo" then + -- add alternative recipe with hemp rope + minetest.register_craft({ + output = "elevator:elevator", + recipe = { + {"technic:cast_iron_ingot", "farming:hemp_rope", "technic:cast_iron_ingot"}, + {"technic:cast_iron_ingot", "default:mese_crystal", "technic:cast_iron_ingot"}, + {"technic:stainless_steel_ingot", "default:glass", "technic:stainless_steel_ingot"}, + }, + }) + + minetest.register_craft({ + output = "elevator:shaft", + recipe = { + {"technic:cast_iron_ingot", "default:glass"}, + {"default:glass", "farming:hemp_rope"}, + }, + }) + + minetest.register_craft({ + output = "elevator:motor", + recipe = { + {"default:diamond", "technic:control_logic_unit", "default:diamond"}, + {"default:steelblock", "technic:motor", "default:steelblock"}, + {"farming:hemp_rope", "default:diamond", "farming:hemp_rope"} + }, + }) + + -- Recipes without technic & chains required. +-- Recipes for default dependency fallback. +else + minetest.register_craft({ + output = "elevator:elevator", + recipe = { + {"default:steel_ingot", "farming:cotton", "default:steel_ingot"}, + {"default:steel_ingot", "default:mese_crystal", "default:steel_ingot"}, + {"xpanes:pane_flat", "default:glass", "xpanes:pane_flat"}, + }, + }) + + minetest.register_craft({ + output = "elevator:shaft", + recipe = { + {"default:steel_ingot", "default:obsidian_glass"}, + {"default:obsidian_glass", "default:steel_ingot"}, + }, + }) + + minetest.register_craft({ + output = "elevator:motor", + recipe = { + {"default:diamond", "default:copper_ingot", "default:diamond"}, + {"default:steelblock", "default:furnace", "default:steelblock"}, + {"farming:cotton", "default:diamond", "farming:cotton"} + }, + }) +end diff --git a/elevator/mod.conf b/elevator/mod.conf new file mode 100644 index 0000000..b86b042 --- /dev/null +++ b/elevator/mod.conf @@ -0,0 +1,3 @@ +name = elevator +depends = default +optional_depends = technic, homedecor, chains, farming diff --git a/elevator/screenshot.png b/elevator/screenshot.png new file mode 100644 index 0000000..9a3c9bc Binary files /dev/null and b/elevator/screenshot.png differ diff --git a/elevator/textures/elevator_box.png b/elevator/textures/elevator_box.png new file mode 100644 index 0000000..714adf0 Binary files /dev/null and b/elevator/textures/elevator_box.png differ diff --git a/elevator/textures/elevator_motor.png b/elevator/textures/elevator_motor.png new file mode 100644 index 0000000..73a226c Binary files /dev/null and b/elevator/textures/elevator_motor.png differ diff --git a/elevator/textures/elevator_shaft.png b/elevator/textures/elevator_shaft.png new file mode 100644 index 0000000..ce9c097 Binary files /dev/null and b/elevator/textures/elevator_shaft.png differ diff --git a/homedecor_modpack/nodes.txt b/homedecor_modpack/nodes.txt new file mode 100644 index 0000000..2e8762d --- /dev/null +++ b/homedecor_modpack/nodes.txt @@ -0,0 +1,4 @@ +Node listing as of Sun May 16 15:15:25 2021 + +In *.lua: +