master
Joachim Stolberg 2017-06-20 21:31:10 +02:00
parent 7aff68370e
commit 34028ced08
10 changed files with 785 additions and 0 deletions

13
LICENSE.txt Normal file
View File

@ -0,0 +1,13 @@
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

3
README.md Normal file
View File

@ -0,0 +1,3 @@
# Hyperloop
tbd.

2
depends.txt Normal file
View File

@ -0,0 +1,2 @@
default

1
description.txt Normal file
View File

@ -0,0 +1 @@
Hyperloop Mod for faster traveling.

371
init.lua Executable file
View File

@ -0,0 +1,371 @@
--[[
Hyperloop Mod
=============
v0.01 by JoSt
Copyright (C) 2017 Joachim Stolberg
LGPLv2.1+
See LICENSE.txt for more information
History:
2017-06-18 v0.01 First version
]]--
hyperloop = {
ringList = {},
}
-- max teleport distance
-- Intllib
local S
if minetest.get_modpath("intllib") then
S = intllib.Getter()
else
S = function(s, a, ...) a = {a, ...}
return s:gsub("@(%d+)", function(n)
return a[tonumber(n)]
end)
end
end
local dist = tonumber(minetest.setting_get("map_generation_limit") or 31000)
dofile(minetest.get_modpath("hyperloop") .. "/utils.lua")
dofile(minetest.get_modpath("hyperloop") .. "/door.lua")
dofile(minetest.get_modpath("hyperloop") .. "/tubes.lua")
----------------------------------------------------------------------------------------------------
local function enter_display(pos, text)
-- Use LCD from digilines. TODO: Own display
local node = minetest.get_node(pos)
local spec = digilines.getspec(node)
if spec then
-- Effector actions --> Receive
if spec.effector then
spec.effector.action(pos, node, "lcd", text)
end
end
end
----------------------------------------------------------------------------------------------------
local function check_coordinates(str)
-- obsolete?
if not str or str == "" then
return nil
end
-- get coords from string
local x, y, z, desc = string.match(str, "^(-?%d+),(-?%d+),(-?%d+),?(.*)$")
-- check coords
if x == nil or string.len(x) > 6
or y == nil or string.len(y) > 6
or z == nil or string.len(z) > 6 then
return nil
end
-- convert string coords to numbers
x = tonumber(x)
y = tonumber(y)
z = tonumber(z)
-- are coords in map range ?
if x > dist or x < -dist
or y > dist or y < -dist
or z > dist or z < -dist then
return nil
end
-- return ok coords
return {x = x, y = y, z = z, desc = desc}
end
----------------------------------------------------------------------------------------------------
-- seat_pos: position of the seat
-- facedir: direction to the display
-- cmnd: "close", "open", or "animate"
local function door_command(seat_pos, facedir, cmnd)
-- one step forward
local lcd_pos = vector.add(seat_pos, facedir2dir(facedir))
-- one step left
local door_pos1 = vector.add(lcd_pos, facedir2dir(facedir + 1))
-- one step up
local door_pos2 = vector.add(door_pos1, {x=0, y=1, z=0})
local node1 = minetest.get_node(door_pos1)
local node2 = minetest.get_node(door_pos2)
-- switch from the radian following facedir to the silly original one
local tbl = {[0]=0, [1]=3, [2]=2, [3]=1}
facedir = (facedir + 3) % 4 -- first turn left
facedir = tbl[facedir]
if cmnd == "open" then
node1.name = "air"
minetest.swap_node(door_pos1, node1)
node2.name = "air"
minetest.swap_node(door_pos2, node2)
elseif cmnd == "close" then
node1.name = "hyperloop:doorBottom"
node1.param2 = facedir
minetest.swap_node(door_pos1, node1)
node2.name = "hyperloop:doorTopPassive"
node2.param2 = facedir
minetest.swap_node(door_pos2, node2)
elseif cmnd == "animate" then
node2.name = "hyperloop:doorTopActive"
node2.param2 = facedir
minetest.swap_node(door_pos2, node2)
end
end
----------------------------------------------------------------------------------------------------
local function on_open_door(pos, facedir)
-- open the door and play sound
local meta = minetest.get_meta(pos)
meta:set_int("arrival_time", 0) -- finished
-- open the door
minetest.sound_play("door", {
pos = pos,
gain = 0.5,
max_hear_distance = 10,
})
door_command(pos, facedir, "open")
-- prepare dislay for the next trip
local text = "We will start | in a few | seconds"
enter_display(lcd_pos, text)
end
----------------------------------------------------------------------------------------------------
local function on_arrival(player, src_pos, dst_pos, snd, radiant)
-- open the door an the departure station
local meta = minetest.get_meta(src_pos)
local facedir = meta:get_int("facedir")
door_command(src_pos, facedir, "open")
-- get coords from arrival station
meta = minetest.get_meta(dst_pos)
facedir = meta:get_int("facedir")
--print("on_arrival "..dump(dst_pos))----------------------------------------------
-- close the door at arrival station
door_command(dst_pos, facedir, "close")
-- move player to the arrival station
player:setpos(dst_pos)
-- rotate player to look in correct arrival direction
-- calculate the look correction
local offs = radiant - player:get_look_horizontal()
local yaw = facedir2rad(facedir) + offs
player:set_look_yaw(yaw)
-- play arrival sound
minetest.sound_stop(snd)
minetest.sound_play("down", {
pos = dst_pos,
gain = 1.0,
max_hear_distance = 10
})
-- activate display
local lcd_pos = vector.add(dst_pos, facedir2dir(facedir))
lcd_pos.y = lcd_pos.y + 1
--print("LCD "..dump(pos)..dump(lcd_pos))
local text = "Wellcome in | | Hauptstadt"
enter_display(lcd_pos, text)
minetest.after(6.0, on_open_door, dst_pos, facedir)
end
----------------------------------------------------------------------------------------------------
local function on_travel(src_pos, facedir, player, dst_pos, radiant)
-- play sound and switch door state
-- radiant is the player look direction at departure
local snd = minetest.sound_play("normal", {
pos = src_pos,
gain = 1.0,
max_hear_distance = 1,
loop = true,
})
door_command(src_pos, facedir, "animate")
minetest.after(6.0, on_arrival, player, src_pos, dst_pos, snd, radiant)
end
----------------------------------------------------------------------------------------------------
local function display_timer(pos, elapsed)
-- update display with trip data
local meta = minetest.get_meta(pos)
local atime = meta:get_int("arrival_time") - 1
meta:set_int("arrival_time", atime)
local lcd_pos = minetest.string_to_pos(meta:get_string("lcd_pos"))
local text = meta:get_string("lcd_text")
if atime > 0 then
enter_display(lcd_pos, text..atime.." sec")
return true
else
enter_display(lcd_pos, "We will start | in a view | minutes..")
return false
end
end
----------------------------------------------------------------------------------------------------
local function on_start_travel(pos, node, clicker)
-- place the player, close the door, activate display
local meta = minetest.get_meta(pos)
local facedir = meta:get_int("facedir")
if meta:get_int("arrival_time") ~= 0 then
return
end
local target_coords = {
x = meta:get_int("x"),
y = meta:get_int("y"),
z = meta:get_int("z")
}
minetest.sound_play("up", {
pos = pos,
gain = 1.0,
max_hear_distance = 10
})
-- place player on the seat
clicker:setpos(pos)
-- rotate player to look in move direction
clicker:set_look_horizontal(facedir2rad(facedir))
-- activate display
local lcd_pos = vector.add(pos, facedir2dir(facedir))
lcd_pos.y = lcd_pos.y + 1
--print("LCD "..dump(pos)..dump(lcd_pos))
local text = "Next stop: | Hauptstadt | Dist: 2.2km | Arrival in: | "
local atime = 15
enter_display(lcd_pos, text..atime.." sec")
-- store some data
meta:set_int("arrival_time", atime)
meta:set_string("lcd_pos", minetest.pos_to_string(lcd_pos))
meta:set_string("lcd_text", text)
meta:set_string("lcd_text", text)
minetest.get_node_timer(pos):start(1.0)
--print("on_rightclick "..dump(pos))----------------------------------------------
-- close the door
minetest.sound_play("door", {
pos = pos,
gain = 0.5,
max_hear_distance = 10,
})
door_command(pos, facedir, "close")
minetest.after(4.9, on_travel, pos, facedir, clicker, target_coords, facedir2rad(facedir))
end
-- Hyperloop Seat
minetest.register_node("hyperloop:seat", {
tiles = {
"seat-top.png",
"seat-side.png",
"seat-side.png",
"seat-side.png",
"seat-side.png",
"seat-side.png",
},
drawtype = "nodebox",
paramtype2 = "facedir",
is_ground_content = false,
walkable = false,
--description = S("Hyperloop Pad (place and right-click to enchant location)"),
groups = {snappy = 3},
node_box = {
type = "fixed",
fixed = {
{ -6/16, -8/16, -8/16, 6/16, -2/16, 5/16},
{ -8/16, -8/16, -8/16, -6/16, 4/16, 8/16},
{ 6/16, -8/16, -8/16, 8/16, 4/16, 8/16},
{ -6/16, -8/16, 4/16, 6/16, 6/16, 8/16},
},
},
selection_box = {
type = "fixed",
fixed = { -8/16, -8/16, -8/16, 8/16, -2/16, 8/16 },
},
on_timer = display_timer,
on_construct = function(pos)
local meta = minetest.get_meta(pos)
-- text entry formspec
meta:set_string("formspec", "field[text;" .. S("Enter teleport coords (e.g. 200,20,-200,Home)") .. ";${text}]")
meta:set_string("infotext", S("Right-click to enchant teleport location"))
meta:set_string("text", pos.x .. "," .. pos.y .. "," .. pos.z)
meta:set_int("arrival_time", 0)
-- set default coords
meta:set_int("x", pos.x)
meta:set_int("y", pos.y)
meta:set_int("z", pos.z)
end,
after_place_node = function(pos, placer)
local meta = minetest.get_meta(pos)
local yaw = placer:get_look_horizontal()
-- facedir according to radiant
local facedir = rad2facedir(yaw)
-- do a 180 degree correction
meta:set_int("facedir", (facedir + 2) % 4)
print("on_construct "..dump(pos))----------------------------------------------
end,
-- once entered, check coords, if ok then return potion
on_receive_fields = function(pos, formname, fields, sender)
local name = sender:get_player_name()
if minetest.is_protected(pos, name) then
minetest.record_protection_violation(pos, name)
return
end
local coords = check_coordinates(fields.text)
if coords then
local meta = minetest.get_meta(pos)
print("on_receive_fields "..dump(pos))----------------------------------------------
meta:set_int("x", coords.x)
meta:set_int("y", coords.y)
meta:set_int("z", coords.z)
meta:set_string("text", fields.text)
if coords.desc and coords.desc ~= "" then
meta:set_string("infotext", S("Teleport to @1", coords.desc))
else
meta:set_string("infotext", S("Pad Active (@1,@2,@3)",
coords.x, coords.y, coords.z))
end
-- delete formspec so that right-click will work
meta:set_string("formspec", nil)
else
minetest.chat_send_player(name, S("Teleport Pad coordinates failed!"))
end
end,
on_rightclick = on_start_travel,
})
print ("[MOD] Hyperloop loaded")

