mods that hopefully work for a TechAge-based pack
@@ -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 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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?
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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", ""},
|
||||
},
|
||||
})
|
||||
|
After Width: | Height: | Size: 121 KiB |
@@ -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",
|
||||
},
|
||||
})
|
||||
@@ -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()
|
||||
@@ -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")
|
||||
@@ -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=
|
||||
@@ -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)
|
||||
@@ -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 = {
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 = {
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
"",
|
||||
}
|
||||
}
|
||||
@@ -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")
|
||||
@@ -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"},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -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)
|
||||
@@ -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")
|
||||
|
||||
@@ -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"},
|
||||
},
|
||||
})
|
||||
@@ -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,
|
||||
--})
|
||||
|
||||
|
||||
@@ -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
|
||||
|
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
|
||||
@@ -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"}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
|
After Width: | Height: | Size: 108 B |
|
After Width: | Height: | Size: 138 B |
|
After Width: | Height: | Size: 148 B |
|
After Width: | Height: | Size: 182 B |
|
After Width: | Height: | Size: 572 B |
|
After Width: | Height: | Size: 262 B |
|
After Width: | Height: | Size: 1.4 KiB |
|
After Width: | Height: | Size: 222 B |
|
After Width: | Height: | Size: 264 B |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 201 B |
|
After Width: | Height: | Size: 2.6 KiB |
|
After Width: | Height: | Size: 195 B |
|
After Width: | Height: | Size: 128 B |
|
After Width: | Height: | Size: 111 B |
|
After Width: | Height: | Size: 126 B |
|
After Width: | Height: | Size: 141 B |
|
After Width: | Height: | Size: 154 B |
|
After Width: | Height: | Size: 151 B |
|
After Width: | Height: | Size: 174 B |
|
After Width: | Height: | Size: 123 B |
|
After Width: | Height: | Size: 263 B |
|
After Width: | Height: | Size: 166 B |
|
After Width: | Height: | Size: 126 B |
|
After Width: | Height: | Size: 202 B |
|
After Width: | Height: | Size: 111 B |
@@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
pngquant --skip-if-larger --quality=80 --strip *.png --ext .png --force
|
||||
@@ -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,
|
||||
})
|
||||