Many bugfixes and improvements
parent
113cfcd9ae
commit
480ff8613f
17
README.md
17
README.md
|
@ -26,21 +26,15 @@ license).
|
|||
3. https://github.com/stujones11/railcart/
|
||||
|
||||
|
||||
Original Cart Features
|
||||
----------------------
|
||||
|
||||
- A fast cart for your railway or roller coaster (up to 7 m/s!)
|
||||
- Boost and brake rails
|
||||
- Rail junction switching with the 'right-left' walking keys
|
||||
- Handbrake with the 'back' key
|
||||
|
||||
|
||||
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
|
||||
|
@ -82,8 +76,7 @@ Introduction
|
|||
10. Check the cart state via the chat command: /mycart <num>
|
||||
'<num>' is the cart number
|
||||
11. Drop items into the Minecart and punch the cart to start it, or "sneak+click" the
|
||||
Minecart to get the items back
|
||||
12. Dig the empty cart with a second "sneak+click" (as usual)
|
||||
Minecart to get cart and items back
|
||||
|
||||
|
||||
Hopper
|
||||
|
@ -97,6 +90,7 @@ 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.
|
||||
|
||||
|
||||
|
||||
History
|
||||
-------
|
||||
|
||||
|
@ -118,3 +112,4 @@ History
|
|||
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
|
||||
|
|
39
baselib.lua
39
baselib.lua
|
@ -30,6 +30,7 @@ local param2_to_dir = {[0]=
|
|||
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]
|
||||
|
@ -55,6 +56,33 @@ function minecart.get_node_lvm(pos)
|
|||
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})
|
||||
if minetest.get_name_from_content_id(data[idx]) then
|
||||
return {x = x, y = y, z = z}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Marker entities for debugging purposes
|
||||
function minecart.set_marker(pos, text)
|
||||
local marker = minetest.add_entity(pos, "minecart:marker_cube")
|
||||
|
@ -128,7 +156,7 @@ function minecart.is_owner(player, owner)
|
|||
end
|
||||
|
||||
function minecart.get_buffer_pos(pos, player_name)
|
||||
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
|
||||
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
|
||||
|
@ -138,7 +166,7 @@ function minecart.get_buffer_pos(pos, player_name)
|
|||
end
|
||||
|
||||
function minecart.get_buffer_name(pos)
|
||||
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
|
||||
local pos1 = minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"})
|
||||
if pos1 then
|
||||
local name = M(pos1):get_string("name")
|
||||
if name ~= "" then
|
||||
|
@ -174,11 +202,12 @@ function minecart.manage_attachment(player, entity, get_on)
|
|||
end
|
||||
end
|
||||
|
||||
function minecart.register_cart_names(node_name, entity_name)
|
||||
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)
|
||||
|
@ -212,6 +241,7 @@ function minecart.add_nodecart(pos, node_name, param2, cargo, owner, userID)
|
|||
if ndef.after_place_node then
|
||||
ndef.after_place_node(pos2)
|
||||
end
|
||||
return pos2
|
||||
else
|
||||
minetest.add_item(pos, ItemStack({name = node_name}))
|
||||
end
|
||||
|
@ -251,7 +281,7 @@ function minecart.start_entitycart(self, pos)
|
|||
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)
|
||||
route.checkpoints, route.junctions, self.cargo or {})
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -264,6 +294,7 @@ function minecart.remove_nodecart(pos)
|
|||
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})
|
||||
return cargo, owner, userID
|
||||
|
|
6
doc.lua
6
doc.lua
|
@ -33,11 +33,11 @@ local summary_doc = table.concat({
|
|||
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, or 'sneak+click' the Minecart to get the items back."),
|
||||
S("12. Dig the empty cart with a second 'sneak+click' (as usual)."),
|
||||
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."),
|
||||
}, "\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 the items back")
|
||||
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")
|
||||
|
||||
|
|
|
@ -51,18 +51,22 @@ local function running(self)
|
|||
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 cart_pos, cart_speed, new_speed
|
||||
local cart_pos, wayp_pos, cart_speed, new_speed, is_junction
|
||||
|
||||
if self.reenter then -- through monitoring
|
||||
cart_pos = H2P(self.reenter[1])
|
||||
wayp_pos = cart_pos
|
||||
cart_speed = self.reenter[3]
|
||||
is_junction = false
|
||||
self.waypoint = {pos = H2P(self.reenter[2]), power = 0, limit = MAX_SPEED * 100, dot = self.reenter[4]}
|
||||
self.reenter = nil
|
||||
print("reenter", P2S(cart_pos), cart_speed)
|
||||
--print("reenter", P2S(cart_pos), cart_speed)
|
||||
elseif not self.waypoint then
|
||||
-- get waypoint
|
||||
cart_pos = vector.round(self.object:get_pos())
|
||||
wayp_pos = cart_pos
|
||||
cart_speed = 2
|
||||
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
|
||||
|
@ -71,19 +75,20 @@ local function running(self)
|
|||
end
|
||||
else
|
||||
-- next waypoint
|
||||
cart_pos = vector.new(self.waypoint.pos)
|
||||
cart_pos = vector.new(self.waypoint.cart_pos or self.waypoint.pos)
|
||||
--print("next waypoint", P2S(cart_pos))
|
||||
wayp_pos = self.waypoint.pos
|
||||
local vel = self.object:get_velocity()
|
||||
cart_speed = math.sqrt((vel.x+vel.z)^2 + vel.y^2)
|
||||
self.waypoint = get_waypoint(cart_pos, facedir, get_ctrl(self, cart_pos), cart_speed < 0.1)
|
||||
self.waypoint, is_junction = get_waypoint(wayp_pos, facedir, get_ctrl(self, wayp_pos), cart_speed < 0.1)
|
||||
end
|
||||
|
||||
if not self.waypoint then
|
||||
stop_cart(self, cart_pos)
|
||||
stop_cart(self, wayp_pos)
|
||||
return
|
||||
end
|
||||
|
||||
-- Check if direction changed
|
||||
if facedir ~= math.floor((self.waypoint.dot - 1) / 3) then
|
||||
if is_junction then
|
||||
self.ctrl = nil
|
||||
end
|
||||
|
||||
|
@ -110,18 +115,15 @@ local function running(self)
|
|||
end
|
||||
|
||||
-- Slope corrections
|
||||
--print("Slope corrections", P2S(new_dir), P2S(cart_pos))
|
||||
if new_dir.y ~= 0 then
|
||||
cart_pos = vector.add(cart_pos, {x = new_dir.x / 2, y = 0.2, z = new_dir.z / 2})
|
||||
elseif dir.y == 1 then
|
||||
cart_pos = vector.subtract(cart_pos, {x = dir.x / 2, y = 0, z = dir.z / 2})
|
||||
elseif dir.y == -1 then
|
||||
cart_pos = vector.add(cart_pos, {x = dir.x / 2, y = 0, z = dir.z / 2})
|
||||
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
|
||||
local dist = vector.distance(cart_pos, self.waypoint.pos)
|
||||
local dist = vector.distance(cart_pos, self.waypoint.cart_pos or self.waypoint.pos)
|
||||
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
|
||||
|
@ -129,9 +131,9 @@ local function running(self)
|
|||
self.num_sections = (self.num_sections or 0) + 1
|
||||
|
||||
-- Got stuck somewhere
|
||||
if new_speed < 0.1 or dist < 0.5 then
|
||||
if new_speed < 0.1 or dist < 0.2 then
|
||||
print("Got stuck somewhere", new_speed, dist)
|
||||
stop_cart(self, cart_pos)
|
||||
stop_cart(self, wayp_pos)
|
||||
return
|
||||
end
|
||||
|
||||
|
@ -194,23 +196,32 @@ end
|
|||
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
|
||||
-- Dig cart
|
||||
if self.driver then
|
||||
-- remove cart as driver
|
||||
if not self.only_dig_if_empty or not next(self.cargo) then
|
||||
-- drop items
|
||||
local pos = vector.round(self.object:get_pos())
|
||||
minecart.stop_recording(self, pos)
|
||||
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
|
||||
local pos = vector.round(self.object:get_pos())
|
||||
minecart.monitoring_remove_cart(self.owner, self.userID)
|
||||
minecart.remove_entity(self, pos, puncher)
|
||||
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)
|
||||
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()
|
||||
self.object:set_rotation({x = 0, y = yaw, z = 0})
|
||||
end
|
||||
minecart.start_entitycart(self, pos)
|
||||
minecart.start_recording(self, pos)
|
||||
end
|
||||
|
@ -219,7 +230,7 @@ end
|
|||
|
||||
-- Player get on / off
|
||||
local function on_entitycard_rightclick(self, clicker)
|
||||
if clicker and clicker:is_player() then
|
||||
if clicker and clicker:is_player() and self.driver_allowed then
|
||||
-- Get on / off
|
||||
if self.driver then
|
||||
-- get off
|
||||
|
@ -255,7 +266,7 @@ function minecart.get_entitycart_nearby(pos, param2, radius)
|
|||
end
|
||||
|
||||
function minecart.push_entitycart(self, punch_dir)
|
||||
print("push_entitycart")
|
||||
--print("push_entitycart")
|
||||
local vel = self.object:get_velocity()
|
||||
punch_dir.y = 0
|
||||
local yaw = minetest.dir_to_yaw(punch_dir)
|
||||
|
@ -264,7 +275,7 @@ function minecart.push_entitycart(self, punch_dir)
|
|||
self.arrival_time = 0
|
||||
end
|
||||
|
||||
function minecart.register_cart_entity(entity_name, node_name, entity_def)
|
||||
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
|
||||
|
@ -279,6 +290,6 @@ function minecart.register_cart_entity(entity_name, node_name, entity_def)
|
|||
|
||||
minetest.register_entity(entity_name, entity_def)
|
||||
-- register node for punching
|
||||
minecart.register_cart_names(node_name, entity_name)
|
||||
minecart.register_cart_names(node_name, entity_name, cart_type)
|
||||
end
|
||||
|
||||
|
|
502
i18n.py
502
i18n.py
|
@ -1,79 +1,469 @@
|
|||
#!/usr/bin/env python
|
||||
#!/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
|
||||
# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer
|
||||
# LGPLv2.1+
|
||||
#
|
||||
# Copy the script into the mod root folder and adapt the last code lines to you needs.
|
||||
#
|
||||
# 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
|
||||
import os, fnmatch, re, shutil, errno
|
||||
from sys import argv as _argv
|
||||
from sys import stderr as _stderr
|
||||
|
||||
pattern_lua = re.compile(r'[ \.=^\t]S\("(.+?)"\)', re.DOTALL)
|
||||
pattern_tr = re.compile(r'(.+?[^@])=(.+)')
|
||||
# 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']
|
||||
}
|
||||
|
||||
def gen_template(templ_file, lkeyStrings):
|
||||
lOut = []
|
||||
lkeyStrings.sort()
|
||||
for s in lkeyStrings:
|
||||
lOut.append("%s=" % s)
|
||||
open(templ_file, "wt").write("\n".join(lOut))
|
||||
# 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 = []
|
||||
text = open(lua_file).read()
|
||||
for s in pattern_lua.findall(text):
|
||||
s = re.sub(r'"\.\.\s+"', "", s)
|
||||
s = re.sub("@[^@=n]", "@@", s)
|
||||
s = s.replace("\n", "@n")
|
||||
s = s.replace("\\n", "@n")
|
||||
s = s.replace("=", "@=")
|
||||
lOut.append(s)
|
||||
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
|
||||
|
||||
def inport_tr_file(tr_file):
|
||||
# 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):
|
||||
for line in open(tr_file, "r").readlines():
|
||||
s = line.strip()
|
||||
if s == "" or s[0] == "#":
|
||||
continue
|
||||
match = pattern_tr.match(s)
|
||||
if match:
|
||||
dOut[match.group(1)] = match.group(2)
|
||||
return dOut
|
||||
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
|
||||
|
||||
def generate_template(templ_file):
|
||||
lOut = []
|
||||
for root, dirs, files in os.walk('./'):
|
||||
# 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)
|
||||
print(fname, len(found))
|
||||
lOut.extend(found)
|
||||
lOut = list(set(lOut))
|
||||
lOut.sort()
|
||||
gen_template(templ_file, lOut)
|
||||
return lOut
|
||||
if params["verbose"]:
|
||||
print(f"{fname}: {str(len(found))} translatable strings")
|
||||
|
||||
def update_tr_file(lNew, mod_name, tr_file):
|
||||
lOut = ["# textdomain: %s\n" % mod_name]
|
||||
if os.path.exists(tr_file):
|
||||
shutil.copyfile(tr_file, tr_file+".old")
|
||||
dOld = inport_tr_file(tr_file)
|
||||
for key in lNew:
|
||||
val = dOld.get(key, "")
|
||||
lOut.append("%s=%s" % (key, val))
|
||||
lOut.append("##### not used anymore #####")
|
||||
for key in dOld:
|
||||
if key not in lNew:
|
||||
lOut.append("%s=%s" % (key, dOld[key]))
|
||||
open(tr_file, "w").write("\n".join(lOut))
|
||||
|
||||
data = generate_template("./locale/template.txt")
|
||||
update_tr_file(data, "minecart", "./locale/minecart.de.tr")
|
||||
print("Done.\n")
|
||||
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()
|
||||
|
|
1
init.lua
1
init.lua
|
@ -37,6 +37,7 @@ dofile(MP .. "/buffer.lua")
|
|||
dofile(MP .. "/protection.lua")
|
||||
dofile(MP .. "/tool.lua")
|
||||
dofile(MP .. "/signs.lua")
|
||||
dofile(MP .. "/terminal.lua")
|
||||
|
||||
if minecart.hopper_enabled then
|
||||
dofile(MP .. "/hopper.lua")
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
# textdomain: minecart
|
||||
|
||||
Station name=Stationsname
|
||||
Stop time/sec=Haltezeit/s
|
||||
connected to=verbunden mit
|
||||
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.
|
||||
10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=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, or 'sneak+click' the Minecart to get the items back.=11: Lege Gegenstände in ein Wagen (Taste Q) und starte dann den Wagen durch Anklicken. Klicke mit gedrückter Shift-Taste auf den Wagen, um Gegenstände wieder auszuladen.
|
||||
12. Dig the empty cart with a second 'sneak+click' (as usual).=10. Klicke erneut mit gedrückter Shift-Taste auf den Wagen, um diesen zu entfernen.
|
||||
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)
|
||||
|
@ -12,26 +13,37 @@
|
|||
7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time.=7. Optional: Konfiguriere die Wagenwartezeit in einem oder 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.
|
||||
Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen
|
||||
10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=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.=10. Klicke mit gedrückter Shift-Taste auf den Wagen, um diesen zu entfernen. Die Gegenstände fallen dann zu Boden.
|
||||
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 (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts)
|
||||
Minecart, the lean railway transportation automation system=Minecart, das schlanke Schienentransport Automatisierungssystem
|
||||
Minecart Cart=Wagen
|
||||
Minecart Hopper=Minecart Hopper
|
||||
Minecart Landmark=Minecart Meilenstein
|
||||
Minecart Railway Buffer=Minecart Prellbock
|
||||
Minecart, the lean railway transportation automation system=Minecart, das schlanke Schienentransport Automatisierungssystem
|
||||
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 the 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 die Gegenstände wieder auszuladen
|
||||
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)
|
||||
Station name=Stationsname
|
||||
Stop time/sec=Haltezeit/s
|
||||
Summary=Zusammenfassung
|
||||
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.
|
||||
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 (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
|
||||
Enter cart number=Gebe Cart Nummer ein
|
||||
Save=Speichern
|
||||
[minecart] Area is protected!=[minecart] Bereich ist geschützt!
|
||||
[minecart] Cart is protected by = Wagen ist geschützt durch
|
||||
[minecart] Recording canceled!=[minecart] Aufzeichnung abgebrochen!
|
||||
Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen
|
||||
Minecart Landmark=Minecart Meilenstein
|
||||
left=links
|
||||
right=rechts
|
||||
straight=geradeaus
|
||||
Recording=Aufzeichnung
|
||||
speed=Tempo
|
||||
next junction=nächste Weiche
|
||||
Travel time=Fahrzeit
|
||||
[minecart] Route stored!=[minecart] Strecke gespeichert
|
||||
[minecart] Start route recording!=[minecart] Starte die Streckenaufzeichnung!
|
||||
connected to=verbunden mit
|
||||
##### not used anymore #####
|
||||
|
||||
[minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m=[minecart] Geschw. @= %u m/s, Zeit @= %u s, Routenlänge @= %u m
|
||||
Speed "1"=Tempo "1"
|
||||
Speed "2"=Tempo "2"
|
||||
Speed "4"=Tempo "4"
|
||||
Speed "8"=Tempo "8"
|
||||
Cart List=Cart Liste
|
||||
Cart Terminal=Cart Terminal
|
||||
|
|
|
@ -1,7 +1,10 @@
|
|||
# textdomain: minecart
|
||||
Station name=
|
||||
Stop time/sec=
|
||||
connected to=
|
||||
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.=
|
||||
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, or 'sneak+click' the Minecart to get the items back.=
|
||||
12. Dig the empty cart with a second 'sneak+click' (as usual).=
|
||||
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)=
|
||||
|
@ -10,24 +13,37 @@
|
|||
7. Optional: Configure the Minecart stop time in one or 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.=
|
||||
Allow to dig/place rails in Minecart Landmark areas=
|
||||
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.=
|
||||
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 (Sneak+Click to pick up)=
|
||||
Minecart, the lean railway transportation automation system=
|
||||
Minecart Cart=
|
||||
Minecart Hopper=
|
||||
Minecart Landmark=
|
||||
Minecart Railway Buffer=
|
||||
Minecart, the lean railway transportation automation system=
|
||||
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 the items back=
|
||||
Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=
|
||||
Station name=
|
||||
Stop time/sec=
|
||||
Summary=
|
||||
Used as buffer on both rail ends. Needed to be able to record the cart routes=
|
||||
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 (Sneak+Click to pick up)=
|
||||
Output cart state and position, or a list of carts, if no cart number is given.=
|
||||
List of carts=
|
||||
Enter cart number=
|
||||
Save=
|
||||
[minecart] Area is protected!=
|
||||
[minecart] Cart is protected by =
|
||||
[minecart] Recording canceled!=
|
||||
Allow to dig/place rails in Minecart Landmark areas=
|
||||
Minecart Landmark=
|
||||
left=
|
||||
right=
|
||||
straight=
|
||||
Recording=
|
||||
speed=
|
||||
next junction=
|
||||
Travel time=
|
||||
[minecart] Route stored!=
|
||||
[minecart] Start route recording!=
|
||||
connected to=
|
||||
[minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m=
|
||||
Speed "1"=
|
||||
Speed "2"=
|
||||
Speed "4"=
|
||||
Speed "8"=
|
||||
Cart List=
|
||||
Cart Terminal=
|
||||
|
|
11
minecart.lua
11
minecart.lua
|
@ -13,7 +13,7 @@
|
|||
local S = minecart.S
|
||||
local M = minetest.get_meta
|
||||
|
||||
minetest.register_node("minecart:cart_node", {
|
||||
minetest.register_node("minecart:cart", {
|
||||
description = S("Minecart (Sneak+Click to pick up)"),
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
|
@ -57,7 +57,7 @@ minetest.register_node("minecart:cart_node", {
|
|||
if clicker and clicker:is_player() then
|
||||
if M(pos):get_int("userID") ~= 0 then
|
||||
-- enter the cart
|
||||
local object = minecart.node_to_entity(pos, "minecart:cart_node", "minecart:cart")
|
||||
local object = minecart.node_to_entity(pos, "minecart:cart", "minecart:cart_entity")
|
||||
minecart.manage_attachment(clicker, object:get_luaentity(), true)
|
||||
else
|
||||
minecart.show_formspec(pos, clicker)
|
||||
|
@ -84,20 +84,21 @@ minetest.register_node("minecart:cart_node", {
|
|||
end,
|
||||
})
|
||||
|
||||
minecart.register_cart_entity("minecart:cart", "minecart:cart_node", {
|
||||
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_node"},
|
||||
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_node",
|
||||
output = "minecart:cart",
|
||||
recipe = {
|
||||
{"default:steel_ingot", "default:cobble", "default:steel_ingot"},
|
||||
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
|
||||
|
|
|
@ -63,21 +63,27 @@ 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)
|
||||
local entity = obj:get_luaentity()
|
||||
entity.reenter = checkpoint
|
||||
entity.junctions = cart.junctions
|
||||
entity.is_running = true
|
||||
entity.arrival_time = 0
|
||||
cart.objID = entity.objID
|
||||
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 = math.random(1, #cart.checkpoints)
|
||||
cart.idx = #cart.checkpoints
|
||||
cp = cart.checkpoints[cart.idx]
|
||||
end
|
||||
return cp
|
||||
local pos = H2P(cp[1])
|
||||
if M(pos):contains("waypoints") then
|
||||
print("get_checkpoint", P2S(H2P(cp[1])), P2S(H2P(cp[2])))
|
||||
end
|
||||
return cp, cart.idx == #cart.checkpoints
|
||||
end
|
||||
|
||||
-- Function returns the cart state ("running" / "stopped") and
|
||||
|
@ -91,14 +97,24 @@ local function get_cart_state_and_loc(name, userID, query_pos)
|
|||
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))
|
||||
math.floor(vector.distance(pos, query_pos)), cart.node_name
|
||||
else
|
||||
return "running", math.floor(vector.distance(pos, query_pos))
|
||||
return "running", math.floor(vector.distance(pos, query_pos)), cart.node_name
|
||||
end
|
||||
end
|
||||
return "unknown", 0
|
||||
return "unknown", 0, "unknown"
|
||||
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 monitoring(cycle)
|
||||
local cart = pop(cycle)
|
||||
|
||||
|
@ -111,18 +127,18 @@ local function monitoring(cycle)
|
|||
local pos = entity.object:get_pos()
|
||||
if pos then
|
||||
cart.last_pos = vector.round(pos)
|
||||
--print("entity card " .. cart.userID .. " at " .. P2S(cart.last_pos))
|
||||
print("entity card " .. cart.userID .. " at " .. P2S(cart.last_pos))
|
||||
else
|
||||
print("entity card without pos!")
|
||||
end
|
||||
push(cycle, cart)
|
||||
elseif cart.checkpoints then
|
||||
local cp = get_checkpoint(cart)
|
||||
local cp, last_cp = get_checkpoint(cart)
|
||||
if cp then
|
||||
local pos = H2P(cp[1])
|
||||
--print("zombie " .. cart.userID .. " at " .. P2S(pos))
|
||||
if is_player_nearby(pos) then
|
||||
zombie_to_entity(pos, cart, cp)
|
||||
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
|
||||
|
@ -130,7 +146,7 @@ local function monitoring(cycle)
|
|||
end
|
||||
else
|
||||
local pos = cart.last_pos or cart.pos
|
||||
minecart.add_nodecart(pos, cart.node_name, 0, cart.cargo, cart.owner, cart.userID)
|
||||
pos = minecart.add_nodecart(pos, cart.node_name, 0, cart.cargo, cart.owner, cart.userID)
|
||||
cart.objID = 0
|
||||
cart.pos = pos
|
||||
print("cart to node", cycle, cart.userID, P2S(pos))
|
||||
|
@ -147,7 +163,7 @@ end
|
|||
minetest.after(5, monitoring, 2)
|
||||
|
||||
|
||||
function minecart.monitoring_add_cart(owner, userID, pos, node_name, entity_name, cargo)
|
||||
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] = {
|
||||
|
@ -158,18 +174,18 @@ function minecart.monitoring_add_cart(owner, userID, pos, node_name, entity_name
|
|||
idx = 0,
|
||||
node_name = node_name,
|
||||
entity_name = entity_name,
|
||||
cargo = cargo,
|
||||
}
|
||||
minecart.store_carts()
|
||||
end
|
||||
|
||||
function minecart.start_monitoring(owner, userID, pos, objID, checkpoints, junctions)
|
||||
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()
|
||||
|
@ -214,26 +230,20 @@ minecart.push = push
|
|||
|
||||
minetest.register_chatcommand("mycart", {
|
||||
params = "<cart-num>",
|
||||
description = "Output cart state and position, or a list of carts, if no cart number is given.",
|
||||
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
|
||||
local state, loc = get_cart_state_and_loc(owner, userID, query_pos)
|
||||
if type(loc) == "number" then
|
||||
return true, "Cart #" .. userID .. " " .. state .. " " .. loc .. " m away "
|
||||
else
|
||||
return true, "Cart #" .. userID .. " " .. state .. " at ".. loc .. " "
|
||||
end
|
||||
return false, "Cart #" .. userID .. " is unknown "
|
||||
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, "List of carts: "..table.concat(tbl, ", ").." "
|
||||
return true, S("List of carts") .. ": "..table.concat(tbl, ", ").." "
|
||||
end
|
||||
end
|
||||
})
|
||||
|
@ -248,6 +258,23 @@ function minecart.cmnd_cart_location(name, userID, query_pos)
|
|||
return loc
|
||||
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.global_exists("techage") then
|
||||
techage.icta_register_condition("cart_state", {
|
||||
|
|
18
nodelib.lua
18
nodelib.lua
|
@ -35,6 +35,10 @@ function minecart.start_nodecart(pos, node_name, puncher)
|
|||
local obj = minecart.node_to_entity(pos, node_name, entity_name)
|
||||
if obj then
|
||||
local entity = obj:get_luaentity()
|
||||
if puncher then
|
||||
local yaw = puncher:get_look_horizontal()
|
||||
entity.object:set_rotation({x = 0, y = yaw, z = 0})
|
||||
end
|
||||
minecart.start_entitycart(entity, pos)
|
||||
end
|
||||
end
|
||||
|
@ -44,10 +48,10 @@ function minecart.show_formspec(pos, clicker)
|
|||
local owner = M(pos):get_string("owner")
|
||||
if minecart.is_owner(clicker, owner) then
|
||||
minetest.show_formspec(owner, "minecart:userID_node",
|
||||
"size[4,3]" ..
|
||||
"label[0,0;Enter cart number:]" ..
|
||||
"field[1,1;3,1;userID;;]" ..
|
||||
"button_exit[1,2;2,1;exit;Save]")
|
||||
"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
|
||||
|
||||
|
@ -83,7 +87,7 @@ end
|
|||
|
||||
-- Start the node cart (or dig by shift+leftclick)
|
||||
function minecart.on_nodecart_punch(pos, node, puncher, pointed_thing)
|
||||
print("on_nodecart_punch")
|
||||
--print("on_nodecart_punch")
|
||||
local owner = M(pos):get_string("owner")
|
||||
local userID = M(pos):get_int("userID")
|
||||
if minecart.is_owner(puncher, owner) then
|
||||
|
@ -101,7 +105,7 @@ end
|
|||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname == "minecart:userID_node" then
|
||||
if fields.exit == "Save" or fields.key_enter == "true" 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
|
||||
|
@ -113,7 +117,7 @@ minetest.register_on_player_receive_fields(function(player, formname, fields)
|
|||
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, {})
|
||||
minecart.monitoring_add_cart(owner, userID, cart_pos, node.name, entity_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,7 +3,7 @@
|
|||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2020 Joachim Stolberg
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
@ -198,3 +198,8 @@ 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")
|
||||
|
||||
|
|
91
rails.lua
91
rails.lua
|
@ -94,20 +94,11 @@ for dot = 1,12 do
|
|||
end
|
||||
|
||||
|
||||
local function check_front_up_down(pos, facedir, test_for_slope)
|
||||
local function check_front_up_down(pos, facedir)
|
||||
local npos
|
||||
|
||||
npos = vector.add(pos, Facedir2Dir[facedir])
|
||||
if tRailsExt[get_node_lvm(npos).name] then
|
||||
-- We also have to check the next node to find the next upgoing rail.
|
||||
if test_for_slope then
|
||||
npos = vector.add(npos, Facedir2Dir[facedir])
|
||||
npos.y = npos.y + 1
|
||||
if tRailsExt[get_node_lvm(npos).name] then
|
||||
--print("check_front_up_down: 2up")
|
||||
return facedir * 3 + 3 -- up
|
||||
end
|
||||
end
|
||||
--print("check_front_up_down: front")
|
||||
return facedir * 3 + 2 -- front
|
||||
end
|
||||
|
@ -153,7 +144,7 @@ local function delete_rail_metadata(pos, nearby)
|
|||
if nearby then
|
||||
local pos1 = {x = pos.x - 1, y = pos.y, z = pos.z - 1}
|
||||
local pos2 = {x = pos.x + 1, y = pos.y, z = pos.z + 1}
|
||||
for _, npos in ipairs(minetest.find_nodes_in_area_under_air(pos1, pos2, lRailsExt)) do
|
||||
for _, npos in ipairs(minetest.find_nodes_in_area(pos1, pos2, lRailsExt)) do
|
||||
delete(npos)
|
||||
end
|
||||
else
|
||||
|
@ -188,11 +179,27 @@ local function check_speed_limit(dot, pos)
|
|||
end
|
||||
end
|
||||
|
||||
local function slope_detection(dot1, dot2)
|
||||
local y1 = ((dot1 - 1) % 3) - 1
|
||||
local y2 = ((dot2 - 1) % 3) - 1
|
||||
local fd1 = math.floor((dot1 - 1) / 3)
|
||||
local fd2 = math.floor((dot2 - 1) / 3)
|
||||
|
||||
if fd1 == fd2 then -- no dir change
|
||||
if y1 ~= y2 then -- slope kink
|
||||
-- return the direction
|
||||
return (y1 == 1 or y2 == 1) and 1 or -1
|
||||
elseif dot1 == dot1 then -- speed sign on a slope
|
||||
-- return the direction
|
||||
return y1 == 1 and 1 or -1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function find_next_waypoint(pos, dot)
|
||||
--print("find_next_waypoint", P2S(pos), dot)
|
||||
local npos = vector.new(pos)
|
||||
local facedir = math.floor((dot - 1) / 3)
|
||||
local y = ((dot - 1) % 3) - 1
|
||||
local power = 0
|
||||
local cnt = 0
|
||||
|
||||
|
@ -207,18 +214,11 @@ local function find_next_waypoint(pos, dot)
|
|||
return npos, power
|
||||
elseif #dots > 1 then -- junction
|
||||
return npos, power
|
||||
elseif dots[1] ~= dot then -- curve
|
||||
-- If the direction changes to upwards,
|
||||
-- the destination pos must be one block further to hit the next rail.
|
||||
if y == 1 then
|
||||
local dir = Dot2Dir[dot]
|
||||
npos = vector.add(npos, Facedir2Dir[facedir])
|
||||
return npos, power
|
||||
end
|
||||
return npos, power
|
||||
elseif dots[1] ~= dot then -- curve or slope
|
||||
return npos, power, slope_detection(dot, dots[1])
|
||||
elseif speed then -- sign detected
|
||||
--print("check_speed_limit", speed)
|
||||
return npos, power
|
||||
return npos, power, slope_detection(dot, dots[1])
|
||||
end
|
||||
cnt = cnt + 1
|
||||
end
|
||||
|
@ -285,12 +285,12 @@ local function determine_waypoints(pos)
|
|||
local waypoints = {}
|
||||
local dots = {}
|
||||
for _,dot in ipairs(find_all_rails_nearby(pos, 0)) do
|
||||
local npos, power = find_next_waypoint(pos, dot)
|
||||
local npos, power, slope = find_next_waypoint(pos, dot)
|
||||
power = math.floor(recalc_power(dot, power, pos, npos) * 100)
|
||||
-- check for speed limit
|
||||
local limit = (check_speed_limit(dot, pos) or MAX_SPEED) * 100
|
||||
local facedir = math.floor((dot - 1) / 3)
|
||||
waypoints[facedir] = {dot = dot, pos = npos, power = power, limit = limit}
|
||||
waypoints[facedir] = {dot = dot, pos = npos, power = power, limit = limit, slope = slope}
|
||||
dots[#dots + 1] = dot
|
||||
end
|
||||
M(pos):set_string("waypoints", minetest.serialize(waypoints))
|
||||
|
@ -316,6 +316,28 @@ local function get_metadata(pos)
|
|||
end
|
||||
end
|
||||
|
||||
-- Cart position correction, if it is a change in y direction (slope kink)
|
||||
local function slope_handling(wp)
|
||||
if wp.slope then -- slope kink
|
||||
local dir = Dot2Dir[wp.dot]
|
||||
local pos = wp.pos
|
||||
|
||||
if wp.slope == 1 then -- up
|
||||
wp.cart_pos = {
|
||||
x = pos.x - dir.x / 2,
|
||||
y = pos.y,
|
||||
z = pos.z - dir.z / 2}
|
||||
else -- down
|
||||
wp.cart_pos = {
|
||||
x = pos.x + dir.x / 2,
|
||||
y = pos.y,
|
||||
z = pos.z + dir.z / 2}
|
||||
end
|
||||
end
|
||||
return wp
|
||||
end
|
||||
|
||||
-- Return the new waypoint and a bool "was junction"
|
||||
local function get_waypoint(pos, facedir, ctrl, uturn)
|
||||
local hash = P2H(pos)
|
||||
tWaypoints[hash] = tWaypoints[hash] or get_metadata(pos) or determine_waypoints(pos)
|
||||
|
@ -325,14 +347,14 @@ local function get_waypoint(pos, facedir, ctrl, uturn)
|
|||
local right = (facedir + 1) % 4
|
||||
local back = (facedir + 2) % 4
|
||||
|
||||
if ctrl.right and t[right] then return t[right] end
|
||||
if ctrl.left and t[left] then return t[left] end
|
||||
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 t[facedir] end
|
||||
if t[right] then return t[right] end
|
||||
if t[left] then return t[left] end
|
||||
if t[facedir] then return slope_handling(t[facedir]), false end
|
||||
if t[right] then return slope_handling(t[right]), false end
|
||||
if t[left] then return slope_handling(t[left]), false end
|
||||
|
||||
if uturn and t[back] then return t[back] end
|
||||
if uturn and t[back] then return t[back], false end
|
||||
end
|
||||
|
||||
local function after_dig_node(pos, oldnode, oldmetadata, digger)
|
||||
|
@ -354,7 +376,7 @@ minecart.MAX_SPEED = MAX_SPEED
|
|||
minecart.Dot2Dir = Dot2Dir
|
||||
minecart.Dir2Dot = Dir2Dot
|
||||
minecart.get_waypoint = get_waypoint
|
||||
minecart.delete_waypoint = delete_rail_metadata
|
||||
minecart.delete_waypoint = delete_rail_metadata -- used by carts
|
||||
minecart.lRails = lRails
|
||||
|
||||
-- used by speed limit signs
|
||||
|
@ -376,6 +398,13 @@ function minecart.add_raillike_nodes(name)
|
|||
lRailsExt[#lRailsExt + 1] = name
|
||||
end
|
||||
|
||||
-- For debugging purposes
|
||||
function minecart.get_waypoints(pos)
|
||||
local hash = P2H(pos)
|
||||
tWaypoints[hash] = tWaypoints[hash] or get_metadata(pos) or determine_waypoints(pos)
|
||||
return tWaypoints[hash]
|
||||
end
|
||||
|
||||
minetest.register_lbm({
|
||||
label = "Delete waypoints",
|
||||
name = "minecart:rails",
|
||||
|
|
|
@ -50,17 +50,34 @@ 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 num = self.num_sections or 0
|
||||
local dir = (self.ctrl and self.ctrl.left and "left") or
|
||||
(self.ctrl and self.ctrl.right and "right") or "straight"
|
||||
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.speed or 0) + 0.5)
|
||||
local s = string.format("Recording: speed = %.1f | dir = %-8s | %u sections",
|
||||
speed, dir, num)
|
||||
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, false)
|
||||
return waypoint.pos
|
||||
end
|
||||
return self.waypoint.pos
|
||||
end
|
||||
|
||||
--
|
||||
-- Route recording
|
||||
--
|
||||
|
@ -74,7 +91,9 @@ function minecart.start_recording(self, pos)
|
|||
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)
|
||||
|
@ -96,6 +115,10 @@ function minecart.stop_recording(self, pos)
|
|||
}
|
||||
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))
|
||||
end
|
||||
end
|
||||
dashboard_destroy(self)
|
||||
|
@ -107,10 +130,12 @@ end
|
|||
|
||||
function minecart.recording_waypoints(self)
|
||||
local pos = vector.round(self.object:get_pos())
|
||||
self.sum_speed = self.sum_speed + self.speed
|
||||
local wp_pos = check_waypoint(self, pos)
|
||||
self.checkpoints[#self.checkpoints+1] = {
|
||||
-- cart_pos, new_pos, speed, dot
|
||||
-- cart_pos, next_waypoint_pos, speed, dot
|
||||
P2H(pos),
|
||||
P2H(self.waypoint.pos),
|
||||
P2H(wp_pos),
|
||||
math.floor(self.speed + 0.5),
|
||||
self.waypoint.dot
|
||||
}
|
||||
|
@ -131,6 +156,7 @@ function minecart.recording_junctions(self, speed)
|
|||
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
|
||||
|
||||
|
|
20
tool.lua
20
tool.lua
|
@ -17,6 +17,8 @@ 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, ", ")
|
||||
|
@ -25,11 +27,11 @@ local function DOTS(dots)
|
|||
end
|
||||
end
|
||||
|
||||
local function test_get_route(pos, node, player, ctrl)
|
||||
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, ctrl)
|
||||
local route = minecart.get_waypoint(pos, facedir, {})
|
||||
if route then
|
||||
-- print(dump(route))
|
||||
print("test_get_route", string.format("dist = %u, dot = %u, power = %d",
|
||||
|
@ -38,12 +40,22 @@ local function test_get_route(pos, node, player, ctrl)
|
|||
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
|
||||
end
|
||||
|
||||
local function click_left(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_route(pos, node, placer, {left = false})
|
||||
test_get_route(pos, node, placer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
@ -53,7 +65,7 @@ local function click_right(itemstack, placer, pointed_thing)
|
|||
local pos = pointed_thing.under
|
||||
local node = minetest.get_node(pos)
|
||||
if node.name == "carts:rail" or node.name == "carts:powerrail" then
|
||||
test_get_route(pos, node, placer, {right = false})
|
||||
test_get_connections(pos, node, placer)
|
||||
elseif node.name == "minecart:buffer" then
|
||||
local route = minecart.get_route(P2S(pos))
|
||||
print(dump(route))
|
||||
|
|
Loading…
Reference in New Issue