1
mod.conf Normal file
View File

@ -0,0 +1 @@
name = hyperloop

2
settingtypes.txt Normal file
View File

@ -0,0 +1,2 @@
# Max distance in blocks between two stations
hyperloop_max_distance (Hyperloop max station distance) int 1000

84
station.lua Normal file
View File

@ -0,0 +1,84 @@
-- We need:
-- * tube_power0, can be placed and docked by a tube
-- * tube_power1, can be docked by a second tube
-- * tube_power2, can't be docked by a third tube
for idx = 0,2 do
local img
if idx < 2 then
img = "hyperloop_power_tube_green.png"
else
img = "hyperloop_power_tube_red.png"
end
minetest.register_node("hyperloop:tube_power"..idx, {
description = "Hyperloop Power Tube",
tiles = {
{
name = img,
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 2.0,
},
},
},
after_place_node = function(pos, placer, itemstack, pointed_thing)
local nodes = scan_tube_neighbours(pos, true, {"hyperloop:tube_head", "hyperloop:tube_power1"})
-- power node can't be placed nearby another power node
for _,node in ipairs(nodes) do
if node.name == "hyperloop:tube_power0" or node.name == "hyperloop:tube_power1" then
-- remove node again
minetest.chat_send_player(placer:get_player_name(), "Power Tube block can't be placed here.")
minetest.remove_node(pos)
return
end
end
local meta = minetest.get_meta(pos)
local station_name = meta:get_string("station") or "<unknown>"
local ring_addr
if #nodes == 0 then -- are we the one and only?
--print("start ring")----------------------
-- a new ring starts here
ring_addr = determine_ring_addr(pos)
else
--print("degrade to tubes")----------------------
-- degrade neighbor nodes
swap_to_tube(pos, placer, nodes)
ring_addr = meta:get_string("ring_addr")
-- already connected with two tube nodes?
if #nodes == 2 then
--print("switch to tube_power1")----------------------
-- tube_power1 cant be docked by a third tube
local node = minetest.get_node(pos)
node.name = "hyperloop:tube_power1"
minetest.swap_node(pos, node)
end
end
meta:set_string("infotext", "Power Tube block "..idx..". ring at: "..ring_addr.." Station Name: "..station_name)
-- store ring_addr in ring list
if hyperloop.ringList[ring_addr] ~= nil then
table.insert(hyperloop.ringList[ring_addr], pos)
else
hyperloop.ringList[ring_addr] = {pos}
end
--print("store ring_addr in ring list")-------------------------------------
--hyperloop.dbg_ringlist()------------------------------------------
end,
on_destruct = function(pos)
local nodes = scan_tube_neighbours(pos, true, {"hyperloop:tube"})
-- upgrade neighbor nodes
swap_to_tube_head(pos, nodes)
end,
paramtype2 = "facedir",
groups = {cracky=2, not_in_creative_inventory=idx},
is_ground_content = false,
drop = "hyperloop:tube_power0",
})
end

