5
.editorconfig
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
root = true
|
||||||
|
|
||||||
|
[*]
|
||||||
|
indent_style = space
|
||||||
|
indent_size = 4
|
||||||
17
.github/workflows/luacheck.yml
vendored
Normal 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
|
|
@ -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
|
|
@ -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
|
|
@ -0,0 +1,8 @@
|
||||||
|
# Realtime Elevators for Minetest
|
||||||
|
[](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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
After Width: | Height: | Size: 96 KiB |
14
settingtypes.txt
Normal 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
|
|
@ -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
|
After Width: | Height: | Size: 424 B |
BIN
textures/elevator_box_mcl.png
Normal file
|
After Width: | Height: | Size: 209 B |
BIN
textures/elevator_motor.png
Normal file
|
After Width: | Height: | Size: 702 B |
BIN
textures/elevator_motor_mcl.png
Normal file
|
After Width: | Height: | Size: 373 B |
BIN
textures/elevator_shaft.png
Normal file
|
After Width: | Height: | Size: 466 B |
BIN
textures/elevator_shaft_mcl.png
Normal file
|
After Width: | Height: | Size: 209 B |
BIN
textures/elevator_steel_block.png
Normal file
|
After Width: | Height: | Size: 350 B |