mods that hopefully work for a TechAge-based pack

This commit is contained in:
2026-09-09 06:27:51 -06:00
parent 4e6b08d194
commit c27db2a537
6234 changed files with 375578 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
Minecart
========
**Minecart, the lean railway transportation automation system**
Browse on: [GitHub](https://github.com/joe7575/minecart)
Download: [GitHub](https://github.com/joe7575/minecart/archive/master.zip)
![minecart](https://github.com/joe7575/minecart/blob/master/screenshot.png)
Minecart is based on carts, which is
based almost entirely on the mod boost_cart [1], which
itself is based on (and fully compatible with) the carts mod [2].
The model was originally designed by stujones11 [3] (CC-0).
Cart textures are based on original work from PixelBOX by Gambit (permissive
license).
1. https://github.com/SmallJoker/boost_cart/
2. https://github.com/PilzAdam/carts/
3. https://github.com/stujones11/railcart/
## Minecart Features
The mod Minecart has its own cart (called Minecart) in addition to the standard cart.
Minecarts are used for automated item transport on private and public rail networks.
The mod features are:
- a fast cart for your railway or roller coaster (up to 8 m/s!)
- boost rails and speed limit signs
- rail junction switching with the 'right-left' walking keys
- configurable timetables and routes for Minecarts
- automated loading/unloading of Minecarts by means of a Minecart Hopper
- rail network protection based on protection blocks called Land Marks
- protection of minecarts and cargo
- Minecarts run through unloaded areas (only the stations/hopper have to be loaded)
- Extra Minecart privs for rail workers
- Ingame documentation (German and English), based on the mod "doc" and/or
doclib/techage
- API to register carts from other mods
- chat command `/mycart <num>` to output cart state and location
- Command interface for Techage (Lua and ICTA) and for Beduino Controllers
## Technical Background
The Minecart can "run" through unloaded areas. This is done by means of recorded
and stored routes. If the area is unloaded the cart will simply follow the
predefined route until an area is loaded again. In this case the cart will be
spawned and run as usual.
## Manual
see [Wiki](https://github.com/joe7575/minecart/wiki)
## Command Interface
### Techage ICTA Controller
The ICTA Controller support the conditions:
- "read cart state" (function returns "stopped" or "running")
- "read cart location" (function returns the distance or the station/buffer name)
See help page of the ICTA controller block.
### Techage Lua Controller
The Lua controller support the functions:
- `$cart_state(num)` (function returns "stopped" or "running")
- `$cart_location(num)` (function returns the distance or the station/buffer name)
See help page of the Lua controller block.
### Cart Terminal
The Cart Terminal has a Techage command interface with the commands:
| Command | Data | Description |
| ---------- | ---------- | ------------------------------------------------------- |
| `state` | \<cart-ID> | Returns `unknown`, `stopped`, or `running` |
| `distance` | \<cart-ID> | Returns the distance from the cart to the Cart Terminal |
### Beduino Controller
The Cart Terminal has a Beduino command interface with the commands:
| Command | Topic | Data | Response | Description |
| -------- | ----- | --------- | ---------- | ------------------------------------------------------- |
| State | 129 | [cart-id] | [state] | Returns 0 = UNKNOWN, 1 = STOPPED, 2 = RUNNING |
| Distance | 130 | [cart-id] | [distance] | Returns the distance from the cart to the Cart Terminal |
# History
2019-04-19 v0.01 first commit
2019-04-21 v0.02 functional, with junctions support
2019-04-23 v0.03 bug fixes and improvements
2019-04-25 v0.04 Landmarks and Minecart protection added
2019-05-04 v0.05 Route recording protection added
2019-05-22 v0.06 Pick up items from a cart improved
2019-06-23 v0.07 'doc' mod support and German translation added
2020-01-04 v1.00 Hopper added, buffer improved
2020-02-09 v1.01 cart loading bugfix
2020-02-24 v1.02 Hopper improved
2020-03-05 v1.03 Hopper again improved
2020-03-28 v1.04 cart unloading bugfix
2020-05-14 v1.05 API changed to be able to register carts
2020-06-14 v1.06 API changed and chat command added
2020-06-27 v1.07 Route storage and cart command bugfixes
2020-07-24 V1.08 Adapted to new techage ICTA style
2020-08-14 V1.09 Hopper support for digtron, protector:chest and default:furnace added
2020-11-12 V1.10 Make carts more robust against server lag
2021-04-10 V2.00 Complete revision to make carts robust against server load/lag,
Speed limit signs and cart terminal added
2021-09-02 V2.01 Chat command /stopcart added
2021-10-18 V2.02 Cart reproduction bug fixed
2023-01-04 V2.03 Techage and Beduino command interface added
2023-02-05 V2.04 New API functions added, EOL blanks removed
2023-08-25 V2.05 Support for doclib added
+83
View File
@@ -0,0 +1,83 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
--
-- API functions
--
-- 'pos' is the position of the puncher/sensor, the cart
-- position will be determined by means of 'param2' and 'radius'
-- Function returns true for all standing carts (entities and nodes)
function minecart.is_cart_available(pos, param2, radius)
return minecart.get_nodecart_nearby(pos, param2, radius) ~= nil or
minecart.get_entitycart_nearby(pos, param2, radius) ~= nil
end
-- Function returns true if a standing cart as node is avaliable
function minecart.is_nodecart_available(pos, param2, radius)
return minecart.get_nodecart_nearby(pos, param2, radius) ~= nil
end
-- Function returns true if a standing cart as entity is avaliable
function minecart.is_entitycart_available(pos, param2, radius)
return minecart.get_entitycart_nearby(pos, param2, radius) ~= nil
end
function minecart.punch_cart(pos, param2, radius, punch_dir)
local pos2, node = minecart.get_nodecart_nearby(pos, param2, radius)
if pos2 then
minecart.start_nodecart(pos2, node.name, nil, punch_dir)
return true
end
-- The entity check is needed for a cart with driver
local entity = minecart.get_entitycart_nearby(pos, param2, radius)
if entity and entity.driver then
minecart.push_entitycart(entity, punch_dir)
return true
end
end
--------------------------------------------------------------------------------------------
-- API functions for other mods to add/remove carts
--------------------------------------------------------------------------------------------
function minecart.is_cart(name)
return minecart.tNodeNames[name] ~= nil
end
-- Remove a cart, available as node
function minecart.remove_cart(pos)
local node = minecart.get_node_lvm(pos)
local cargo, owner, userID = minecart.remove_nodecart(pos, node)
minecart.monitoring_remove_cart(owner, userID)
local cartdef = {cargo = cargo, owner = owner, userID = userID}
return cartdef
end
-- Place and start the cart
function minecart.place_and_start_cart(pos, node, cartdef, player)
local name = minecart.get_node_lvm(pos).name
if minecart.is_rail(pos, name) or minecart.is_cart(name) then
local vel = {x = 0, y = 0, z = 0}
local entity_name = minecart.tNodeNames[node.name]
local obj = minecart.add_entitycart(pos, node.name, entity_name, vel,
cartdef.cargo, cartdef.owner, cartdef.userID)
local entity = obj:get_luaentity()
minecart.monitoring_add_cart(cartdef.owner, cartdef.userID, pos, node.name, entity_name)
if player then
minecart.manage_attachment(player, entity, true)
end
minecart.start_entitycart(entity, pos, 0)
else
minecart.add_nodecart(pos, node.name, node.param2, cartdef.cargo, cartdef.owner, cartdef.userID, true)
end
end
+414
View File
@@ -0,0 +1,414 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local param2_to_dir = {[0]=
{x=0, y=0, z=1},
{x=1, y=0, z=0},
{x=0, y=0, z=-1},
{x=-1, y=0, z=0},
{x=0, y=-1, z=0},
{x=0, y=1, z=0}
}
-- Registered carts
minecart.tNodeNames = {} -- [<cart_node_name>] = <cart_entity_name>
minecart.tEntityNames = {} -- [<cart_entity_name>] = true
minecart.lCartNodeNames = {} -- {<cart_node_name>, <cart_node_name>, ...}
minecart.tCartTypes = {}
function minecart.param2_to_dir(param2)
return param2_to_dir[param2 % 6]
end
function minecart.get_node_lvm(pos)
local node = minetest.get_node_or_nil(pos)
if node then
return node
end
local vm = minetest.get_voxel_manip()
local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
local data = vm:get_data()
local param2_data = vm:get_param2_data()
local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
local idx = area:indexp(pos)
if data[idx] and param2_data[idx] then
return {
name = minetest.get_name_from_content_id(data[idx]),
param2 = param2_data[idx]
}
end
return {name="ignore", param2=0}
end
function minecart.find_node_near_lvm(pos, radius, items)
local npos = minetest.find_node_near(pos, radius, items)
if npos then
return npos
end
local tItems = {}
for _,v in ipairs(items) do
tItems[v] = true
end
local pos1 = {x = pos.x - radius, y = pos.y - radius, z = pos.z - radius}
local pos2 = {x = pos.x + radius, y = pos.y + radius, z = pos.z + radius}
local vm = minetest.get_voxel_manip()
local MinEdge, MaxEdge = vm:read_from_map(pos1, pos2)
local data = vm:get_data()
local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
for x = pos1.x, pos2.x do
for y = pos1.y, pos2.y do
for z = pos1.z, pos2.z do
local idx = area:indexp({x = x, y = y, z = z})
local name = minetest.get_name_from_content_id(data[idx])
if name and tItems[name] then
return {x = x, y = y, z = z}
end
end
end
end
end
-- Marker entities for debugging purposes
function minecart.set_marker(pos, text, size, ttl)
local marker = minetest.add_entity(pos, "minecart:marker_cube")
if marker ~= nil then
marker:set_nametag_attributes({color = "#FFFFFF", text = text})
size = size or 1
marker:set_properties({visual_size = {x = size, y = size}})
if ttl then
minetest.after(ttl, marker.remove, marker)
end
end
end
minetest.register_entity("minecart:marker_cube", {
initial_properties = {
visual = "cube",
textures = {
"minecart_marker_cube.png",
"minecart_marker_cube.png",
"minecart_marker_cube.png",
"minecart_marker_cube.png",
"minecart_marker_cube.png",
"minecart_marker_cube.png",
},
physical = false,
visual_size = {x = 1, y = 1},
collisionbox = {-0.25,-0.25,-0.25, 0.25,0.25,0.25},
glow = 8,
static_save = false,
},
on_punch = function(self)
self.object:remove()
end,
})
function minecart.set_land_marker(pos, radius, ttl)
local offs = radius + 0.5
local posses = {
{x = pos.x + offs, y = pos.y, z=pos.z},
{x = pos.x, y = pos.y, z=pos.z + offs},
{x = pos.x - offs, y = pos.y, z=pos.z},
{x = pos.x, y = pos.y, z=pos.z - offs},
}
for i, pos in ipairs(posses) do
local marker = minetest.add_entity(pos, "minecart:marker")
if marker ~= nil then
marker:set_properties({
visual_size = {x = 2 * offs, y = 2 * offs},
collisionbox = {-offs, -offs, 0, offs, offs, 0},
})
marker:set_yaw(math.pi / 2 * i)
minetest.after(ttl, marker.remove, marker)
end
end
end
minetest.register_entity("minecart:marker", {
initial_properties = {
visual = "upright_sprite",
textures = {"minecart_marker_cube.png"},
use_texture_alpha = minecart.CLIP,
physical = false,
glow = 12,
static_save = false,
},
on_punch = function(self)
self.object:remove()
end,
})
function minecart.is_air_like(name)
local ndef = minetest.registered_nodes[name]
if ndef and ndef.buildable_to then
return true
end
return false
end
function minecart.range(val, min, max)
val = tonumber(val)
if val < min then return min end
if val > max then return max end
return val
end
function minecart.get_next_node(pos, param2)
local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos
local node = minetest.get_node(pos2)
return pos2, node
end
function minecart.get_object_id(object)
for id, entity in pairs(minetest.luaentities) do
if entity.object == object then
return id
end
end
end
function minecart.is_owner(player, owner)
if not player or not player:is_player() or not owner or owner == "" then
return true
end
local name = player:get_player_name()
if minetest.check_player_privs(name, "minecart") then
return true
end
return name == owner
end
function minecart.get_buffer_pos(pos, player_name)
if pos then
local pos1 = minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"})
if pos1 then
local meta = minetest.get_meta(pos1)
if player_name == nil or player_name == meta:get_string("owner") then
return pos1
end
end
end
end
function minecart.get_buffer_name(pos)
if pos then
local pos1 = minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"})
if pos1 then
local name = M(pos1):get_string("name")
if name ~= "" then
return name
end
return P2S(pos1)
end
end
end
function minecart.manage_attachment(player, entity, get_on)
if not player then
return
end
local player_name = player:get_player_name()
if player_api.player_attached[player_name] == get_on then
return
end
player_api.player_attached[player_name] = get_on
local obj = entity.object
if get_on then
player:set_attach(obj, "", {x=0, y=-4.5, z=-4}, {x=0, y=0, z=0})
player:set_eye_offset({x=0, y=-6, z=0},{x=0, y=-6, z=0})
player:set_properties({visual_size = {x = 2.5, y = 2.5}})
player_api.set_animation(player, "sit")
entity.driver = player:get_player_name()
else
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}})
player_api.set_animation(player, "stand")
entity.driver = nil
end
end
function minecart.register_cart_names(node_name, entity_name, cart_type)
minecart.tNodeNames[node_name] = entity_name
minecart.tEntityNames[entity_name] = true
minecart.lCartNodeNames[#minecart.lCartNodeNames+1] = node_name
minecart.add_raillike_nodes(node_name)
minecart.tCartTypes[node_name] = cart_type
end
function minecart.add_nodecart(pos, node_name, param2, cargo, owner, userID, force)
if pos and node_name and param2 and cargo and owner and userID then
local pos2
if force then
pos2 = pos
elseif not minecart.is_rail(pos) then
pos2 = minetest.find_node_near(pos, 1, minecart.lRails)
if not pos2 or not minecart.is_rail(pos2) then
-- If no rail is around, use an available cart as new search center
pos2 = minetest.find_node_near(pos, 1, minecart.lRailsExt)
-- ...and search again.
if pos2 then
pos2 = minetest.find_node_near(pos2, 1, minecart.lRails)
end
end
else
pos2 = vector.new(pos)
end
if pos2 then
local node = minetest.get_node(pos2)
local ndef = minetest.registered_nodes[node_name]
minetest.swap_node(pos2, {name = node_name, param2 = param2})
local meta = M(pos2)
meta:set_string("removed_rail", node.name)
meta:set_string("removed_rail_param2", node.param2)
meta:set_string("owner", owner)
meta:set_int("userID", userID)
meta:set_string("infotext", owner .. ": " .. userID)
if cargo and ndef.set_cargo then
ndef.set_cargo(pos2, cargo)
end
if ndef.after_place_node then
ndef.after_place_node(pos2)
end
return pos2
end
end
end
function minecart.add_entitycart(pos, node_name, entity_name, vel, cargo, owner, userID)
local obj = minetest.add_entity(pos, entity_name)
local objID = minecart.get_object_id(obj)
if objID then
local entity = obj:get_luaentity()
entity.start_pos = pos
entity.owner = owner
entity.node_name = node_name
entity.userID = userID
entity.objID = objID
entity.cargo = cargo
obj:set_nametag_attributes({color = "#ffff00", text = owner..": "..userID})
obj:set_velocity(vel)
return obj
end
end
function minecart.start_entitycart(self, pos, facedir)
local route = {}
self.is_running = true
self.arrival_time = 0
self.start_pos = minecart.get_buffer_pos(pos, self.owner) or minecart.get_next_buffer(pos, facedir)
if self.start_pos then
-- Read buffer route for the junction info
route = minecart.get_route(self.start_pos) or {}
self.junctions = route and route.junctions
end
-- If set the start waypoint will be deleted
self.no_normal_start = self.start_pos == nil
if self.driver == nil then
minecart.start_monitoring(self.owner, self.userID, pos, self.objID,
route.checkpoints, route.junctions, self.cargo or {})
end
end
function minecart.remove_nodecart(pos, node)
node = node or minetest.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
local meta = M(pos)
local rail = meta:get_string("removed_rail")
local param2 = meta:get_int("removed_rail_param2")
if rail == "" then rail = "air" end
local userID = meta:get_int("userID")
local owner = meta:get_string("owner")
meta:set_string("infotext", "")
meta:set_string("formspec", "")
local cargo = ndef.get_cargo and ndef.get_cargo(pos) or {}
minetest.swap_node(pos, {name = rail, param2 = param2})
return cargo, owner, userID
end
function minecart.is_usable_cart(pos, node)
-- cart needs a rail bwelow
local rail = M(pos):get_string("removed_rail")
return minecart.is_rail(pos, rail)
end
function minecart.node_to_entity(pos, node_name, entity_name)
-- Remove node
local cargo, owner, userID = minecart.remove_nodecart(pos)
local obj = minecart.add_entitycart(pos, node_name, entity_name,
{x = 0, y = 0, z = 0}, cargo, owner, userID)
if obj then
return obj
else
print("Entity has no ID")
end
end
function minecart.entity_to_node(pos, entity)
-- Stop sound
if entity.sound_handle then
minetest.sound_stop(entity.sound_handle)
entity.sound_handle = nil
end
local rot = entity.object:get_rotation()
local dir = minetest.yaw_to_dir(rot.y)
local facedir = minetest.dir_to_facedir(dir)
minecart.stop_recording(entity, pos)
local pos2 = minecart.add_nodecart(pos, entity.node_name, facedir, entity.cargo, entity.owner, entity.userID)
if pos2 then
minecart.stop_monitoring(entity.owner, entity.userID, pos2)
entity.object:remove()
else
minecart.start_entitycart(entity, pos, facedir)
end
end
function minecart.add_node_to_player_inventory(pos, player, node_name)
local inv = player:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(player:get_player_name()))
or not inv:contains_item("main", node_name) then
local leftover = inv:add_item("main", node_name)
-- If no room in inventory, drop the cart
if not leftover:is_empty() then
minetest.add_item(pos, leftover)
end
end
end
-- Player removes the node
function minecart.remove_entity(self, pos, player)
-- Stop sound
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
self.sound_handle = nil
end
if player then
minecart.add_node_to_player_inventory(pos, player, self.node_name or "minecart:cart")
end
minecart.stop_monitoring(self.owner, self.userID, pos)
minecart.stop_recording(self, pos)
self.object:remove()
end
+93
View File
@@ -0,0 +1,93 @@
--[[
Minecart
========
Copyright (C) 2019-2023 Joachim Stolberg
MIT
See license.txt for more information
]]--
local minecart_lib = [[
// Minecart library for reading status and distance
// from running carts. To do this, a cart terminal
// must be connected to an I/O Module.
import "lib/techage.c"
var payload[1];
var resp[1];
// Read cart state.
// Parameters:
// - ip_port: IOM port to the Cart Terminal
// - card_id: Cart number
// Function returns:
// - 0 for unknown/missing
// - 1 for stopped
// - 2 for running
func mc_get_state(io_port, cart_id) {
var sts;
payload[0] = cart_id;
request_data(io_port, 129, payload, resp);
if(sts == 0) {
return resp[0];
}
return 0;
}
// Read cart distance.
// Parameters:
// - ip_port: IOM port to the Cart Terminal
// - card_id: Cart number
// Function returns the distance between
// Cart Terminal and cart in meter.
func mc_get_distance(io_port, cart_id) {
var sts;
payload[0] = cart_id;
request_data(io_port, 130, payload, resp);
if(sts == 0) {
return resp[0];
}
return 0;
}
]]
local minecart_demo = [[
import "sys/stdio.asm"
import "sys/os.c"
import "lib/minecart.c"
func init() {
setstdout(1); // use terminal window for stdout
putstr("### Minecart Demo ###\n");
}
func cart_state(io_port, cart_id) {
putstr("Cart #");
putnum(cart_id);
putstr(": state = ");
putnum(mc_get_state(io_port, cart_id));
putstr(", distance = ");
putnum(mc_get_distance(io_port, cart_id));
putstr("\n");
}
func loop() {
// Adapt IO port and cart ID to your needs
cart_state(0, 1);
sleep(20);
}
]]
minetest.register_on_mods_loaded(function()
if minetest.global_exists("vm16") and minetest.global_exists("beduino") then
vm16.register_ro_file("beduino", "lib/minecart.c", minecart_lib)
vm16.register_ro_file("beduino", "demo/minecart.c", minecart_demo)
end
end)
+178
View File
@@ -0,0 +1,178 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local S = minecart.S
local CYCLE_TIME = 2
local StopTime = {}
local function formspec(pos)
local name = M(pos):get_string("name")
local time = M(pos):get_int("time")
return "size[4,4.2]" ..
"label[0,0;Configuration]" ..
"field[0.5,1.2;3.6,1;name;"..S("Station name")..":;"..name.."]"..
"button_exit[1,3.4;2,1;exit;Save]"..
"field[0.5,2.5;3.6,1;time;"..S("Waiting time/sec")..":;"..time.."]"
end
local function remote_station_name(pos)
local route = minecart.get_route(pos)
if route and route.dest_pos then
return M(route.dest_pos):get_string("name")
end
end
function minecart.update_buffer_infotext(pos)
local name = M(pos):get_string("name")
local dest = remote_station_name(pos)
if dest then
M(pos):set_string("infotext", name .. ": " .. S("connected to") .. " " .. dest)
else
M(pos):set_string("infotext", name .. ": " .. S("Not connected!"))
end
end
local function on_punch(pos, node, puncher)
minecart.update_buffer_infotext(pos)
M(pos):set_string("formspec", formspec(pos))
minetest.get_node_timer(pos):start(CYCLE_TIME)
-- Optional Teleport function
if not minecart.teleport_enabled then return end
local route = minecart.get_route(pos)
if route and route.dest_pos and puncher and puncher:is_player() then
-- only teleport if the user is not pressing shift
if not puncher:get_player_control()['sneak'] then
local playername = puncher:get_player_name()
local teleport = function()
-- Make sure the player object still exists
local player = minetest.get_player_by_name(playername)
if player then player:set_pos(route.dest_pos) end
end
minetest.after(0.25, teleport)
end
end
end
minetest.register_node("minecart:buffer", {
description = S("Minecart Railway Buffer"),
tiles = {
'default_junglewood.png',
'default_junglewood.png',
'default_junglewood.png',
'default_junglewood.png',
'default_junglewood.png',
'default_junglewood.png^minecart_buffer.png',
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-8/16, -8/16, -8/16, 8/16, -4/16, 8/16},
{-8/16, -4/16, -8/16, 8/16, 0/16, 4/16},
{-8/16, 0/16, -8/16, 8/16, 4/16, 0/16},
{-8/16, 4/16, -8/16, 8/16, 8/16, -4/16},
},
},
selection_box = {
type = "fixed",
fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
},
after_place_node = function(pos, placer)
M(pos):set_string("owner", placer:get_player_name())
minecart.del_route(pos)
M(pos):set_string("formspec", formspec(pos))
minetest.get_node_timer(pos):start(CYCLE_TIME)
end,
on_timer = function(pos, elapsed)
local time = M(pos):get_int("time")
if time > 0 then
local hash = minetest.hash_node_position(pos)
local param2 = (minetest.get_node(pos).param2 + 2) % 4
if minecart.is_cart_available(pos, param2, 0.5) then
if StopTime[hash] then
if StopTime[hash] < minetest.get_gametime() then
StopTime[hash] = nil
local dir = minetest.facedir_to_dir(param2)
minecart.punch_cart(pos, param2, 0.5, dir)
end
else
StopTime[hash] = minetest.get_gametime() + time
end
else
StopTime[hash] = nil
end
end
return true
end,
after_dig_node = function(pos)
minecart.del_route(pos)
local hash = minetest.hash_node_position(pos)
StopTime[hash] = nil
end,
on_receive_fields = function(pos, formname, fields, player)
if M(pos):get_string("owner") ~= player:get_player_name() then
return
end
if (fields.key_enter == "true" or fields.exit == "Save") and fields.name ~= "" then
M(pos):set_string("name", fields.name)
M(pos):set_int("time", tonumber(fields.time) or 0)
M(pos):set_string("formspec", formspec(pos))
local dest = remote_station_name(pos)
if dest then
M(pos):set_string("infotext", fields.name .. ": " .. S("connected to") .. " " .. dest)
else
M(pos):set_string("infotext", fields.name .. ": " .. S("Not connected!"))
end
minetest.get_node_timer(pos):start(CYCLE_TIME)
end
end,
on_punch = on_punch,
paramtype = "light",
sunlight_propagates = true,
on_rotate = screwdriver.disallow,
paramtype2 = "facedir",
groups = {cracky=2, crumbly=2, choppy=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
minetest.register_craft({
output = "minecart:buffer",
recipe = {
{"dye:red", "", "dye:white"},
{"default:steel_ingot", "default:junglewood", "default:steel_ingot"},
},
})
minetest.register_lbm({
label = "Delete metadata",
name = "minecart:metadata",
nodenames = {"minecart:buffer"},
run_at_every_load = true,
action = function(pos, node)
-- delete old metadata around the buffer (bugfix)
local pos1 = {x = pos.x - 2, y = pos.y - 2, z = pos.z - 2}
local pos2 = {x = pos.x + 2, y = pos.y + 2, z = pos.z + 2}
for _, pos in ipairs(minetest.find_nodes_with_meta(pos1, pos2)) do
minecart.del_metadata(pos)
end
end,
})
@@ -0,0 +1,4 @@
default
carts
screwdriver
doc?
+132
View File
@@ -0,0 +1,132 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
minecart.doc = {}
if not minetest.get_modpath("doc") then
return
end
local S = minecart.S
local summary_doc = table.concat({
S("Summary"),
"------------",
"",
S("1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint."),
S("2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information)."),
S("3. Give both Railway Buffers unique station names, like Oxford and Cambridge."),
S("4. Place a Minecart at a buffer and give it a cart number (1..999)"),
S("5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart)."),
S("6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge')."),
S("7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time."),
S("8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters)."),
S("9. Place a Minecart in front of the buffer and check whether it starts after the configured time."),
S("10. Check the cart state via the chat command: /mycart <num>\n '<num>' is the cart number"),
S("11. Drop items into the Minecart and punch the cart to start it."),
S("12. Dig the cart with 'sneak+click' (as usual). The items will be drop down."),
S("13. To retrieve lost carts, use the chat command: /stopcart <num>"),
}, "\n")
local cart_doc = S("Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back")
local buffer_doc = S("Used as buffer on both rail ends. Needed to be able to record the cart routes")
local landmark_doc = S("Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)")
local hopper_doc = S("Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.")
local pusher_doc = S([[If several carts are running on one route,
it can happen that a buffer position is already occupied and one cart therefore stops earlier.
In this case, the cart pusher is used to push the cart towards the buffer again.
This block must be placed under the rail at a distance of 2 m in front of the buffer.]])
local speed_doc = S([[Limit the cart speed with speed limit signs.
As before, the speed of the carts is also influenced by power rails.
Brake rails are irrelevant, the cart does not brake here.
The maximum speed is 8 m/s. This assumes a ratio of power rails
to normal rails of 1 to 4 on a flat section of rail. A rail section is a
series of rail nodes without a change of direction. After every curve / kink,
the speed for the next section of the route is newly determined,
taking into account the swing of the cart. This means that a cart can
roll over short rail sections without power rails.
In order to additionally brake the cart at certain points
(at switches or in front of a buffer), speed limit signs can be placed
on the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.
The "No speed limit" sign can be used to remove the speed limit.
The speed limit signs must be placed next to the track so that they can
be read from the cart. This allows different speeds in each direction of travel.]])
local function formspec(data)
if data.image then
local image = "image["..(doc.FORMSPEC.ENTRY_WIDTH - 3)..",0;3,2;"..data.image.."]"
local formstring = doc.widgets.text(data.text, doc.FORMSPEC.ENTRY_START_X, doc.FORMSPEC.ENTRY_START_Y+1.6, doc.FORMSPEC.ENTRY_WIDTH, doc.FORMSPEC.ENTRY_HEIGHT - 1.6)
return image..formstring
elseif data.item then
local box = "box["..(doc.FORMSPEC.ENTRY_WIDTH - 1.6)..",0;1,1.1;#BBBBBB]"
local image = "item_image["..(doc.FORMSPEC.ENTRY_WIDTH - 1.5)..",0.1;1,1;"..data.item.."]"
local formstring = doc.widgets.text(data.text, doc.FORMSPEC.ENTRY_START_X, doc.FORMSPEC.ENTRY_START_Y+0.8, doc.FORMSPEC.ENTRY_WIDTH, doc.FORMSPEC.ENTRY_HEIGHT - 0.8)
return box..image..formstring
else
return doc.entry_builders.text(data.text)
end
end
doc.add_category("minecart",
{
name = S("Minecart"),
description = S("Minecart, the lean railway transportation automation system"),
sorting = "custom",
sorting_data = {"summary", "cart"},
build_formspec = formspec,
})
doc.add_entry("minecart", "summary", {
name = S("Summary"),
data = {text=summary_doc, image="minecart_doc_image.png"},
})
doc.add_entry("minecart", "cart", {
name = S("Minecart Cart"),
data = {text=cart_doc, item="minecart:cart"},
})
doc.add_entry("minecart", "buffer", {
name = S("Minecart Railway Buffer"),
data = {text=buffer_doc, item="minecart:buffer"},
})
doc.add_entry("minecart", "landmark", {
name = S("Minecart Landmark"),
data = {text = landmark_doc, item="minecart:landmark"},
})
doc.add_entry("minecart", "speed signs", {
name = S("Minecart Speed Signs"),
data = {text = speed_doc, item="minecart:speed4"},
})
doc.add_entry("minecart", "cart pusher", {
name = S("Cart Pusher"),
data = {text = pusher_doc, item="minecart:cart_pusher"},
})
if minecart.hopper_enabled then
doc.add_entry("minecart", "hopper", {
name = S("Minecart Hopper"),
data = {text=hopper_doc, item="minecart:hopper"},
})
end
+349
View File
@@ -0,0 +1,349 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local LEN = function(t) local c = 0; for _ in pairs(t) do c = c + 1 end; return c; end
local MAX_SPEED = minecart.MAX_SPEED
local dot2dir = minecart.dot2dir
local get_waypoint = minecart.get_waypoint
local recording_waypoints = minecart.recording_waypoints
local recording_junctions = minecart.recording_junctions
local set_junctions = minecart.set_junctions
local player_ctrl = minecart.player_ctrl
local tEntityNames = minecart.tEntityNames
local function stop_cart(self, cart_pos)
local player
self.is_running = false
self.arrival_time = 0
if self.driver then
player = minetest.get_player_by_name(self.driver)
if player then
minecart.stop_recording(self, cart_pos)
minecart.manage_attachment(player, self, false)
end
end
if not minecart.get_buffer_pos(cart_pos, self.owner) then
-- Probably somewhere in the pampas
minecart.delete_cart_waypoint(cart_pos)
end
minecart.entity_to_node(cart_pos, self)
if player then
player:set_pos(cart_pos)
end
end
local function get_ctrl(self, pos)
-- Use player ctrl or junction data from recorded routes
return (self.driver and self.ctrl) or (self.junctions and self.junctions[P2H(pos)]) or {}
end
local function new_speed(self, new_dir)
self.cart_speed = self.cart_speed or 0
local rail_speed = (self.waypoint.speed or 0) / 10
if rail_speed <= 0 then
rail_speed = math.max(self.cart_speed + rail_speed, 0)
elseif rail_speed <= self.cart_speed then
rail_speed = math.max((self.cart_speed + rail_speed) / 2, 0)
end
-- Speed corrections
if new_dir.y == 1 then
if rail_speed < 1 then rail_speed = 0 end
else
if rail_speed < 0.4 then rail_speed = 0 end
end
self.cart_speed = rail_speed -- store for next cycle
return rail_speed
end
local function running(self)
local rot = self.object:get_rotation()
local dir = minetest.yaw_to_dir(rot.y)
dir.y = math.floor((rot.x / (math.pi/4)) + 0.5)
dir = vector.round(dir)
-- If running in a 45 degree direction (extra cycle), use the old dir
-- to calculate face_dir. Otherwise the junction detection will not work as expected.
local facedir
if self.waypoint and self.waypoint.old_dir then
facedir = minetest.dir_to_facedir(self.waypoint.old_dir)
else
facedir = minetest.dir_to_facedir(dir)
end
local cart_pos, wayp_pos, is_junction
if self.reenter then -- through monitoring
cart_pos = H2P(self.reenter[1])
-- pos correction on slopes
if not minecart.is_rail(cart_pos) then
cart_pos.y = cart_pos.y - 1
end
wayp_pos = cart_pos
is_junction = false
self.waypoint = {pos = H2P(self.reenter[2]), power = 0, dot = self.reenter[4]}
self.cart_speed = self.reenter[3]
self.speed_limit = MAX_SPEED
self.reenter = nil
elseif not self.waypoint then
-- get waypoint
cart_pos = vector.round(self.object:get_pos())
wayp_pos = cart_pos
is_junction = false
self.waypoint = get_waypoint(cart_pos, facedir, get_ctrl(self, cart_pos), true)
if self.no_normal_start then
-- Probably somewhere in the pampas
minecart.delete_waypoint(cart_pos)
self.no_normal_start = nil
end
self.cart_speed = 2 -- push speed
self.speed_limit = MAX_SPEED
else
-- next waypoint
cart_pos = vector.new(self.waypoint.cart_pos or self.waypoint.pos)
wayp_pos = self.waypoint.pos
local vel = self.object:get_velocity()
self.waypoint, is_junction = get_waypoint(wayp_pos, facedir, get_ctrl(self, wayp_pos), self.cart_speed < 0.1)
end
if not self.waypoint then
stop_cart(self, wayp_pos)
return
end
if is_junction then
if self.is_recording then
set_junctions(self, wayp_pos)
end
self.ctrl = nil
end
--print("running", LEN(self.junctions))
local dist = vector.distance(cart_pos, self.waypoint.cart_pos or self.waypoint.pos)
local new_dir = dot2dir(self.waypoint.dot)
local new_speed = new_speed(self, new_dir)
local straight_ahead = vector.equals(new_dir, dir)
-- If straight_ahead, then it's probably a speed limit sign
if straight_ahead then
self.speed_limit = minecart.get_speedlimit(wayp_pos, facedir) or self.speed_limit
end
new_speed = math.min(new_speed, self.speed_limit)
local new_cart_pos, extra_cycle = minecart.get_current_cart_pos_correction(
wayp_pos, facedir, dir.y, self.waypoint.dot) -- TODO: Why has self.waypoint no dot?
if extra_cycle and not vector.equals(cart_pos, new_cart_pos) then
self.waypoint = {pos = wayp_pos, cart_pos = new_cart_pos, old_dir = vector.new(dir)}
new_dir = vector.direction(cart_pos, new_cart_pos)
dist = vector.distance(cart_pos, new_cart_pos)
--print("extra_cycle", P2S(cart_pos), P2S(wayp_pos), P2S(new_cart_pos), new_speed)
end
-- Slope corrections
--print("Slope corrections", P2S(new_dir), P2S(cart_pos))
if new_dir.y ~= 0 then
cart_pos.y = cart_pos.y + 0.2
end
-- Calc velocity, rotation and arrival_time
local yaw = minetest.dir_to_yaw(new_dir)
local pitch = new_dir.y * math.pi/4
--print("new_speed", new_speed / (new_dir.y ~= 0 and 1.41 or 1))
local vel = vector.multiply(new_dir, new_speed / ((new_dir.y ~= 0) and 1.41 or 1))
self.arrival_time = self.timebase + (dist / new_speed)
-- needed for recording
self.curr_speed = new_speed
self.num_sections = (self.num_sections or 0) + 1
-- Got stuck somewhere
if new_speed < 0.1 or dist < 0 then
minetest.log("warning", "[Minecart] Got stuck somewhere " .. new_speed .. " " .. dist)
stop_cart(self, wayp_pos)
return
end
self.object:set_pos(cart_pos)
self.object:set_rotation({x = pitch, y = yaw, z = 0})
self.object:set_velocity(vel)
return
end
local function play_sound(self)
if self.sound_handle then
local handle = self.sound_handle
self.sound_handle = nil
minetest.after(0.2, minetest.sound_stop, handle)
end
if self.object then
self.sound_handle = minetest.sound_play(
"carts_cart_moving", {
object = self.object,
gain = (self.curr_speed or 0) / MAX_SPEED,
})
end
end
local function on_step(self, dtime)
self.timebase = (self.timebase or 0) + dtime
if self.is_running then
if self.arrival_time <= self.timebase then
running(self)
end
if (self.sound_ttl or 0) <= self.timebase then
play_sound(self)
self.sound_ttl = self.timebase + 1.0
end
else
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
self.sound_handle = nil
end
end
if self.driver then
if self.is_recording then
if self.rec_time <= self.timebase then
recording_waypoints(self)
self.rec_time = self.rec_time + 2.0
end
if recording_junctions(self) then
local pos = vector.round(self.object:get_pos())
minecart.stop_recording(self, pos, true)
local player = minetest.get_player_by_name(self.driver)
minecart.manage_attachment(player, self, false)
minecart.entity_to_node(pos, self)
end
else
if player_ctrl(self) then
local pos = vector.round(self.object:get_pos())
local player = minetest.get_player_by_name(self.driver)
minecart.manage_attachment(player, self, false)
minecart.entity_to_node(pos, self)
end
end
end
end
local function on_entitycard_activate(self, staticdata, dtime_s)
self.object:set_armor_groups({immortal=1})
end
-- Start the entity cart (or dig by shift+leftclick)
local function on_entitycard_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)
if minecart.is_owner(puncher, self.owner) then
if puncher:get_player_control().sneak then
if not self.only_dig_if_empty or not next(self.cargo) then
-- drop items
local pos = vector.round(self.object:get_pos())
for _,item in ipairs(self.cargo or {}) do
minetest.add_item(pos, ItemStack(item))
end
-- Dig cart
if self.driver then
-- remove cart as driver
minecart.stop_recording(self, pos, true)
minecart.monitoring_remove_cart(self.owner, self.userID)
minecart.remove_entity(self, pos, puncher)
minecart.manage_attachment(puncher, self, false)
else
-- remove cart from outside
minecart.monitoring_remove_cart(self.owner, self.userID)
minecart.remove_entity(self, pos, puncher)
end
end
elseif not self.is_running then
-- start the cart
local pos = vector.round(self.object:get_pos())
if puncher then
local yaw = puncher:get_look_horizontal()
dir = minetest.yaw_to_dir(yaw)
self.object:set_rotation({x = 0, y = yaw, z = 0})
end
local facedir = minetest.dir_to_facedir(dir or {x=0, y=0, z=0})
minecart.start_entitycart(self, pos, facedir or 0)
minecart.start_recording(self, pos)
end
end
end
-- Player get on / off
local function on_entitycard_rightclick(self, clicker)
if clicker and clicker:is_player() and self.driver_allowed then
-- Get on / off
if self.driver then
-- get off
local pos = vector.round(self.object:get_pos())
minecart.stop_recording(self, pos, true)
minecart.manage_attachment(clicker, self, false)
minecart.entity_to_node(pos, self)
else
-- get on
local pos = vector.round(self.object:get_pos())
minecart.stop_recording(self, pos, true)
minecart.manage_attachment(clicker, self, true)
end
end
end
local function on_entitycard_detach_child(self, child)
if child and child:get_player_name() == self.driver then
self.driver = nil
end
end
function minecart.get_entitycart_nearby(pos, param2, radius)
local pos2 = param2 and vector.add(pos, minecart.param2_to_dir(param2)) or pos
for _, object in pairs(minetest.get_objects_inside_radius(pos2, radius or 0.5)) do
local entity = object:get_luaentity()
if entity and entity.name and tEntityNames[entity.name] then
local vel = object:get_velocity()
if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing?
return entity
end
end
end
end
function minecart.push_entitycart(self, punch_dir)
--print("push_entitycart")
local vel = self.object:get_velocity()
punch_dir.y = 0
local yaw = minetest.dir_to_yaw(punch_dir)
self.object:set_rotation({x = 0, y = yaw, z = 0})
self.is_running = true
self.arrival_time = 0
end
function minecart.register_cart_entity(entity_name, node_name, cart_type, entity_def)
entity_def.entity_name = entity_name
entity_def.node_name = node_name
entity_def.on_activate = on_entitycard_activate
entity_def.on_punch = on_entitycard_punch
entity_def.on_step = on_step
entity_def.on_rightclick = on_entitycard_rightclick
entity_def.on_detach_child = on_entitycard_detach_child
entity_def.owner = nil
entity_def.driver = nil
entity_def.cargo = {}
minetest.register_entity(entity_name, entity_def)
-- register node for punching
minecart.register_cart_names(node_name, entity_name, cart_type)
end
+172
View File
@@ -0,0 +1,172 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
local NUM_ITEMS = 4
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local S = minecart.S
local function scan_for_objects(pos, inv)
for _, object in pairs(minetest.get_objects_inside_radius(pos, 1)) do
local lua_entity = object:get_luaentity()
if not object:is_player() and lua_entity and lua_entity.name == "__builtin:item" then
if lua_entity.itemstring ~= "" then
local stack = ItemStack(lua_entity.itemstring)
if inv:room_for_item("main", stack) then
inv:add_item("main", stack)
object:remove()
end
end
end
end
end
local function pull_push_item(pos, param2)
local items = minecart.take_items(pos, param2, NUM_ITEMS)
if items then
local leftover = minecart.put_items(pos, param2, items)
if leftover then
-- place item back
minecart.untake_items(pos, param2, leftover)
return false
end
return true
else
items = minecart.take_items({x=pos.x, y=pos.y+1, z=pos.z}, nil, NUM_ITEMS)
if items then
local leftover = minecart.put_items(pos, param2, items)
if leftover then
-- place item back
minecart.untake_items({x=pos.x, y=pos.y+1, z=pos.z}, nil, leftover)
return false
end
return true
end
end
return false
end
local function push_item(pos, inv, param2)
local taken = minecart.inv_take_items(inv, "main", NUM_ITEMS)
if taken then
local leftover = minecart.put_items(pos, param2, taken)
if leftover then
inv:add_item("main", leftover)
end
end
end
local formspec = "size[8,6.5]"..
"list[context;main;3,0;2,2;]"..
"list[current_player;main;0,2.7;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
minetest.register_node("minecart:hopper", {
description = S("Minecart Hopper"),
tiles = {
-- up, down, right, left, back, front
"default_cobble.png^minecart_appl_hopper_top.png",
"default_cobble.png^minecart_appl_hopper.png",
"default_cobble.png^minecart_appl_hopper_right.png",
"default_cobble.png^minecart_appl_hopper.png",
"default_cobble.png^minecart_appl_hopper.png",
"default_cobble.png^minecart_appl_hopper.png",
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-8/16, 2/16, -8/16, 8/16, 8/16, -6/16},
{-8/16, 2/16, 6/16, 8/16, 8/16, 8/16},
{-8/16, 2/16, -8/16, -6/16, 8/16, 8/16},
{ 6/16, 2/16, -8/16, 8/16, 8/16, 8/16},
{-6/16, 0/16, -6/16, 6/16, 3/16, 6/16},
{-5/16, -4/16, -5/16, 5/16, 0/16, 5/16},
{ 0/16, -4/16, -3/16, 11/16, 2/16, 3/16},
},
},
selection_box = {
type = "fixed",
fixed = {
{-8/16, 2/16, -8/16, 8/16, 8/16, 8/16},
{-5/16, -4/16, -5/16, 5/16, 0/16, 5/16},
{ 0/16, -4/16, -3/16, 11/16, 2/16, 3/16},
},
},
on_construct = function(pos)
local inv = M(pos):get_inventory()
inv:set_size('main', 4)
end,
after_place_node = function(pos, placer)
local meta = M(pos)
meta:set_string("owner", placer:get_player_name())
meta:set_string("formspec", formspec)
minetest.get_node_timer(pos):start(2)
end,
on_timer = function(pos, elapsed)
local inv = M(pos):get_inventory()
local param2 = minetest.get_node(pos).param2
param2 = (param2 + 1) % 4 -- output is on the right
if not pull_push_item(pos, param2) then
scan_for_objects({x=pos.x, y=pos.y+1, z=pos.z}, inv)
push_item(pos, inv, param2)
end
return true
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
minetest.get_node_timer(pos):start(2)
return stack:get_count()
end,
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
if minetest.is_protected(pos, player:get_player_name()) then
return 0
end
return stack:get_count()
end,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
for _,stack in ipairs(oldmetadata.inventory.main) do
minetest.add_item(pos, stack)
end
end,
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = minecart.CLIP,
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
})
minetest.register_craft({
output = "minecart:hopper",
recipe = {
{"default:stone", "", "default:stone"},
{"default:stone", "default:gold_ingot", "default:stone"},
{"", "default:stone", ""},
},
})
Binary file not shown.