201
tubes.lua Normal file
View File

@ -0,0 +1,201 @@
--[[
Hyperloop Mod
=============
Copyright (C) 2017 Joachim Stolberg
LGPLv2.1+
See LICENSE.txt for more information
History:
see init.lua
]]--
local function scan_neighbours(pos)
-- Scan all 8 neighbor positions for tube nodes.
-- Return:
-- 0, nodes - no node available
-- 1, nodes - one tube1 available
-- 3, nodes - two tube1 available
-- 4, nodes - invalid position
local nodes = {}
local node, npos, idx
local res = 0
for _,dir in ipairs(hyperloop.NeighborPos) do
npos = vector.add(pos, dir)
node = minetest.get_node(npos)
if string.find(node.name, "hyperloop:tube") then
idx = string.byte(node.name, -1) - 48
if idx == 0 then -- starter tube node?
idx = 1
elseif
idx == 2 then -- normal tube node
return 4, nodes
end
res = res * 2 + idx
if res > 3 then
return 4, nodes
end
node.pos = npos
table.insert(nodes, node)
end
end
return res, nodes
end
local function degrade_tupe_node(node)
if node.name == "hyperloop:tube0" then
node.name = "hyperloop:tube1"
elseif node.name == "hyperloop:tube1" then
node.name = "hyperloop:tube2"
node.diggable = false
else
return
end
minetest.swap_node(node.pos, node)
end
local function upgrade_node(pos, node)
-- Upgrade one node.
-- Is needed when a tube node is digged.
local meta_local = minetest.get_meta(pos)
local meta_head = minetest.get_meta(node.pos)
meta_head:set_string("other", meta_local:get_string("other"))
meta_head:set_string("me", minetest.pos_to_string(node.pos))
node.diggable = true
if node.name == "hyperloop:tube2" then -- 2 connections?
node.name = "hyperloop:tube1"
elseif node.name == "hyperloop:tube1" then -- 1 connection?
node.name = "hyperloop:tube0"
end
minetest.swap_node(node.pos, node)
end
local function starter_node(node)
local meta = minetest.get_meta(node.pos)
meta:set_string("local", minetest.pos_to_string(node.pos))
meta:set_string("remote", minetest.pos_to_string(node.pos))
-- upgrade self to starter node
node.name = "hyperloop:tube0"
minetest.swap_node(node.pos, node)
end
local function head_node(node, node1)
local meta_local = minetest.get_meta(node.pos)
local meta_head = minetest.get_meta(node1.pos)
-- set local data
meta_local:set_string("local", minetest.pos_to_string(node.pos))
meta_local:set_string("remote", meta_head:get_string("remote"))
-- set remote data
local rpos = minetest.string_to_pos(meta_head:get_string("remote"))
local rmeta = minetest.get_meta(rpos)
rmeta:set_string("remote", minetest.pos_to_string(node.pos))
-- upgrade self
node.name = "hyperloop:tube1"
minetest.swap_node(node.pos, node)
-- degrade old head
degrade_tupe_node(node1)
end
local function link_node(node, node1, node2)
-- determine the meta data from both remote heads
local meta_head1 = minetest.get_meta(node1.pos)
local meta_head2 = minetest.get_meta(node2.pos)
local pos1 = minetest.string_to_pos(meta_head1:get_string("remote"))
local pos2 = minetest.string_to_pos(meta_head2:get_string("remote"))
local meta_rmt1 = minetest.get_meta(pos1)
local meta_rmt2 = minetest.get_meta(pos2)
-- exchange position data
meta_rmt2:set_string("remote", meta_rmt1:get_string("local"))
meta_rmt1:set_string("remote", meta_rmt2:get_string("local"))
-- degrade all three nodes
degrade_tupe_node(node)
degrade_tupe_node(node1)
degrade_tupe_node(node2)
end
local function remove_node(pos, node)
---minetest.remove_node(pos) can't call because "on_destruct" will then be called, too
node.name = "air"
node.diggable = true
minetest.swap_node(pos, node)
end
-- simple tube without logic or "memory"
minetest.register_node("hyperloop:tube2", {
description = "Hyperloop Tube",
tiles = {
{
name = "hyperloop_tube_red.png",
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 2.0,
},
},
},
diggable = false,
paramtype2 = "facedir",
groups = {cracky=1, not_in_creative_inventory=1},
is_ground_content = false,
})
-- single-node and head-node with meta info about the counter part node
for idx = 0,1 do
minetest.register_node("hyperloop:tube"..idx, {
description = "Hyperloop Tube",
tiles = {
{
name = "hyperloop_tube_green.png",
animation = {
type = "vertical_frames",
aspect_w = 32,
aspect_h = 32,
length = 2.0,
},
},
},
after_place_node = function(pos, placer, itemstack, pointed_thing)
local res, nodes = scan_neighbours(pos)
local node = minetest.get_node(pos)
node.pos = pos
hyperloop.dbg_nodes(nodes)-------------------------------------
if res == 0 then -- no neighbor available?
starter_node(node)
elseif res == 1 then -- one neighbor available?
head_node(node, nodes[1])
elseif res == 3 then -- two neighbours available?
link_node(node, nodes[1], nodes[2])
else -- invalid position
minetest.chat_send_player(placer:get_player_name(), "Invalid tube block position")
remove_node(pos, node)
end
end,
on_destruct = function(pos)
print("on_destruct")------------------------------
local res, nodes = scan_neighbours(pos)
if res == 1 then
upgrade_node(pos, nodes[1])
end
end,
on_punch = function(pos, node, puncher, pointed_thing)
local meta = minetest.get_meta(pos)
loc = meta:get_string("local")
rmt = meta:get_string("remote")
minetest.chat_send_player(puncher:get_player_name(), "local="..loc.." remote="..rmt)
end,
paramtype2 = "facedir",
groups = {cracky=2, not_in_creative_inventory=idx},
is_ground_content = false,
drop = "hyperloop:tube0",
})
end

