Many bugfixes and improvements

master
Joachim Stolberg 2021-04-13 18:44:33 +02:00
parent 113cfcd9ae
commit 480ff8613f
15 changed files with 791 additions and 231 deletions

View File

@ -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

View File

@ -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

View File

@ -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")

View File

@ -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
View File

@ -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()

View File

@ -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")

View File

@ -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

View File

@ -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=

View File

@ -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"},

View File

@ -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", {

View File

@ -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

View File

@ -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")

View File

@ -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",

View File

@ -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

View File

@ -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))