init; changed textures
Some checks failed
luacheck / build (push) Has been cancelled

This commit is contained in:
Rainer 2026-02-12 12:48:27 +01:00
commit 2dfc96ee15
22 changed files with 1268 additions and 0 deletions

5
.editorconfig Normal file
View file

@ -0,0 +1,5 @@
root = true
[*]
indent_style = space
indent_size = 4

17
.github/workflows/luacheck.yml vendored Normal file
View file

@ -0,0 +1,17 @@
name: luacheck
on: [push]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: apt
run: sudo apt-get install -y luarocks
- name: luacheck install
run: luarocks install --local luacheck
- name: luacheck run
run: $HOME/.luarocks/bin/luacheck ./

27
.luacheckrc Normal file
View file

@ -0,0 +1,27 @@
unused_args = false
ignore = {
"631",
}
globals = {
"elevator",
}
read_globals = {
-- Stdlib
string = {fields = {"split"}},
table = {fields = {"copy", "getn"}},
-- Minetest
"minetest",
"core",
"vector",
"VoxelManip",
-- deps
"default", "screwdriver",
"farming", "armor",
"mcl_core", "mcl_sounds",
"aurum",
}

27
LICENSE.txt Normal file
View file

@ -0,0 +1,27 @@
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.
----
Elevator motor and elevator textures for MCL2 are based on "default_glass.png",
included with MCL2 game, original copyright:
"MIT License.
The textures are taken from the Minecraft resource pack “Faithful 1.11” by
Vattic and xMrVizzy and contributers."
Elevator motor design is (c) minertestdude, redistributed under MIT license.
elevator_steel_block.png CC BY-SA 3.0 (https://github.com/minetest/minetest_game)

8
README.md Normal file
View file

@ -0,0 +1,8 @@
# Realtime Elevators for Minetest
[![ContentDB](https://content.minetest.net/packages/shacknetisp/elevator/shields/title/)](https://content.minetest.net/packages/shacknetisp/elevator/)
## Dependencies
This mod has no dependencies. This mod supports MineClone 2 and Minetest Game with improved recipes with Technic and either homedecor/chains or farming redo.
## 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.

423
components.lua Normal file
View file

@ -0,0 +1,423 @@
local phash = elevator.phash
local get_node = elevator.get_node
local homedecor_path = minetest.get_modpath("homedecor")
local mineclone_path = core.get_modpath("mcl_core") and mcl_core
local default_path = core.get_modpath("default") and default
local aurum_path = core.get_modpath("aurum") and aurum
local moditems = {} -- local table to hold substitutes
-- Use homedecor's placeholder if possible.
if homedecor_path then
minetest.register_alias("elevator:placeholder", "homedecor:expansion_placeholder")
else
-- Placeholder node, in the style of homedecor.
minetest.register_node("elevator: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="elevator:placeholder"})
end
})
end
if mineclone_path then
moditems.el_shaft_groups = {pickaxey=1,axey=1,handy=1,swordy=1,transport=1,dig_by_piston=1}
moditems.el_motor_groups = {pickaxey=1,axey=1,handy=1,swordy=1,transport=1,dig_by_piston=1}
moditems.elevator_groups = {pickaxey=1,axey=1,handy=1,swordy=1,transport=1,dig_by_piston=1}
moditems.elevator_special_groups = {not_in_creative_inventory=1,pickaxey=1,axey=1,handy=1,swordy=1,transport=1,dig_by_piston=1}
moditems.sounds_stone = mcl_sounds.node_sound_stone_defaults
moditems.el_motor_gfx = "elevator_motor_mcl.png"
moditems.el_shaft_gfx = "elevator_shaft_mcl.png"
moditems.el_box_gfx = "elevator_box_mcl.png"
moditems.steel_block_image = "default_steel_block.png"
elseif default_path then
moditems.el_shaft_groups = {cracky=2,oddly_breakable_by_hand=0} -- removing ability to destroy by hand to prevent accidental breakage of whole elevators
moditems.el_motor_groups = {cracky=1}
moditems.elevator_groups = {cracky=1,choppy=1,snappy=1}
moditems.elevator_special_groups = {not_in_creative_inventory=1}
moditems.sounds_stone = default.node_sound_stone_defaults
moditems.el_motor_gfx = "elevator_motor.png"
moditems.el_shaft_gfx = "elevator_shaft.png"
moditems.el_box_gfx = "elevator_box.png"
moditems.steel_block_image = "default_steel_block.png"
elseif aurum_path then
moditems.el_shaft_groups = {dig_pick = 2}
moditems.el_motor_groups = {dig_pick = 1}
moditems.elevator_groups = {dig_pick = 1}
moditems.elevator_special_groups = {not_in_creative_inventory=1}
moditems.sounds_stone = aurum.sounds.stone
moditems.el_motor_gfx = "elevator_motor.png"
moditems.el_shaft_gfx = "elevator_shaft.png"
moditems.el_box_gfx = "elevator_box.png"
moditems.steel_block_image = "aurum_ore_white.png^[colorize:#cbcdcd:255^aurum_ore_bumps.png^aurum_ore_block.png"
else
-- fallback for unknown games
moditems.el_shaft_groups = {cracky=2, oddly_breakable_by_hand=0}
moditems.el_motor_groups = {cracky=1}
moditems.elevator_groups = {cracky=1, choppy=1, snappy=1}
moditems.elevator_special_groups = {not_in_creative_inventory=1}
moditems.sounds_stone = function() end
moditems.el_motor_gfx = "elevator_motor.png"
moditems.el_shaft_gfx = "elevator_shaft.png"
moditems.el_box_gfx = "elevator_box.png"
moditems.steel_block_image = "elevator_steel_block.png"
end
if minetest.global_exists("screwdriver") then
moditems.on_rotate_disallow = screwdriver.disallow
end
minetest.register_node("elevator:shaft", {
description = "Elevator Shaft",
_doc_items_longdesc = "An elevator shaft that connects elevators to other elevators and motors.",
_doc_items_usagehelp = "Building a vertical stack of elevators and shafts with an elevator motor on top allows vertical transportation.",
tiles = { moditems.el_shaft_gfx },
drawtype = "nodebox",
use_texture_alpha = "clip",
paramtype = "light",
on_rotate = moditems.on_rotate_disallow,
sunlight_propagates = true,
groups = moditems.el_shaft_groups,
is_ground_content = false,
sounds = moditems.sounds_stone(),
climbable = true,
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.
elevator.build_motor(elevator.locate_motor(pos))
end,
on_destruct = function(pos)
-- Remove boxes and deactivate elevators below us.
elevator.unbuild(pos, 1)
end,
_mcl_blast_resistance = 15, -- mineclone2 specific
_mcl_hardness = 5, -- mineclone2 specific
})
minetest.register_node("elevator:motor", {
description = "Elevator Motor",
_doc_items_longdesc = "The engine powering an elevator shaft. Placed at the top.",
_doc_items_usagehelp = "Place the motor on the top of a stack of elevators and elevator shafts. The elevators will activate and you can then use them.",
tiles = {
moditems.steel_block_image,
moditems.steel_block_image,
moditems.el_motor_gfx,
moditems.el_motor_gfx,
moditems.el_motor_gfx,
moditems.el_motor_gfx,
},
groups = moditems.el_motor_groups,
is_ground_content = false,
sounds = moditems.sounds_stone(),
after_place_node = function(pos, placer, itemstack)
-- Set up the motor table.
elevator.motors[phash(pos)] = {
elevators = {},
pnames = {},
labels = {},
}
elevator.save_elevator()
elevator.build_motor(phash(pos))
end,
on_destruct = function(pos)
-- Destroy everything related to this motor.
elevator.boxes[phash(pos)] = nil
elevator.motors[phash(pos)] = nil
elevator.save_elevator()
end,
_mcl_blast_resistance = 15, -- mineclone2 specific
_mcl_hardness = 5, -- mineclone2 specific
})
-- Box of the active entitity.
local box_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_box,
},
node_box = {
type = "fixed",
fixed = box_box,
},
tiles = {
moditems.steel_block_image,
moditems.steel_block_image,
moditems.el_box_gfx,
moditems.el_box_gfx,
moditems.el_box_gfx,
moditems.el_box_gfx,
},
groups = moditems.elevator_special_groups,
is_ground_content = false,
use_texture_alpha = "clip",
light_source = 4,
_mcl_blast_resistance = 15, -- mineclone2 specific
_mcl_hardness = 5, -- mineclone2 specific
})
for _,mode in ipairs({"on", "off"}) do
local nodename = "elevator:elevator_"..mode
local on = (mode == "on")
local box
local cbox
local groups = table.copy(moditems.elevator_groups)
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
groups.not_in_creative_inventory = 1
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 = moditems.on_rotate_disallow,
climbable = true,
_doc_items_longdesc = on and "An active elevator, ready for transporting." or "An inactive elevator, not connected to a motor.",
_doc_items_usagehelp = on and "Step inside this elevator and use it (right-click) to be transported to any other elevator along the shaft." or "This elevator is inactive; it is disconnected from a motor. It may be extended with shafts and other elevators in a vertical line with an elevator motor on top to power the whole shaft and enable transport.",
selection_box = {
type = "fixed",
fixed = box,
},
collision_box = {
type = "fixed",
fixed = cbox,
},
node_box = {
type = "fixed",
fixed = box,
},
tiles = on and {
moditems.steel_block_image,
moditems.steel_block_image,
moditems.el_box_gfx,
moditems.el_box_gfx,
moditems.el_box_gfx,
moditems.el_box_gfx,
} or {
moditems.el_box_gfx,
moditems.el_box_gfx,
moditems.el_box_gfx,
moditems.el_box_gfx,
moditems.el_box_gfx,
moditems.el_box_gfx,
},
use_texture_alpha = "clip",
groups = groups,
is_ground_content = false,
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", elevator.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="elevator:placeholder", paramtype2="facedir", param2=p2})
-- Try to build a motor above.
local motor = elevator.locate_motor(pos)
if motor then
elevator.build_motor(motor)
end
end,
after_dig_node = function(pos, node, meta, digger)
elevator.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 ~= "elevator:placeholder") then
return
end
return minetest.item_place(itemstack, placer, pointed_thing)
end,
on_rightclick = function(pos, node, sender, itemstack, pointed_thing)
if not sender or not sender:is_player() then
return
end
-- When the player is holding elevator components, just place them instead of opening the formspec.
if ({
["elevator:elevator_off"] = true,
["elevator:shaft"] = true,
["elevator:motor"] = true,
})[sender:get_wielded_item():get_name()] then
return core.item_place_node(itemstack, sender, pointed_thing)
end
local formspec
local meta = minetest.get_meta(pos)
elevator.formspecs[sender:get_player_name()] = {pos}
local motorhash = meta:get_string("motor")
if on and elevator.motors[motorhash] then
if vector.distance(sender:get_pos(), pos) > 1 or minetest.get_node(sender:get_pos()).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 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
elevator.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[motorhash] 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 elevator.boxes[motorhash] 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 == "elevator:placeholder" then
minetest.remove_node(p)
end
end,
_mcl_blast_resistance = 15, -- mineclone2 specific
_mcl_hardness = 5, -- mineclone2 specific
})
end
-- Compatability with an older version.
minetest.register_alias("elevator:elevator", "elevator:elevator_off")