107
utils.lua Normal file
View File

@ -0,0 +1,107 @@
--[[
Hyperloop Mod
=============
Copyright (C) 2017 Joachim Stolberg
LGPLv2.1+
See LICENSE.txt for more information
History:
see init.lua
]]--
local PI = 3.1415926
hyperloop.NeighborPos = {
{ x=1, y=0, z=0},
{ x=-1, y=0, z=0},
{ x=0, y=1, z=0},
{ x=0, y=-1, z=0},
{ x=0, y=0, z=1},
{ x=0, y=0, z=-1},
}
function hyperloop.rad2facedir(yaw)
-- radiant (0..2*PI) to my facedir (0..3) from N, W, S to E
return math.floor((yaw + PI/4) / PI * 2) % 4
end
function hyperloop.facedir2rad(facedir)
-- my facedir (0..3) from N, W, S to E to radiant (0..2*PI)
return facedir / 2 * PI
end
function hyperloop.facedir2dir(facedir)
-- my facedir (0..3) from N, W, S to E to dir vector
local tbl = {
[0] = { x=0, y=0, z=1},
[1] = { x=-1, y=0, z=0},
[2] = { x=0, y=0, z=-1},
[3] = { x=1, y=0, z=0},
}
return tbl[facedir % 4]
end
function hyperloop.turnright(dir)
local facedir = minetest.dir_to_facedir(dir)
return minetest.facedir_to_dir((facedir + 1) % 4)
end
function hyperloop.turnleft(dir)
local facedir = minetest.dir_to_facedir(dir)
return minetest.facedir_to_dir((facedir + 3) % 4)
end
-- File writing / reading utilities
local wpath = minetest.get_worldpath()
function hyperloop.file2table(filename)
local f = io.open(wpath..DIR_DELIM..filename, "r")
if f == nil then return {} end
local t = f:read("*all")
f:close()
if t == "" or t == nil then return {} end
return minetest.deserialize(t)
end
function hyperloop.table2file(filename, table)
local f = io.open(wpath..DIR_DELIM..filename, "w")
f:write(minetest.serialize(table))
f:close()
end
function hyperloop.store_ring_list()
hyperloop.table2file("hyperloop_ringlist", hyperloop.ringList)
end
function hyperloop.dbg_ringlist()
print("RingList:")
print(dump(hyperloop.ringList))-------------------------------------------
for addr,list in ipairs(hyperloop.ringList) do
for idx, pos in ipairs(list) do
print("addr:"..addr.." idx:"..idx.." pos:"..minetest.pos_to_string(pos))
end
end
end
function hyperloop.dbg_nodes(nodes)
print("Nodes:")
for _,node in ipairs(nodes) do
print("name:"..node.name)
end
end
-- Store and read the RingList to / from a file
-- so that upcoming actions are remembered when the game
-- is restarted
hyperloop.ringList = hyperloop.file2table("hyperloop_ringlist")
minetest.register_on_shutdown(hyperloop.store_ring_list)
-- store ring list once a day
minetest.after(60*60*24, hyperloop.store_ring_list)