After

Width:  |  Height:  |  Size: 121 KiB

+141
View File
@@ -0,0 +1,141 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local RegisteredInventories = {}
-- Take the given number of items from the inv.
-- Returns nil if ItemList is empty.
function minecart.inv_take_items(inv, listname, num)
if inv:is_empty(listname) then
return nil
end
local size = inv:get_size(listname)
for idx = 1, size do
local items = inv:get_stack(listname, idx)
if items:get_count() > 0 then
local taken = items:take_item(num)
inv:set_stack(listname, idx, items)
return taken
end
end
return nil
end
function minecart.take_items(pos, param2, num)
local npos, node
if param2 then
npos, node = minecart.get_next_node(pos, (param2 + 2) % 4)
else
npos, node = pos, minetest.get_node(pos)
end
local def = RegisteredInventories[node.name]
local owner = M(pos):get_string("owner")
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and def.take_listname and (not def.allow_take or def.allow_take(npos, nil, owner)) then
return minecart.inv_take_items(inv, def.take_listname, num)
elseif def and def.take_item then
return def.take_item(npos, num, owner)
else
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.minecart_hopper_takeitem then
return ndef.minecart_hopper_takeitem(npos, num)
end
end
end
function minecart.put_items(pos, param2, stack)
local npos, node = minecart.get_next_node(pos, param2)
local def = RegisteredInventories[node.name]
local owner = M(pos):get_string("owner")
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and def.put_listname and (not def.allow_put or def.allow_put(npos, stack, owner)) then
local leftover = inv:add_item(def.put_listname, stack)
if leftover:get_count() > 0 then
return leftover
end
elseif def and def.put_item then
return def.put_item(npos, stack, owner)
elseif minecart.is_air_like(node.name) or minecart.is_nodecart_available(npos) then
minetest.add_item(npos, stack)
else
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.minecart_hopper_additem then
local leftover = ndef.minecart_hopper_additem(npos, stack)
if leftover:get_count() > 0 then
return leftover
end
else
return stack
end
end
end
function minecart.untake_items(pos, param2, stack)
local npos, node
if param2 then
npos, node = minecart.get_next_node(pos, (param2 + 2) % 4)
else
npos, node = pos, minetest.get_node(pos)
end
local def = RegisteredInventories[node.name]
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and def.take_listname then
return inv:add_item(def.take_listname, stack)
elseif def and def.untake_item then
return def.untake_item(npos, stack)
else
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.minecart_hopper_untakeitem then
return ndef.minecart_hopper_untakeitem(npos, stack)
end
end
end
-- Register inventory node for hopper access
-- (for example, see below)
function minecart.register_inventory(node_names, def)
for _, name in ipairs(node_names) do
RegisteredInventories[name] = {
allow_put = def.put and def.put.allow_inventory_put,
put_listname = def.put and def.put.listname,
allow_take = def.take and def.take.allow_inventory_take,
take_listname = def.take and def.take.listname,
put_item = def.put and def.put.put_item,
take_item = def.take and def.take.take_item,
untake_item = def.take and def.take.untake_item,
}
end
end
-- Allow the hopper the access to itself
minecart.register_inventory({"minecart:hopper"}, {
put = {
allow_inventory_put = function(pos, stack, player_name)
local owner = M(pos):get_string("owner")
return owner == player_name
end,
listname = "main",
},
take = {
allow_inventory_take = function(pos, stack, player_name)
local owner = M(pos):get_string("owner")
return owner == player_name
end,
listname = "main",
},
})
+469
View File
@@ -0,0 +1,469 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Script to generate the template file and update the translation files.
# Copy the script into the mod or modpack root folder and run it there.
#
# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer
# LGPLv2.1+
#
# See https://github.com/minetest-tools/update_translations for
# potential future updates to this script.
from __future__ import print_function
import os, fnmatch, re, shutil, errno
from sys import argv as _argv
from sys import stderr as _stderr
# Running params
params = {"recursive": False,
"help": False,
"mods": False,
"verbose": False,
"folders": [],
"no-old-file": False,
"break-long-lines": False,
"sort": False,
"print-source": False
}
# Available CLI options
options = {"recursive": ['--recursive', '-r'],
"help": ['--help', '-h'],
"mods": ['--installed-mods', '-m'],
"verbose": ['--verbose', '-v'],
"no-old-file": ['--no-old-file', '-O'],
"break-long-lines": ['--break-long-lines', '-b'],
"sort": ['--sort', '-s'],
"print-source": ['--print-source', '-p']
}
# Strings longer than this will have extra space added between
# them in the translation files to make it easier to distinguish their
# beginnings and endings at a glance
doublespace_threshold = 80
def set_params_folders(tab: list):
'''Initialize params["folders"] from CLI arguments.'''
# Discarding argument 0 (tool name)
for param in tab[1:]:
stop_param = False
for option in options:
if param in options[option]:
stop_param = True
break
if not stop_param:
params["folders"].append(os.path.abspath(param))
def set_params(tab: list):
'''Initialize params from CLI arguments.'''
for option in options:
for option_name in options[option]:
if option_name in tab:
params[option] = True
break
def print_help(name):
'''Prints some help message.'''
print(f'''SYNOPSIS
{name} [OPTIONS] [PATHS...]
DESCRIPTION
{', '.join(options["help"])}
prints this help message
{', '.join(options["recursive"])}
run on all subfolders of paths given
{', '.join(options["mods"])}
run on locally installed modules
{', '.join(options["no-old-file"])}
do not create *.old files
{', '.join(options["sort"])}
sort output strings alphabetically
{', '.join(options["break-long-lines"])}
add extra line breaks before and after long strings
{', '.join(options["verbose"])}
add output information
''')
def main():
'''Main function'''
set_params(_argv)
set_params_folders(_argv)
if params["help"]:
print_help(_argv[0])
elif params["recursive"] and params["mods"]:
print("Option --installed-mods is incompatible with --recursive")
else:
# Add recursivity message
print("Running ", end='')
if params["recursive"]:
print("recursively ", end='')
# Running
if params["mods"]:
print(f"on all locally installed modules in {os.path.expanduser('~/.minetest/mods/')}")
run_all_subfolders(os.path.expanduser("~/.minetest/mods"))
elif len(params["folders"]) >= 2:
print("on folder list:", params["folders"])
for f in params["folders"]:
if params["recursive"]:
run_all_subfolders(f)
else:
update_folder(f)
elif len(params["folders"]) == 1:
print("on folder", params["folders"][0])
if params["recursive"]:
run_all_subfolders(params["folders"][0])
else:
update_folder(params["folders"][0])
else:
print("on folder", os.path.abspath("./"))
if params["recursive"]:
run_all_subfolders(os.path.abspath("./"))
else:
update_folder(os.path.abspath("./"))
#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ')
#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote
pattern_lua_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL)
pattern_lua_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL)
pattern_lua_bracketed_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL)
pattern_lua_bracketed_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL)
# Handles "concatenation" .. " of strings"
pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL)
pattern_tr = re.compile(r'(.*?[^@])=(.*)')
pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)')
pattern_tr_filename = re.compile(r'\.tr$')
pattern_po_language_code = re.compile(r'(.*)\.po$')
#attempt to read the mod's name from the mod.conf file or folder name. Returns None on failure
def get_modname(folder):
try:
with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf:
for line in mod_conf:
match = pattern_name.match(line)
if match:
return match.group(1)
except FileNotFoundError:
if not os.path.isfile(os.path.join(folder, "modpack.txt")):
folder_name = os.path.basename(folder)
# Special case when run in Minetest's builtin directory
if folder_name == "builtin":
return "__builtin"
else:
return folder_name
else:
return None
return None
#If there are already .tr files in /locale, returns a list of their names
def get_existing_tr_files(folder):
out = []
for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
for name in files:
if pattern_tr_filename.search(name):
out.append(name)
return out
# A series of search and replaces that massage a .po file's contents into
# a .tr file's equivalent
def process_po_file(text):
# The first three items are for unused matches
text = re.sub(r'#~ msgid "', "", text)
text = re.sub(r'"\n#~ msgstr ""\n"', "=", text)
text = re.sub(r'"\n#~ msgstr "', "=", text)
# comment lines
text = re.sub(r'#.*\n', "", text)
# converting msg pairs into "=" pairs
text = re.sub(r'msgid "', "", text)
text = re.sub(r'"\nmsgstr ""\n"', "=", text)
text = re.sub(r'"\nmsgstr "', "=", text)
# various line breaks and escape codes
text = re.sub(r'"\n"', "", text)
text = re.sub(r'"\n', "\n", text)
text = re.sub(r'\\"', '"', text)
text = re.sub(r'\\n', '@n', text)
# remove header text
text = re.sub(r'=Project-Id-Version:.*\n', "", text)
# remove double-spaced lines
text = re.sub(r'\n\n', '\n', text)
return text
# Go through existing .po files and, if a .tr file for that language
# *doesn't* exist, convert it and create it.
# The .tr file that results will subsequently be reprocessed so
# any "no longer used" strings will be preserved.
# Note that "fuzzy" tags will be lost in this process.
def process_po_files(folder, modname):
for root, dirs, files in os.walk(os.path.join(folder, 'locale/')):
for name in files:
code_match = pattern_po_language_code.match(name)
if code_match == None:
continue
language_code = code_match.group(1)
tr_name = f'{modname}.{language_code}.tr'
tr_file = os.path.join(root, tr_name)
if os.path.exists(tr_file):
if params["verbose"]:
print(f"{tr_name} already exists, ignoring {name}")
continue
fname = os.path.join(root, name)
with open(fname, "r", encoding='utf-8') as po_file:
if params["verbose"]:
print(f"Importing translations from {name}")
text = process_po_file(po_file.read())
with open(tr_file, "wt", encoding='utf-8') as tr_out:
tr_out.write(text)
# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612
# Creates a directory if it doesn't exist, silently does
# nothing if it already exists
def mkdir_p(path):
try:
os.makedirs(path)
except OSError as exc: # Python >2.5
if exc.errno == errno.EEXIST and os.path.isdir(path):
pass
else: raise
# Converts the template dictionary to a text to be written as a file
# dKeyStrings is a dictionary of localized string to source file sets
# dOld is a dictionary of existing translations and comments from
# the previous version of this text
def strings_to_text(dkeyStrings, dOld, mod_name, header_comments):
lOut = [f"# textdomain: {mod_name}"]
if header_comments is not None:
lOut.append(header_comments)
dGroupedBySource = {}
for key in dkeyStrings:
sourceList = list(dkeyStrings[key])
if params["sort"]:
sourceList.sort()
sourceString = "\n".join(sourceList)
listForSource = dGroupedBySource.get(sourceString, [])
listForSource.append(key)
dGroupedBySource[sourceString] = listForSource
lSourceKeys = list(dGroupedBySource.keys())
lSourceKeys.sort()
for source in lSourceKeys:
localizedStrings = dGroupedBySource[source]
if params["sort"]:
localizedStrings.sort()
if params["print-source"]:
if lOut[-1] != "":
lOut.append("")
lOut.append(source)
for localizedString in localizedStrings:
val = dOld.get(localizedString, {})
translation = val.get("translation", "")
comment = val.get("comment")
if params["break-long-lines"] and len(localizedString) > doublespace_threshold and not lOut[-1] == "":
lOut.append("")
if comment != None and comment != "" and not comment.startswith("# textdomain:"):
lOut.append(comment)
lOut.append(f"{localizedString}={translation}")
if params["break-long-lines"] and len(localizedString) > doublespace_threshold:
lOut.append("")
unusedExist = False
for key in dOld:
if key not in dkeyStrings:
val = dOld[key]
translation = val.get("translation")
comment = val.get("comment")
# only keep an unused translation if there was translated
# text or a comment associated with it
if translation != None and (translation != "" or comment):
if not unusedExist:
unusedExist = True
lOut.append("\n\n##### not used anymore #####\n")
if params["break-long-lines"] and len(key) > doublespace_threshold and not lOut[-1] == "":
lOut.append("")
if comment != None:
lOut.append(comment)
lOut.append(f"{key}={translation}")
if params["break-long-lines"] and len(key) > doublespace_threshold:
lOut.append("")
return "\n".join(lOut) + '\n'
# Writes a template.txt file
# dkeyStrings is the dictionary returned by generate_template
def write_template(templ_file, dkeyStrings, mod_name):
# read existing template file to preserve comments
existing_template = import_tr_file(templ_file)
text = strings_to_text(dkeyStrings, existing_template[0], mod_name, existing_template[2])
mkdir_p(os.path.dirname(templ_file))
with open(templ_file, "wt", encoding='utf-8') as template_file:
template_file.write(text)
# Gets all translatable strings from a lua file
def read_lua_file_strings(lua_file):
lOut = []
with open(lua_file, encoding='utf-8') as text_file:
text = text_file.read()
#TODO remove comments here
text = re.sub(pattern_concat, "", text)
strings = []
for s in pattern_lua_s.findall(text):
strings.append(s[1])
for s in pattern_lua_bracketed_s.findall(text):
strings.append(s)
for s in pattern_lua_fs.findall(text):
strings.append(s[1])
for s in pattern_lua_bracketed_fs.findall(text):
strings.append(s)
for s in strings:
s = re.sub(r'"\.\.\s+"', "", s)
s = re.sub("@[^@=0-9]", "@@", s)
s = s.replace('\\"', '"')
s = s.replace("\\'", "'")
s = s.replace("\n", "@n")
s = s.replace("\\n", "@n")
s = s.replace("=", "@=")
lOut.append(s)
return lOut
# Gets strings from an existing translation file
# returns both a dictionary of translations
# and the full original source text so that the new text
# can be compared to it for changes.
# Returns also header comments in the third return value.
def import_tr_file(tr_file):
dOut = {}
text = None
header_comment = None
if os.path.exists(tr_file):
with open(tr_file, "r", encoding='utf-8') as existing_file :
# save the full text to allow for comparison
# of the old version with the new output
text = existing_file.read()
existing_file.seek(0)
# a running record of the current comment block
# we're inside, to allow preceeding multi-line comments
# to be retained for a translation line
latest_comment_block = None
for line in existing_file.readlines():
line = line.rstrip('\n')
if line.startswith("###"):
if header_comment is None and not latest_comment_block is None:
# Save header comments
header_comment = latest_comment_block
# Strip textdomain line
tmp_h_c = ""
for l in header_comment.split('\n'):
if not l.startswith("# textdomain:"):
tmp_h_c += l + '\n'
header_comment = tmp_h_c
# Reset comment block if we hit a header
latest_comment_block = None
continue
elif line.startswith("#"):
# Save the comment we're inside
if not latest_comment_block:
latest_comment_block = line
else:
latest_comment_block = latest_comment_block + "\n" + line
continue
match = pattern_tr.match(line)
if match:
# this line is a translated line
outval = {}
outval["translation"] = match.group(2)
if latest_comment_block:
# if there was a comment, record that.
outval["comment"] = latest_comment_block
latest_comment_block = None
dOut[match.group(1)] = outval
return (dOut, text, header_comment)
# Walks all lua files in the mod folder, collects translatable strings,
# and writes it to a template.txt file
# Returns a dictionary of localized strings to source file sets
# that can be used with the strings_to_text function.
def generate_template(folder, mod_name):
dOut = {}
for root, dirs, files in os.walk(folder):
for name in files:
if fnmatch.fnmatch(name, "*.lua"):
fname = os.path.join(root, name)
found = read_lua_file_strings(fname)
if params["verbose"]:
print(f"{fname}: {str(len(found))} translatable strings")
for s in found:
sources = dOut.get(s, set())
sources.add(f"### {os.path.basename(fname)} ###")
dOut[s] = sources
if len(dOut) == 0:
return None
templ_file = os.path.join(folder, "locale/template.txt")
write_template(templ_file, dOut, mod_name)
return dOut
# Updates an existing .tr file, copying the old one to a ".old" file
# if any changes have happened
# dNew is the data used to generate the template, it has all the
# currently-existing localized strings
def update_tr_file(dNew, mod_name, tr_file):
if params["verbose"]:
print(f"updating {tr_file}")
tr_import = import_tr_file(tr_file)
dOld = tr_import[0]
textOld = tr_import[1]
textNew = strings_to_text(dNew, dOld, mod_name, tr_import[2])
if textOld and textOld != textNew:
print(f"{tr_file} has changed.")
if not params["no-old-file"]:
shutil.copyfile(tr_file, f"{tr_file}.old")
with open(tr_file, "w", encoding='utf-8') as new_tr_file:
new_tr_file.write(textNew)
# Updates translation files for the mod in the given folder
def update_mod(folder):
modname = get_modname(folder)
if modname is not None:
process_po_files(folder, modname)
print(f"Updating translations for {modname}")
data = generate_template(folder, modname)
if data == None:
print(f"No translatable strings found in {modname}")
else:
for tr_file in get_existing_tr_files(folder):
update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file))
else:
print(f"\033[31mUnable to find modname in folder {folder}.\033[0m", file=_stderr)
exit(1)
# Determines if the folder being pointed to is a mod or a mod pack
# and then runs update_mod accordingly
def update_folder(folder):
is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf"))
if is_modpack:
subfolders = [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')]
for subfolder in subfolders:
update_mod(subfolder)
else:
update_mod(folder)
print("Done.")
def run_all_subfolders(folder):
for modfolder in [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')]:
update_folder(modfolder)
main()
+51
View File
@@ -0,0 +1,51 @@
--[[
Minecart
========
Copyright (C) 2019-2023 Joachim Stolberg
MIT
See license.txt for more information
]]--
minecart = {}
-- Version for compatibility checks, see readme.md/history
minecart.version = 2.05
minecart.hopper_enabled = minetest.settings:get_bool("minecart_hopper_enabled") ~= false
minecart.teleport_enabled = minetest.settings:get_bool("minecart_teleport_enabled") == true
-- Test for MT 5.4 new string mode
minecart.CLIP = minetest.features.use_texture_alpha_string_modes and "clip" or false
minecart.S = minetest.get_translator("minecart")
local MP = minetest.get_modpath("minecart")
dofile(MP .. "/baselib.lua")
dofile(MP .. "/storage.lua")
dofile(MP .. "/rails.lua")
dofile(MP .. "/monitoring.lua")
dofile(MP .. "/recording.lua")
dofile(MP .. "/hopperlib.lua")
dofile(MP .. "/nodelib.lua")
dofile(MP .. "/entitylib.lua")
dofile(MP .. "/api.lua")
dofile(MP .. "/minecart.lua")
dofile(MP .. "/buffer.lua")
dofile(MP .. "/protection.lua")
--dofile(MP .. "/tool.lua") -- for debugging only
dofile(MP .. "/signs.lua")
dofile(MP .. "/terminal.lua")
dofile(MP .. "/pusher.lua")
dofile(MP .. "/beduino.lua")
if minecart.hopper_enabled then
dofile(MP .. "/hopper.lua")
dofile(MP .. "/mods_support.lua")
end
dofile(MP .. "/doc.lua")
dofile(MP .. "/manual.lua")
minetest.log("info", "[MOD] Minecart loaded")
+54
View File
@@ -0,0 +1,54 @@
License of source code
----------------------
The MIT License (MIT)
Copyright (C) 2012-2016 PilzAdam
Copyright (C) 2014-2016 SmallJoker
Copyright (C) 2012-2016 Various Minetest developers and contributors
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
sell copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
For more details:
https://opensource.org/licenses/MIT
Licenses of media
-----------------
CC-0, see: https://creativecommons.org/share-your-work/public-domain/cc0/, except
if other license is mentioned.
Authors
---------
Originally from PixelBOX (Gambit):
carts_cart_side.png
carts_cart_top.png
carts_cart_front.png*
carts_cart.png*
sofar + stujones11:
carts_cart.b3d and carts_cart.blend
hexafraction, modified by sofar
carts_rail_*.png
http://www.freesound.org/people/YleArkisto/sounds/253159/ - YleArkisto - CC-BY-3.0
carts_cart_moving.*.ogg
@@ -0,0 +1,67 @@
# textdomain: minecart
Station name=Stationsname
Waiting time/sec=Wartezeit/s
connected to=verbunden mit
Not connected!=Nicht verbunden!
Minecart Railway Buffer=Minecart Prellbock
Summary=Zusammenfassung
1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint.=1. Baue eine Schienenstrecke mit zwei Enden. Kreuzungen sind zulässig, solange jede Route ihre eigenen Start- und Endpunkte hat.
2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information).=2. Platziere einen Prellbock an beide Schienenenden (Prellböcke sind zwingend notwendig, sie speichern die Routen- und Zeit-Informationen).
3. Give both Railway Buffers unique station names, like Oxford and Cambridge.=3. Gib beiden Prellböcken eindeutige Stationsnamen wie: Stuttgart und München.
4. Place a Minecart at a buffer and give it a cart number (1..999)=4. Platziere einen Minecart Wagen an einem Prellbock und gib dem Wagen eine Wagennummer (1..999)
5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=5. Um eine Route aufzuzeichnen, fahre die Route in beide Richtungen von Prellbock zu Prellbock mit dem Minecart Wagen(!). Nutze 'links-rechts' Tasten zur Steuerung.
6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=6. Schlage auf die Prellböcke um die Verbindungsdaten zu prüfen (bspw.: 'München: verbunden mit Stuttgart')
7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time.=7. Optional: Konfiguriere die Wagenwartezeit in beiden Prellböcken. Der Wagen startet dann nach dieser Zeit automatisch.
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=8. Optional: Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke).
9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=9. Platziere einen Wagen direkt vor einem Prellbock und prüfe, ob er nach der konfigurierten Zeit startet.
10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=10. Prüfe den Status des Wagen mit dem Chat Kommando: /mycart <num>@n <num> ist die Wagennummer
11. Drop items into the Minecart and punch the cart to start it.=11: Lege Gegenstände in ein Wagen (Taste Q) und starte dann den Wagen durch Anklicken.
12. Dig the cart with 'sneak+click' (as usual). The items will be drop down.=12. Klicke mit gedrückter Shift-Taste auf den Wagen, um diesen zu entfernen. Die Gegenstände fallen dann zu Boden.
13. To retrieve lost carts, use the chat command: /stopcart <num>=13. Um verirrte Waren zurückzuholen, verwende den Chat-Befehl: /stopcart <num>
Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back=Primär für den Transport von Gegenständen genutzt. Du kannst Gegenstände in ein Cart legen (Taste Q) und dann den Wagen durch Anklicken starten. Klicke mit gedrückter Shift-Taste auf den Wagen, um Cart und Gegenstände zurückzuerhalten
Used as buffer on both rail ends. Needed to be able to record the cart routes=Preckblöcke müssen an beiden Schienenenden platziert sein, so dass Aufzeichnungen der Strecke gemacht werden können.
Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke der Strecke entlang)
Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.=Um Wagen zu be- und entladen. Der Hopper kann Gegenstände aus Kisten Holen und legen, sowie diese in Wagen fallen lassen bzw. aus Wagen entnehmen. Um einen Wagen zu entladen, muss der Hopper unter die Schiene platziert werden. Um einen Wagen zu beladen, muss der Hopper direkt neben die Schiene platziert werden.
Minecart=Minecart
Minecart, the lean railway transportation automation system=Minecart, das schlanke Schienentransport Automatisierungssystem
Minecart Cart=Wagen
Minecart Speed Signs=Geschwindigkeitsbegrenzungszeichen
If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.=Wenn mehrere Wagen auf einer Route fahren, kann es vorkommen,@ndass eine Prellbock Position bereits belegt ist und ein Wagen daher früher anhält.@nDer Cart Anschieber dient in diesem Fall dazu, die Wagen wieder in Richtung Prellbock anzuschieben.@nDieser Block muss unter der Schiene mit 2 m Abstand vor dem Prellbock platziert werden.
Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.=Begrenze die Geschwindigkeit der Wagen mit Geschwindigkeitsbegrenzungszeichen@n@nDie Geschwindigkeit der Carts wird wie bisher auch über "power rails" beeinflusst. "Brake rails" sind ohne Bedeutung, das Cart bremst hier nicht. Die maximale Geschwindigkeit beträgt 8 m/s. Dies setzt eine Verhältnis von "power rails" zu "normal rails" von 1 zu 4 auf einem ebenen Streckenabschnitt voraus. Ein Streckenabschnitt ist dabei ein Reihe von Schienenblöcken ohne Richtungsänderung. Nach jeder Kurve/Knick wird die Geschwindigkeit für den nächsten Streckenabschnitt neu bestimmt, wobei hier der Schwung des Carts mit berücksichtigt wird. So kann ein Cart auch über kurze Streckenabschnitt ohne "power rails" rollen.@n@nUm das Cart zusätzlich an bestimmten Stellen abzubremsen (an Weichen oder vor einen Puffer), können Geschwindigkeitsbegrenzungszeichen an der Strecke platziert werden. Durch diese Zeichen kann die Geschwindigkeit auf 4, 2, oder 1 m/s reduziert werden. Durch das Aufhebungszeichen kann die Geschwindigkeitsbegrenzung wieder aufgehoben werden.@n@nDie Geschwindigkeitsbegrenzungszeichen müssen so neben die Strecke platziert werden, dass sie vom Cart ablesbar sind. Dies erlaubt damit unterschiedliche Geschwindigkeiten pro Fahrtrichtung.
Minecart Hopper=Minecart Hopper
Minecart (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts)
Output cart state and position, or a list of carts, if no cart number is given.=Gibt Status und Position des Wagens, oder eine Liste aller Wagen aus, wenn keine Wagennummer angegeben ist.
List of carts=Liste aller Wagen
Stop and return/drop a missing/running cart.=Stoppe und hole einen vermissten Wagen zurück.
Cart=Wagen
dropped=fallen gelassen
is not existing!=existiert nicht!
Enter cart number=Gebe Cart Nummer ein
Save=Speichern
[minecart] Area is protected!=[minecart] Bereich ist geschützt!
Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen
Minecart Landmark=Minecart Meilenstein
Cart Pusher=Wagen Anschieber
left=links
right=rechts
straight=geradeaus
Recording=Aufzeichnung
speed=Tempo
next junction=nächste Weiche
Travel time=Fahrzeit
[minecart] Recording canceled!=
[minecart] Route stored!=[minecart] Strecke gespeichert
[minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m=[minecart] Geschw. @= %u m/s, Zeit @= %u s, Routenlänge @= %u m
[minecart] Your route is too short to record!=[minecart] Deine Strecke ist zu kurz für eine Aufzeichnung!
Speed "1"=Tempo "1"
Speed "2"=Tempo "2"
Speed "4"=Tempo "4"
No speed limit=Keine Geschwindigkeitsbegrenzung
Cart List=Cart Liste
Cart Terminal=Cart Terminal
##### not used anymore #####
Stop amd return a missing/running cart.=Stoppe und hole einen vermissten Wagen zurück.
stopped=gestoppt
@@ -0,0 +1,61 @@
# textdomain: minecart
Station name=Имя станции
Waiting time/sec=Время ожидания/сек
connected to=соединено с
Not connected!=Нет соединения!
Minecart Railway Buffer=Буфер вагонетки
Summary=Резюме
1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint.=1. Проложите рельсы и создайте маршрут с двумя конечными точками. Разветвления разрешены, если у каждого маршрута есть свои начало и конец.
2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information).=2. Установите буферы вагонеток на обоих концах пути. Они обязательны для сохранения маршрута и настроек времени.
3. Give both Railway Buffers unique station names, like Oxford and Cambridge.=3. Дайте обеим Буферам уникальные названия, такие как Оксфорд и Кембридж.
4. Place a Minecart at a buffer and give it a cart number (1..999)=4. Поместите вагонетку на рельсы рядом с буфером и присвойте ей номер (1-999)
5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=5. Проедьте между буферами в обоих направлениях на Вагонетке(!), чтобы сохранить маршруты (управление: клавиши «A» и «D» / «влево» и «вправо»).
6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=6. Ударьте по буферам, чтобы проверить данные о соединении (например: 'Оксфорд: соединение с Кембриджем установлено').
7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time.=7. Дополнительно: вы можете задать время стоянки на буферах. В этом случае вагонетка будет трогаться сама, как только выйдет время ожидания.
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=8. Дополнительно: защитите свою железнодорожную сеть с помощью Защитных вех (минимум одна веха на каждые 16 блоков/метров).
9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=9. Поставьте вагонетку перед буфером и проверьте, запустится ли она после установленного времени.
10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=10. Проверьте состояние вагонетки с помощью чат-команды: /mycart <номер>@n где '<номер>' — это номер вагонетки
11. Drop items into the Minecart and punch the cart to start it.=11. Сбросьте предметы в вагонетку и ударьте по ней, чтобы она тронулась.
12. Dig the cart with 'sneak+click' (as usual). The items will be drop down.=12. Возьмите вагонетку с помощью 'Присесть' + 'ЛКМ'. Предметы выпадут из нее.
13. To retrieve lost carts, use the chat command: /stopcart <num>=13. Чтобы вернуть утерянные вагонетки, используйте команду чата: /stopcart <номер вагонетки>
Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back=В основном используется для транспортировки предметов. Вы можете сбросить предметы в вагонетку и ударить по ней, чтобы она тронулась. Нажмите 'Присесть' + 'ЛКМ' на вагонетке, чтобы забрать её вместе с предметами.
Used as buffer on both rail ends. Needed to be able to record the cart routes=Используется в качестве тупикового упора на обоих концах пути. Необходим для корректной записи маршрутов вагонетки.
Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=Защитите Ваши рельсы Защитными вехами (минимум одна веха на каждые 16 блоков/метров, установленная рядом с рельсами)
Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.=Служит для загрузки и выгрузки вещей. Воронка умеет перекладывать предметы между сундуками и вагонетками. Чтобы опустошить вагонетку, поставьте воронку снизу под рельс. Чтобы наполнить её — поставьте воронку прямо рядом с ней.
Minecart=
Minecart, the lean railway transportation automation system=Minecart — эффективная система автоматизации железнодорожных перевозок
Minecart Cart=Вагонетка Minecart
Minecart Speed Signs=Скоростные указатели Minecart
If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.=Если на одном маршруте работает несколько вагонеток, позиция у буфера может быть занята, из-за чего следующая вагонетка остановится раньше. В этом случае используется Толкатель вагонеток, чтобы дослать её к буферу.@nЭтот блок должен быть установлен под рельсом на расстоянии 2 метров(блоков) перед буфером.
Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.=Ограничивайте скорость вагонеток с помощью знаков ограничения скорости.@nКак и прежде, на скорость влияют энергорельсы.@nТормозные рельсы не имеют значения — вагонетка на них не тормозит.@nМаксимальная скорость составляет 8 м/с.@nЭто достижимо при соотношении 1 энергорельса к 4 обычным на плоском участке.@nУчастком пути считается непрерывная серия рельсов без смены направления.@nПосле каждого поворота или изгиба скорость для следующего участка определяется заново с учётом инерции вагонетки. Это означает, что вагонетка может проезжать короткие отрезки без энергорельсов.@nЧтобы дополнительно замедлить вагонетку в определённых местах (на стрелках или перед Буфером), на путях можно установить знаки ограничения скорости.@nС их помощью скорость можно снизить до 4, 2 или 1 м/с.@nЗнак «Снятие ограничений» возвращает исходную скорость.@nЗнаки должны быть установлены сбоку от путей так, чтобы они были «видны» из вагонетки. Это позволяет задавать разную скорость для каждого направления движения.
Minecart Hopper=Minecart Воронка
Minecart (Sneak+Click to pick up)=Вагонетка (Присесть+ЛКМ, чтобы взять)
Output cart state and position, or a list of carts, if no cart number is given.=Выводит состояние и координаты вагонетки. Если номер не указан, выводит список всех вагонеток.
List of carts=Список вагонеток
Stop and return/drop a missing/running cart.=Остановить и вернуть/сбросить потерянную/движущуюся вагонетку.
Cart=Вагонетка
dropped=сброшена
is not existing!=не существует!
Enter cart number=Введите номер вагонетки
Save=Сохранить
[minecart] Area is protected!=[minecart] Зона защищена!
Allow to dig/place rails in Minecart Landmark areas=Разрешить установку/демонтаж рельсов в зоне Защитных вех
Minecart Landmark=Защитная веха
Cart Pusher=Толкатель вагонеток
left=влево
right=вправо
straight=прямо
Recording=Идет запись
speed=скорость
next junction=след. развилка
Travel time=Время следования
[minecart] Recording canceled!=[minecart] Запись отменена!
[minecart] Route stored!=[minecart] Маршрут сохранен!
[minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m=[minecart] Скорость @= %u м/с, Время @= %u с, Длина маршрута @= %u м
[minecart] Your route is too short to record!=[minecart] Маршрут слишком короткий для записи!
Speed "1"=Скорость "1"
Speed "2"=Скорость "2"
Speed "4"=Скорость "4"
No speed limit=Снятие ограничений
Cart List=Список вагонеток
Cart Terminal=Терминал вагонеток
@@ -0,0 +1,61 @@
# textdomain: minecart
Station name=
Waiting time/sec=
connected to=
Not connected!=
Minecart Railway Buffer=
Summary=
1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint.=
2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information).=
3. Give both Railway Buffers unique station names, like Oxford and Cambridge.=
4. Place a Minecart at a buffer and give it a cart number (1..999)=
5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=
6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=
7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time.=
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=
9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=
10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=
11. Drop items into the Minecart and punch the cart to start it.=
12. Dig the cart with 'sneak+click' (as usual). The items will be drop down.=
13. To retrieve lost carts, use the chat command: /stopcart <num>=
Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back=
Used as buffer on both rail ends. Needed to be able to record the cart routes=
Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=
Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.=
Minecart=
Minecart, the lean railway transportation automation system=
Minecart Cart=
Minecart Speed Signs=
If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.=
Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.=
Minecart Hopper=
Minecart (Sneak+Click to pick up)=
Output cart state and position, or a list of carts, if no cart number is given.=
List of carts=
Stop and return/drop a missing/running cart.=
Cart=
dropped=
is not existing!=
Enter cart number=
Save=
[minecart] Area is protected!=
Allow to dig/place rails in Minecart Landmark areas=
Minecart Landmark=
Cart Pusher=
left=
right=
straight=
Recording=
speed=
next junction=
Travel time=
[minecart] Recording canceled!=
[minecart] Route stored!=
[minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m=
[minecart] Your route is too short to record!=
Speed "1"=
Speed "2"=
Speed "4"=
No speed limit=
Cart List=
Cart Terminal=
+149
View File
@@ -0,0 +1,149 @@
--[[
Minecart
========
Copyright (C) 2019-2023 Joachim Stolberg
MIT
See license.txt for more information
InGame Documentation for techage or doclib
]]--
local MP = minetest.get_modpath("minecart")
if not minetest.get_modpath("techage") and
minetest.get_modpath("doclib") then
minetest.register_node("minecart:manual", {
description = "Minecart Manual (EN)",
inventory_image = "minecart_book_inv.png",
tiles = {
-- up, down, right, left, back, front
"minecart_book.png",
"minecart_book.png",
"minecart_book.png^[transformR270",
"minecart_book.png^[transformR90",
"minecart_book.png^[transformR180",
"minecart_book.png"
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -8/32, -16/32, -12/32, 8/32, -12/32, 12/32},
},
},
after_place_node = function(pos, placer, itemstack)
minetest.get_meta(pos):set_string("infotext", "Minecart Manual (EN)")
minetest.get_meta(pos):set_string("formspec", doclib.formspec(pos, "minecart", "EN"))
end,
on_receive_fields = function(pos, formname, fields, player)
local player_name = player:get_player_name()
if minetest.is_protected(pos, player_name) then
return
end
minetest.get_meta(pos):set_string("formspec", doclib.formspec(pos, "minecart", "EN", fields))
end,
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = "clip",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
})
minetest.register_node("minecart:handbuch", {
description = "Minecart Handbuch (DE)",
inventory_image = "minecart_book_inv.png",
tiles = {
-- up, down, right, left, back, front
"minecart_book.png",
"minecart_book.png",
"minecart_book.png^[transformR270",
"minecart_book.png^[transformR90",
"minecart_book.png^[transformR180",
"minecart_book.png"
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -8/32, -16/32, -12/32, 8/32, -12/32, 12/32},
},
},
after_place_node = function(pos, placer, itemstack)
minetest.get_meta(pos):set_string("infotext", "Minecart Handbuch (DE)")
minetest.get_meta(pos):set_string("formspec", doclib.formspec(pos, "minecart", "DE"))
end,
on_receive_fields = function(pos, formname, fields, player)
local player_name = player:get_player_name()
if minetest.is_protected(pos, player_name) then
return
end
minetest.get_meta(pos):set_string("formspec", doclib.formspec(pos, "minecart", "DE", fields))
end,
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = "clip",
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2},
})
minetest.register_craft({
output = "minecart:manual",
recipe = {
{"dye:red", "default:paper", "default:paper"},
{"dye:black", "default:paper", "default:paper"},
{"dye:red", "default:paper", "default:paper"},
},
})
minetest.register_craft({
type = "shapeless",
output = "minecart:handbuch",
recipe = {"minecart:manual"},
})
minetest.register_craft({
type = "shapeless",
output = "minecart:manual",
recipe = {"minecart:handbuch"},
})
end
minetest.register_on_mods_loaded(function()
if minetest.get_modpath("techage") then
-- Use the Techage Construction Board
local content = dofile(MP.."/manual_EN.lua")
doclib.add_to_manual("techage", "EN", content)
local content = dofile(MP.."/manual_DE.lua")
doclib.add_to_manual("techage", "DE", content)
elseif minetest.get_modpath("doclib") then
-- Create own manual book
local settings = {
symbol_item = "minecart_manual_image.png",
}
doclib.create_manual("minecart", "EN", settings)
local content = dofile(MP.."/manual_EN.lua")
doclib.add_to_manual("minecart", "EN", content)
doclib.create_manual("minecart", "DE", settings)
local content = dofile(MP.."/manual_DE.lua")
doclib.add_to_manual("minecart", "DE", content)
end
end)
+126
View File
@@ -0,0 +1,126 @@
return {
titles = {
"1,Minecart",
"2,Kurzanleitung",
"2,Minecart-Blöcke",
"3,Wagen",
"3,Puffer",
"3,Landmarken",
"3,Trichter/Hopper",
"3,Wagenschieber (Cart Pusher)",
"3,Geschwindigkeitsbegrenzungsschilder",
"2,Chat-Befehle",
"2,Online-Handbuch",
},
texts = {
"Die Mod Minecart verfügt zusätzlich zu den Standard-Wagen/Loren über eigene Wagen\n"..
"(Minecart genannt).\n"..
"Minecarts werden für den automatisierten Gütertransport auf privaten und öffentlichen\n"..
"Schienennetzen eingesetzt.\n"..
"\n"..
"Die Hauptmerkmale sind:\n"..
"\n"..
" - Gütertransport von Station zu Station\n"..
" - Wagen können durch unbeladene Bereiche (Mapblocks) fahren\n(es müssen nur beide Stationen geladen sein)\n"..
" - Automatisiertes Be-/Entladen von Minecarts mittels Minecart Hopper\n"..
" - Die Schienen können durch Landmarken geschützt werden\n"..
"\n"..
"Wenn die Mod Techage verfügbar ist:\n"..
"\n"..
" - Sind zwei zusätzliche Wagen für den Transport von Gegenständen und Flüssigkeiten verfügbar\n"..
" - Können diese Wagen mit Hilfe von Techage Schiebern und Pumpen be- und entladen werden\n"..
"\n"..
"Du kannst:\n"..
"\n"..
" - den Wagen mit einem Rechtsklick besteigen\n"..
" - den Wagen mit einem Sprung oder Rechtsklick wieder verlassen\n"..
" - den Wagen mit einem Linksklick anschieben/starten\n"..
"\n"..
"Aber Minecarts haben ihren Besitzer und du kannst fremde Minecarts nicht starten\\, stoppen oder entfernen.\n"..
"Minecarts können nur am Puffer/Prellbock gestartet werden. Wenn ein Minecarts unterwegs stehen bleibt\\,\n"..
"entferne es und platziere es wieder an einer Puffer-Position.\n"..
"\n"..
"\n"..
"\n",
" - Platziere die Schienen und baue eine Route mit zwei Endpunkten.\nKreuzungen sind erlaubt\\, solange jede Route einen eigenen Start- und Endpunkt hat.\n"..
" - Platziere an beiden Endpunkten einen Prellbock/Puffer (Puffer werden immer benötigt\\,\nsie speichern die Routen- und Zeitinformationen).\n"..
" - Gebe beiden Prellböcken eindeutige Bahnhofsnamen\\, z. B. Stuttgart und München.\n"..
" - Stelle ein Minecart an einen Puffer und gebe ihm eine Wagennummer (1..999).\n"..
" - Fahre mit dem Minecart(!) von Puffer zu Puffer in beide Richtungen\\, um die\nStecke aufzuzeichnen (verwende die Rechts/Links-Tasten\\, um das Minecart zu steuern).\n"..
" - Schlage auf die Puffer\\, um die Verbindungsdaten zu überprüfen\n (z. B. \"Stuttgart: verbunden mit München\").\n"..
" - Optional: Konfiguriere die Minecart-Wartezeit in beiden Puffern.\nDas Minecart startet dann automatisch nach der konfigurierten Zeit.\n"..
" - Stelle ein Minecart vor den Puffer und prüfe\\, ob es nach der konfigurierten Zeit\nstartet.\n"..
" - Lege Gegenstände in das Minecart und schlage auf den Wagen\\, um ihn zu starten.\n"..
" - Entferne den Wagen mit Shift + Rechts-Click“.\n"..
"\n"..
"\n"..
"\n",
"\n"..
"\n",
"Wird hauptsächlich zum Transport von Gegenständen verwendet. Du kannst Gegenstände\n"..
"in das Minecart legen und auf den Wagen schlagen\\, um ihn zu starten.\n"..
"\n"..
"\n"..
"\n",
"Wird als Prellbock an beiden Schienenenden verwendet. Wird benötigt\\, um die\n"..
"Routen der Minecarts aufzeichnen und speichern zu können.\n"..
"\n"..
"\n"..
"\n",
"Schütze deine Schienen mit den Landmarken (mindestens alle 16 Blöcke in\n"..
"der Nähe der Schiene eine Landmarke).\n"..
"\n"..
"\n"..
"\n",
"Wird zum Laden/Entladen von Minecarts verwendet. Der Hopper kann Gegenstände\n"..
"zu/von Truhen schieben/ziehen und Gegenstände zu/von Minecarts abgeben/abholen.\n"..
"Um ein Minecart zu entladen\\, platziere den Trichter unterhalb der Schiene.\n"..
"Um das Minecart zu beladen\\, platziere den Trichter direkt neben dem Minecart.\n"..
"\n"..
"\n"..
"\n",
"Wenn mehrere Wagen auf einer Strecke fahren\\, kann es vorkommen\\, dass eine Pufferposition\n"..
"bereits belegt ist und ein Wagen daher früher stoppt.\n"..
"In diesem Fall dient der Wagenschieber dazu\\, den Wagen wieder in Richtung Puffer zu schieben.\n"..
"Dieser Block muss im Abstand von 2 m vor dem Puffer unter der Schiene platziert werden.\n"..
"\n"..
"\n"..
"\n",
"Begrenze die Geschwindigkeit der Minecarts mit Geschwindigkeitsbegrenzungsschildern.\n"..
"\n"..
"\n"..
"\n",
" - Befehl „/mycart <num>“\\, um den Status und den Standort des Minecarts auszugeben\n"..
" - Befehl „/stopcart <num>“\\, um verlorene Minecarts abzurufen\n"..
"\n",
"Ein umfassendes Handbuch ist online verfügbar.\n"..
"Siehe: https://github.com/joe7575/minecart/wiki\n"..
"\n",
},
images = {
"minecart_manual_image.png",
"minecart_manual_image.png",
"minecart:cart",
"minecart:cart",
"minecart:buffer",
"minecart:landmark",
"minecart:hopper",
"minecart:cart_pusher",
"minecart:speed2",
"",
"",
},
plans = {
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
}
}
+121
View File
@@ -0,0 +1,121 @@
# Minecart
Die Mod Minecart verfügt zusätzlich zu den Standard-Wagen/Loren über eigene Wagen
(Minecart genannt).
Minecarts werden für den automatisierten Gütertransport auf privaten und öffentlichen
Schienennetzen eingesetzt.
Die Hauptmerkmale sind:
- Gütertransport von Station zu Station
- Wagen können durch unbeladene Bereiche (Mapblocks) fahren
(es müssen nur beide Stationen geladen sein)
- Automatisiertes Be-/Entladen von Minecarts mittels Minecart Hopper
- Die Schienen können durch Landmarken geschützt werden
Wenn die Mod Techage verfügbar ist:
- Sind zwei zusätzliche Wagen für den Transport von Gegenständen und Flüssigkeiten verfügbar
- Können diese Wagen mit Hilfe von Techage Schiebern und Pumpen be- und entladen werden
Du kannst:
- den Wagen mit einem Rechtsklick besteigen
- den Wagen mit einem Sprung oder Rechtsklick wieder verlassen
- den Wagen mit einem Linksklick anschieben/starten
Aber Minecarts haben ihren Besitzer und du kannst fremde Minecarts nicht starten, stoppen oder entfernen.
Minecarts können nur am Puffer/Prellbock gestartet werden. Wenn ein Minecarts unterwegs stehen bleibt,
entferne es und platziere es wieder an einer Puffer-Position.
[minecart_manual_image.png|image]
## Kurzanleitung
1. Platziere die Schienen und baue eine Route mit zwei Endpunkten.
Kreuzungen sind erlaubt, solange jede Route einen eigenen Start- und Endpunkt hat.
2. Platziere an beiden Endpunkten einen Prellbock/Puffer (Puffer werden immer benötigt,
sie speichern die Routen- und Zeitinformationen).
3. Gebe beiden Prellböcken eindeutige Bahnhofsnamen, z. B. Stuttgart und München.
4. Stelle ein Minecart an einen Puffer und gebe ihm eine Wagennummer (1..999).
5. Fahre mit dem Minecart(!) von Puffer zu Puffer in beide Richtungen, um die
Stecke aufzuzeichnen (verwende die Rechts/Links-Tasten, um das Minecart zu steuern).
6. Schlage auf die Puffer, um die Verbindungsdaten zu überprüfen
(z. B. "Stuttgart: verbunden mit München").
7. Optional: Konfiguriere die Minecart-Wartezeit in beiden Puffern.
Das Minecart startet dann automatisch nach der konfigurierten Zeit.
9. Stelle ein Minecart vor den Puffer und prüfe, ob es nach der konfigurierten Zeit
startet.
10. Lege Gegenstände in das Minecart und schlage auf den Wagen, um ihn zu starten.
11. Entferne den Wagen mit Shift + Rechts-Click“.
[minecart_manual_image.png|image]
## Minecart-Blöcke
[minecart:cart|image]
### Wagen
Wird hauptsächlich zum Transport von Gegenständen verwendet. Du kannst Gegenstände
in das Minecart legen und auf den Wagen schlagen, um ihn zu starten.
[minecart:cart|image]
### Puffer
Wird als Prellbock an beiden Schienenenden verwendet. Wird benötigt, um die
Routen der Minecarts aufzeichnen und speichern zu können.
[minecart:buffer|image]
### Landmarken
Schütze deine Schienen mit den Landmarken (mindestens alle 16 Blöcke in
der Nähe der Schiene eine Landmarke).
[minecart:landmark|image]
### Trichter/Hopper
Wird zum Laden/Entladen von Minecarts verwendet. Der Hopper kann Gegenstände
zu/von Truhen schieben/ziehen und Gegenstände zu/von Minecarts abgeben/abholen.
Um ein Minecart zu entladen, platziere den Trichter unterhalb der Schiene.
Um das Minecart zu beladen, platziere den Trichter direkt neben dem Minecart.
[minecart:hopper|image]
### Wagenschieber (Cart Pusher)
Wenn mehrere Wagen auf einer Strecke fahren, kann es vorkommen, dass eine Pufferposition
bereits belegt ist und ein Wagen daher früher stoppt.
In diesem Fall dient der Wagenschieber dazu, den Wagen wieder in Richtung Puffer zu schieben.
Dieser Block muss im Abstand von 2 m vor dem Puffer unter der Schiene platziert werden.
[minecart:cart_pusher|image]
### Geschwindigkeitsbegrenzungsschilder
Begrenze die Geschwindigkeit der Minecarts mit Geschwindigkeitsbegrenzungsschildern.
[minecart:speed2|image]
## Chat-Befehle
- Befehl „/mycart <num>“, um den Status und den Standort des Minecarts auszugeben
- Befehl „/stopcart <num>“, um verlorene Minecarts abzurufen
## Online-Handbuch
Ein umfassendes Handbuch ist online verfügbar.
Siehe: https://github.com/joe7575/minecart/wiki
+120
View File
@@ -0,0 +1,120 @@
return {
titles = {
"1,Minecart",
"2,Quick start guide",
"2,Minecart Blocks",
"3,Cart",
"3,Buffer",
"3,Landmark",
"3,Hopper",
"3,Cart pusher",
"3,Speed limit signs",
"2,Chat commands",
"2,Online manual",
},
texts = {
"The mod Minecart has its own carts (called Minecart) in addition to the standard carts.\n"..
"Minecarts are used for automated item transport on private and public rail networks.\n"..
"The main features are:\n"..
"\n"..
" - Item transport from station to station\n"..
" - Carts can run through unloaded areas (only both stations have to be loaded)\n"..
" - Automated loading/unloading of Minecarts by means of the Minecart Hopper\n"..
" - The rails can be protected by means of landmarks\n"..
"\n"..
"If the mod Techage is available\\, then:\n"..
"\n"..
" - Two additional carts for item and liquid transportation are available\n"..
" - Carts can be loaded/unloaded by means of Techage pusher and pumps\n"..
"\n"..
"You can:\n"..
"\n"..
" - Enter the cart with a right-click\n"..
" - Leave the cart with a jump or a right-click\n"..
" - Push/start the cart with a left-click\n"..
"\n"..
"But carts have their owner and you can't start\\, stop\\, or remove foreign carts.\n"..
"Carts can only be started at the buffer. If a cart stops on the way\\,\n"..
"remove it and place it at the buffer position.\n"..
"\n"..
"\n"..
"\n",
" - Place your rails and build a route with two endpoints.\nJunctions are allowed as long as each route has its own start and endpoint.\n"..
" - Place a Railway Buffer at both endpoints (buffers are always needed\\,\nthey store the route and timing information).\n"..
" - Give both Railway Buffers unique station names\\, like Oxford and Cambridge.\n"..
" - Place a Minecart at a buffer and give it a cart number (1..999)\n"..
" - Drive from buffer to buffer in both directions using the Minecart(!) to\nrecord the routes (use 'right-left' keys to control the Minecart).\n"..
" - Punch the buffers to check the connection data\n(e.g. 'Oxford: connected to Cambridge').\n"..
" - Optional: Configure the Minecart waiting time in both buffers.\nThe Minecart will then start automatically after the configured time.\n"..
" - Place a Minecart in front of the buffer and check whether it starts\nafter the configured time.\n"..
" - Drop items into the Minecart and punch the cart to start it.\n"..
" - Dig the cart with 'sneak+click' (as usual). The items will be drop down.\n"..
"\n"..
"\n"..
"\n",
"\n"..
"\n",
"Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. \n"..
"Sneak+click the cart to get cart and items back.\n"..
"\n"..
"\n"..
"\n",
"Used as buffer on both rail ends. Needed to be able to record the cart routes.\n"..
"\n"..
"\n"..
"\n",
"Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail.\n"..
"\n"..
"\n"..
"\n",
"Used to load/unload Minecarts. The Hopper can push/pull items to/from chests\n"..
"and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper \n"..
"below the rail. To load the Minecart\\, place the hopper right next to the Minecart.\n"..
"\n"..
"\n"..
"\n",
"If several carts are running on one route\\, it can happen that a buffer position\n"..
"is already occupied and one cart therefore stops earlier.\n"..
"In this case\\, the cart pusher is used to push the cart towards the buffer again.\n"..
"This block must be placed under the rail at a distance of 2 m in front of the buffer.\n"..
"\n"..
"\n"..
"\n",
"Limit the cart speed with speed limit signs.\n"..
"\n"..
"\n"..
"\n",
" - Command '/mycart <num>' to output cart state and location\n"..
" - Command '/stopcart <num>' to retrieve lost carts\n"..
"\n",
"A comprehensive manual is available online.\n"..
"See: https://github.com/joe7575/minecart/wiki\n"..
"\n",
},
images = {
"minecart_manual_image.png",
"minecart_manual_image.png",
"minecart:cart",
"minecart:cart",
"minecart:buffer",
"minecart:landmark",
"minecart:hopper",
"minecart:cart_pusher",
"minecart:speed2",
"",
"",
},
plans = {
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
"",
}
}
+114
View File
@@ -0,0 +1,114 @@
# Minecart
The mod Minecart has its own carts (called Minecart) in addition to the standard carts.
Minecarts are used for automated item transport on private and public rail networks.
The main features are:
- Item transport from station to station
- Carts can run through unloaded areas (only both stations have to be loaded)
- Automated loading/unloading of Minecarts by means of the Minecart Hopper
- The rails can be protected by means of landmarks
If the mod Techage is available, then:
- Two additional carts for item and liquid transportation are available
- Carts can be loaded/unloaded by means of Techage pusher and pumps
You can:
- Enter the cart with a right-click
- Leave the cart with a jump or a right-click
- Push/start the cart with a left-click
But carts have their owner and you can't start, stop, or remove foreign carts.
Carts can only be started at the buffer. If a cart stops on the way,
remove it and place it at the buffer position.
[minecart_manual_image.png|image]
## Quick start guide
1. Place your rails and build a route with two endpoints.
Junctions are allowed as long as each route has its own start and endpoint.
2. Place a Railway Buffer at both endpoints (buffers are always needed,
they store the route and timing information).
3. Give both Railway Buffers unique station names, like Oxford and Cambridge.
4. Place a Minecart at a buffer and give it a cart number (1..999)
5. Drive from buffer to buffer in both directions using the Minecart(!) to
record the routes (use 'right-left' keys to control the Minecart).
6. Punch the buffers to check the connection data
(e.g. 'Oxford: connected to Cambridge').
7. Optional: Configure the Minecart waiting time in both buffers.
The Minecart will then start automatically after the configured time.
9. Place a Minecart in front of the buffer and check whether it starts
after the configured time.
10. Drop items into the Minecart and punch the cart to start it.
11. Dig the cart with 'sneak+click' (as usual). The items will be drop down.
[minecart_manual_image.png|image]
## Minecart Blocks
[minecart:cart|image]
### Cart
Primary used to transport items. You can drop items into the Minecart and punch the cart to get started.
Sneak+click the cart to get cart and items back.
[minecart:cart|image]
### Buffer
Used as buffer on both rail ends. Needed to be able to record the cart routes.
[minecart:buffer|image]
### Landmark
Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail.
[minecart:landmark|image]
### Hopper
Used to load/unload Minecarts. The Hopper can push/pull items to/from chests
and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper
below the rail. To load the Minecart, place the hopper right next to the Minecart.
[minecart:hopper|image]
### Cart pusher
If several carts are running on one route, it can happen that a buffer position
is already occupied and one cart therefore stops earlier.
In this case, the cart pusher is used to push the cart towards the buffer again.
This block must be placed under the rail at a distance of 2 m in front of the buffer.
[minecart:cart_pusher|image]
### Speed limit signs
Limit the cart speed with speed limit signs.
[minecart:speed2|image]
## Chat commands
- Command `/mycart <num>` to output cart state and location
- Command `/stopcart <num>` to retrieve lost carts
## Online manual
A comprehensive manual is available online.
See: https://github.com/joe7575/minecart/wiki
@@ -0,0 +1,201 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# install mistune v0.8.4 with: pip install mistune==0.8.4
import re
import mistune
assert(mistune.__version__ == "0.8.4")
__version__ = "1.0"
class WikiLinkInlineLexer(mistune.InlineLexer):
def enable_wiki_link(self):
# add wiki_link rules
self.rules.wiki_link = re.compile(
r'\[' # [
r'([\s\S]+?\|[\s\S]+?)' # name| img-type
r'\](?!\])' # ]
)
# Add wiki_link parser to default rules
# you can insert it some place you like
# but place matters, maybe 3 is not good
self.default_rules.insert(3, 'wiki_link')
def output_wiki_link(self, m):
text = m.group(1)
name, itype = text.split('|')
# you can create an custom render
# you can also return the html if you like
return self.renderer.wiki_link(name, itype)
class MarkdownToLua(mistune.Renderer):
def __init__(self, *args, **kwargs):
mistune.Renderer.__init__(self, *args, **kwargs)
self.item_name = ""
self.plan_table = ""
self.is_first_header = True
self.text_chunck = []
self.lTitle = []
self.lText = []
self.lItemName = []
self.lPlanTable = []
print("Markdown-to-Lua v%s" % __version__)
def m2l_formspec_escape(self, text):
text = text.replace("\\", "")
text = text.replace("[", "\\\\[")
text = text.replace("]", "\\\\]")
text = text.replace(";", "\\\\;")
text = text.replace(",", "\\\\,")
text = text.replace('"', '\\"')
text = text.replace('\n', '\\n')
return text
def m2l_add_last_paragraph(self):
"""
Used to add a text block before the next header or at the end of the document
"""
self.lText.append(self.text_chunck)
self.text_chunck = []
self.lItemName.append(self.item_name)
self.item_name = ""
self.lPlanTable.append(self.plan_table)
self.plan_table = ""
##
## Block Level
##
def block_code(self, code, lang):
text = self.m2l_formspec_escape(code.strip())
lines = text.split("\\n")
lines = [" " + item for item in lines]
self.text_chunck.extend(lines)
self.text_chunck.append("")
return ""
def header(self, text, level, raw=None):
if not self.is_first_header:
self.m2l_add_last_paragraph()
self.is_first_header = False
self.lTitle.append("%u,%s" % (level, self.m2l_formspec_escape(text)))
return ""
def hrule(self):
self.text_chunck.append("\n----------------------------------------------------\n")
return ""
def paragraph(self, text):
lines = text.split("\\n") + [""]
self.text_chunck.extend(lines)
return ""
def list(self, body, ordered=True):
lines = body.split("\n")
self.text_chunck.extend(lines)
return ""
def list_item(self, text):
return " - %s\n" % text.strip()
##
## Span Level
##
def emphasis(self, text):
return "*%s*" % self.m2l_formspec_escape(text)
def double_emphasis(self, text):
return "*%s*" % self.m2l_formspec_escape(text)
def codespan(self, text):
return "'%s'" % self.m2l_formspec_escape(text)
def text(self, text):
return self.m2l_formspec_escape(text)
def link(self, link, title, content):
"""
Used for plans and images:
[myimage](/image/)
[myplan](/plan/)
"""
if link == "/image/":
self.item_name = content
elif link == "/plan/":
self.plan_table = content
return content
def wiki_link(self, name, itype):
"""
Used for plans and images:
[myimage|image]
[myplan|plan]
"""
if itype == "image":
self.item_name = name
elif itype == "plan":
self.plan_table = name
return ""
def autolink(self, link, is_email=False):
return link
def linebreak(self):
return "\\n"
def newline(self):
return "\\n"
def inline_html(self, text):
#print(text)
pass
def parse_md_file(self, src_name):
print(" - Read MD file '%s'" % src_name)
inline = WikiLinkInlineLexer(self)
# enable the feature
inline.enable_wiki_link()
md = mistune.Markdown(renderer=self, inline=inline)
md.renderer.src_name = src_name
md.render(open(src_name, 'r').read())
md.renderer.m2l_add_last_paragraph()
def lua_table(self, key, lData):
lOut = []
lOut.append(" %s = {" % key)
for line in lData:
lOut.append(' "%s",' % line)
lOut.append(" }")
return "\n".join(lOut)
def lua_text_table(self, key, lData):
lOut = []
lOut.append(" %s = {" % key)
for lines in lData:
for line in lines[:-1]:
line = line.replace('<br>', '\\n')
lOut.append(' "%s\\n"..' % line)
if len(lines) > 0:
lOut.append(' "%s\\n",' % lines[-1])
else:
lOut.append(' "",')
lOut.append(" }")
return "\n".join(lOut)
def gen_lua_file(self, dest_name):
print(" - Write Lua file '%s'" % dest_name)
lOut = []
s = ",\n".join([self.lua_table("titles", self.lTitle),
self.lua_text_table("texts", self.lText),
self.lua_table("images", self.lItemName),
self.lua_table("plans", self.lPlanTable)])
open(dest_name, "w").write("return {\n%s\n}" % s)
print("done.")
m2l = MarkdownToLua()
m2l.parse_md_file("./manual_EN.md")
m2l.gen_lua_file("./manual_EN.lua")
m2l = MarkdownToLua()
m2l.parse_md_file("./manual_DE.md")
m2l.gen_lua_file("./manual_DE.lua")
+109
View File
@@ -0,0 +1,109 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
local S = minecart.S
local M = minetest.get_meta
minetest.register_node("minecart:cart", {
description = S("Minecart (Sneak+Click to pick up)"),
tiles = {
-- up, down, right, left, back, front
"carts_cart_top.png^minecart_appl_cart_top.png",
"carts_cart_top.png",
"carts_cart_side.png^minecart_logo.png",
"carts_cart_side.png^minecart_logo.png",
"carts_cart_side.png^minecart_logo.png",
"carts_cart_side.png^minecart_logo.png",
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-8/16,-8/16,-8/16, 8/16, 8/16,-7/16},
{-8/16,-8/16, 7/16, 8/16, 8/16, 8/16},
{-8/16,-8/16,-8/16, -7/16, 8/16, 8/16},
{ 7/16,-8/16,-8/16, 8/16, 8/16, 8/16},
{-8/16,-8/16,-8/16, 8/16,-6/16, 8/16},
},
},
-- collision_box = {
-- type = "fixed",
-- fixed = {
-- {-8/16,-8/16,-8/16, 8/16,-4/16, 8/16},
-- },
-- },
paramtype2 = "facedir",
paramtype = "light",
use_texture_alpha = minecart.CLIP,
sunlight_propagates = true,
is_ground_content = false,
groups = {cracky = 2, crumbly = 2, choppy = 2},
node_placement_prediction = "",
diggable = false,
on_place = minecart.on_nodecart_place,
on_punch = minecart.on_nodecart_punch,
on_rightclick = function(pos, node, clicker)
if clicker and clicker:is_player() then
if M(pos):get_int("userID") ~= 0 then
-- enter the cart
if minecart.is_usable_cart(pos, node) then
local object = minecart.node_to_entity(pos, "minecart:cart", "minecart:cart_entity")
minecart.manage_attachment(clicker, object:get_luaentity(), true)
end
else
minecart.show_formspec(pos, clicker)
end
end
end,
set_cargo = function(pos, data)
for _,item in ipairs(data or {}) do
minetest.add_item(pos, ItemStack(item))
end
end,
get_cargo = function(pos)
local data = {}
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do
local entity = obj:get_luaentity()
if not obj:is_player() and entity and entity.name == "__builtin:item" then
obj:remove()
data[#data + 1] = entity.itemstring
end
end
return data
end,
})
minecart.register_cart_entity("minecart:cart_entity", "minecart:cart", "default", {
initial_properties = {
physical = false,
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
visual = "wielditem",
textures = {"minecart:cart"},
visual_size = {x=0.66, y=0.66, z=0.66},
static_save = false,
},
driver_allowed = true,
})
minetest.register_craft({
output = "minecart:cart",
recipe = {
{"default:steel_ingot", "default:cobble", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})
+5
View File
@@ -0,0 +1,5 @@
name=minecart
depends = default,carts
optional_depends = doc, doclib
description = Minecart, the lean railway transportation automation system
supported_games = minetest_game
@@ -0,0 +1,144 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
Wrapper functions to get hopper support for other mods
]]--
-- for lazy programmers
local M = minetest.get_meta
local CacheForFuelNodeNames = {}
local function is_fuel(stack)
local name = stack:get_name()
if CacheForFuelNodeNames[name] then
return true
end
if minetest.get_craft_result({method="fuel", width=1, items={stack}}).time ~= 0 then
CacheForFuelNodeNames[name] = true
end
return CacheForFuelNodeNames[name]
end
------------------------------------------------------------------------------
-- Signs Bot
------------------------------------------------------------------------------
minecart.register_inventory({"signs_bot:box"}, {
put = {
listname = "main",
},
take = {
listname = "main",
},
})
------------------------------------------------------------------------------
-- default
------------------------------------------------------------------------------
minecart.register_inventory({"default:chest", "default:chest_open"}, {
put = {
listname = "main",
},
take = {
listname = "main",
},
})
minecart.register_inventory({"default:chest_locked", "default:chest_locked_open"}, {
put = {
allow_inventory_put = function(pos, stack, player_name)
local owner = M(pos):get_string("owner")
return owner == player_name
end,
listname = "main",
},
take = {
allow_inventory_take = function(pos, stack, player_name)
local owner = M(pos):get_string("owner")
return owner == player_name
end,
listname = "main",
},
})
minecart.register_inventory({"default:furnace", "default:furnace_active"}, {
put = {
-- distinguish between fuel and other items
put_item = function(pos, stack, player_name)
local inv = minetest.get_inventory({type="node", pos=pos})
local listname = is_fuel(stack) and "fuel" or "src"
local leftover = inv:add_item(listname, stack)
minetest.get_node_timer(pos):start(1.0)
if leftover:get_count() > 0 then
return leftover
end
end,
},
take = {
-- fuel can't be taken
listname = "dst",
},
})
------------------------------------------------------------------------------
-- digtron
------------------------------------------------------------------------------
minecart.register_inventory({"digtron:inventory"}, {
put = {
listname = "main",
},
take = {
listname = "main",
},
})
minecart.register_inventory({"digtron:fuelstore"}, {
put = {
listname = "fuel",
},
take = {
listname = "fuel",
},
})
minecart.register_inventory({"digtron:combined_storage"}, {
put = {
-- distinguish between fuel and other items
put_item = function(pos, stack, player_name)
local inv = minetest.get_inventory({type="node", pos=pos})
local listname = is_fuel(stack) and "fuel" or "main"
local leftover = inv:add_item(listname, stack)
if leftover:get_count() > 0 then
return leftover
end
end,
},
take = {
-- fuel can't be taken
listname = "main",
},
})
------------------------------------------------------------------------------
-- protector
------------------------------------------------------------------------------
minecart.register_inventory({"protector:chest"}, {
put = {
listname = "main",
},
take = {
listname = "main",
},
})
@@ -0,0 +1,440 @@
--[[
Minecart
========
Copyright (C) 2019-2023 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local S = minecart.S
local tCartsOnRail = minecart.CartsOnRail
local Queue = {}
local first = 0
local last = -1
local function push(cycle, item)
last = last + 1
item.cycle = cycle
Queue[last] = item
end
local function pop(cycle)
if first > last then return end
local item = Queue[first]
if item.cycle < cycle then
Queue[first] = nil -- to allow garbage collection
first = first + 1
return item
end
end
local function is_player_nearby(pos)
for _, object in pairs(minetest.get_objects_inside_radius(pos, 64)) do
if object:is_player() then
return true
end
end
end
local function zombie_to_entity(pos, cart, checkpoint)
local vel = {x = 0, y = 0, z = 0}
local obj = minecart.add_entitycart(pos, cart.node_name, cart.entity_name,
vel, cart.cargo, cart.owner, cart.userID)
if obj then
local entity = obj:get_luaentity()
entity.reenter = checkpoint
entity.junctions = cart.junctions
entity.is_running = true
entity.arrival_time = 0
cart.objID = entity.objID
end
end
local function get_checkpoint(cart)
local cp = cart.checkpoints[cart.idx]
if not cp then
cart.idx = #cart.checkpoints
cp = cart.checkpoints[cart.idx]
end
local pos = H2P(cp[1])
return cp, cart.idx == #cart.checkpoints
end
-- Function returns the cart state ("running" / "stopped") and
-- the station name or position string, or if cart is running,
-- the distance to the query_pos.
local function get_cart_state_and_loc(name, userID, query_pos)
if tCartsOnRail[name] and tCartsOnRail[name][userID] then
local cart = tCartsOnRail[name][userID]
local pos = cart.last_pos or cart.pos
if pos then
local loc = minecart.get_buffer_name(cart.pos) or
math.floor(vector.distance(pos, query_pos))
if cart.objID == 0 then
return "stopped", minecart.get_buffer_name(cart.pos) or
math.floor(vector.distance(pos, query_pos)), cart.node_name
else
return "running", math.floor(vector.distance(pos, query_pos)), cart.node_name
end
end
end
return "unknown", 0, "unknown"
end
-- Return the cart distance to the query_pos.
local function get_cart_distance(name, userID, query_pos)
if tCartsOnRail[name] and tCartsOnRail[name][userID] then
local cart = tCartsOnRail[name][userID]
if cart.last_pos or cart.pos then
if cart.objID == 0 then -- stopped
return math.floor(vector.distance(cart.pos or cart.last_pos, query_pos))
else
return math.floor(vector.distance(cart.last_pos or cart.pos, query_pos))
end
end
end
return 0
end
local function get_cart_info(owner, userID, query_pos)
local state, loc, name = get_cart_state_and_loc(owner, userID, query_pos)
local cart_type = minecart.tCartTypes[name] or "unknown"
if type(loc) == "number" then
return "Cart #" .. userID .. " (" .. cart_type .. ") " .. state .. " " .. loc .. " m away "
else
return "Cart #" .. userID .. " (" .. cart_type .. ") " .. state .. " at ".. loc .. " "
end
end
local function logging(cart, err)
local s = string.format("[Minecart] Cart %s/%u %s!", cart.owner, cart.userID, err)
minetest.log("warning", s)
end
-- check cart data
local function valid_cart(cart)
if cart.objID == nil or cart.objID == 0 then
return false
end
if tCartsOnRail[cart.owner] and tCartsOnRail[cart.owner][cart.userID] then
return true
end
logging(cart, "with invalid data")
local entity = minetest.luaentities[cart.objID]
if entity then
entity.object:remove()
end
return false
end
local function monitoring(cycle)
local cart = pop(cycle)
-- All running cars
while cart do
if valid_cart(cart) then
cart.idx = cart.idx + 1
local entity = minetest.luaentities[cart.objID]
if entity then -- cart entity running
local pos = entity.object:get_pos()
if pos then
cart.last_pos = vector.round(pos)
else
logging(cart, "without pos")
end
push(cycle, cart)
elseif cart.checkpoints then
local cp, last_cp = get_checkpoint(cart)
if cp then
cart.last_pos = H2P(cp[1])
--print("zombie " .. cart.userID .. " at " .. P2S(cart.last_pos))
if is_player_nearby(cart.last_pos) or last_cp then
zombie_to_entity(cart.last_pos, cart, cp)
end
push(cycle, cart)
else
logging(cart, "as zombie got lost")
end
else
local pos = cart.last_pos or cart.pos
pos = minecart.add_nodecart(pos, cart.node_name, 0, cart.cargo, cart.owner, cart.userID)
minecart.stop_monitoring(cart.owner, cart.userID, pos)
logging(cart, "stopped at " .. (P2S(pos) or "unknown"))
end
elseif not cart.objID and tCartsOnRail[cart.owner] then
-- Delete carts marked as "to be deleted"
tCartsOnRail[cart.owner][cart.userID] = nil
end
cart = pop(cycle)
end
minetest.after(2, monitoring, cycle + 1)
end
minetest.after(5, monitoring, 2)
function minecart.monitoring_add_cart(owner, userID, pos, node_name, entity_name)
--print("monitoring_add_cart", owner, userID)
tCartsOnRail[owner] = tCartsOnRail[owner] or {}
tCartsOnRail[owner][userID] = {
owner = owner,
userID = userID,
objID = 0,
pos = pos,
idx = 0,
node_name = node_name,
entity_name = entity_name,
}
minecart.store_carts()
end
function minecart.start_monitoring(owner, userID, pos, objID, checkpoints, junctions, cargo)
--print("start_monitoring", owner, userID)
if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then
tCartsOnRail[owner][userID].pos = pos
tCartsOnRail[owner][userID].objID = objID
tCartsOnRail[owner][userID].checkpoints = checkpoints
tCartsOnRail[owner][userID].junctions = junctions
tCartsOnRail[owner][userID].cargo = cargo
tCartsOnRail[owner][userID].idx = 0
push(0, tCartsOnRail[owner][userID])
minecart.store_carts()
end
end
function minecart.stop_monitoring(owner, userID, pos)
--print("stop_monitoring", owner, userID)
if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then
tCartsOnRail[owner][userID].pos = pos
-- Mark as "stopped"
tCartsOnRail[owner][userID].objID = 0
minecart.store_carts()
end
end
function minecart.monitoring_remove_cart(owner, userID)
--print("monitoring_remove_cart", owner, userID)
if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then
-- Cart stopped?
if tCartsOnRail[owner][userID].objID == 0 then
-- Mark as "to be deleted" by monitoring (if part of monitoring)
tCartsOnRail[owner][userID].objID = nil
-- And delete directly in addition
tCartsOnRail[owner][userID] = nil
else -- Cart running
-- Mark as "to be deleted" by monitoring
tCartsOnRail[owner][userID].objID = nil
end
minecart.store_carts()
end
end
function minecart.monitoring_valid_cart(owner, userID, pos, node_name)
if tCartsOnRail[owner] and tCartsOnRail[owner][userID] and tCartsOnRail[owner][userID].pos then
return vector.equals(tCartsOnRail[owner][userID].pos, pos) and
tCartsOnRail[owner][userID].node_name == node_name
end
end
function minecart.userID_available(owner, userID)
return not tCartsOnRail[owner] or tCartsOnRail[owner][userID] == nil
end
function minecart.get_cart_monitoring_data(owner, userID)
if tCartsOnRail[owner] then
return tCartsOnRail[owner][userID]
end
end
--
-- API functions
--
-- Needed by storage to re-construct the queue after server start
minecart.push = push
minetest.register_chatcommand("mycart", {
params = "<cart-num>",
description = S("Output cart state and position, or a list of carts, if no cart number is given."),
func = function(owner, param)
local userID = tonumber(param)
local query_pos = minetest.get_player_by_name(owner):get_pos()
if userID then
return true, get_cart_info(owner, userID, query_pos)
elseif tCartsOnRail[owner] then
-- Output a list with all numbers
local tbl = {}
for userID, cart in pairs(tCartsOnRail[owner]) do
tbl[#tbl + 1] = userID
end
return true, S("List of carts") .. ": "..table.concat(tbl, ", ").." "
end
end
})
minetest.register_chatcommand("stopcart", {
params = "<cart-num>",
description = S("Stop and return/drop a missing/running cart."),
func = function(owner, param)
local userID = tonumber(param)
local player_pos = minetest.get_player_by_name(owner):get_pos()
if userID then
local data = minecart.get_cart_monitoring_data(owner, userID)
if data and data.objID then
local entity = minetest.luaentities[data.objID]
--print("stopcart", userID, data.pos, data.objID, entity)
if data.objID == 0 then
-- Cart as node
if data.pos then
local meta = M(data.pos)
if owner == meta:get_string("owner") and userID == meta:get_int("userID") then
minecart.remove_nodecart(data.pos)
end
end
elseif entity then
-- Cart as entity
minecart.remove_entity(entity, data.pos)
else
-- Cart as zombie/invalid/corrupted
minetest.log("warning", "[Minecart] data.objID ~= 0, but no entity available!")
end
minetest.add_item(player_pos, ItemStack({name = data.node_name}))
minecart.monitoring_remove_cart(owner, userID)
return true, S("Cart") .. " " .. userID .. " " .. S("dropped")
else
return false, S("Cart") .. " " .. userID .. " " .. S("is not existing!")
end
else
return false
end
end
})
function minecart.cmnd_cart_state(name, userID)
local state, loc = get_cart_state_and_loc(name, userID, {x=0, y=0, z=0})
return state
end
function minecart.cmnd_cart_location(name, userID, query_pos)
local state, loc = get_cart_state_and_loc(name, userID, query_pos)
return loc
end
function minecart.cmnd_cart_distance(name, userID, query_pos)
return get_cart_distance(name, userID, query_pos)
end
function minecart.get_cart_list(pos, name)
local userIDs = {}
local carts = {}
for userID, cart in pairs(tCartsOnRail[name] or {}) do
userIDs[#userIDs + 1] = userID
end
table.sort(userIDs, function(a,b) return a < b end)
for _, userID in ipairs(userIDs) do
carts[#carts + 1] = get_cart_info(name, userID, pos)
end
return table.concat(carts, "\n")
end
minetest.register_on_mods_loaded(function()
if minetest.get_modpath("techage") then
techage.icta_register_condition("cart_state", {
title = "read cart state",
formspec = {
{
type = "digits",
name = "number",
label = "cart number",
default = "",
},
{
type = "label",
name = "lbl",
label = "Read state from one of your carts",
},
},
button = function(data, environ) -- default button label
local number = tonumber(data.number) or 0
return 'cart_state('..number..')'
end,
code = function(data, environ)
local condition = function(env, idx)
local number = tonumber(data.number) or 0
return minecart.cmnd_cart_state(environ.owner, number)
end
local result = function(val)
return val ~= 0
end
return condition, result
end,
})
techage.icta_register_condition("cart_location", {
title = "read cart location",
formspec = {
{
type = "digits",
name = "number",
label = "cart number",
default = "",
},
{
type = "label",
name = "lbl",
label = "Read location from one of your carts",
},
},
button = function(data, environ) -- default button label
local number = tonumber(data.number) or 0
return 'cart_loc('..number..')'
end,
code = function(data, environ)
local condition = function(env, idx)
local number = tonumber(data.number) or 0
return minecart.cmnd_cart_location(environ.owner, number, env.pos)
end
local result = function(val)
return val ~= 0
end
return condition, result
end,
})
techage.lua_ctlr.register_function("cart_state", {
cmnd = function(self, num)
num = tonumber(num) or 0
return minecart.cmnd_cart_state(self.meta.owner, num)
end,
help = " $cart_state(num)\n"..
" Read state from one of your carts.\n"..
' "num" is the cart number\n'..
' example: sts = $cart_state(2)'
})
techage.lua_ctlr.register_function("cart_location", {
cmnd = function(self, num)
num = tonumber(num) or 0
return minecart.cmnd_cart_location(self.meta.owner, num, self.meta.pos)
end,
help = " $cart_location(num)\n"..
" Read location from one of your carts.\n"..
' "num" is the cart number\n'..
' example: sts = $cart_location(2)'
})
end
end)
+148
View File
@@ -0,0 +1,148 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
function minecart.get_nodecart_nearby(pos, param2, radius)
local pos2 = param2 and vector.add(pos, minecart.param2_to_dir(param2)) or pos
local pos3 = minetest.find_node_near(pos2, radius or 0.5, minecart.lCartNodeNames, true)
if pos3 then
return pos3, minetest.get_node(pos3)
end
end
-- Convert node to entity and start cart
function minecart.start_nodecart(pos, node_name, puncher, punch_dir)
local owner = M(pos):get_string("owner")
local userID = M(pos):get_int("userID")
-- check if valid cart
if not minecart.monitoring_valid_cart(owner, userID, pos, node_name) then
--print("Lost cart", owner, userID, P2S(pos), node_name)
local entity_name = minecart.tNodeNames[node_name]
if owner ~= "" and userID ~= "" and entity_name then
minecart.monitoring_add_cart(owner, userID, pos, node_name, entity_name)
else
M(pos):set_string("infotext",
minetest.get_color_escape_sequence("#FFFF00") .. owner .. ": 0")
return
end
end
-- Only the owner or a noplayer can start the cart, but owner has to be online
if minecart.is_owner(puncher, owner) and minetest.get_player_by_name(owner) and
userID ~= 0 then
local entity_name = minecart.tNodeNames[node_name]
local obj = minecart.node_to_entity(pos, node_name, entity_name)
if obj then
local entity = obj:get_luaentity()
local facedir
if puncher then
local yaw = puncher:get_look_horizontal()
entity.object:set_rotation({x = 0, y = yaw, z = 0})
local dir = minetest.yaw_to_dir(yaw)
facedir = minetest.dir_to_facedir(dir)
elseif punch_dir then
local yaw = minetest.dir_to_yaw(punch_dir)
entity.object:set_rotation({x = 0, y = yaw, z = 0})
facedir = minetest.dir_to_facedir(punch_dir)
end
minecart.start_entitycart(entity, pos, facedir or 0)
end
end
end
function minecart.show_formspec(pos, clicker)
local owner = M(pos):get_string("owner")
if minecart.is_owner(clicker, owner) then
clicker:get_meta():set_string("cart_pos", P2S(pos))
minetest.show_formspec(owner, "minecart:userID_node",
"size[4,3]" ..
"label[0,0;" .. S("Enter cart number") .. ":]" ..
"field[1,1;3,1;userID;;]" ..
"button_exit[1,2;2,1;exit;" .. S("Save") .. "]")
end
end
-- Player places the node
function minecart.on_nodecart_place(itemstack, placer, pointed_thing)
local node_name = itemstack:get_name()
local param2 = minetest.dir_to_facedir(placer:get_look_dir())
local owner = placer:get_player_name()
-- Add node
if minecart.is_rail(pointed_thing.under) then
minecart.add_nodecart(pointed_thing.under, node_name, param2, {}, owner, 0)
placer:get_meta():set_string("cart_pos", P2S(pointed_thing.under))
minecart.show_formspec(pointed_thing.under, placer)
elseif minecart.is_rail(pointed_thing.above) then
minecart.add_nodecart(pointed_thing.above, node_name, param2, {}, owner, 0)
placer:get_meta():set_string("cart_pos", P2S(pointed_thing.above))
minecart.show_formspec(pointed_thing.above, placer)
else
return itemstack
end
minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
{pos = pointed_thing.above})
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(placer:get_player_name())) then
itemstack:take_item()
end
return itemstack
end
-- Start the node cart (or dig by shift+leftclick)
function minecart.on_nodecart_punch(pos, node, puncher, pointed_thing)
local owner = M(pos):get_string("owner")
local rail = M(pos):get_string("removed_rail")
if minecart.is_owner(puncher, owner) then
if puncher:get_player_control().sneak or not minecart.is_rail(pos, rail) then
local ndef = minetest.registered_nodes[node.name]
if not ndef.has_cargo or not ndef.has_cargo(pos) then
local _, owner, userID = minecart.remove_nodecart(pos, node)
minecart.add_node_to_player_inventory(pos, puncher, node.name)
minecart.monitoring_remove_cart(owner, userID)
end
else
minecart.start_nodecart(pos, node.name, puncher)
end
end
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "minecart:userID_node" then
if fields.exit or fields.key_enter == "true" then
local cart_pos = S2P(player:get_meta():get_string("cart_pos"))
local owner = M(cart_pos):get_string("owner")
if minecart.is_owner(player, owner) then
local userID = tonumber(fields.userID) or 0
if minecart.userID_available(owner, userID) then
M(cart_pos):set_int("userID", userID)
M(cart_pos):set_string("infotext",
minetest.get_color_escape_sequence("#FFFF00") ..
player:get_player_name() .. ": " .. userID)
local node = minetest.get_node(cart_pos)
local entity_name = minecart.tNodeNames[node.name]
minecart.monitoring_add_cart(owner, userID, cart_pos, node.name, entity_name)
end
end
end
return true
end
return false
end)
@@ -0,0 +1,217 @@
--[[
Minecart
========
Copyright (C) 2019-2023 Joachim Stolberg
MIT
See license.txt for more information
]]--
local S = minecart.S
local RANGE = 8
local IsNodeUnderObservation = {}
-- Register all nodes, which should be protected by the "minecart:landmark"
function minecart.register_protected_node(name)
IsNodeUnderObservation[name] = true
end
local function landmark_found(pos, name, range)
local pos1 = {x=pos.x-range, y=pos.y-range, z=pos.z-range}
local pos2 = {x=pos.x+range, y=pos.y+range, z=pos.z+range}
for _,npos in ipairs(minetest.find_nodes_in_area(pos1, pos2, {"minecart:landmark"})) do
if minetest.get_meta(npos):get_string("owner") ~= name then
return true
end
end
return false
end
local function is_protected(pos, name, range)
if minetest.check_player_privs(name, "minecart")
or not landmark_found(pos, name, range) then
return false
end
return true
end
local old_is_protected = minetest.is_protected
function minetest.is_protected(pos, name)
if pos and name then
local node = minetest.get_node(pos)
if IsNodeUnderObservation[node.name] and is_protected(pos, name, RANGE) then
return true
end
end
return old_is_protected(pos, name)
end
minetest.register_node("minecart:landmark", {
description = S("Minecart Landmark"),
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-3/16, -8/16, -3/16, 3/16, 4/16, 3/16},
{-2/16, 4/16, -3/16, 2/16, 5/16, 3/16},
},
},
tiles = {
'default_mossycobble.png',
'default_mossycobble.png',
'default_mossycobble.png',
'default_mossycobble.png',
'default_mossycobble.png^minecart_protect.png',
'default_mossycobble.png^minecart_protect.png',
},
after_place_node = function(pos, placer, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
meta:set_string("owner", placer:get_player_name())
if is_protected(pos, placer:get_player_name(), RANGE+3) then
minetest.remove_node(pos)
return true
end
end,
can_dig = function(pos, digger)
local meta = minetest.get_meta(pos)
if meta:get_string("owner") == digger:get_player_name() then
return true
end
if minetest.check_player_privs(digger:get_player_name(), "minecart") then
return true
end
minetest.chat_send_player(digger:get_player_name(),
S("[minecart] Area is protected!").." (owner: "..meta:get_string("owner")..")")
return false
end,
on_punch = function(pos, node, puncher, pointed_thing)
minecart.set_land_marker(pos, RANGE, 20)
end,
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = minecart.CLIP,
groups = {cracky = 3, stone = 1},
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
})
minetest.register_craft({
output = "minecart:landmark 6",
recipe = {
{"", "default:mossycobble", ""},
{"", "default:mossycobble", ""},
{"", "default:mossycobble", ""},
},
})
minetest.register_node("minecart:ballast", {
description = "Minecart Ballast",
tiles = {"minecart_ballast.png"},
groups = {crumbly = 1, cracky = 3},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("minecart:ballast_slope", {
description = "Minecart Ballast Slope",
tiles = {"minecart_ballast.png"},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-8/16, -8/16, -8/16, 8/16, -4/16, 8/16},
{-8/16, -4/16, -4/16, 8/16, 0/16, 8/16},
{-8/16, 0/16, 0/16, 8/16, 4/16, 8/16},
{-8/16, 4/16, 4/16, 8/16, 8/16, 8/16},
},
},
selection_box = {
type = "fixed",
fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
},
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = minecart.CLIP,
groups = {crumbly = 1, cracky = 3},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_node("minecart:ballast_ramp", {
description = "Minecart Ballast Ramp",
tiles = {"minecart_ballast.png"},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
{-8/16, -4/16, -4/16, 8/16, 12/16, 8/16},
{-8/16, 0/16, 0/16, 8/16, 16/16, 8/16},
{-8/16, 4/16, 4/16, 8/16, 20/16, 8/16},
},
},
selection_box = {
type = "fixed",
fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
},
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = minecart.CLIP,
groups = {crumbly = 1, cracky = 3},
sounds = default.node_sound_stone_defaults(),
})
minetest.register_craft({
output = "minecart:ballast 6",
recipe = {
{"", "", ""},
{"default:cobble", "default:stone", "default:cobble"},
{"default:cobble", "default:stone", "default:cobble"},
},
})
minetest.register_craft({
output = "minecart:ballast_slope 6",
recipe = {
{"", "", "default:cobble"},
{"", "default:stone", "default:cobble"},
{"default:cobble", "default:stone", "default:cobble"},
},
})
minetest.register_craft({
output = "minecart:ballast_ramp 2",
recipe = {
{"", "", ""},
{"minecart:ballast_slope", "", ""},
{"minecart:ballast", "", ""},
},
})
minetest.register_privilege("minecart", {
description = S("Allow to dig/place rails in Minecart Landmark areas"),
give_to_singleplayer = false,
give_to_admin = true,
})
minecart.register_protected_node("carts:rail")
minecart.register_protected_node("carts:powerrail")
minecart.register_protected_node("carts:brakerail")
minecart.register_protected_node("minecart:buffer")
minecart.register_protected_node("minecart:ballast")
minecart.register_protected_node("minecart:ballast_slope")
minecart.register_protected_node("minecart:ballast_ramp")
minecart.register_protected_node("minecart:speed1")
minecart.register_protected_node("minecart:speed2")
minecart.register_protected_node("minecart:speed4")
minecart.register_protected_node("minecart:speed8")
+67
View File
@@ -0,0 +1,67 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local CYCLE_TIME = 4
local function node_timer(pos)
local node = minetest.get_node(pos)
local dir = minetest.facedir_to_dir(node.param2)
minecart.punch_cart({x = pos.x, y = pos.y + 1, z = pos.z}, nil, 1, dir)
return true
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos, oldnode, oldmetadata)
end
minetest.register_node("minecart:cart_pusher", {
description = S("Cart Pusher"),
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-8/16,-8/16,-8/16, 8/16, 8/16, 8/16},
{-1/16, 8/16,-4/16, 1/16, 10/16, 4/16},
},
},
tiles = {
-- up, down, right, left, back, front
"default_steel_block.png^minecart_pusher_top.png",
"default_steel_block.png",
"default_steel_block.png^minecart_pusher.png",
"default_steel_block.png^minecart_pusher.png",
"default_steel_block.png^minecart_pusher.png",
"default_steel_block.png^minecart_pusher.png",
},
after_place_node = function(pos)
minetest.get_node_timer(pos):start(CYCLE_TIME)
end,
on_timer = node_timer,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = true,
sounds = default.node_sound_metal_defaults(),
})
minetest.register_craft({
output = "minecart:cart_pusher",
recipe = {
{"dye:black", "default:steel_ingot", "dye:yellow"},
{"default:steel_ingot", "default:mese_crystal", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})
+601
View File
@@ -0,0 +1,601 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P2H = minetest.hash_node_position
local get_node_lvm = minecart.get_node_lvm
local MAX_SPEED = 8
local SLOWDOWN = 0.3
local MAX_NODES = 100
--waypoint = {
-- dot = travel direction,
-- pos = destination pos,
-- speed = 10 times the section speed (as int),
-- limit = 10 times the speed limit (as int),
--}
--
-- waypoints = {facedir = waypoint,...}
local tWaypoints = {} -- {pos_hash = waypoints, ...}
local tRailsPower = {
["carts:rail"] = 0,
["carts:powerrail"] = 1,
["minecart:rail"] = 0,
["minecart:powerrail"] = 1,
["carts:brakerail"] = 0,
}
-- Real rails from the mod carts
local tRails = {
["carts:rail"] = true,
["carts:powerrail"] = true,
["carts:brakerail"] = true,
["minecart:rail"] = true,
["minecart:powerrail"] = true,
}
-- Rails plus node carts. Used to find waypoints. Added via add_raillike_nodes
local tRailsExt = {
["carts:rail"] = true,
["carts:powerrail"] = true,
["carts:brakerail"] = true,
["minecart:rail"] = true,
["minecart:powerrail"] = true,
}
local tSigns = {
["minecart:speed1"] = 1,
["minecart:speed2"] = 2,
["minecart:speed4"] = 4,
["minecart:speed8"] = 8,
}
-- Real rails from the mod carts
local lRails = {"carts:rail", "carts:powerrail", "carts:brakerail", "minecart:rail", "minecart:powerrail"}
-- Rails plus node carts used to find waypoints, , added via add_raillike_nodes
local lRailsExt = {"carts:rail", "carts:powerrail", "carts:brakerail", "minecart:rail", "minecart:powerrail"}
minecart.MAX_SPEED = MAX_SPEED
minecart.lRails = lRails
minecart.tRails = tRails
minecart.tRailsExt = tRailsExt
minecart.lRailsExt = lRailsExt
local Dot2Dir = {}
local Dir2Dot = {}
local Facedir2Dir = {[0] =
{x= 0, y=0, z= 1},
{x= 1, y=0, z= 0},
{x= 0, y=0, z=-1},
{x=-1, y=0, z= 0},
{x= 0, y=-1, z= 0},
{x= 0, y=1, z= 0},
}
local flip = {
[0] = 2,
[1] = 3,
[2] = 0,
[3] = 1,
[4] = 5,
[5] = 4,
}
-- facedir = math.floor(dot / 4)
-- y = (dot % 4) - 1
-- Create helper tables
for facedir = 0,3 do
for y = -1,1 do
local dot = 1 + facedir * 4 + y
local dir = vector.new(Facedir2Dir[facedir])
dir.y = y
Dot2Dir[dot] = dir
Dir2Dot[P2H(dir)] = dot
end
end
local function dot2dir(dot) return vector.new(Dot2Dir[dot]) end
local function facedir2dir(fd) return vector.new(Facedir2Dir[fd]) end
minecart.dot2dir = dot2dir
minecart.facedir2dir = facedir2dir
-------------------------------------------------------------------------------
-- waypoint metadata
-------------------------------------------------------------------------------
local function has_metadata(pos)
return M(pos):contains("waypoints")
end
local function get_metadata(pos)
local hash = P2H(pos)
if tWaypoints[hash] then
return tWaypoints[hash]
end
local s = M(pos):get_string("waypoints")
if s ~= "" then
tWaypoints[hash] = minetest.deserialize(s)
return tWaypoints[hash]
end
end
local function get_oldmetadata(meta)
local s = meta:get_string("waypoints")
if s ~= "" then
return minetest.deserialize(s)
end
end
local function set_metadata(pos, t)
local hash = P2H(pos)
tWaypoints[hash] = t
local s = minetest.serialize(t)
M(pos):set_string("waypoints", s)
-- visualization
local name = get_node_lvm(pos).name
if name == "carts:rail" then
minetest.swap_node(pos, {name = "minecart:rail"})
elseif name == "carts:powerrail" then
minetest.swap_node(pos, {name = "minecart:powerrail"})
end
end
local function del_metadata(pos)
local hash = P2H(pos)
tWaypoints[hash] = nil
local meta = M(pos)
if meta:contains("waypoints") then
meta:set_string("waypoints", "")
-- visualization
local name = get_node_lvm(pos).name
if name == "minecart:rail" then
minetest.swap_node(pos, {name = "carts:rail"})
elseif name == "minecart:powerrail" then
minetest.swap_node(pos, {name = "carts:powerrail"})
end
end
end
-------------------------------------------------------------------------------
-- find_next_waypoint
-------------------------------------------------------------------------------
local function check_right(pos, facedir)
local fdr = (facedir + 1) % 4 -- right
local new_pos = vector.add(pos, facedir2dir(fdr))
local name = get_node_lvm(new_pos).name
if tRailsExt[name] or tSigns[name] then
return true
end
new_pos.y = new_pos.y - 1
if tRailsExt[get_node_lvm(new_pos).name] then
return true
end
end
local function check_left(pos, facedir)
local fdl = (facedir + 3) % 4 -- left
local new_pos = vector.add(pos, facedir2dir(fdl))
local name = get_node_lvm(new_pos).name
if tRailsExt[name] or tSigns[name] then
return true
end
new_pos.y = new_pos.y - 1
if tRailsExt[get_node_lvm(new_pos).name] then
return true
end
end
local function get_next_pos(pos, facedir, y)
local new_pos = vector.add(pos, facedir2dir(facedir))
new_pos.y = new_pos.y + y
local name = get_node_lvm(new_pos).name
return tRailsExt[name] ~= nil, new_pos, tRailsPower[name] or 0
end
local function is_ramp(pos)
return tRailsExt[get_node_lvm({x = pos.x, y = pos.y + 1, z = pos.z}).name] ~= nil
end
-- Check also the next position to detect a ramp
local function slope_detection(pos, facedir)
local is_rail, new_pos = get_next_pos(pos, facedir, 0)
if not is_rail then
return is_ramp(new_pos)
end
end
local function find_next_waypoint(pos, facedir, y)
local cnt = 0
local name = get_node_lvm(pos).name
local speed = tRailsPower[name] or 0
local is_rail, new_pos, _speed
while cnt < MAX_NODES do
is_rail, new_pos, _speed = get_next_pos(pos, facedir, y)
speed = speed + _speed
if not is_rail then
return pos, y == 0 and is_ramp(new_pos), speed
end
if y == 0 then -- no slope
if check_right(new_pos, facedir) then
return new_pos, slope_detection(new_pos, facedir), speed
elseif check_left(new_pos, facedir) then
return new_pos, slope_detection(new_pos, facedir), speed
end
end
pos = new_pos
cnt = cnt + 1
end
return new_pos, false, speed
end
-------------------------------------------------------------------------------
-- find_all_next_waypoints
-------------------------------------------------------------------------------
local function check_front_up_down(pos, facedir)
local new_pos = vector.add(pos, facedir2dir(facedir))
if tRailsExt[get_node_lvm(new_pos).name] then
return 0
end
new_pos.y = new_pos.y - 1
if tRailsExt[get_node_lvm(new_pos).name] then
return -1
end
new_pos.y = new_pos.y + 2
if tRailsExt[get_node_lvm(new_pos).name] then
return 1
end
end
-- Search for rails in all 4 directions
local function find_all_rails_nearby(pos)
--print("find_all_rails_nearby")
local tbl = {}
for fd = 0, 3 do
tbl[#tbl + 1] = check_front_up_down(pos, fd, true)
end
return tbl
end
-- Recalc the value based on waypoint length and slope
local function recalc_speed(num_pow_rails, pos1, pos2, y)
local num_norm_rails = vector.distance(pos1, pos2) - num_pow_rails
local ratio, speed
if y ~= 0 then
num_norm_rails = math.floor(num_norm_rails / 1.41 + 0.5)
end
if y ~= -1 then
if num_pow_rails == 0 then
return num_norm_rails * -SLOWDOWN
else
ratio = math.floor(num_norm_rails / num_pow_rails)
ratio = minecart.range(ratio, 0, 11)
end
else
ratio = 3 + num_norm_rails * SLOWDOWN + num_pow_rails
end
if y == 1 then
speed = 7 - ratio
elseif y == -1 then
speed = 15 - ratio
else
speed = 11 - ratio
end
return minecart.range(speed, 0, 8)
end
local function find_all_next_waypoints(pos)
local wp = {}
local dots = {}
for facedir = 0,3 do
local y = check_front_up_down(pos, facedir)
if y then
local new_pos, is_ramp, speed = find_next_waypoint(pos, facedir, y)
--print("find_all_next_waypoints", P2S(new_pos), is_ramp, speed)
local dot = 1 + facedir * 4 + y
speed = recalc_speed(speed, pos, new_pos, y) * 10
wp[facedir] = {dot = dot, pos = new_pos, speed = speed, is_ramp = is_ramp}
end
end
return wp
end
-------------------------------------------------------------------------------
-- get_waypoint
-------------------------------------------------------------------------------
-- If ramp, stop 0.5 nodes earlier or later
local function ramp_correction(pos, wp, facedir)
if wp.is_ramp or pos.y < wp.pos.y then -- ramp detection
local dir = facedir2dir(facedir)
local pos = wp.pos
wp.cart_pos = {
x = pos.x - dir.x / 2,
y = pos.y,
z = pos.z - dir.z / 2}
elseif pos.y > wp.pos.y then
local dir = facedir2dir(facedir)
local pos = wp.pos
wp.cart_pos = {
x = pos.x + dir.x / 2,
y = pos.y,
z = pos.z + dir.z / 2}
end
return wp
end
-- Returns waypoint and is_junction
function minecart.get_waypoint(pos, facedir, ctrl, uturn)
local t = get_metadata(pos)
if not t then
t = find_all_next_waypoints(pos)
set_metadata(pos, t)
end
local left = (facedir + 3) % 4
local right = (facedir + 1) % 4
local back = (facedir + 2) % 4
if ctrl.right and t[right] then return t[right], t[facedir] ~= nil or t[left] ~= nil end
if ctrl.left and t[left] then return t[left] , t[facedir] ~= nil or t[right] ~= nil end
if t[facedir] then return ramp_correction(pos, t[facedir], facedir), false end
if t[right] then return ramp_correction(pos, t[right], right), false end
if t[left] then return ramp_correction(pos, t[left], left), false end
if uturn and t[back] then return t[back], false end
end
-------------------------------------------------------------------------------
-- delete waypoints
-------------------------------------------------------------------------------
local function delete_counterpart_metadata(pos, wp)
for facedir = 0,3 do
if wp[facedir] then
del_metadata(wp[facedir].pos)
end
end
del_metadata(pos)
end
local function delete_next_metadata(pos, facedir, y)
local cnt = 0
while cnt <= MAX_NODES do
local is_rail, new_pos = get_next_pos(pos, facedir, y)
if not is_rail then
return
end
if has_metadata(new_pos) then
del_metadata(new_pos)
end
pos = new_pos
cnt = cnt + 1
end
if has_metadata(pos) then
del_metadata(pos)
end
end
function minecart.delete_waypoint(pos)
if has_metadata(pos) then
local wp = get_metadata(pos)
delete_counterpart_metadata(pos, wp)
return
end
for facedir = 0,3 do
local y = check_front_up_down(pos, facedir)
if y then
local new_pos = vector.add(pos, facedir2dir(facedir))
new_pos.y = new_pos.y + y
if has_metadata(new_pos) then
local wp = get_metadata(new_pos)
delete_counterpart_metadata(new_pos, wp)
else
delete_next_metadata(pos, facedir, y)
end
end
end
end
-------------------------------------------------------------------------------
-- find next buffer (needed as starting position)
-------------------------------------------------------------------------------
local function get_next_waypoints(pos)
local t = get_metadata(pos)
if not t then
t = find_all_next_waypoints(pos)
end
return t
end
local function get_next_pos_and_facedir(waypoints, facedir)
local cnt = 0
local newpos, newfacedir
facedir = (facedir + 2) % 4 -- opposite dir
for i = 0, 3 do
if waypoints[i] then
cnt = cnt + 1
if i ~= facedir then -- not the same way back
newpos = vector.new(waypoints[i].pos)
newfacedir = i
end
end
end
-- no junction and valid facedir
if cnt < 3 and newfacedir then
return newpos, newfacedir
end
end
local function get_next_buffer(pos, facedir)
facedir = (facedir + 2) % 4 -- opposite dir
for i = 1,5 do -- limit search depth
local waypoints = get_next_waypoints(pos) or {}
local pos1, facedir1 = get_next_pos_and_facedir(waypoints, facedir)
if pos1 then
pos, facedir = pos1, facedir1
else
return minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"})
end
end
end
carts:register_rail("minecart:rail", {
description = "Rail",
tiles = {
"carts_rail_straight.png^minecart_waypoint.png", "carts_rail_curved.png^minecart_waypoint.png",
"carts_rail_t_junction.png^minecart_waypoint.png", "carts_rail_crossing.png^minecart_waypoint.png"
},
inventory_image = "carts_rail_straight.png",
wield_image = "carts_rail_straight.png",
groups = carts:get_rail_groups({not_in_creative_inventory = 1}),
drop = "carts:rail",
}, {})
carts:register_rail("minecart:powerrail", {
description = "Powered Rail",
tiles = {
"carts_rail_straight_pwr.png^minecart_waypoint.png", "carts_rail_curved_pwr.png^minecart_waypoint.png",
"carts_rail_t_junction_pwr.png^minecart_waypoint.png", "carts_rail_crossing_pwr.png^minecart_waypoint.png"
},
inventory_image = "carts_rail_straight.png",
wield_image = "carts_rail_straight.png",
groups = carts:get_rail_groups({not_in_creative_inventory = 1}),
drop = "carts:powerrail",
}, {})
for name,_ in pairs(tRails) do
minetest.override_item(name, {
after_destruct = minecart.delete_waypoint,
after_place_node = minecart.delete_waypoint,
})
end
-------------------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------------------
-- Return new cart pos and if an extra move cycle is needed
function minecart.get_current_cart_pos_correction(curr_pos, curr_fd, curr_y, new_dot)
if new_dot then
local new_y = (new_dot % 4) - 1
local new_fd = math.floor(new_dot / 4)
if curr_y == -1 or new_y == -1 then
local new_fd = math.floor(new_dot / 4)
local dir = facedir2dir(new_fd)
return {
x = curr_pos.x + dir.x / 2,
y = curr_pos.y,
z = curr_pos.z + dir.z / 2}, new_y == -1
elseif curr_y == 1 and curr_fd ~= new_fd then
local dir = facedir2dir(new_fd)
return {
x = curr_pos.x + dir.x / 2,
y = curr_pos.y,
z = curr_pos.z + dir.z / 2}, true
elseif curr_y == 1 or new_y == 1 then
local dir = facedir2dir(curr_fd)
return {
x = curr_pos.x - dir.x / 2,
y = curr_pos.y,
z = curr_pos.z - dir.z / 2}, false
end
end
return curr_pos, false
end
-- Called by carts, returns the speed value or nil
function minecart.get_speedlimit(pos, facedir)
local fd = (facedir + 1) % 4 -- right
local new_pos = vector.add(pos, facedir2dir(fd))
local node = get_node_lvm(new_pos)
if tSigns[node.name] and node.param2 == facedir then
return tSigns[node.name]
end
fd = (facedir + 3) % 4 -- left
new_pos = vector.add(pos, facedir2dir(fd))
node = get_node_lvm(new_pos)
if tSigns[node.name] and node.param2 == facedir then
return tSigns[node.name]
end
end
-- Called by carts, to delete temporarily created waypoints
function minecart.delete_cart_waypoint(pos)
del_metadata(pos)
end
-- Called by signs, to delete the rail waypoints nearby
function minecart.delete_signs_waypoint(pos)
local node = minetest.get_node(pos)
local facedir = (node.param2 + 1) % 4 -- right
local new_pos = vector.add(pos, facedir2dir(facedir))
if tRailsExt[get_node_lvm(new_pos).name] then
minecart.delete_waypoint(new_pos)
end
facedir = (node.param2 + 3) % 4 -- left
new_pos = vector.add(pos, facedir2dir(facedir))
if tRailsExt[get_node_lvm(new_pos).name] then
minecart.delete_waypoint(new_pos)
end
end
function minecart.is_rail(pos, name)
return tRails[name or get_node_lvm(pos).name] ~= nil
end
-- To register node cart names
function minecart.add_raillike_nodes(name)
tRailsExt[name] = true
lRailsExt[#lRailsExt + 1] = name
end
-- minecart.get_next_buffer(pos, facedir)
minecart.get_next_buffer = get_next_buffer
-- minecart.del_metadata(pos)
minecart.del_metadata = del_metadata
--minetest.register_lbm({
-- label = "Delete waypoints",
-- name = "minecart:del_meta",
-- nodenames = {"minecart:rail", "minecart:powerrail"},
-- run_at_every_load = true,
-- action = function(pos, node)
-- del_metadata(pos)
-- end,
--})
+198
View File
@@ -0,0 +1,198 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local S = minecart.S
local function dashboard_destroy(self)
if self.driver and self.hud_id then
local player = minetest.get_player_by_name(self.driver)
if player then
player:hud_remove(self.hud_id)
self.hud_id = nil
end
end
end
local function dashboard_create(self)
if self.driver then
local player = minetest.get_player_by_name(self.driver)
if player then
dashboard_destroy(self)
self.hud_id = player:hud_add({
name = "minecart",
hud_elem_type = "text",
position = {x = 0.4, y = 0.25},
scale = {x=100, y=100},
text = "Recording:",
number = 0xFFFFFF,
size = {x = 1},
})
end
end
end
local function dashboard_update(self)
if self.driver and self.hud_id then
local player = minetest.get_player_by_name(self.driver)
if player then
local time = self.runtime or 0
local dir = (self.ctrl and self.ctrl.left and S("left")) or
(self.ctrl and self.ctrl.right and S("right")) or S("straight")
local speed = math.floor((self.curr_speed or 0) + 0.5)
local s = string.format(S("Recording") ..
" | " .. S("speed") ..
": %.1f | " .. S("next junction") ..
": %-8s | " .. S("Travel time") .. ": %.1f s",
speed, dir, time)
player:hud_change(self.hud_id, "text", s)
end
end
end
local function check_waypoint(self, pos)
-- If next waypoint already reached but not handled
-- determine next waypoint
if vector.equals(pos, self.waypoint.pos) then
local rot = self.object:get_rotation()
local dir = minetest.yaw_to_dir(rot.y)
dir.y = math.floor((rot.x / (math.pi/4)) + 0.5)
local facedir = minetest.dir_to_facedir(dir)
local waypoint = minecart.get_waypoint(pos, facedir, self.ctrl or {}, false)
if waypoint then
return waypoint.pos
end
end
return self.waypoint.pos
end
--
-- Route recording
--
function minecart.start_recording(self, pos)
--print("start_recording")
if self.driver then
self.start_pos = minecart.get_buffer_pos(pos, self.driver)
if self.start_pos then
self.checkpoints = {} -- {cart_pos, next_waypoint_pos, speed, dot}
self.junctions = {}
self.is_recording = true
self.rec_time = self.timebase
self.hud_time = self.timebase
self.runtime = 0
self.num_sections = 0
self.sum_speed = 0
self.ctrl = {}
dashboard_create(self)
dashboard_update(self, 0)
end
end
end
function minecart.stop_recording(self, pos, force_exit)
--print("stop_recording")
if self.driver and self.is_recording then
local dest_pos = minecart.get_buffer_pos(pos, self.driver)
local player = minetest.get_player_by_name(self.driver)
if force_exit then
minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!"))
elseif dest_pos and player and #self.checkpoints > 3 then
-- Remove last checkpoint, because it is potentially too close to the dest_pos
table.remove(self.checkpoints)
if self.start_pos then
local route = {
dest_pos = dest_pos,
checkpoints = self.checkpoints,
junctions = self.junctions,
}
minecart.store_route(self.start_pos, route)
minetest.chat_send_player(self.driver, S("[minecart] Route stored!"))
local speed = self.sum_speed / #self.checkpoints
local length = speed * self.runtime
local fmt = S("[minecart] Speed = %u m/s, Time = %u s, Route length = %u m")
minetest.chat_send_player(self.driver, string.format(fmt, speed, self.runtime, length))
minecart.update_buffer_infotext(self.start_pos)
end
elseif #self.checkpoints <= 3 then
minetest.chat_send_player(self.driver, S("[minecart] Your route is too short to record!"))
end
dashboard_destroy(self)
end
self.is_recording = false
self.checkpoints = nil
self.waypoints = nil
self.junctions = nil
end
function minecart.recording_waypoints(self)
local pos = vector.round(self.object:get_pos())
-- pos correction on slopes
if not minecart.is_rail(pos) then
pos.y = pos.y - 1
end
-- hier müsste überprüfung dest_pos rein
self.sum_speed = self.sum_speed + self.curr_speed
local wp_pos = check_waypoint(self, pos)
self.checkpoints[#self.checkpoints+1] = {
-- cart_pos, next_waypoint_pos, speed, dot
P2H(pos),
P2H(wp_pos),
math.floor(self.curr_speed + 0.5),
self.waypoint.dot
}
end
function minecart.recording_junctions(self)
local player = minetest.get_player_by_name(self.driver)
if player then
local ctrl = player:get_player_control()
if ctrl.left then
self.ctrl = {left = true}
elseif ctrl.right then
self.ctrl = {right = true}
elseif ctrl.up or ctrl.down then
self.ctrl = nil
elseif ctrl.jump then
return true
end
end
if self.hud_time <= self.timebase then
dashboard_update(self)
self.hud_time = self.timebase + 0.5
self.runtime = self.runtime + 0.5
end
end
function minecart.set_junctions(self, wayp_pos)
if self.ctrl then
self.junctions[P2H(wayp_pos)] = self.ctrl
end
end
function minecart.player_ctrl(self)
local player = minetest.get_player_by_name(self.driver)
if player then
local ctrl = player:get_player_control()
if ctrl.left then
self.ctrl = {left = true}
elseif ctrl.right then
self.ctrl = {right = true}
elseif ctrl.jump then
return true
end
end
end
Binary file not shown.

After

Width:  |  Height:  |  Size: 305 KiB

@@ -0,0 +1,3 @@
# If enabled, allows the complete automation of Minecarts by means of Hopper and station stop times.
minecart_hopper_enabled (Hopper enabled) bool true
minecart_teleport_enabled (Teleport enabled) bool false
+107
View File
@@ -0,0 +1,107 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg 4
MIT
See license.txt for more information
]]--
local S = minecart.S
local function register_sign(def)
minetest.register_node("minecart:"..def.name, {
description = def.description,
inventory_image = def.image,
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -1/16, -8/16, -1/16, 1/16,-2/16, 1/16},
{ -5/16, -2/16, -1/32, 5/16, 8/16, 1/32},
},
},
paramtype2 = "facedir",
tiles = {
"default_steel_block.png",
"default_steel_block.png",
"default_steel_block.png",
"default_steel_block.png",
"default_steel_block.png",
"default_steel_block.png^"..def.image,
},
after_place_node = minecart.delete_signs_waypoint,
preserve_metadata = minecart.delete_signs_waypoint,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = minecart.CLIP,
sunlight_propagates = true,
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, minecart_sign = 1},
sounds = default.node_sound_wood_defaults(),
})
end
register_sign({
name = "speed1",
description = S('Speed "1"'),
image = "minecart_sign1.png",
})
register_sign({
name = "speed2",
description = S('Speed "2"'),
image = "minecart_sign2.png",
})
register_sign({
name = "speed4",
description = S('Speed "4"'),
image = "minecart_sign4.png",
})
register_sign({
name = "speed8",
description = S('No speed limit'),
image = "minecart_sign8.png",
})
minetest.register_craft({
output = "minecart:speed8 8",
recipe = {
{"default:tin_ingot", "dye:red", "default:tin_ingot"},
{"", "default:steel_ingot", ""},
{"", "default:steel_ingot", ""}
}
})
minetest.register_craft({
type = "shapeless",
output = "minecart:speed4",
recipe = {"minecart:speed8"}
})
minetest.register_craft({
type = "shapeless",
output = "minecart:speed2",
recipe = {"minecart:speed4"}
})
minetest.register_craft({
type = "shapeless",
output = "minecart:speed1",
recipe = {"minecart:speed2"}
})
minetest.register_craft({
type = "shapeless",
output = "minecart:speed8",
recipe = {"minecart:speed1"}
})
+105
View File
@@ -0,0 +1,105 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local S = minecart.S
local storage = minetest.get_mod_storage()
local function place_carts(t)
local Carts = {
["minecart:cart"] = "minecart:cart",
["techage:tank_cart_entity"] = "techage:tank_cart",
["techage:chest_cart_entity"] = "techage:chest_cart",
}
for id, item in pairs(t) do
local pos = vector.round((item.start_pos or item.last_pos))
local name = Carts[item.entity_name] or "minecart:cart"
--print(P2S(pos), name, item.owner, item.userID)
if minetest.registered_nodes[name] then
minecart.add_nodecart(pos, name, 0, {}, item.owner or "", item.userID or 0)
end
end
end
-------------------------------------------------------------------------------
-- Store data of running carts
-------------------------------------------------------------------------------
minecart.CartsOnRail = {}
minetest.register_on_mods_loaded(function()
local version = storage:get_int("version")
if version < 2 then
local t = minetest.deserialize(storage:get_string("CartsOnRail")) or {}
minetest.after(5, place_carts, t)
storage:set_int("version", 2)
else
local t = minetest.deserialize(storage:get_string("CartsOnRail")) or {}
for owner, carts in pairs(t) do
minecart.CartsOnRail[owner] = minecart.CartsOnRail[owner] or {}
for userID, cart in pairs(carts) do
if cart.objID then
minecart.CartsOnRail[owner][userID] = cart
-- mark all entity carts as zombified
if cart.objID ~= 0 then
cart.objID = -1
end
minecart.push(1, cart)
end
end
end
end
end)
minetest.register_on_shutdown(function()
storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail))
print("minecart shutdown finished!!!")
end)
function minecart.store_carts()
storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail))
end
-------------------------------------------------------------------------------
-- Store routes (in buffers)
-------------------------------------------------------------------------------
function minecart.store_route(pos, route)
if pos and route then
M(pos):set_string("route", minetest.serialize(route))
return true
end
return false
end
function minecart.get_route(pos)
if pos then
local s = M(pos):get_string("route")
if s ~= "" then
local route = minetest.deserialize(s)
if route.waypoints then
M(pos):set_string("route", "")
M(pos):set_int("time", 0)
return
end
return route
end
end
end
function minecart.del_route(pos)
M(pos):set_string("route", "")
end
+134
View File
@@ -0,0 +1,134 @@
--[[
Minecart
========
Copyright (C) 2019-2023 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local function is_player_nearby(pos)
for _, object in pairs(minetest.get_objects_inside_radius(pos, 6)) do
if object:is_player() then
return true
end
end
end
local function formspec(pos, text)
text = minetest.formspec_escape(text)
text = text:gsub("\n", ",")
return "size[11,9]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;10.8,0.5;#c6e8ff]"..
"label[4.5,-0.1;"..minetest.colorize( "#000000", S("Cart List")).."]"..
"style_type[table,field;font=mono]"..
"table[0,0.5;10.8,8.6;output;"..text..";200]"
end
minetest.register_node("minecart:terminal", {
description = S("Cart Terminal"),
inventory_image = "minecart_terminal_front.png",
tiles = {
"minecart_terminal_top.png",
"minecart_terminal_top.png",
"minecart_terminal_side.png",
"minecart_terminal_side.png",
"minecart_terminal_back.png",
"minecart_terminal_front.png",
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -8/16, -8/16, 0/16, 8/16, 8/16, 8/16},
},
},
after_place_node = function(pos, placer)
local meta = M(pos)
meta:set_string("owner", placer:get_player_name())
meta:set_string("formspec", formspec(pos, ""))
if minetest.get_modpath("techage") then
local number = techage.add_node(pos, "minecart:terminal")
meta:set_string("node_number", number)
meta:set_string("infotext", "Cart Terminal " .. number)
end
minetest.get_node_timer(pos):start(2)
end,
on_timer = function(pos, elapsed)
if is_player_nearby(pos) then
local text = minecart.get_cart_list(pos, M(pos):get_string("owner"))
M(pos):set_string("formspec", formspec(pos, text))
end
return true
end,
paramtype2 = "facedir",
paramtype = "light",
use_texture_alpha = minecart.CLIP,
on_rotate = screwdriver.disallow,
sunlight_propagates = true,
is_ground_content = false,
groups = {cracky = 2, level = 2},
sounds = default.node_sound_metal_defaults(),
})
minetest.register_craft({
output = "minecart:terminal",
recipe = {
{"", "default:obsidian_glass", "default:steel_ingot"},
{"", "default:obsidian_glass", "default:copper_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})
minetest.register_on_mods_loaded(function()
if minetest.get_modpath("techage") then
techage.register_node({"minecart:terminal"}, {
on_recv_message = function(pos, src, topic, payload)
local number = tonumber(payload)
if number then
local owner = M(pos):get_string("owner")
if topic == "state" then
return minecart.cmnd_cart_state(owner, number)
elseif topic == "distance" then
return minecart.cmnd_cart_distance(owner, number, pos)
else
return "unsupported"
end
end
end,
on_beduino_receive_cmnd = function(pos, src, topic, payload)
return 2 -- unknown or invalid topic
end,
on_beduino_request_data = function(pos, src, topic, payload)
if topic == 128 then
return 0, "minecart:terminal"
elseif topic == 129 then -- state
local owner = M(pos):get_string("owner")
local STATE = {unknown = 0, stopped = 1, running = 2}
local state = STATE[minecart.cmnd_cart_state(owner, payload[1])] or 0
return 0, {state}
elseif topic == 130 then -- distance
local owner = M(pos):get_string("owner")
local dist = minecart.cmnd_cart_distance(owner, payload[1], pos)
return 0, {dist}
else
return 2, "" -- topic is unknown or invalid
end
end,
})
end
end)
Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 262 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

@@ -0,0 +1,2 @@
#!/bin/bash
pngquant --skip-if-larger --quality=80 --strip *.png --ext .png --force
+111
View File
@@ -0,0 +1,111 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local sDir = {[0] = "north", "east", "south", "west"}
local function DOTS(dots)
if dots then
return table.concat(dots, ", ")
else
return ""
end
end
local old_pos
local function test_get_buffer(pos, player)
local yaw = player:get_look_horizontal()
local dir = minetest.yaw_to_dir(yaw)
local facedir = minetest.dir_to_facedir(dir)
local pos1 = minecart.get_next_buffer(pos, facedir)
if pos1 then
minecart.set_marker(pos1, "buffer", 1.2, 10)
end
end
local function test_get_route(pos, node, player)
local yaw = player:get_look_horizontal()
local dir = minetest.yaw_to_dir(yaw)
local facedir = minetest.dir_to_facedir(dir)
local route = minecart.get_waypoint(pos, facedir, {})
if route then
-- print(dump(route))
minecart.set_marker(route.pos, "pos", 0.3, 10)
if route.cart_pos then
minecart.set_marker(route.cart_pos, "cart", 0.3, 10)
end
-- determine some kind of current y
old_pos = old_pos or pos
local curr_y = pos.y > old_pos.y and 1 or pos.y < old_pos.y and -1 or 0
local cart_pos, extra_cycle = minecart.get_current_cart_pos_correction(pos, facedir, curr_y, route.dot)
minecart.set_marker(cart_pos, "curr", 0.3, 10)
old_pos = pos
print(string.format("Route: dist = %u, dot = %u, speed = %d, extra cycle = %s",
vector.distance(pos, route.pos), route.dot, route.speed or 0, extra_cycle))
end
end
local function test_get_connections(pos, node, player, ctrl)
local wp = minecart.get_waypoints(pos)
for i = 0,3 do
if wp[i] then
local dir = minecart.Dot2Dir[ wp[i].dot]
print(sDir[i], vector.distance(pos, wp[i].pos), dir.y)
end
end
print(dump(M(pos):to_table()))
end
local function click_left(itemstack, placer, pointed_thing)
if pointed_thing.type == "node" then
local pos = pointed_thing.under
if minecart.is_rail(pos) then
--test_get_buffer(pos, placer)
minecart.delete_waypoint(pos)
end
end
end
local function click_right(itemstack, placer, pointed_thing)
if pointed_thing.type == "node" then
local pos = pointed_thing.under
local node = minetest.get_node(pos)
if node.name == "carts:rail" or node.name == "carts:powerrail" then
test_get_connections(pos, node, placer)
elseif node.name == "minecart:buffer" then
local route = minecart.get_route(P2S(pos))
print(dump(route))
end
end
end
minetest.register_node("minecart:tool", {
description = "Tool",
inventory_image = "minecart_tool.png",
wield_image = "minecart_tool.png",
liquids_pointable = true,
use_texture_alpha = minecart.CLIP,
groups = {cracky=1, book=1},
on_use = click_left,
on_place = click_right,
node_placement_prediction = "",
stack_max = 1,
})