168
crafts.lua Normal file
View file

@ -0,0 +1,168 @@
-- Detect optional mods.
local technic_path = minetest.get_modpath("technic")
local chains_path = minetest.get_modpath("chains")
local mineclone_path = core.get_modpath("mcl_core") and mcl_core
local aurum_path = core.get_modpath("aurum") and aurum
local basic_materials_path = core.get_modpath("basic_materials")
if mineclone_path then
minetest.register_craft({
output = "elevator:elevator_off",
recipe = {
{"mcl_core:iron_ingot", "mcl_core:paper", "mcl_core:iron_ingot"},
{"mcl_core:iron_ingot", "mcl_core:gold_ingot", "mcl_core:iron_ingot"},
{"mcl_core:clay_lump", "group:glass", "mcl_core:clay_lump"},
},
})
minetest.register_craft({
output = "elevator:shaft",
recipe = {
{"mcl_core:iron_ingot", "group:wood"},
{"group:wood", "mcl_core:iron_ingot"},
},
})
minetest.register_craft({
output = "elevator:motor",
recipe = {
{"mcl_core:gold_ingot", "mcl_core:iron_ingot", "mcl_core:gold_ingot"},
{"mcl_core:ironblock", "mcl_furnaces:furnace", "mcl_core:ironblock"},
{"mcl_core:paper", "mcl_core:gold_ingot", "mcl_core:paper"}
},
})
elseif aurum_path then
minetest.register_craft({
output = "elevator:elevator_off",
recipe = {
{"aurum_ore:iron_ingot", "group:glass", "aurum_ore:iron_ingot"},
{"aurum_ore:iron_ingot", "aurum_ore:mana_bean", "aurum_ore:iron_ingot"},
{"aurum_ore:iron_ingot", "group:glass", "aurum_ore:iron_ingot"},
},
})
minetest.register_craft({
output = "elevator:shaft",
recipe = {
{"group:glass", "aurum_ore:iron_ingot"},
{"group:wood", "group:glass"},
},
})
minetest.register_craft({
output = "elevator:motor",
recipe = {
{"aurum_ore:gold_ingot", "aurum_ore:iron_ingot", "aurum_ore:gold_ingot"},
{"aurum_ore:iron_block", "aurum_cook:oven", "aurum_ore:iron_block"},
{"aurum_ore:gold_ingot", "aurum_ore:mana_bean", "aurum_ore:gold_ingot"}
},
})
elseif technic_path and chains_path then
minetest.register_craft({
output = "elevator:elevator_off",
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 basic_materials_path then
minetest.register_craft({
output = "elevator:elevator_off",
recipe = {
{"technic:cast_iron_ingot", "basic_materials:chain_steel", "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", "basic_materials:chain_steel"},
},
})
minetest.register_craft({
output = "elevator:motor",
recipe = {
{"default:diamond", "technic:control_logic_unit", "default:diamond"},
{"default:steelblock", "technic:motor", "default:steelblock"},
{"basic_materials:chain_steel", "default:diamond", "basic_materials:chain_steel"}
},
})
elseif technic_path and farming and farming.mod and ( farming.mod == "redo" or farming.mod == "undo" ) then
-- add alternative recipe with hemp rope
minetest.register_craft({
output = "elevator:elevator_off",
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_off",
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

90
formspecs.lua Normal file
View file

@ -0,0 +1,90 @@
local punhash = elevator.punhash
minetest.register_on_player_receive_fields(function(sender, formname, fields)
if formname ~= "elevator:elevator" then
return
end
local pos = elevator.formspecs[sender:get_player_name()] and elevator.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")
elevator.build_motor(elevator.motors[motorhash] and motorhash or elevator.locate_motor(pos))
return true
end
-- Double check if it's ok to go.
if vector.distance(sender:get_pos(), 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 = elevator.locate_motor(pos)
motor = elevator.motors[motorhash]
if motor then
meta:set_string("motor", "")
elevator.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 elevator.formspecs[sender:get_player_name()][2] or not elevator.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 = elevator.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 elevator.boxes[motorhash] then
minetest.chat_send_player(sender:get_player_name(), "This elevator is in use.")
return true
end
local obj = elevator.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
for _,object in ipairs(minetest.get_objects_inside_radius(punhash(p), 0.6)) do
if object.is_player and object:is_player() then
if object:get_player_name() ~= obj:get_luaentity().attached then
elevator.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)

38
helpers.lua Normal file
View file

@ -0,0 +1,38 @@
-- Try to teleport player away from any closed (on) elevator node.
elevator.teleport_player_from_elevator = function(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:get_pos())
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:set_pos(front)
end
end
end
elevator.phash = function(pos)
return minetest.pos_to_string(pos)
end
elevator.punhash = function(pos)
return minetest.string_to_pos(pos)
end
-- Helper function to read unloaded nodes.
elevator.get_node = function(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

61
hooks.lua Normal file
View file

@ -0,0 +1,61 @@
-- Globalstep timer.
local time = 0
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:get_pos()
local name = p:get_player_name()
newriding[name] = elevator.riding[name]
-- If the player is indeed riding, update their position.
if newriding[name] then
newriding[name].pos = pos
end
end
elevator.riding = newriding
for name,r in pairs(elevator.riding) do
-- If the box is no longer loaded or existent, create another.
local ok = r.box and r.box.get_pos and r.box:get_pos() 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, elevator.create_box, r.motor, r.pos, r.target, minetest.get_player_by_name(name))
end
end
-- Ensure boxes are deleted after <PTIMEOUT> seconds if there are no players nearby.
for motor,obj in pairs(elevator.boxes) do
if type(obj) ~= "table" then
return
end
elevator.lastboxes[motor] = elevator.lastboxes[motor] and math.min(elevator.lastboxes[motor], elevator.PTIMEOUT) or elevator.PTIMEOUT
elevator.lastboxes[motor] = math.max(elevator.lastboxes[motor] - 1, 0)
local pos = obj:get_pos()
if pos then
for _,object in ipairs(minetest.get_objects_inside_radius(pos, 5)) do
if object.is_player and object:is_player() then
elevator.lastboxes[motor] = elevator.PTIMEOUT
break
end
end
if elevator.lastboxes[motor] < 1 then
minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to lack of players.")
elevator.boxes[motor] = false
end
else
minetest.log("action", "[elevator] "..minetest.pos_to_string(pos).." broke due to lack of position during player check.")
elevator.boxes[motor] = false
end
end
end)
minetest.register_on_leaveplayer(function(player)
-- We don't want players potentially logging into open elevators.
elevator.teleport_player_from_elevator(player)
end)

350
init.lua Normal file
View file

@ -0,0 +1,350 @@
-- Detect optional mods.
local armor_path = minetest.get_modpath("3d_armor")
-- global runtime storage for data and references
-- contains .motors loaded from mod storage
-- runtime variables and api functions
elevator = {
SPEED = tonumber(minetest.settings:get("elevator_speed")) or 10, -- Initial speed of a box.
ACCEL = tonumber(minetest.settings:get("elevator_accel")) or 0.1, -- Acceleration of a box.
VISUAL_INCREASE = 1.75,
VERSION = 8, -- Elevator interface/database version.
PTIMEOUT = tonumber(minetest.settings:get("elevator_time")) or 120, -- Maximum time a box can go without players nearby.
SLOW_DIST = tonumber(minetest.settings:get("elevator_slow_dist")) or 16,
SLOW_SPEED_FACTOR = tonumber(minetest.settings:get("elevator_slow_speed_factor")) or 0.11,
boxes = {}, -- Elevator boxes in action.
lastboxes = {}, -- Player near box timeout.
riding = {}, -- Players riding boxes.
formspecs = {}, -- Player formspecs.
}
local MP = minetest.get_modpath(minetest.get_current_modname())
dofile(MP .. "/helpers.lua")
dofile(MP .. "/storage.lua")
dofile(MP .. "/crafts.lua")
dofile(MP .. "/components.lua")
dofile(MP .. "/hooks.lua")
dofile(MP .. "/formspecs.lua")
local phash = elevator.phash
local punhash = elevator.punhash
local get_node = elevator.get_node
-- Cause <sender> to ride <motorhash> beginning at <pos> and targetting <target>.
elevator.create_box = function(motorhash, pos, target, sender)
-- First create the box.
local obj = minetest.add_entity(pos, "elevator:box")
obj:set_pos(pos)
-- Attach the player.
sender:set_pos(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=elevator.VISUAL_INCREASE, y=elevator.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
-- FIX for "overshooting"
local delta_y = math.abs(pos.y - target.y)
local speed = elevator.SPEED
if delta_y < elevator.SLOW_DIST then
speed = elevator.SPEED * elevator.SLOW_SPEED_FACTOR
end
-- Set the speed.
obj:set_velocity({x=0, y=speed*obj:get_luaentity().vmult, z=0})
obj:set_acceleration({x=0, y=elevator.ACCEL*obj:get_luaentity().vmult, z=0})
-- Set the tables.
elevator.boxes[motorhash] = obj
elevator.riding[sender:get_player_name()] = {
motor = motorhash,
pos = pos,
target = target,
box = obj,
}
return obj
end
-- Starting from <pos>, locate a motor hash.
elevator.locate_motor = function(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
elevator.build_motor = function(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)
-- And ignore motors that aren't motors.
if get_node(p).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 next_node = get_node(p)
if next_node.name == "elevator:shaft" then
-- Shaft, just keep going down.
p.y = p.y - 1
else
-- Wasn't shaft. Go down one, to skip placeholders, and then test for elevators.
p.y = p.y - 1
local elevator_node = get_node(p)
if elevator_node.name == "elevator:elevator_on" or elevator_node.name == "elevator:elevator_off" then
-- Was elevator, insert into table and continue.
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
-- Was no elevator, this is the end of the shaft.
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", elevator.VERSION)
if meta:get_string("motor") ~= hash then
elevator.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
elevator.save_elevator()
end
end
elevator.unbuild = function(pos, add)
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
if get_node(p).name == "elevator:shaft" then
p.y = p.y - 1
else
p.y = p.y - 1
local elevator_node = get_node(p)
if elevator_node.name == "elevator:elevator_on" or elevator_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)
if not p2 or not add then
return
end
p2.y = p2.y + add
local motorhash = elevator.locate_motor(p2)
elevator.build_motor(motorhash)
-- If there's a box below this point, break it.
if elevator.boxes[motorhash] and elevator.boxes[motorhash]:get_pos() and p2.y >= elevator.boxes[motorhash]:get_pos().y then
elevator.boxes[motorhash] = nil
end
-- If the box does not exist, just clear it.
if elevator.boxes[motorhash] and not elevator.boxes[motorhash]:get_pos() then
elevator.boxes[motorhash] = nil
end
end, table.copy(pos))
end
-- Ensure an elevator is up to the latest version.
local function upgrade_elevator(pos, meta)
if meta:get_int("version") ~= elevator.VERSION then
minetest.log("action", "[elevator] Updating elevator with old version at "..minetest.pos_to_string(pos))
minetest.after(0, function() elevator.build_motor(elevator.locate_motor(pos)) end)
meta:set_int("version", elevator.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 elevator.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 elevator.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)",
})
-- 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:set_pos(pos)
minetest.after(0.1, function(pl, p)
pl:set_pos(p)
end, player, pos)
end
elevator.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:get_pos()
-- First, check if this box needs removed.
-- If the motor has a box and it isn't this box.
if elevator.boxes[self.motor] and elevator.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()
elevator.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()
elevator.boxes[self.motor] = nil
return
end
-- If our motor's box is nil, we should self-destruct.
if not elevator.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()
elevator.boxes[self.motor] = nil
return
end
minetest.get_player_by_name(self.attached):set_pos(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 vector.distance(p,self.target) < elevator.SLOW_DIST then
self.object:set_velocity({x=0, y=elevator.SPEED*elevator.SLOW_SPEED_FACTOR*self.vmult, z=0})
end
if 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()
elevator.boxes[self.motor] = nil
offabm(self.target, node)
return
end
elseif node.name ~= "elevator:shaft" then
-- 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)
elevator.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)

7
mod.conf Normal file
View file

@ -0,0 +1,7 @@
name = elevator
description = An entity-based elevator allowing fast realtime travel in Minetest. Supports Minetest Game, MineClone2, and Aurum
optional_depends = default, technic, homedecor, chains, farming, mcl_core, mcl_sounds, aurum, screwdriver, doc_items, basic_materials
min_minetest_version = 5.4
release = 23816
author = shacknetisp
title = Realtime Elevator

BIN
screenshot.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 96 KiB

14
settingtypes.txt Normal file
View file

@ -0,0 +1,14 @@
# Initial speed of the box
elevator_speed (Initial elevator speed) float 10
# Acceleration of the box
elevator_accel (Elevator acceleration) float 0.1
# Maximum time a box can go without players nearby.
elevator_time (Maximum idle time without players) int 120
# When the elevator is within this number of nodes of the destination, slow down to avoid lag.
elevator_slow_dist (Elevator slow distance) int 16
# Multiplier for speed when the elevator approaches its slow target.
elevator_slow_speed_factor (Elevator slow speed factor) float 0.11

33
storage.lua Normal file
View file

@ -0,0 +1,33 @@
local elevator_file = minetest.get_worldpath() .. "/elevator"
local str = minetest.get_mod_storage and minetest.get_mod_storage()
-- Central "network" table.
elevator.motors = {}
local function load_elevator()
local data = nil
if str and ((str.contains and str:contains("data")) or (str:get_string("data") and str:get_string("data") ~= "")) then
data = minetest.deserialize(str:get_string("data"))
else
local file = io.open(elevator_file)
if file then
data = minetest.deserialize(file:read("*all")) or {}
file:close()
end
end
elevator.motors = (data and data.motors) and data.motors or {}
end
elevator.save_elevator = function()
if str then
str:set_string("data", minetest.serialize({motors = elevator.motors}))
return
end
local f = io.open(elevator_file, "w")
f:write(minetest.serialize({motors = elevator.motors}))
f:close()
end
load_elevator()

BIN
textures/elevator_box.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 424 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

BIN
textures/elevator_motor.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 373 B

BIN
textures/elevator_shaft.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 466 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 209 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 350 B