built on 14/05/2021 18:50:16

This commit is contained in:
Joachim Stolberg 2021-05-14 18:50:16 +02:00
parent 65dc6febbd
commit 39de44275d
344 changed files with 10258 additions and 8400 deletions

View File

@ -184,7 +184,9 @@ local function update_node(pos)
nnode = minetest.get_node(npos)
if NodeTbl1[nnode.name] and NodeTbl3[node.name] then
node.name = node.name .. "1"
if minetest.registered_nodes[node.name] then
minetest.swap_node(pos, node)
end
return
end
-- check case 2
@ -192,7 +194,9 @@ local function update_node(pos)
nnode = minetest.get_node(npos)
if NodeTbl2[nnode.name] then
node.name = string.sub(node.name,1,-1) .. "2"
if minetest.registered_nodes[node.name] then
minetest.swap_node(pos, node)
end
return
end
-- check case 3
@ -203,7 +207,9 @@ local function update_node(pos)
if NodeTbl1[nnode.name] and NodeTbl3[node.name] then
node.name = node.name .. "1"
node.param2 = 3
if minetest.registered_nodes[node.name] then
minetest.swap_node(pos, node)
end
return
end
-- check case 4
@ -212,7 +218,9 @@ local function update_node(pos)
if NodeTbl2[nnode.name] then
node.name = string.sub(node.name,1,-1) .. "2"
node.param2 = 3
if minetest.registered_nodes[node.name] then
minetest.swap_node(pos, node)
end
return
end
end

View File

@ -0,0 +1,8 @@
stages:
- test
luacheck:
stage: test
image: pipelinecomponents/luacheck:latest
script:
- luacheck .

View File

@ -2,7 +2,7 @@ local S = minetest.get_translator("compost")
compost = {}
local CYCLE_TIME = 10
local CYCLE_TIME = 30
-- Version for compatibility checks
compost.version = 1.0

View File

@ -11,8 +11,7 @@ It is the fast and modern way of travelling.
* It can be used even on small servers without lagging
* No configuration or programming of the tube network is necessary (only the station names have to be entered)
**![See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)**
**[See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)**
![screenshot](https://github.com/joe7575/Minetest-Hyperloop/blob/master/screenshot.png)
@ -33,9 +32,9 @@ The mod includes many different kind of blocks:
..and more.
Browse on: ![GitHub](https://github.com/joe7575/Minetest-Hyperloop)
Browse on: [GitHub](https://github.com/joe7575/Minetest-Hyperloop)
Download: ![GitHub](https://github.com/joe7575/Minetest-Hyperloop/archive/master.zip)
Download: [GitHub](https://github.com/joe7575/Minetest-Hyperloop/archive/master.zip)
## Migration from v1 to v2
@ -59,7 +58,7 @@ has some risks. Therefore:
## Introduction
**![See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)**
**[See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)**
## Configuration
@ -68,23 +67,25 @@ The following can be changed in the minetest menu (Settings -> Advanced Settings
* "WiFi block crafting enabled" - To enable the crafting of WiFi blocks (default: false)
* "free tube placement enabled" - If enabled Hyperloop Tubes and Elevator Shafts can be build in all directions (default: true)
When this option is disabled, Hyperloop tubes can only be built in the horizontal direction and elevator shafts in the vertical direction.
* "enable building of subnets" - If enabled the ticket block has an additional field for specifying a subnet name. Stations with the same subnet name (optional) represent an isolated subnet within the Hyperloop network.
Example for 'minetest.conf':
```LUA
hyperloop_wifi_enabled = true
hyperloop_wifi_crafting_enabled = false
hyperloop_free_tube_placement_enabled = true
hyperloop_wifi_enabled = true -- WiFi block enabled
hyperloop_wifi_crafting_enabled = false -- WiFi block crafting enabled
hyperloop_free_tube_placement_enabled = true -- free tube placement enabled
hyperloop_subnet_enabled = true -- enable building of subnets
```
## Dependencies
tubelib2 (![GitHub](https://github.com/joe7575/tubelib2))
tubelib2 ([GitHub](https://github.com/joe7575/tubelib2))
default
intllib
optional: worldedit, techage
# License
Copyright (C) 2017,2020 Joachim Stolberg
Copyright (C) 2017,2021 Joachim Stolberg
Code: Licensed under the GNU LGPL version 2.1 or later. See LICENSE.txt and http://www.gnu.org/licenses/lgpl-2.1.txt
Textures: CC0
Display: Derived from the work of kaeza, sofar and others (digilines) LGPLv2.1+

View File

@ -75,35 +75,72 @@ local function remove_junctions(sortedList)
return tbl
end
local function station_list_as_string(pos)
-- Generate a distance sorted list of all connected stations
local sortedList = Stations:station_list(pos, pos, "dist")
-- Delete the own station from list
table.remove(sortedList, 1)
local function filter_subnet(sortedList, subnet)
if hyperloop.subnet_enabled then
if subnet == "" then
subnet = nil
end
local tbl = {}
for idx,item in ipairs(sortedList) do
if item.subnet == subnet then
tbl[#tbl+1] = item
end
end
return tbl
end
return sortedList
end
-- Used to update the station list for booking machine
-- and teleport list.
local function station_list_as_string(pos, subnet)
local meta = M(pos)
-- Generate a name sorted list of all connected stations
local sortedList = Stations:station_list(pos, pos, "name")
-- remove all junctions from the list
sortedList = remove_junctions(sortedList)
-- use subnet pattern to reduce the list
sortedList = filter_subnet(sortedList, subnet)
-- store the list for later use
store_station_list(pos, sortedList)
-- Generate the formspec string
return generate_string(sortedList)
end
local naming_formspec = nil
local function naming_formspec(pos)
local meta = minetest.get_meta(pos)
local formspec = "size[6,4]"..
if hyperloop.subnet_enabled then
naming_formspec = function(pos)
local meta = M(pos)
local formspec = "size[7,5.4]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"label[0,0;"..S("Please enter the station name to\nwhich this booking machine belongs.").."]" ..
"field[0.5,1.5;5,1;name;"..S("Station name")..";MyTown]" ..
"field[0.5,2.7;5,1;info;"..S("Additional station information")..";]" ..
"button_exit[2,3.6;2,1;exit;Save]"
"field[0.2,1.5;7.1,1;name;"..S("Station name")..";MyTown]" ..
"field[0.2,2.7;7.1,1;info;"..S("Additional station information")..";]" ..
"field[0.2,3.9;7.1,1;subnet;"..S("Subnet name (optional)")..";]" ..
"button_exit[2.5,4.7;2,1;exit;Save]"
meta:set_string("formspec", formspec)
meta:set_int("change_counter", 0)
end
else
naming_formspec = function(pos)
local meta = M(pos)
local formspec = "size[7,4.4]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"label[0,0;"..S("Please enter the station name to\nwhich this booking machine belongs.").."]" ..
"field[0.2,1.5;7.1,1;name;"..S("Station name")..";MyTown]" ..
"field[0.2,2.7;7.1,1;info;"..S("Additional station information")..";]" ..
"button_exit[2.5,3.7;2,1;exit;Save]"
meta:set_string("formspec", formspec)
meta:set_int("change_counter", 0)
end
end
local function booking_machine_update(pos)
local meta = M(pos)
local sStationPos = meta:get_string("sStationPos")
@ -111,25 +148,19 @@ local function booking_machine_update(pos)
local station_pos = P(sStationPos)
local counter = meta:get_int("change_counter") or 0
local changed, newcounter = Stations:changed(counter)
if changed then
meta:set_string("formspec", station_list_as_string(station_pos))
if changed or not tStationList[sStationPos] then
local subnet = meta:get_string("subnet")
meta:set_string("formspec", station_list_as_string(station_pos, subnet))
meta:set_int("change_counter", newcounter)
end
if not tStationList[sStationPos] then
local sortedList = Stations:station_list(station_pos, station_pos, "dist")
-- Delete the own station from list
table.remove(sortedList, 1)
-- remove all junctions from the list
sortedList = remove_junctions(sortedList)
-- store the list for later use
store_station_list(station_pos, sortedList)
end
end
end
local function on_rightclick(pos)
booking_machine_update(pos)
end
local function on_receive_fields(pos, formname, fields, player)
booking_machine_update(pos)
-- station name entered?
if fields.name ~= nil then
local station_name = string.trim(fields.name)
@ -142,17 +173,25 @@ local function on_receive_fields(pos, formname, fields, player)
hyperloop.chat(player, S("Station has already a booking machine!"))
return
end
-- add subnet name if available
local subnet = string.trim(fields.subnet or "")
if subnet == "" then
subnet = nil
end
-- store meta and generate station formspec
Stations:update(stationPos, {
name = station_name,
booking_pos = pos,
booking_info = string.trim(fields.info),
subnet = subnet,
})
local meta = M(pos)
meta:set_string("sStationPos", SP(stationPos))
meta:set_string("infotext", "Station: "..station_name)
meta:set_string("formspec", station_list_as_string(stationPos))
meta:set_string("subnet", string.trim(fields.subnet or ""))
meta:set_int("change_counter", 0) -- force update
booking_machine_update(pos)
else
hyperloop.chat(player, S("Invalid station name!"))
end
@ -233,6 +272,7 @@ minetest.register_node("hyperloop:booking", {
on_rotate = screwdriver.disallow,
on_receive_fields = on_receive_fields,
on_destruct = on_destruct,
on_rightclick = on_rightclick,
paramtype = 'light',
light_source = 2,
@ -279,10 +319,4 @@ minetest.register_node("hyperloop:booking_ground", {
})
minetest.register_lbm({
label = "[Hyperloop] Booking machine update",
name = "hyperloop:update",
nodenames = {"hyperloop:booking", "hyperloop:booking_ground"},
run_at_every_load = true,
action = booking_machine_update
})

View File

@ -5,7 +5,7 @@
v2.06 by JoSt
Copyright (C) 2017-2019 Joachim Stolberg
Copyright (C) 2017-2021 Joachim Stolberg
LGPLv2.1+
See LICENSE.txt for more information
@ -34,6 +34,7 @@
2020-01-03 v2.04 Elevator door bugfix (MT 5+)
2020-03-12 v2.05 minetest translator added (thanks to acmgit/Clyde)
2020-06-14 v2.06 The default value for `hyperloop_free_tube_placement_enabled` is now true
2021-02-07 v2.07 tube_crowbar: Add tube length check
]]--
@ -65,6 +66,7 @@ else
hyperloop.wifi_enabled = minetest.settings:get_bool("hyperloop_wifi_enabled")
hyperloop.wifi_crafting_enabled = minetest.settings:get_bool("hyperloop_wifi_crafting_enabled")
hyperloop.free_tube_placement_enabled = minetest.settings:get_bool("hyperloop_free_tube_placement_enabled", true)
hyperloop.subnet_enabled = minetest.settings:get_bool("hyperloop_subnet_enabled")
dofile(minetest.get_modpath("hyperloop") .. "/network.lua")
dofile(minetest.get_modpath("hyperloop") .. "/data_base.lua")

View File

@ -96,6 +96,15 @@ local function sort_based_on_distance(tStations, pos)
return lStations
end
-- Return a list with sorted stations
local function sort_based_on_name(tStations, pos)
local lStations = table_to_list(table.copy(tStations))
-- Add distance
lStations = add_distance_to_list(lStations, pos)
table.sort(lStations, function(a,b) return a.name < b.name end)
return lStations
end
--
-- Class Network
@ -233,8 +242,12 @@ function Network:station_list(pos, station_pos, sorted)
end
if sorted == "dist" then
lStations = sort_based_on_distance(tStations, pos)
else
elseif sorted == "level" then
lStations = sort_based_on_level(tStations)
else
-- delete own station from list
tStations[S(station_pos)] = nil
lStations = sort_based_on_name(tStations, pos)
end
return lStations
end

View File

@ -8,3 +8,8 @@ hyperloop_wifi_crafting_enabled (WiFi block crafting enabled) bool false
# If disabled, connected stations have to be on one level,
# typically underground.
hyperloop_free_tube_placement_enabled (free tube placement enabled) bool false
# The ticket block has an additional field for specifying a subnet name.
# Stations with the same subnet name (optional) represent an isolated
# subnet within the Hyperloop network.
hyperloop_subnet_enabled (enable building of subnets) bool false

View File

@ -4,9 +4,9 @@ Minecart
**Minecart, the lean railway transportation automation system**
Browse on: ![GitHub](https://github.com/joe7575/minecart)
Browse on: [GitHub](https://github.com/joe7575/minecart)
Download: ![GitHub](https://github.com/joe7575/minecart/archive/master.zip)
Download: [GitHub](https://github.com/joe7575/minecart/archive/master.zip)
![minecart](https://github.com/joe7575/minecart/blob/master/screenshot.png)
@ -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
@ -73,17 +67,16 @@ Introduction
5. Drive from buffer to buffer in both directions using the Minecart(!) to record the
routes (use 'right-left' keys to control the Minecart)
6. Punch the buffers to check the connection data (e.g. "Oxford: connected to Cambridge")
7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart
7. Optional: Configure the Minecart waiting time in both buffers. The Minecart
will then start automatically after the configured time
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark
at least every 16 nodes/meters)
9. Place a Minecart in front of the buffer and check whether it starts after the
configured time
10. Check the cart state via the chat command: /mycart <num>
'<num>' is the cart number
'<num>' is the cart number, or get a list of carts with /mycart
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,43 @@ 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.
Cart Pusher
-----------
Used to push a cart if the cart does not stop directly at a buffer.
The block has to be placed below the rail.
Cart Speed / Speed Limit Signs
------------------------------
As before, the speed of the carts is also influenced by power rails.
Brake rails are irrelevant, the cart does not brake here.
The maximum speed is 8 m/s. This assumes a ratio of power rails
to normal rails of 1 to 4 on a flat section of rail. A rail section is a
series of rail nodes without a change of direction. After every curve / kink,
the speed for the next section of the route is newly determined,
taking into account the swing of the cart. This means that a cart can
roll over short rail sections without power rails.
In order to additionally brake the cart at certain points
(at switches or in front of a buffer), speed limit signs can be placed
on the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.
The "No speed limit" sign can be used to remove the speed limit.
The speed limit signs must be placed next to the track so that they can
be read from the cart. This allows different speeds in each direction of travel.
Migration to v2
---------------
The way how carts are monitored and the cart speed is calculated has changed.
Therefore, it is necessary that all carts are repositioned and the
recording is repeated.
Rails and buffers are not affected and can be kept unchanged.
History
-------
@ -118,3 +148,5 @@ 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,
Speed limit signs and cart terminal added

52
minecart/api.lua Normal file
View File

@ -0,0 +1,52 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
--
-- API functions
--
-- 'pos' is the position of the puncher/sensor, the cart
-- position will be determined by means of 'param2' and 'radius'
function minecart.is_cart_available(pos, param2, radius)
local pos2 = minecart.get_nodecart_nearby(pos, param2, radius)
if pos2 then
return true
end
-- The entity check is needed for a cart with driver
local entity = minecart.get_entitycart_nearby(pos, param2, radius)
if entity then
return true
end
end
function minecart.is_nodecart_available(pos, param2, radius)
local pos2 = minecart.get_nodecart_nearby(pos, param2, radius)
if pos2 then
return true
end
end
-- 'pos' is the position of the puncher/sensor, the cart
-- position will be determined by means of 'param2' and 'radius'
function minecart.punch_cart(pos, param2, radius, punch_dir)
local pos2, node = minecart.get_nodecart_nearby(pos, param2, radius)
if pos2 then
minecart.start_nodecart(pos2, node.name, nil, punch_dir)
return true
end
-- The entity check is needed for a cart with driver
local entity = minecart.get_entitycart_nearby(pos, param2, radius)
if entity and entity.driver then
minecart.push_entitycart(entity, punch_dir)
return true
end
end

360
minecart/baselib.lua Normal file
View File

@ -0,0 +1,360 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local param2_to_dir = {[0]=
{x=0, y=0, z=1},
{x=1, y=0, z=0},
{x=0, y=0, z=-1},
{x=-1, y=0, z=0},
{x=0, y=-1, z=0},
{x=0, y=1, z=0}
}
-- Registered carts
minecart.tNodeNames = {} -- [<cart_node_name>] = <cart_entity_name>
minecart.tEntityNames = {} -- [<cart_entity_name>] = true
minecart.lCartNodeNames = {} -- {<cart_node_name>, <cart_node_name>, ...}
minecart.tCartTypes = {}
function minecart.param2_to_dir(param2)
return param2_to_dir[param2 % 6]
end
function minecart.get_node_lvm(pos)
local node = minetest.get_node_or_nil(pos)
if node then
return node
end
local vm = minetest.get_voxel_manip()
local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
local data = vm:get_data()
local param2_data = vm:get_param2_data()
local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
local idx = area:indexp(pos)
if data[idx] and param2_data[idx] then
return {
name = minetest.get_name_from_content_id(data[idx]),
param2 = param2_data[idx]
}
end
return {name="ignore", param2=0}
end
function minecart.find_node_near_lvm(pos, radius, items)
local npos = minetest.find_node_near(pos, radius, items)
if npos then
return npos
end
local tItems = {}
for _,v in ipairs(items) do
tItems[v] = true
end
local pos1 = {x = pos.x - radius, y = pos.y - radius, z = pos.z - radius}
local pos2 = {x = pos.x + radius, y = pos.y + radius, z = pos.z + radius}
local vm = minetest.get_voxel_manip()
local MinEdge, MaxEdge = vm:read_from_map(pos1, pos2)
local data = vm:get_data()
local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
for x = pos1.x, pos2.x do
for y = pos1.y, pos2.y do
for z = pos1.z, pos2.z do
local idx = area:indexp({x = x, y = y, z = z})
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, size, ttl)
local marker = minetest.add_entity(pos, "minecart:marker_cube")
if marker ~= nil then
marker:set_nametag_attributes({color = "#FFFFFF", text = text})
size = size or 1
marker:set_properties({visual_size = {x = size, y = size}})
if ttl then
minetest.after(ttl, marker.remove, marker)
end
end
end
minetest.register_entity(":minecart:marker_cube", {
initial_properties = {
visual = "cube",
textures = {
"minecart_marker_cube.png",
"minecart_marker_cube.png",
"minecart_marker_cube.png",
"minecart_marker_cube.png",
"minecart_marker_cube.png",
"minecart_marker_cube.png",
},
physical = false,
visual_size = {x = 1, y = 1},
collisionbox = {-0.25,-0.25,-0.25, 0.25,0.25,0.25},
glow = 8,
static_save = false,
},
on_punch = function(self)
self.object:remove()
end,
})
function minecart.is_air_like(name)
local ndef = minetest.registered_nodes[name]
if ndef and ndef.buildable_to then
return true
end
return false
end
function minecart.range(val, min, max)
val = tonumber(val)
if val < min then return min end
if val > max then return max end
return val
end
function minecart.get_next_node(pos, param2)
local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos
local node = minetest.get_node(pos2)
return pos2, node
end
function minecart.get_object_id(object)
for id, entity in pairs(minetest.luaentities) do
if entity.object == object then
return id
end
end
end
function minecart.is_owner(player, owner)
if not player or not player:is_player() or not owner or owner == "" then
return true
end
local name = player:get_player_name()
if minetest.check_player_privs(name, "minecart") then
return true
end
return name == owner
end
function minecart.get_buffer_pos(pos, player_name)
local pos1 = minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"})
if pos1 then
local meta = minetest.get_meta(pos1)
if player_name == nil or player_name == meta:get_string("owner") then
return pos1
end
end
end
function minecart.get_buffer_name(pos)
local pos1 = minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"})
if pos1 then
local name = M(pos1):get_string("name")
if name ~= "" then
return name
end
return P2S(pos1)
end
end
function minecart.manage_attachment(player, entity, get_on)
if not player then
return
end
local player_name = player:get_player_name()
if player_api.player_attached[player_name] == get_on then
return
end
player_api.player_attached[player_name] = get_on
local obj = entity.object
if get_on then
player:set_attach(obj, "", {x=0, y=-4.5, z=-4}, {x=0, y=0, z=0})
player:set_eye_offset({x=0, y=-6, z=0},{x=0, y=-6, z=0})
player:set_properties({visual_size = {x = 2.5, y = 2.5}})
player_api.set_animation(player, "sit")
entity.driver = player:get_player_name()
else
player:set_detach()
player:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0})
player:set_properties({visual_size = {x = 1, y = 1}})
player_api.set_animation(player, "stand")
entity.driver = nil
end
end
function minecart.register_cart_names(node_name, entity_name, cart_type)
minecart.tNodeNames[node_name] = entity_name
minecart.tEntityNames[entity_name] = true
minecart.lCartNodeNames[#minecart.lCartNodeNames+1] = node_name
minecart.add_raillike_nodes(node_name)
minecart.tCartTypes[node_name] = cart_type
end
function minecart.add_nodecart(pos, node_name, param2, cargo, owner, userID)
if pos and node_name and param2 and cargo and owner and userID then
local pos2
if not minecart.is_rail(pos) then
pos2 = minetest.find_node_near(pos, 1, minecart.lRails)
if not pos2 or not minecart.is_rail(pos2) then
pos2 = minetest.find_node_near(pos, 2, minecart.lRails)
if not pos2 or not minecart.is_rail(pos2) then
pos2 = minetest.find_node_near(pos, 2, {"air"})
end
end
else
pos2 = vector.new(pos)
end
if pos2 then
local node = minetest.get_node(pos2)
local ndef = minetest.registered_nodes[node_name]
local rail = node.name
minetest.swap_node(pos2, {name = node_name, param2 = param2})
local meta = M(pos2)
meta:set_string("removed_rail", rail)
meta:set_string("owner", owner)
meta:set_int("userID", userID)
meta:set_string("infotext", owner .. ": " .. userID)
if cargo and ndef.set_cargo then
ndef.set_cargo(pos2, cargo)
end
if ndef.after_place_node then
ndef.after_place_node(pos2)
end
return pos2
else
minetest.add_item(pos, ItemStack({name = node_name}))
end
end
end
function minecart.add_entitycart(pos, node_name, entity_name, vel, cargo, owner, userID)
local obj = minetest.add_entity(pos, entity_name)
local objID = minecart.get_object_id(obj)
if objID then
local entity = obj:get_luaentity()
entity.start_pos = pos
entity.owner = owner
entity.node_name = node_name
entity.userID = userID
entity.objID = objID
entity.cargo = cargo
obj:set_nametag_attributes({color = "#ffff00", text = owner..": "..userID})
obj:set_velocity(vel)
return obj
end
end
function minecart.start_entitycart(self, pos)
local route = {}
self.is_running = true
self.arrival_time = 0
self.start_pos = minecart.get_buffer_pos(pos, self.owner)
if self.start_pos then
-- Read buffer route for the junction info
route = minecart.get_route(self.start_pos) or {}
self.junctions = route and route.junctions
end
-- If set the start waypoint will be deleted
self.no_normal_start = self.start_pos == nil
if self.driver == nil then
minecart.start_monitoring(self.owner, self.userID, pos, self.objID,
route.checkpoints, route.junctions, self.cargo or {})
end
end
function minecart.remove_nodecart(pos)
local node = minetest.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
local meta = M(pos)
local rail = meta:get_string("removed_rail")
if rail == "" then rail = "air" end
local userID = meta:get_int("userID")
local owner = meta:get_string("owner")
meta:set_string("infotext", "")
meta:set_string("formspec", "")
local cargo = ndef.get_cargo and ndef.get_cargo(pos) or {}
minetest.swap_node(pos, {name = rail})
return cargo, owner, userID
end
function minecart.node_to_entity(pos, node_name, entity_name)
-- Remove node
local cargo, owner, userID = minecart.remove_nodecart(pos)
local obj = minecart.add_entitycart(pos, node_name, entity_name,
{x = 0, y = 0, z = 0}, cargo, owner, userID)
if obj then
return obj
else
print("Entity has no ID")
end
end
function minecart.entity_to_node(pos, entity)
-- Stop sound
if entity.sound_handle then
minetest.sound_stop(entity.sound_handle)
entity.sound_handle = nil
end
local rot = entity.object:get_rotation()
local dir = minetest.yaw_to_dir(rot.y)
local facedir = minetest.dir_to_facedir(dir)
minecart.stop_recording(entity, pos)
entity.object:remove()
local pos2 = minecart.add_nodecart(pos, entity.node_name, facedir, entity.cargo, entity.owner, entity.userID)
minecart.stop_monitoring(entity.owner, entity.userID, pos2)
end
function minecart.add_node_to_player_inventory(pos, player, node_name)
local inv = player:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(player:get_player_name()))
or not inv:contains_item("main", node_name) then
local leftover = inv:add_item("main", node_name)
-- If no room in inventory, drop the cart
if not leftover:is_empty() then
minetest.add_item(pos, leftover)
end
end
end
-- Player removes the node
function minecart.remove_entity(self, pos, player)
-- Stop sound
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
self.sound_handle = nil
end
minecart.add_node_to_player_inventory(pos, player, self.node_name or "minecart:cart")
minecart.stop_monitoring(self.owner, self.userID, pos)
minecart.stop_recording(self, pos)
minecart.monitoring_remove_cart(self.owner, self.userID)
self.object:remove()
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
@ -23,21 +23,17 @@ local StopTime = {}
local function formspec(pos)
local name = M(pos):get_string("name")
local time = M(pos):get_int("time")
local s = "size[4,4.2]" ..
return "size[4,4.2]" ..
"label[0,0;Configuration]" ..
"field[0.5,1.2;3.6,1;name;"..S("Station name")..":;"..name.."]"..
"button_exit[1,3.4;2,1;exit;Save]"
if minecart.hopper_enabled then
return s.."field[0.5,2.5;3.6,1;time;"..S("Stop time/sec")..":;"..time.."]"
end
return s
"button_exit[1,3.4;2,1;exit;Save]"..
"field[0.5,2.5;3.6,1;time;"..S("Waiting time/sec")..":;"..time.."]"
end
local function remote_station_name(pos)
local route = minecart.get_route(P2S(pos))
local route = minecart.get_route(pos)
if route and route.dest_pos then
local pos2 = S2P(route.dest_pos)
return M(pos2):get_string("name")
return M(route.dest_pos):get_string("name")
end
return "none"
end
@ -46,23 +42,21 @@ local function on_punch(pos, node, puncher)
local name = M(pos):get_string("name")
M(pos):set_string("infotext", name..": "..S("connected to").." "..remote_station_name(pos))
M(pos):set_string("formspec", formspec(pos))
if minecart.hopper_enabled then
minetest.get_node_timer(pos):start(CYCLE_TIME)
end
-- Optional Teleport function
if not minecart.teleport_enabled then return end
local route = minecart.get_route(P2S(pos))
local route = minecart.get_route(pos)
if route and route.dest_pos and puncher and puncher:is_player() then
-- only teleport if the user is not pressing shift
if not puncher:get_player_control()['sneak'] then
local playername = puncher:get_player_name()
local pos = S2P(route.dest_pos)
local teleport = function()
-- Make sure the player object still exists
local player = minetest.get_player_by_name(playername)
if player and pos then player:set_pos(pos) end
if player then player:set_pos(route.dest_pos) end
end
minetest.after(0.25, teleport)
end
@ -95,24 +89,21 @@ minetest.register_node("minecart:buffer", {
},
after_place_node = function(pos, placer)
M(pos):set_string("owner", placer:get_player_name())
minecart.del_route(minetest.pos_to_string(pos))
minecart.del_route(pos)
M(pos):set_string("formspec", formspec(pos))
if minecart.hopper_enabled then
minetest.get_node_timer(pos):start(CYCLE_TIME)
end
end,
on_timer = function(pos, elapsed)
local time = M(pos):get_int("time")
if time > 0 then
local hash = minetest.hash_node_position(pos)
local param2 = (minetest.get_node(pos).param2 + 2) % 4
if minecart.check_cart_for_pushing(pos, param2) then
if minecart.is_cart_available(pos, param2, 0.5) then
if StopTime[hash] then
if StopTime[hash] < minetest.get_gametime() then
StopTime[hash] = nil
local node = minetest.get_node(pos)
local dir = minetest.facedir_to_dir(node.param2)
minecart.punch_cart(pos, param2, 0, dir)
local dir = minetest.facedir_to_dir(param2)
minecart.punch_cart(pos, param2, 0.5, dir)
end
else
StopTime[hash] = minetest.get_gametime() + time
@ -124,7 +115,7 @@ minetest.register_node("minecart:buffer", {
return true
end,
after_dig_node = function(pos)
minecart.del_route(minetest.pos_to_string(pos))
minecart.del_route(pos)
local hash = minetest.hash_node_position(pos)
StopTime[hash] = nil
end,
@ -137,6 +128,7 @@ minetest.register_node("minecart:buffer", {
M(pos):set_int("time", tonumber(fields.time) or 0)
M(pos):set_string("formspec", formspec(pos))
M(pos):set_string("infotext", fields.name.." "..S("connected to").." "..remote_station_name(pos))
minetest.get_node_timer(pos):start(CYCLE_TIME)
end
end,
on_punch = on_punch,
@ -156,3 +148,15 @@ minetest.register_craft({
{"default:steel_ingot", "default:junglewood", "default:steel_ingot"},
},
})
minetest.register_lbm({
label = "Delete waiting times",
name = "minecart:del_time",
nodenames = {"minecart:buffer"},
run_at_every_load = false,
action = function(pos, node)
-- delete old data
minecart.get_route(pos)
M(pos):set_string("formspec", formspec(pos))
end,
})

View File

@ -1,397 +0,0 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart library functions (level 1)
]]--
-- Notes:
-- 1) Only the owner can punch der cart
-- 2) Only the owner can start the recording
-- 3) But any player can act as cargo, cart punched by owner or buffer
local SLOPE_ACCELERATION = 3
local MAX_SPEED = 7
local PUNCH_SPEED = 3
local SLOWDOWN = 0.4
local RAILTYPE = minetest.get_item_group("carts:rail", "connect_to_raillike")
local Y_OFFS_ON_SLOPES = 0.5
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local MP = minetest.get_modpath("minecart")
local D = function(pos) return minetest.pos_to_string(vector.round(pos)) end
local tRails = {
["carts:rail"] = true,
["carts:powerrail"] = true,
["carts:brakerail"] = true,
}
local lRails = {"carts:rail", "carts:powerrail", "carts:brakerail"}
local function get_rail_node(pos)
local rail_pos = vector.round(pos)
local node = minecart.get_node_lvm(rail_pos)
if tRails[node.name] then
return rail_pos, node
end
end
local function find_rail_node(pos)
local rail_pos = vector.round(pos)
local node = get_rail_node(rail_pos)
if node then
return rail_pos, node
end
local pos1 = {x=rail_pos.x-1, y=rail_pos.y-1, z=rail_pos.z-1}
local pos2 = {x=rail_pos.x+1, y=rail_pos.y+1, z=rail_pos.z+1}
for _,pos3 in ipairs(minetest.find_nodes_in_area(pos1, pos2, lRails)) do
--print("invalid position1", D(pos), D(pos3))
return pos3, minecart.get_node_lvm(pos3)
end
--print("invalid position2", D(pos))
end
local function get_pitch(dir)
local pitch = 0
if dir.y == -1 then
pitch = -math.pi/4
elseif dir.y == 1 then
pitch = math.pi/4
end
return pitch * (dir.z == 0 and -1 or 1)
end
local function get_yaw(dir)
local yaw = 0
if dir.x < 0 then
yaw = math.pi/2*3
elseif dir.x > 0 then
yaw = math.pi/2
elseif dir.z < 0 then
yaw = math.pi
end
return yaw
end
local function push_cart(self, pos, punch_dir, puncher)
local vel = self.object:get_velocity()
punch_dir = punch_dir or carts:velocity_to_dir(puncher:get_look_dir())
punch_dir.y = 0
local cart_dir = carts:get_rail_direction(pos, punch_dir, nil, nil, RAILTYPE)
-- Always start in horizontal direction
cart_dir.y = 0
if vector.equals(cart_dir, {x=0, y=0, z=0}) then return end
local speed = vector.multiply(cart_dir, PUNCH_SPEED)
local new_vel = vector.add(vel, speed)
local yaw = get_yaw(cart_dir)
local pitch = get_pitch(cart_dir)
self.object:set_rotation({x = pitch, y = yaw, z = 0})
self.object:set_velocity(new_vel)
self.old_pos = vector.round(pos)
self.stopped = false
end
local api = {}
function api:init(is_node_cart)
local lib
if is_node_cart then
lib = dofile(MP.."/cart_lib2n.lua")
else
lib = dofile(MP.."/cart_lib2e.lua")
end
-- add lib to local api
for k,v in pairs(lib) do
api[k] = v
end
end
-- Player get on / off
function api:on_rightclick(clicker)
if not clicker or not clicker:is_player() then
return
end
local player_name = clicker:get_player_name()
if self.driver and player_name == self.driver then
self.driver = nil
carts:manage_attachment(clicker, nil)
elseif not self.driver then
self.driver = player_name
carts:manage_attachment(clicker, self.object)
-- player_api does not update the animation
-- when the player is attached, reset to default animation
player_api.set_animation(clicker, "stand")
end
end
function api:on_activate(staticdata, dtime_s)
self.object:set_armor_groups({immortal=1})
end
function api:on_detach_child(child)
if child and child:get_player_name() == self.driver then
self.driver = nil
end
end
function api:on_punch(puncher, time_from_last_punch, tool_capabilities, direction)
local pos = self.object:get_pos()
local vel = self.object:get_velocity()
local stopped = vector.equals(vel, {x=0, y=0, z=0})
local is_minecart = self.node_name == nil
local node_name = self.node_name or "minecart:cart"
local puncher_name = puncher and puncher:is_player() and puncher:get_player_name()
local puncher_is_owner = puncher_name and (not self.owner or self.owner == "" or
puncher_name == self.owner or
minetest.check_player_privs(puncher_name, "minecart"))
local puncher_is_driver = self.driver and self.driver == puncher_name
local sneak_punch = puncher_name and puncher:get_player_control().sneak
local no_cargo = next(self.cargo or {}) == nil
-- driver wants to leave/remove the empty Minecart by sneak-punch
if is_minecart and sneak_punch and puncher_is_driver and no_cargo then
if puncher_is_owner then
api.remove_cart(self, pos, puncher)
end
carts:manage_attachment(puncher, nil)
return
end
-- Punched by non-authorized player
if puncher_name and not puncher_is_owner then
minetest.chat_send_player(puncher_name, S("[minecart] Cart is protected by ")..(self.owner or ""))
return
end
-- Punched by non-player
if not puncher_name then
local cart_dir = carts:get_rail_direction(pos, direction, nil, nil, RAILTYPE)
if vector.equals(cart_dir, {x=0, y=0, z=0}) then
return
end
api.load_cargo(self, pos)
push_cart(self, pos, cart_dir)
minecart.start_cart(pos, self.myID)
return
end
-- Sneak-punched by owner
if sneak_punch then
-- Unload the cargo
if api.add_cargo_to_player_inv(self, pos, puncher) then
return
end
-- detach driver
if self.driver then
carts:manage_attachment(puncher, nil)
end
-- Pick up cart
api.remove_cart(self, pos, puncher)
return
end
-- Cart with driver punched to start recording
if puncher_is_driver then
minecart.start_recording(self, pos, vel, puncher)
else
minecart.start_cart(pos, self.myID)
end
api.load_cargo(self, pos)
push_cart(self, pos, nil, puncher)
end
-- sound refresh interval = 1.0sec
local function rail_sound(self, dtime)
if not self.sound_ttl then
self.sound_ttl = 1.0
return
elseif self.sound_ttl > 0 then
self.sound_ttl = self.sound_ttl - dtime
return
end
self.sound_ttl = 1.0
if self.sound_handle then
local handle = self.sound_handle
self.sound_handle = nil
minetest.after(0.2, minetest.sound_stop, handle)
end
if not self.stopped then
local vel = self.object:get_velocity() or {x=0, y=0, z=0}
local speed = vector.length(vel)
self.sound_handle = minetest.sound_play(
"carts_cart_moving", {
object = self.object,
gain = (speed / carts.speed_max) / 2,
loop = true,
})
end
end
local function rail_on_step(self)
-- Check if same position as before
local pos = self.object:get_pos()
local rot = self.object:get_rotation()
local on_slope = rot.x ~= 0
--print("rail_on_step_new", P2S(pos), rot.x)
-- cart position correction on slopes
if on_slope then
pos.y = pos.y - Y_OFFS_ON_SLOPES
end
-- Used as fallback position
self.old_pos = self.old_pos or pos
local pos_rounded = vector.round(pos)
-- Same pos as before
if vector.equals(pos_rounded, self.old_pos) then
return -- nothing todo
end
-- Check if stopped
local vel = self.object:get_velocity()
local stopped = not on_slope and minecart.stopped(vel)
local is_minecart = self.node_name == nil
local recording = is_minecart and self.driver == self.owner
if stopped then
if not self.stopped then
local param2 = minetest.dir_to_facedir(self.old_dir)
api.stop_cart(pos, self, self.node_name or "minecart:cart", param2)
if recording then
minecart.stop_recording(self, pos_rounded, vel, self.driver)
end
api.unload_cargo(self, pos)
self.stopped = true
end
self.old_pos = pos_rounded
return -- nothing todo
end
-- Check if invalid position (not on rail anymore)
local rail_pos, node = get_rail_node(pos)
if not node then
rail_pos, node = find_rail_node(self.old_pos)
if rail_pos then
pos_rounded = rail_pos
if on_slope then
self.object:set_pos({x=rail_pos.x, y=rail_pos.y + Y_OFFS_ON_SLOPES, z=rail_pos.z})
else
self.object:set_pos(rail_pos)
end
else
self.object:set_pos(pos)
minetest.log("error", "[minecart] No valid position "..(P2S(pos) or "nil"))
return -- no valid position
end
end
-- Calc speed (value)
local speed = math.sqrt((vel.x+vel.z)^2 + vel.y^2)
-- Check if slope position
if pos_rounded.y > self.old_pos.y then
speed = speed - SLOPE_ACCELERATION
elseif pos_rounded.y < self.old_pos.y then
speed = speed + SLOPE_ACCELERATION
else
speed = speed - SLOWDOWN
end
-- Add power/brake rail acceleration
local acc = (carts.railparams[node.name] or {}).acceleration or 0
speed = speed + acc
-- Determine new direction
local dir = carts:velocity_to_dir(vel)
if speed < 0 then
if on_slope then
dir = vector.multiply(dir, -1)
-- start with a value > 0
speed = 0.5
else
speed = 0
end
end
-- Get player controls
local ctrl, player
if recording then
player = minetest.get_player_by_name(self.driver)
if player then
ctrl = player:get_player_control()
end
end
-- new_dir: New moving direction of the cart
-- keys: Currently pressed L/R key, used to ignore the key on the next rail node
local new_dir, keys = carts:get_rail_direction(rail_pos, dir, ctrl, self.old_keys, RAILTYPE)
-- handle junctions
if recording and keys then
minecart.set_junction(self, rail_pos, new_dir, keys)
else -- normal run
new_dir, keys = minecart.get_junction(self, rail_pos, new_dir)
end
self.old_keys = keys
-- Detect U-turn
if (dir.x ~= 0 and dir.x == -new_dir.x) or (dir.z ~= 0 and dir.z == -new_dir.z) then
-- Stop the cart
self.object:set_velocity({x=0, y=0, z=0})
self.object:move_to(pos_rounded)
return
-- New direction
elseif not vector.equals(dir, new_dir) then
if new_dir.y ~= 0 then
self.object:set_pos({x=pos_rounded.x, y=pos_rounded.y + Y_OFFS_ON_SLOPES, z=pos_rounded.z})
else
self.object:set_pos(pos_rounded)
end
end
-- Set velocity and rotation
local new_vel = vector.multiply(new_dir, math.min(speed, MAX_SPEED))
local yaw = get_yaw(new_dir)
local pitch = get_pitch(new_dir)
self.object:set_rotation({x = pitch, y = yaw, z = 0})
self.object:set_velocity(new_vel)
if recording then
minecart.store_next_waypoint(self, rail_pos, vel)
end
self.old_pos = pos_rounded
end
function api:on_step(dtime)
self.delay = (self.delay or 0) + dtime
if self.delay > 0.09 then
rail_on_step(self)
rail_sound(self, self.delay)
self.delay = 0
end
end
return api

View File

@ -1,150 +0,0 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart library functions for entity based carts (level 2)
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local MP = minetest.get_modpath("minecart")
local api = dofile(MP.."/cart_lib3.lua")
-- Add node, set metadata, and load carge
local function add_cart(pos, node_name, param2, owner, userID, cargo)
local obj = minetest.add_entity(pos, node_name)
local myID = api.get_object_id(obj)
if myID then
-- Copy item data to cart entity
local entity = obj:get_luaentity()
entity.owner = owner
entity.userID = userID
entity.cargo = cargo
entity.myID = myID
obj:set_nametag_attributes({color = "#FFFF00", text = owner..": "..userID})
minecart.add_to_monitoring(obj, myID, owner, userID)
return myID
else
print("Entity has no ID")
end
end
function api.stop_cart(pos, entity, node_name, param2)
-- Stop sound
if entity.sound_handle then
minetest.sound_stop(entity.sound_handle)
entity.sound_handle = nil
end
minecart.stop_cart(pos, entity.myID)
end
-- Player adds the node
function api.add_cart(itemstack, placer, pointed_thing, node_name)
local owner = placer:get_player_name()
local meta = placer:get_meta()
local param2 = minetest.dir_to_facedir(placer:get_look_dir())
local userID = 0
local cargo = {}
-- Add node
if carts:is_rail(pointed_thing.under) then
add_cart(pointed_thing.under, node_name, param2, owner, userID, cargo)
meta:set_string("cart_pos", P2S(pointed_thing.under))
elseif carts:is_rail(pointed_thing.above) then
add_cart(pointed_thing.above, node_name, param2, owner, userID, cargo)
meta:set_string("cart_pos", P2S(pointed_thing.above))
else
return
end
minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
{pos = pointed_thing.above})
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(placer:get_player_name())) then
itemstack:take_item()
end
minetest.show_formspec(owner, "minecart:userID_entity",
"size[4,3]" ..
"label[0,0;Enter cart number:]" ..
"field[1,1;3,1;userID;;]" ..
"button_exit[1,2;2,1;exit;Save]")
return itemstack
end
-- Player removes the node
function api.remove_cart(self, pos, player)
-- Add cart to player inventory
local inv = player:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(player:get_player_name()))
or not inv:contains_item("main", "minecart:cart") then
local leftover = inv:add_item("main", "minecart:cart")
-- If no room in inventory add a replacement cart to the world
if not leftover:is_empty() then
minetest.add_item(pos, leftover)
end
end
minecart.remove_from_monitoring(self.myID)
self.object:remove()
-- Stop sound
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
self.sound_handle = nil
end
return true
end
function api.load_cargo(self, pos)
self.cargo = self.cargo or {}
for _, obj_ in pairs(minetest.get_objects_inside_radius(pos, 1)) do
local entity = obj_:get_luaentity()
if not obj_:is_player() and entity and entity.name == "__builtin:item" then
obj_:remove()
self.cargo[#self.cargo + 1] = entity.itemstring
end
end
end
function api.unload_cargo(self, pos)
-- Spawn loaded items again
for _,item in ipairs(self.cargo or {}) do
minetest.add_item(pos, ItemStack(item))
end
self.cargo = {}
end
-- in the case the owner punches the cart
function api.add_cargo_to_player_inv(self, pos, puncher)
local added = false
local inv = puncher:get_inventory()
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do
local entity = obj:get_luaentity()
if not obj:is_player() and entity and entity.name == "__builtin:item" then
obj:remove()
local item = ItemStack(entity.itemstring)
local leftover = inv:add_item("main", item)
if leftover:get_count() > 0 then
minetest.add_item(pos, leftover)
end
added = true -- don't dig the cart
end
end
return added
end
return api

View File

@ -1,198 +0,0 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart library functions for node based carts (level 2)
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local MP = minetest.get_modpath("minecart")
local api = dofile(MP.."/cart_lib3.lua")
-- Add node, set metadata, and load carge
local function add_cart(pos, node_name, param2, owner, userID, cargo)
local ndef = minetest.registered_nodes[node_name]
local node = minetest.get_node(pos)
local meta = M(pos)
local rail = node.name
minetest.add_node(pos, {name = node_name, param2 = param2})
meta:set_string("removed_rail", rail)
meta:set_string("owner", owner)
meta:set_string("userID", userID)
meta:set_string("infotext", minetest.get_color_escape_sequence("#FFFF00")..owner..": "..userID)
if ndef.after_place_node then
ndef.after_place_node(pos)
end
if cargo and ndef.set_cargo then
ndef.set_cargo(pos, cargo)
end
end
-- called after punch cart
local function start_cart(pos, node_name, entity_name, puncher, dir)
-- Read node metadata
local ndef = minetest.registered_nodes[node_name]
if ndef then
local meta = M(pos)
local rail = meta:get_string("removed_rail")
local userID = meta:get_int("userID")
local cart_owner = meta:get_string("owner")
local cargo = ndef.get_cargo and ndef.get_cargo(pos) or {}
-- swap node to rail
minetest.remove_node(pos)
minetest.add_node(pos, {name = rail})
-- Add entity
local obj = minetest.add_entity(pos, entity_name)
-- Determine ID
local myID = api.get_object_id(obj)
if myID then
-- Copy metadata to cart entity
local entity = obj:get_luaentity()
entity.owner = cart_owner
entity.userID = userID
entity.cargo = cargo
entity.myID = myID
obj:set_nametag_attributes({color = "#ffff00", text = cart_owner..": "..userID})
minecart.add_to_monitoring(obj, myID, cart_owner, userID)
minecart.node_at_station(cart_owner, userID, nil)
-- punch cart to prevent the stopped handling
obj:punch(puncher or obj, 1, {
full_punch_interval = 1.0,
damage_groups = {fleshy = 1},
}, dir)
return myID
else
print("Entity has no ID")
end
end
end
function api.stop_cart(pos, entity, node_name, param2)
-- rail buffer reached?
if api.get_route_key(pos) then
-- Read entity data
local owner = entity.owner or ""
local userID = entity.userID or 0
local cargo = entity.cargo or {}
-- Remove entity
minecart.remove_from_monitoring(entity.myID)
minecart.node_at_station(owner, userID, pos)
entity.object:remove()
-- Add cart node
add_cart(pos, node_name, param2, owner, userID, cargo)
end
-- Stop sound
if entity.sound_handle then
minetest.sound_stop(entity.sound_handle)
entity.sound_handle = nil
end
end
-- Player adds the node
function api.add_cart(itemstack, placer, pointed_thing, node_name)
local owner = placer:get_player_name()
local meta = placer:get_meta()
local param2 = minetest.dir_to_facedir(placer:get_look_dir())
local userID = 0
local cargo = {}
-- Add node
if carts:is_rail(pointed_thing.under) then
add_cart(pointed_thing.under, node_name, param2, owner, userID, cargo)
meta:set_string("cart_pos", P2S(pointed_thing.under))
elseif carts:is_rail(pointed_thing.above) then
add_cart(pointed_thing.above, node_name, param2, owner, userID, cargo)
meta:set_string("cart_pos", P2S(pointed_thing.above))
else
return
end
minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
{pos = pointed_thing.above})
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(placer:get_player_name())) then
itemstack:take_item()
end
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]")
return itemstack
end
function api.node_on_punch(pos, node, puncher, pointed_thing, entity_name, dir)
local ndef = minetest.registered_nodes[node.name]
-- Player digs cart by sneak-punch
if puncher and puncher:get_player_control().sneak then
api.remove_cart(nil, pos, puncher)
return
end
start_cart(pos, node.name, entity_name, puncher, dir)
end
local function add_to_player_inventory(pos, player, node_name)
local inv = player:get_inventory()
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(player:get_player_name()))
or not inv:contains_item("main", node_name) then
local leftover = inv:add_item("main", node_name)
-- If no room in inventory add a replacement cart to the world
if not leftover:is_empty() then
minetest.add_item(pos, leftover)
end
end
end
-- Player removes the node
function api.remove_cart(self, pos, player)
if self then -- cart is still an entity
add_to_player_inventory(pos, player, self.node_name or "minecart:cart")
minecart.remove_from_monitoring(self.myID)
self.object:remove()
else
local node = minetest.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
if ndef.can_dig and ndef.can_dig(pos, player) then
add_to_player_inventory(pos, player, node.name)
node.name = M(pos):get_string("removed_rail")
if node.name == "" then
node.name = "carts:rail"
end
minetest.remove_node(pos)
minetest.add_node(pos, node)
end
end
end
function api.load_cargo()
-- nothing to load
end
function api.unload_cargo()
-- nothing to unload
end
function api.add_cargo_to_player_inv()
-- nothing to do
end
-- needed by minecart.punch_cart and node carts
minecart.node_on_punch = api.node_on_punch
return api

View File

@ -1,90 +0,0 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart library base functions (level 3)
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local api = {}
function api.get_object_id(object)
for id, entity in pairs(minetest.luaentities) do
if entity.object == object then
return id
end
end
end
function api.get_route_key(pos, player_name)
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
if pos1 then
local meta = minetest.get_meta(pos1)
if player_name == nil or player_name == meta:get_string("owner") then
return P2S(pos1)
end
end
end
function api.get_station_name(pos)
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
if pos1 then
local name = M(pos1):get_string("name")
if name ~= "" then
return name
end
return "-"
end
end
function api.load_cart(pos, vel, pitch, yaw, item)
-- Add cart to map
local obj = minetest.add_entity(pos, item.entity_name or "minecart:cart", nil)
-- Determine ID
local myID = api.get_object_id(obj)
if myID then
-- Copy item data to cart entity
local entity = obj:get_luaentity()
entity.owner = item.owner or ""
entity.userID = item.userID or 0
entity.cargo = item.cargo or {}
entity.myID = myID
obj:set_nametag_attributes({color = "#FFFF00", text = entity.owner..": "..entity.userID})
-- Update item data
item.owner = entity.owner
item.cargo = nil
-- Start cart
obj:set_velocity(vel)
obj:set_rotation({x = pitch or 0, y = yaw or 0, z = 0})
return myID
else
print("Entity has no ID")
end
end
function api.unload_cart(pos, vel, entity, item)
-- Copy entity data to item
item.cargo = entity.cargo
item.entity_name = entity.object:get_entity_name()
-- Remove entity from map
entity.object:remove()
-- Stop sound
if entity.sound_handle then
minetest.sound_stop(entity.sound_handle)
entity.sound_handle = nil
end
end
return api

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
@ -29,15 +29,15 @@ local summary_doc = table.concat({
S("4. Place a Minecart at a buffer and give it a cart number (1..999)"),
S("5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart)."),
S("6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge')."),
S("7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time."),
S("7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time."),
S("8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters)."),
S("9. Place a Minecart in front of the buffer and check whether it starts after the configured time."),
S("10. Check the cart state via the chat command: /mycart <num>\n '<num>' is the cart number"),
S("11. Drop items into the Minecart and punch the cart to start it, 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")
@ -45,6 +45,29 @@ local landmark_doc = S("Protect your rails with the Landmarks (one Landmark at l
local hopper_doc = S("Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.")
local pusher_doc = S([[If several carts are running on one route,
it can happen that a buffer position is already occupied and one cart therefore stops earlier.
In this case, the cart pusher is used to push the cart towards the buffer again.
This block must be placed under the rail at a distance of 2 m in front of the buffer.]])
local speed_doc = S([[Limit the cart speed with speed limit signs.
As before, the speed of the carts is also influenced by power rails.
Brake rails are irrelevant, the cart does not brake here.
The maximum speed is 8 m/s. This assumes a ratio of power rails
to normal rails of 1 to 4 on a flat section of rail. A rail section is a
series of rail nodes without a change of direction. After every curve / kink,
the speed for the next section of the route is newly determined,
taking into account the swing of the cart. This means that a cart can
roll over short rail sections without power rails.
In order to additionally brake the cart at certain points
(at switches or in front of a buffer), speed limit signs can be placed
on the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.
The "No speed limit" sign can be used to remove the speed limit.
The speed limit signs must be placed next to the track so that they can
be read from the cart. This allows different speeds in each direction of travel.]])
local function formspec(data)
if data.image then
@ -90,6 +113,16 @@ doc.add_entry("minecart", "landmark", {
data = {text = landmark_doc, item="minecart:landmark"},
})
doc.add_entry("minecart", "speed signs", {
name = S("Minecart Speed Signs"),
data = {text = speed_doc, item="minecart:speed4"},
})
doc.add_entry("minecart", "cart pusher", {
name = S("Cart Pusher"),
data = {text = pusher_doc, item="minecart:cart_pusher"},
})
if minecart.hopper_enabled then
doc.add_entry("minecart", "hopper", {
name = S("Minecart Hopper"),

318
minecart/entitylib.lua Normal file
View File

@ -0,0 +1,318 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local MAX_SPEED = minecart.MAX_SPEED
local dot2dir = minecart.dot2dir
local get_waypoint = minecart.get_waypoint
local recording_waypoints = minecart.recording_waypoints
local recording_junctions = minecart.recording_junctions
local set_junctions = minecart.set_junctions
local player_ctrl = minecart.player_ctrl
local tEntityNames = minecart.tEntityNames
local function stop_cart(self, cart_pos)
self.is_running = false
self.arrival_time = 0
if self.driver then
local player = minetest.get_player_by_name(self.driver)
if player then
minecart.stop_recording(self, cart_pos)
minecart.manage_attachment(player, self, false)
end
end
if not minecart.get_buffer_pos(cart_pos, self.owner) then
-- Probably somewhere in the pampas
minecart.delete_cart_waypoint(cart_pos)
end
minecart.entity_to_node(cart_pos, self)
end
local function get_ctrl(self, pos)
-- Use player ctrl or junction data from recorded routes
return (self.driver and self.ctrl) or (self.junctions and self.junctions[P2H(pos)]) or {}
end
local function new_speed(self, new_dir)
self.cart_speed = self.cart_speed or 0
local rail_speed = (self.waypoint.speed or 0) / 10
if rail_speed <= 0 then
rail_speed = math.max(self.cart_speed + rail_speed, 0)
elseif rail_speed <= self.cart_speed then
rail_speed = math.max((self.cart_speed + rail_speed) / 2, 0)
end
-- Speed corrections
if new_dir.y == 1 then
if rail_speed < 1 then rail_speed = 0 end
else
if rail_speed < 0.4 then rail_speed = 0 end
end
self.cart_speed = rail_speed -- store for next cycle
return rail_speed
end
local function running(self)
local rot = self.object:get_rotation()
local dir = minetest.yaw_to_dir(rot.y)
dir.y = math.floor((rot.x / (math.pi/4)) + 0.5)
dir = vector.round(dir)
local facedir = minetest.dir_to_facedir(dir)
local cart_pos, wayp_pos, is_junction
if self.reenter then -- through monitoring
cart_pos = H2P(self.reenter[1])
wayp_pos = cart_pos
is_junction = false
self.waypoint = {pos = H2P(self.reenter[2]), power = 0, dot = self.reenter[4]}
self.cart_speed = self.reenter[3]
self.speed_limit = MAX_SPEED
self.reenter = nil
elseif not self.waypoint then
-- get waypoint
cart_pos = vector.round(self.object:get_pos())
wayp_pos = cart_pos
is_junction = false
self.waypoint = get_waypoint(cart_pos, facedir, get_ctrl(self, cart_pos), true)
if self.no_normal_start then
-- Probably somewhere in the pampas
minecart.delete_waypoint(cart_pos)
self.no_normal_start = nil
end
self.cart_speed = 2 -- push speed
self.speed_limit = MAX_SPEED
else
-- next waypoint
cart_pos = vector.new(self.waypoint.cart_pos or self.waypoint.pos)
wayp_pos = self.waypoint.pos
local vel = self.object:get_velocity()
self.waypoint, is_junction = get_waypoint(wayp_pos, facedir, get_ctrl(self, wayp_pos), self.cart_speed < 0.1)
end
if not self.waypoint then
stop_cart(self, wayp_pos)
return
end
if is_junction then
if self.is_recording then
set_junctions(self, wayp_pos)
end
self.ctrl = nil
end
--print("dist", P2S(cart_pos), P2S(self.waypoint.pos), P2S(self.waypoint.cart_pos), self.waypoint.dot)
local dist = vector.distance(cart_pos, self.waypoint.cart_pos or self.waypoint.pos)
local new_dir = dot2dir(self.waypoint.dot)
local new_speed = new_speed(self, new_dir)
local straight_ahead = vector.equals(new_dir, dir)
-- If straight_ahead, then it's probably a speed limit sign
if straight_ahead then
self.speed_limit = minecart.get_speedlimit(wayp_pos, facedir) or self.speed_limit
end
new_speed = math.min(new_speed, self.speed_limit)
local new_cart_pos, extra_cycle = minecart.get_current_cart_pos_correction(
wayp_pos, facedir, dir.y, self.waypoint.dot) -- TODO: Why has self.waypoint no dot?
if extra_cycle and not vector.equals(cart_pos, new_cart_pos) then
self.waypoint = {pos = wayp_pos, cart_pos = new_cart_pos}
new_dir = vector.direction(cart_pos, new_cart_pos)
dist = vector.distance(cart_pos, new_cart_pos)
--print("extra_cycle", P2S(cart_pos), P2S(wayp_pos), P2S(new_cart_pos), new_speed)
end
-- Slope corrections
--print("Slope corrections", P2S(new_dir), P2S(cart_pos))
if new_dir.y ~= 0 then
cart_pos.y = cart_pos.y + 0.2
end
-- Calc velocity, rotation and arrival_time
local yaw = minetest.dir_to_yaw(new_dir)
local pitch = new_dir.y * math.pi/4
--print("new_speed", new_speed / (new_dir.y ~= 0 and 1.41 or 1))
local vel = vector.multiply(new_dir, new_speed / ((new_dir.y ~= 0) and 1.41 or 1))
self.arrival_time = self.timebase + (dist / new_speed)
-- needed for recording
self.curr_speed = new_speed
self.num_sections = (self.num_sections or 0) + 1
-- Got stuck somewhere
if new_speed < 0.1 or dist < 0 then
print("Got stuck somewhere", new_speed, dist)
stop_cart(self, wayp_pos)
return
end
self.object:set_pos(cart_pos)
self.object:set_rotation({x = pitch, y = yaw, z = 0})
self.object:set_velocity(vel)
return
end
local function play_sound(self)
if self.sound_handle then
local handle = self.sound_handle
self.sound_handle = nil
minetest.after(0.2, minetest.sound_stop, handle)
end
if self.object then
self.sound_handle = minetest.sound_play(
"carts_cart_moving", {
object = self.object,
gain = self.curr_speed / MAX_SPEED,
})
end
end
local function on_step(self, dtime)
self.timebase = (self.timebase or 0) + dtime
if self.is_running then
if self.arrival_time <= self.timebase then
running(self)
end
if (self.sound_ttl or 0) <= self.timebase then
play_sound(self)
self.sound_ttl = self.timebase + 1.0
end
else
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
self.sound_handle = nil
end
end
if self.driver then
if self.is_recording then
if self.rec_time <= self.timebase then
recording_waypoints(self)
self.rec_time = self.rec_time + 2.0
end
recording_junctions(self)
else
player_ctrl(self)
end
end
end
local function on_entitycard_activate(self, staticdata, dtime_s)
self.object:set_armor_groups({immortal=1})
end
-- Start the entity cart (or dig by shift+leftclick)
local function on_entitycard_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)
if minecart.is_owner(puncher, self.owner) then
if puncher:get_player_control().sneak then
if not self.only_dig_if_empty or not next(self.cargo) then
-- drop items
local pos = vector.round(self.object:get_pos())
for _,item in ipairs(self.cargo or {}) do
minetest.add_item(pos, ItemStack(item))
end
-- Dig cart
if self.driver then
-- remove cart as driver
minecart.stop_recording(self, pos)
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
end
end
-- Player get on / off
local function on_entitycard_rightclick(self, clicker)
if clicker and clicker:is_player() and self.driver_allowed then
-- Get on / off
if self.driver then
-- get off
local pos = vector.round(self.object:get_pos())
minecart.manage_attachment(clicker, self, false)
minecart.entity_to_node(pos, self)
else
-- get on
local pos = vector.round(self.object:get_pos())
minecart.stop_recording(self, pos)
minecart.manage_attachment(clicker, self, true)
end
end
end
local function on_entitycard_detach_child(self, child)
if child and child:get_player_name() == self.driver then
self.driver = nil
end
end
function minecart.get_entitycart_nearby(pos, param2, radius)
local pos2 = param2 and vector.add(pos, minecart.param2_to_dir(param2)) or pos
for _, object in pairs(minetest.get_objects_inside_radius(pos2, radius or 0.5)) do
local entity = object:get_luaentity()
if entity and entity.name and tEntityNames[entity.name] then
local vel = object:get_velocity()
if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing?
return entity
end
end
end
end
function minecart.push_entitycart(self, punch_dir)
--print("push_entitycart")
local vel = self.object:get_velocity()
punch_dir.y = 0
local yaw = minetest.dir_to_yaw(punch_dir)
self.object:set_rotation({x = 0, y = yaw, z = 0})
self.is_running = true
self.arrival_time = 0
end
function minecart.register_cart_entity(entity_name, node_name, cart_type, entity_def)
entity_def.entity_name = entity_name
entity_def.node_name = node_name
entity_def.on_activate = on_entitycard_activate
entity_def.on_punch = on_entitycard_punch
entity_def.on_step = on_step
entity_def.on_rightclick = on_entitycard_rightclick
entity_def.on_detach_child = on_entitycard_detach_child
entity_def.owner = nil
entity_def.driver = nil
entity_def.cargo = {}
minetest.register_entity(entity_name, entity_def)
-- register node for punching
minecart.register_cart_names(node_name, entity_name, cart_type)
end

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
@ -155,6 +155,7 @@ minetest.register_node("minecart:hopper", {
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = minecart.CLIP,
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),

141
minecart/hopperlib.lua Normal file
View File

@ -0,0 +1,141 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local RegisteredInventories = {}
-- Take the given number of items from the inv.
-- Returns nil if ItemList is empty.
function minecart.inv_take_items(inv, listname, num)
if inv:is_empty(listname) then
return nil
end
local size = inv:get_size(listname)
for idx = 1, size do
local items = inv:get_stack(listname, idx)
if items:get_count() > 0 then
local taken = items:take_item(num)
inv:set_stack(listname, idx, items)
return taken
end
end
return nil
end
function minecart.take_items(pos, param2, num)
local npos, node
if param2 then
npos, node = minecart.get_next_node(pos, (param2 + 2) % 4)
else
npos, node = pos, minetest.get_node(pos)
end
local def = RegisteredInventories[node.name]
local owner = M(pos):get_string("owner")
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and def.take_listname and (not def.allow_take or def.allow_take(npos, nil, owner)) then
return minecart.inv_take_items(inv, def.take_listname, num)
elseif def and def.take_item then
return def.take_item(npos, num, owner)
else
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.minecart_hopper_takeitem then
return ndef.minecart_hopper_takeitem(npos, num)
end
end
end
function minecart.put_items(pos, param2, stack)
local npos, node = minecart.get_next_node(pos, param2)
local def = RegisteredInventories[node.name]
local owner = M(pos):get_string("owner")
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and def.put_listname and (not def.allow_put or def.allow_put(npos, stack, owner)) then
local leftover = inv:add_item(def.put_listname, stack)
if leftover:get_count() > 0 then
return leftover
end
elseif def and def.put_item then
return def.put_item(npos, stack, owner)
elseif minecart.is_air_like(node.name) or minecart.is_nodecart_available(npos) then
minetest.add_item(npos, stack)
else
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.minecart_hopper_additem then
local leftover = ndef.minecart_hopper_additem(npos, stack)
if leftover:get_count() > 0 then
return leftover
end
else
return stack
end
end
end
function minecart.untake_items(pos, param2, stack)
local npos, node
if param2 then
npos, node = minecart.get_next_node(pos, (param2 + 2) % 4)
else
npos, node = pos, minetest.get_node(pos)
end
local def = RegisteredInventories[node.name]
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and def.put_listname then
return inv:add_item(def.put_listname, stack)
elseif def and def.untake_item then
return def.untake_item(npos, stack)
else
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.minecart_hopper_untakeitem then
return ndef.minecart_hopper_untakeitem(npos, stack)
end
end
end
-- Register inventory node for hopper access
-- (for example, see below)
function minecart.register_inventory(node_names, def)
for _, name in ipairs(node_names) do
RegisteredInventories[name] = {
allow_put = def.put and def.put.allow_inventory_put,
put_listname = def.put and def.put.listname,
allow_take = def.take and def.take.allow_inventory_take,
take_listname = def.take and def.take.listname,
put_item = def.put and def.put.put_item,
take_item = def.take and def.take.take_item,
untake_item = def.take and def.take.untake_item,
}
end
end
-- Allow the hopper the access to itself
minecart.register_inventory({"minecart:hopper"}, {
put = {
allow_inventory_put = function(pos, stack, player_name)
local owner = M(pos):get_string("owner")
return owner == player_name
end,
listname = "main",
},
take = {
allow_inventory_take = function(pos, stack, player_name)
local owner = M(pos):get_string("owner")
return owner == player_name
end,
listname = "main",
},
})

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):
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("@[^@=n]", "@@", 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))
for s in found:
sources = dOut.get(s, set())
sources.add(f"### {os.path.basename(fname)} ###")
dOut[s] = sources
data = generate_template("./locale/template.txt")
update_tr_file(data, "minecart", "./locale/minecart.de.tr")
print("Done.\n")
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

@ -3,7 +3,7 @@
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
@ -13,24 +13,37 @@
minecart = {}
-- Version for compatibility checks, see readme.md/history
minecart.version = 1.10
minecart.version = 2.00
minecart.hopper_enabled = minetest.settings:get_bool("minecart_hopper_enabled") ~= false
minecart.teleport_enabled = minetest.settings:get_bool("minecart_teleport_enabled") == true
-- Test for MT 5.4 new string mode
minecart.CLIP = minetest.features.use_texture_alpha_string_modes and "clip" or false
minecart.S = minetest.get_translator("minecart")
local MP = minetest.get_modpath("minecart")
dofile(MP.."/storage.lua")
dofile(MP.."/lib.lua")
dofile(MP.."/monitoring.lua")
dofile(MP.."/recording.lua")
dofile(MP.."/minecart.lua")
dofile(MP.."/buffer.lua")
dofile(MP.."/protection.lua")
dofile(MP .. "/baselib.lua")
dofile(MP .. "/storage.lua")
dofile(MP .. "/rails.lua")
dofile(MP .. "/monitoring.lua")
dofile(MP .. "/recording.lua")
dofile(MP .. "/hopperlib.lua")
dofile(MP .. "/nodelib.lua")
dofile(MP .. "/entitylib.lua")
dofile(MP .. "/api.lua")
dofile(MP .. "/minecart.lua")
dofile(MP .. "/buffer.lua")
dofile(MP .. "/protection.lua")
--dofile(MP .. "/tool.lua") # for debugging only
dofile(MP .. "/signs.lua")
dofile(MP .. "/terminal.lua")
dofile(MP .. "/pusher.lua")
if minecart.hopper_enabled then
dofile(MP.."/hopper.lua")
dofile(MP.."/mods_support.lua")
dofile(MP .. "/hopper.lua")
dofile(MP .. "/mods_support.lua")
end
dofile(MP.."/doc.lua")
dofile(MP .. "/doc.lua")
minetest.log("info", "[MOD] Minecart loaded")

View File

@ -1,326 +0,0 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local S = minecart.S
local RegisteredInventories = {}
local param2_to_dir = {[0]=
{x=0, y=0, z=1},
{x=1, y=0, z=0},
{x=0, y=0, z=-1},
{x=-1, y=0, z=0},
{x=0, y=-1, z=0},
{x=0, y=1, z=0}
}
-- Registered carts
local tValidCarts = {} -- [<cart_name_stopped>] = <cart_name_running>
local lValidCartNodes = {}
local tValidCartEntities = {}
minetest.tValidCarts = tValidCarts
function minecart.register_cart_names(cart_name_stopped, cart_name_running)
tValidCarts[cart_name_stopped] = cart_name_running
if minetest.registered_nodes[cart_name_stopped] then
lValidCartNodes[#lValidCartNodes+1] = cart_name_stopped
end
if minetest.registered_nodes[cart_name_running] then
lValidCartNodes[#lValidCartNodes+1] = cart_name_running
end
if minetest.registered_entities[cart_name_stopped] then
tValidCartEntities[cart_name_stopped] = true
end
if minetest.registered_entities[cart_name_running] then
tValidCartEntities[cart_name_running] = true
end
end
function minecart.get_node_lvm(pos)
local node = minetest.get_node_or_nil(pos)
if node then
return node
end
local vm = minetest.get_voxel_manip()
local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
local data = vm:get_data()
local param2_data = vm:get_param2_data()
local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
local idx = area:indexp(pos)
if data[idx] and param2_data[idx] then
return {
name = minetest.get_name_from_content_id(data[idx]),
param2 = param2_data[idx]
}
end
return {name="ignore", param2=0}
end
function minecart.stopped(vel, tolerance)
tolerance = tolerance or 0.05
return math.abs(vel.x) < tolerance and math.abs(vel.z) < tolerance
end
local function is_air_like(name)
local ndef = minetest.registered_nodes[name]
if ndef and ndef.buildable_to then
return true
end
return false
end
function minecart.range(val, min, max)
val = tonumber(val)
if val < min then return min end
if val > max then return max end
return val
end
function minecart.get_next_node(pos, param2)
local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos
local node = minetest.get_node(pos2)
return pos2, node
end
local function get_cart_object(pos, radius)
for _, object in pairs(minetest.get_objects_inside_radius(pos, radius or 0.5)) do
if tValidCartEntities[object:get_entity_name()] then
local vel = object:get_velocity()
if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing?
return object
end
end
end
end
-- check if cart can be pushed
function minecart.check_cart_for_pushing(pos, param2, radius)
local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos
if minetest.find_node_near(pos2, radius or 0.5, lValidCartNodes, true) then
return true
end
return get_cart_object(pos2, radius) ~= nil
end
-- check if cargo can be loaded
function minecart.check_cart_for_loading(pos, param2, radius)
local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos
if minetest.find_node_near(pos2, radius or 0.5, lValidCartNodes, true) then
return true
end
for _, object in pairs(minetest.get_objects_inside_radius(pos2, radius or 0.5)) do
if object:get_entity_name() == "minecart:cart" then
local vel = object:get_velocity()
if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing?
return true
end
end
end
return false
end
local get_next_node = minecart.get_next_node
local check_cart_for_loading = minecart.check_cart_for_loading
local check_cart_for_pushing = minecart.check_cart_for_pushing
-- Take the given number of items from the inv.
-- Returns nil if ItemList is empty.
function minecart.inv_take_items(inv, listname, num)
if inv:is_empty(listname) then
return nil
end
local size = inv:get_size(listname)
for idx = 1, size do
local items = inv:get_stack(listname, idx)
if items:get_count() > 0 then
local taken = items:take_item(num)
inv:set_stack(listname, idx, items)
return taken
end
end
return nil
end
function minecart.take_items(pos, param2, num)
local npos, node
if param2 then
npos, node = get_next_node(pos, (param2 + 2) % 4)
else
npos, node = pos, minetest.get_node(pos)
end
local def = RegisteredInventories[node.name]
local owner = M(pos):get_string("owner")
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and def.take_listname and (not def.allow_take or def.allow_take(npos, nil, owner)) then
return minecart.inv_take_items(inv, def.take_listname, num)
elseif def and def.take_item then
return def.take_item(npos, num, owner)
else
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.minecart_hopper_takeitem then
return ndef.minecart_hopper_takeitem(npos, num)
end
end
end
function minecart.put_items(pos, param2, stack)
local npos, node = get_next_node(pos, param2)
local def = RegisteredInventories[node.name]
local owner = M(pos):get_string("owner")
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and def.put_listname and (not def.allow_put or def.allow_put(npos, stack, owner)) then
local leftover = inv:add_item(def.put_listname, stack)
if leftover:get_count() > 0 then
return leftover
end
elseif def and def.put_item then
return def.put_item(npos, stack, owner)
elseif is_air_like(node.name) or check_cart_for_loading(npos) then
minetest.add_item(npos, stack)
else
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.minecart_hopper_additem then
local leftover = ndef.minecart_hopper_additem(npos, stack)
if leftover:get_count() > 0 then
return leftover
end
else
return stack
end
end
end
function minecart.untake_items(pos, param2, stack)
local npos, node
if param2 then
npos, node = get_next_node(pos, (param2 + 2) % 4)
else
npos, node = pos, minetest.get_node(pos)
end
local def = RegisteredInventories[node.name]
local inv = minetest.get_inventory({type="node", pos=npos})
if def and inv and def.put_listname then
return inv:add_item(def.put_listname, stack)
elseif def and def.untake_item then
return def.untake_item(npos, stack)
else
local ndef = minetest.registered_nodes[node.name]
if ndef and ndef.minecart_hopper_untakeitem then
return ndef.minecart_hopper_untakeitem(npos, stack)
end
end
end
function minecart.punch_cart(pos, param2, radius, dir)
local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos
local pos3 = minetest.find_node_near(pos2, radius or 0.5, lValidCartNodes, true)
if pos3 then
local node = minetest.get_node(pos3)
--print(node.name)
minecart.node_on_punch(pos3, node, nil, nil, tValidCarts[node.name], dir)
return true
end
local obj = get_cart_object(pos2, radius)
if obj then
obj:punch(obj, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = 1},
}, dir)
end
end
-- Register inventory node for hopper access
-- (for examples, see below)
function minecart.register_inventory(node_names, def)
for _, name in ipairs(node_names) do
RegisteredInventories[name] = {
allow_put = def.put and def.put.allow_inventory_put,
put_listname = def.put and def.put.listname,
allow_take = def.take and def.take.allow_inventory_take,
take_listname = def.take and def.take.listname,
put_item = def.put and def.put.put_item,
take_item = def.take and def.take.take_item,
untake_item = def.take and def.take.untake_item,
}
end
end
function minecart.register_cart_entity(entity_name, node_name, entity_def)
entity_def.velocity = {x=0, y=0, z=0} -- only used on punch
entity_def.old_dir = {x=1, y=0, z=0} -- random value to start the cart on punch
entity_def.old_pos = nil
entity_def.old_switch = 0
entity_def.node_name = node_name
minetest.register_entity(entity_name, entity_def)
-- register node for punching
minecart.register_cart_names(node_name, entity_name)
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
local cart_pos = S2P(player:get_meta():get_string("cart_pos"))
local userID = tonumber(fields.userID) or 0
M(cart_pos):set_int("userID", userID)
M(cart_pos):set_string("infotext", minetest.get_color_escape_sequence("#FFFF00")..player:get_player_name()..": "..userID)
minecart.node_at_station(player:get_player_name(), userID, cart_pos)
end
return true
end
if formname == "minecart:userID_entity" then
if fields.exit == "Save" or fields.key_enter == "true" then
local cart_pos = S2P(player:get_meta():get_string("cart_pos"))
local obj = get_cart_object(cart_pos)
if obj then
local entity = obj:get_luaentity()
entity.userID = tonumber(fields.userID) or 0
obj:set_nametag_attributes({color = "#ffff00", text = entity.owner..": "..entity.userID})
minecart.update_userID(entity.myID, entity.userID)
end
end
return true
end
return false
end)
minecart.register_inventory({"minecart:hopper"}, {
put = {
allow_inventory_put = function(pos, stack, player_name)
local owner = M(pos):get_string("owner")
return owner == player_name
end,
listname = "main",
},
take = {
allow_inventory_take = function(pos, stack, player_name)
local owner = M(pos):get_string("owner")
return owner == player_name
end,
listname = "main",
},
})

View File

@ -1,37 +1,58 @@
# textdomain: minecart
Station name=Stationsname
Waiting time/sec=Wartezeit/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)
5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=5. Um eine Route aufzuzeichnen, fahre die Route in beide Richtungen von Prellbock zu Prellbock mit dem Minecart Wagen(!). Nutze 'links-rechts' Tasten zur Steuerung.
6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=6. Schlage auf die Prellböcke um die Verbindungsdaten zu prüfen (bspw.: 'München: verbunden mit Stuttgart')
7. Optional: Configure the Minecart 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.
7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time.=7. Optional: Konfiguriere die Wagenwartezeit in beiden Prellböcken. Der Wagen startet dann nach dieser Zeit automatisch.
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=8. Optional: Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke).
9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=9. Platziere einen Wagen direkt vor einem Prellbock und prüfe, ob er nach der konfigurierten Zeit startet.
Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen
Minecart=Minecart
Minecart (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts)
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
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, the lean railway transportation automation system=Minecart, das schlanke Schienentransport Automatisierungssystem
Minecart Cart=Wagen
Minecart Speed Signs=Geschwindigkeitsbegrenzungszeichen
If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.=Wenn mehrere Wagen auf einer Route fahren, kann es vorkommen,@ndass eine Prellbock Position bereits belegt ist und ein Wagen daher früher anhält.@nDer Cart Anschieber dient in diesem Fall dazu, die Wagen wieder in Richtung Prellbock anzuschieben.@nDieser Block muss unter der Schiene mit 2 m Abstand vor dem Prellbock platziert werden.
Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.=Begrenze die Geschwindigkeit der Wagen mit Geschwindigkeitsbegrenzungszeichen@n@nDie Geschwindigkeit der Carts wird wie bisher auch über "power rails" beeinflusst. "Brake rails" sind ohne Bedeutung, das Cart bremst hier nicht. Die maximale Geschwindigkeit beträgt 8 m/s. Dies setzt eine Verhältnis von "power rails" zu "normal rails" von 1 zu 4 auf einem ebenen Streckenabschnitt voraus. Ein Streckenabschnitt ist dabei ein Reihe von Schienenblöcken ohne Richtungsänderung. Nach jeder Kurve/Knick wird die Geschwindigkeit für den nächsten Streckenabschnitt neu bestimmt, wobei hier der Schwung des Carts mit berücksichtigt wird. So kann ein Cart auch über kurze Streckenabschnitt ohne "power rails" rollen.@n@nUm das Cart zusätzlich an bestimmten Stellen abzubremsen (an Weichen oder vor einen Puffer), können Geschwindigkeitsbegrenzungszeichen an der Strecke platziert werden. Durch diese Zeichen kann die Geschwindigkeit auf 4, 2, oder 1 m/s reduziert werden. Durch das Aufhebungszeichen kann die Geschwindigkeitsbegrenzung wieder aufgehoben werden.@n@nDie Geschwindigkeitsbegrenzungszeichen müssen so neben die Strecke platziert werden, dass sie vom Cart ablesbar sind. Dies erlaubt damit unterschiedliche Geschwindigkeiten pro Fahrtrichtung.
Minecart Hopper=Minecart Hopper
Minecart (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts)
Output cart state and position, or a list of carts, if no cart number is given.=Gibt Status und Position des Wagens, oder eine Liste aller Wagen aus, wenn keine Wagennummer angegeben ist.
List of carts=Liste aller Wagen
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
Cart Pusher=Wagen Anschieber
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
[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"
No speed limit=Keine Geschwindigkeitsbegrenzung
Cart List=Cart Liste
Cart Terminal=Cart Terminal
##### not used anymore #####
Used to push a cart if the cart does not stop directly at a buffer. Block has to be placed below the rail.=Wird verwendet, um einen Wagen anzuschieben, wenn der Wagen nicht direkt an einem Puffer anhält. Der Block muss unter der Schiene platziert werden.

View File

@ -1,33 +1,53 @@
# textdomain: minecart
Station name=
Waiting 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)=
5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=
6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=
7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time.=
7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time.=
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=
9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=
Allow to dig/place rails in Minecart Landmark areas=
Minecart=
Minecart (Sneak+Click to pick up)=
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=
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, the lean railway transportation automation system=
Minecart Cart=
Minecart Speed Signs=
If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.=
Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.=
Minecart Hopper=
Minecart (Sneak+Click to pick up)=
Output cart state and position, or a list of carts, if no cart number is given.=
List of carts=
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=
Cart Pusher=
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"=
No speed limit=
Cart List=
Cart Terminal=

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
@ -11,69 +11,92 @@
]]--
local S = minecart.S
local MP = minetest.get_modpath("minecart")
local lib = dofile(MP.."/cart_lib1.lua")
local M = minetest.get_meta
lib:init(false)
local cart_entity = {
initial_properties = {
physical = false, -- otherwise going uphill breaks
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
visual = "mesh",
mesh = "carts_cart.b3d",
visual_size = {x=1, y=1},
textures = {"carts_cart.png^minecart_cart.png"},
static_save = false,
},
------------------------------------ changed
owner = nil,
------------------------------------ changed
driver = nil,
punched = false, -- used to re-send velocity and position
velocity = {x=0, y=0, z=0}, -- only used on punch
old_dir = {x=1, y=0, z=0}, -- random value to start the cart on punch
old_pos = nil,
old_switch = 0,
railtype = nil,
cargo = {},
on_rightclick = lib.on_rightclick,
on_activate = lib.on_activate,
on_detach_child = lib.on_detach_child,
on_punch = lib.on_punch,
on_step = lib.on_step,
}
minetest.register_entity("minecart:cart", cart_entity)
minecart.register_cart_names("minecart:cart", "minecart:cart")
minetest.register_craftitem("minecart:cart", {
minetest.register_node("minecart:cart", {
description = S("Minecart (Sneak+Click to pick up)"),
inventory_image = minetest.inventorycube("carts_cart_top.png", "carts_cart_side.png^minecart_logo.png", "carts_cart_side.png^minecart_logo.png"),
wield_image = "carts_cart_side.png",
on_place = function(itemstack, placer, pointed_thing)
-- use cart as tool
local under = pointed_thing.under
local node = minetest.get_node(under)
local udef = minetest.registered_nodes[node.name]
if udef and udef.on_rightclick and
not (placer and placer:is_player() and
placer:get_player_control().sneak) then
return udef.on_rightclick(under, node, placer, itemstack,
pointed_thing) or itemstack
end
tiles = {
-- up, down, right, left, back, front
"carts_cart_top.png^minecart_appl_cart_top.png",
"carts_cart_top.png",
"carts_cart_side.png^minecart_logo.png",
"carts_cart_side.png^minecart_logo.png",
"carts_cart_side.png^minecart_logo.png",
"carts_cart_side.png^minecart_logo.png",
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-8/16,-8/16,-8/16, 8/16, 8/16,-7/16},
{-8/16,-8/16, 7/16, 8/16, 8/16, 8/16},
{-8/16,-8/16,-8/16, -7/16, 8/16, 8/16},
{ 7/16,-8/16,-8/16, 8/16, 8/16, 8/16},
{-8/16,-8/16,-8/16, 8/16,-6/16, 8/16},
},
},
-- collision_box = {
-- type = "fixed",
-- fixed = {
-- {-8/16,-8/16,-8/16, 8/16,-4/16, 8/16},
-- },
-- },
paramtype2 = "facedir",
paramtype = "light",
use_texture_alpha = minecart.CLIP,
sunlight_propagates = true,
is_ground_content = false,
groups = {cracky = 2, crumbly = 2, choppy = 2},
node_placement_prediction = "",
diggable = false,
if not pointed_thing.type == "node" then
return
end
on_place = minecart.on_nodecart_place,
on_punch = minecart.on_nodecart_punch,
return lib.add_cart(itemstack, placer, pointed_thing, "minecart:cart")
on_rightclick = function(pos, node, clicker)
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", "minecart:cart_entity")
minecart.manage_attachment(clicker, object:get_luaentity(), true)
else
minecart.show_formspec(pos, clicker)
end
end
end,
set_cargo = function(pos, data)
for _,item in ipairs(data or {}) do
minetest.add_item(pos, ItemStack(item))
end
end,
get_cargo = function(pos)
local data = {}
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do
local entity = obj:get_luaentity()
if not obj:is_player() and entity and entity.name == "__builtin:item" then
obj:remove()
data[#data + 1] = entity.itemstring
end
end
return data
end,
})
minecart.register_cart_entity("minecart:cart_entity", "minecart:cart", "default", {
initial_properties = {
physical = false,
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
visual = "wielditem",
textures = {"minecart:cart"},
visual_size = {x=0.66, y=0.66, z=0.66},
static_save = false,
},
driver_allowed = true,
})
minetest.register_craft({
output = "minecart:cart",
recipe = {

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

View File

@ -3,274 +3,271 @@
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- Some notes:
-- 1) Entity IDs are volatile. For each server restart all carts get new IDs.
-- 2) Monitoring is performed for entities only. Stopped carts in form of
-- real nodes need no monitoring.
-- 3) But nodes at stations have to call 'node_at_station' to be "visible"
-- for the chat commands
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local S = minecart.S
local MP = minetest.get_modpath("minecart")
local lib = dofile(MP.."/cart_lib3.lua")
local CartsOnRail = minecart.CartsOnRail -- from storage.lua
local get_route = minecart.get_route -- from storage.lua
local NodesAtStation = {}
local tCartsOnRail = minecart.CartsOnRail
local Queue = {}
local first = 0
local last = -1
--
-- Helper functions
--
local function get_pos_vel_pitch_yaw(item)
if item.start_time and item.start_key then -- cart on recorded route
local run_time = minetest.get_gametime() - item.start_time
local waypoints = get_route(item.start_key).waypoints
local waypoint = waypoints[run_time]
if waypoint then
return S2P(waypoint[1]), S2P(waypoint[2]), 0, 0
end
end
if item.last_pos then
item.last_pos = vector.round(item.last_pos)
if carts:is_rail(item.last_pos, minetest.raillike_group("rail")) then
return item.last_pos, item.last_vel, item.last_pitch or 0, item.last_yaw or 0
end
item.last_pos.y = item.last_pos.y - 1
if carts:is_rail(item.last_pos, minetest.raillike_group("rail")) then
return item.last_pos, item.last_vel, item.last_pitch or 0, item.last_yaw or 0
end
end
return item.start_pos, {x=0, y=0, z=0}, 0, 0
local function push(cycle, item)
last = last + 1
item.cycle = cycle
Queue[last] = item
end
--
-- Monitoring of cart entities
--
function minecart.add_to_monitoring(obj, myID, owner, userID)
local pos = vector.round(obj:get_pos())
CartsOnRail[myID] = {
start_key = lib.get_route_key(pos),
start_pos = pos,
owner = owner, -- needed for query API
userID = userID, -- needed for query API
stopped = true,
entity_name = obj:get_entity_name()
}
end
-- Called after cart number formspec is closed
function minecart.update_userID(myID, userID)
if CartsOnRail[myID] then
CartsOnRail[myID].userID = userID
local function pop(cycle)
if first > last then return end
local item = Queue[first]
if item.cycle < cycle then
Queue[first] = nil -- to allow garbage collection
first = first + 1
return item
end
end
-- When cart entity is removed
function minecart.remove_from_monitoring(myID)
if myID then
CartsOnRail[myID] = nil
minecart.store_carts()
end
end
-- For node carts at stations
function minecart.node_at_station(owner, userID, pos)
NodesAtStation[owner] = NodesAtStation[owner] or {}
NodesAtStation[owner][userID] = pos
end
function minecart.start_cart(pos, myID)
local item = CartsOnRail[myID]
if item and item.stopped then
item.stopped = false
item.start_pos = pos
item.start_time = nil
-- cart started from a buffer?
local start_key = lib.get_route_key(pos)
if start_key then
item.start_time = minetest.get_gametime()
item.start_key = start_key
item.junctions = minecart.get_route(start_key).junctions
minecart.store_carts()
local function is_player_nearby(pos)
for _, object in pairs(minetest.get_objects_inside_radius(pos, 64)) do
if object:is_player() then
return true
end
end
return false
end
function minecart.stop_cart(pos, myID)
local item = CartsOnRail[myID]
if item and not item.stopped then
item.start_time = nil
item.start_key = nil
item.start_pos = nil
item.junctions = nil
item.stopped = true
minecart.store_carts()
return true
local function zombie_to_entity(pos, cart, checkpoint)
local vel = {x = 0, y = 0, z = 0}
local obj = minecart.add_entitycart(pos, cart.node_name, cart.entity_name,
vel, cart.cargo, cart.owner, cart.userID)
if obj then
local entity = obj:get_luaentity()
entity.reenter = checkpoint
entity.junctions = cart.junctions
entity.is_running = true
entity.arrival_time = 0
cart.objID = entity.objID
end
return false
end
local function monitoring()
local to_be_added = {}
for key, item in pairs(CartsOnRail) do
local entity = minetest.luaentities[key]
--print("Cart:", key, item.owner, item.userID, item.stopped)
local function get_checkpoint(cart)
local cp = cart.checkpoints[cart.idx]
if not cp then
cart.idx = #cart.checkpoints
cp = cart.checkpoints[cart.idx]
end
local pos = H2P(cp[1])
-- 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
-- the station name or position string, or if cart is running,
-- the distance to the query_pos.
local function get_cart_state_and_loc(name, userID, query_pos)
if tCartsOnRail[name] and tCartsOnRail[name][userID] then
local cart = tCartsOnRail[name][userID]
local pos = cart.last_pos or cart.pos
local loc = minecart.get_buffer_name(cart.pos) or
math.floor(vector.distance(pos, query_pos))
if cart.objID == 0 then
return "stopped", minecart.get_buffer_name(cart.pos) or
math.floor(vector.distance(pos, query_pos)), cart.node_name
else
return "running", math.floor(vector.distance(pos, query_pos)), cart.node_name
end
end
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)
while cart do
-- All running cars
if cart.objID and cart.objID ~= 0 then
cart.idx = cart.idx + 1
local entity = minetest.luaentities[cart.objID]
if entity then -- cart entity running
local pos = entity.object:get_pos()
local vel = entity.object:get_velocity()
local rot = entity.object:get_rotation()
if pos and vel and rot then
if not minetest.get_node_or_nil(pos) then -- unloaded area
lib.unload_cart(pos, vel, entity, item)
item.stopped = minecart.stopped(vel)
end
-- store last pos from cart
item.last_pos, item.last_vel, item.last_pitch, item.last_yaw = pos, vel, rot.x, rot.y
end
else -- no cart running
local pos, vel, pitch, yaw = get_pos_vel_pitch_yaw(item)
if pos and vel then
if minetest.get_node_or_nil(pos) then -- loaded area
if pitch > 0 then
pos.y = pos.y + 0.5
end
local myID = lib.load_cart(pos, vel, pitch, yaw, item)
if myID then
item.stopped = minecart.stopped(vel)
to_be_added[myID] = table.copy(item)
CartsOnRail[key] = nil -- invalid old ID
end
end
item.last_pos, item.last_vel, item.last_pitch, item.last_yaw = pos, vel, pitch, yaw
if pos then
cart.last_pos = vector.round(pos)
--print("entity card " .. cart.userID .. " at " .. P2S(cart.last_pos))
else
-- should never happen
minetest.log("error", "[minecart] Cart of owner "..(item.owner or "nil").." got lost")
CartsOnRail[key] = nil
print("entity card without pos!")
end
push(cycle, cart)
elseif cart.checkpoints then
local cp, last_cp = get_checkpoint(cart)
if cp then
cart.last_pos = H2P(cp[1])
--print("zombie " .. cart.userID .. " at " .. P2S(cart.last_pos))
if is_player_nearby(cart.last_pos) or last_cp then
zombie_to_entity(cart.last_pos, cart, cp)
end
push(cycle, cart)
else
print("zombie got lost")
end
-- table maintenance
local is_changed = false
for key,val in pairs(to_be_added) do
CartsOnRail[key] = val
is_changed = true
else
local pos = cart.last_pos or cart.pos
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))
end
if is_changed then
elseif cart and not cart.objID and tCartsOnRail[cart.owner] then
-- Delete carts marked as "to be deleted"
tCartsOnRail[cart.owner][cart.userID] = nil
end
cart = pop(cycle)
end
minetest.after(2, monitoring, cycle + 1)
end
minetest.after(5, monitoring, 2)
function minecart.monitoring_add_cart(owner, userID, pos, node_name, entity_name)
--print("monitoring_add_cart", owner, userID)
tCartsOnRail[owner] = tCartsOnRail[owner] or {}
tCartsOnRail[owner][userID] = {
owner = owner,
userID = userID,
objID = 0,
pos = pos,
idx = 0,
node_name = node_name,
entity_name = entity_name,
}
minecart.store_carts()
end
function minecart.start_monitoring(owner, userID, pos, objID, checkpoints, junctions, cargo)
--print("start_monitoring", owner, userID)
if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then
tCartsOnRail[owner][userID].pos = pos
tCartsOnRail[owner][userID].objID = objID
tCartsOnRail[owner][userID].checkpoints = checkpoints
tCartsOnRail[owner][userID].junctions = junctions
tCartsOnRail[owner][userID].cargo = cargo
tCartsOnRail[owner][userID].idx = 0
push(0, tCartsOnRail[owner][userID])
minecart.store_carts()
end
minetest.after(1, monitoring)
end
-- delay the start to prevent cart disappear into nirvana
minetest.register_on_mods_loaded(function()
minetest.after(10, monitoring)
end)
function minecart.stop_monitoring(owner, userID, pos)
--print("stop_monitoring", owner, userID)
if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then
tCartsOnRail[owner][userID].pos = pos
tCartsOnRail[owner][userID].objID = 0
minecart.store_carts()
end
end
function minecart.monitoring_remove_cart(owner, userID)
--print("monitoring_remove_cart", owner, userID)
if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then
tCartsOnRail[owner][userID].objID = nil
tCartsOnRail[owner][userID] = nil
minecart.store_carts()
end
end
function minecart.monitoring_valid_cart(owner, userID, pos, node_name)
if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then
return vector.equals(tCartsOnRail[owner][userID].pos, pos) and
tCartsOnRail[owner][userID].node_name == node_name
end
end
function minecart.userID_available(owner, userID)
return not tCartsOnRail[owner] or tCartsOnRail[owner][userID] == nil
end
function minecart.get_cart_monitoring_data(owner, userID)
if tCartsOnRail[owner] then
return tCartsOnRail[owner][userID]
end
end
--
-- API functions
--
-- Return a list of carts with current position and speed.
function minecart.get_cart_list()
local tbl = {}
for id, item in pairs(CartsOnRail) do
local pos, speed = calc_pos_and_vel(item)
tbl[#tbl+1] = {pos = pos, speed = speed, id = id}
end
return tbl
end
local function get_cart_pos(query_pos, cart_pos)
local dist = math.floor(vector.distance(cart_pos, query_pos))
local station = lib.get_station_name(cart_pos)
return station or dist
end
local function get_cart_state(name, userID)
for id, item in pairs(CartsOnRail) do
if item.owner == name and item.userID == userID then
return item.stopped and "stopped" or "running", item.last_pos
end
end
return nil, nil
end
-- Needed by storage to re-construct the queue after server start
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.",
func = function(name, param)
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(name):get_pos()
local query_pos = minetest.get_player_by_name(owner):get_pos()
if userID then
-- First check if it is a node cart at a station
local cart_pos = NodesAtStation[name] and NodesAtStation[name][userID]
if cart_pos then
local pos = get_cart_pos(query_pos, cart_pos)
return true, "Cart #"..userID.." stopped at "..pos.." "
end
-- Check all running carts
local state, cart_pos = get_cart_state(name, userID)
if state and cart_pos then
local pos = get_cart_pos(query_pos, cart_pos)
if type(pos) == "string" then
return true, "Cart #"..userID.." stopped at "..pos.." "
elseif state == "running" then
return true, "Cart #"..userID.." running "..pos.." m away "
else
return true, "Cart #"..userID.." stopped "..pos.." m away "
end
end
return false, "Cart #"..userID.." is unknown"
else
return true, get_cart_info(owner, userID, query_pos)
elseif tCartsOnRail[owner] then
-- Output a list with all numbers
local tbl = {}
for userID, pos in pairs(NodesAtStation[name] or {}) do
for userID, cart in pairs(tCartsOnRail[owner]) do
tbl[#tbl + 1] = userID
end
for id, item in pairs(CartsOnRail) do
if item.owner == name then
tbl[#tbl + 1] = item.userID
end
end
return true, "List of carts: "..table.concat(tbl, ", ").." "
return true, S("List of carts") .. ": "..table.concat(tbl, ", ").." "
end
end
})
function minecart.cmnd_cart_state(name, userID)
-- First check if it is a node cart at a station
local pos = NodesAtStation[name] and NodesAtStation[name][userID]
if pos then
return "stopped"
end
return get_cart_state(name, userID)
local state, loc = get_cart_state_and_loc(name, userID, {x=0, y=0, z=0})
return state
end
function minecart.cmnd_cart_location(name, userID, query_pos)
-- First check if it is a node cart at a station
local station = NodesAtStation[name] and NodesAtStation[name][userID]
if station then
return station
local state, loc = get_cart_state_and_loc(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
local state, cart_pos = get_cart_state(name, userID)
if state then
return get_cart_pos(query_pos, cart_pos)
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()
@ -357,4 +354,3 @@ minetest.register_on_mods_loaded(function()
})
end
end)

139
minecart/nodelib.lua Normal file
View File

@ -0,0 +1,139 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
function minecart.get_nodecart_nearby(pos, param2, radius)
local pos2 = param2 and vector.add(pos, minecart.param2_to_dir(param2)) or pos
local pos3 = minetest.find_node_near(pos2, radius or 0.5, minecart.lCartNodeNames, true)
if pos3 then
return pos3, minetest.get_node(pos3)
end
end
-- Convert node to entity and start cart
function minecart.start_nodecart(pos, node_name, puncher, punch_dir)
local owner = M(pos):get_string("owner")
local userID = M(pos):get_int("userID")
-- check if valid cart
if not minecart.monitoring_valid_cart(owner, userID, pos, node_name) then
--print("invalid cart", owner, userID, P2S(pos), node_name)
M(pos):set_string("infotext",
minetest.get_color_escape_sequence("#FFFF00") .. owner .. ": 0")
return
end
-- Only the owner or a noplayer can start the cart, but owner has to be online
if minecart.is_owner(puncher, owner) and minetest.get_player_by_name(owner) and
userID ~= 0 then
local entity_name = minecart.tNodeNames[node_name]
local obj = minecart.node_to_entity(pos, node_name, entity_name)
if obj then
local entity = obj:get_luaentity()
if puncher then
local yaw = puncher:get_look_horizontal()
entity.object:set_rotation({x = 0, y = yaw, z = 0})
elseif punch_dir then
local yaw = minetest.dir_to_yaw(punch_dir)
entity.object:set_rotation({x = 0, y = yaw, z = 0})
end
minecart.start_entitycart(entity, pos)
end
end
end
function minecart.show_formspec(pos, clicker)
local owner = M(pos):get_string("owner")
if minecart.is_owner(clicker, owner) then
clicker:get_meta():set_string("cart_pos", P2S(pos))
minetest.show_formspec(owner, "minecart:userID_node",
"size[4,3]" ..
"label[0,0;" .. S("Enter cart number") .. ":]" ..
"field[1,1;3,1;userID;;]" ..
"button_exit[1,2;2,1;exit;" .. S("Save") .. "]")
end
end
-- Player places the node
function minecart.on_nodecart_place(itemstack, placer, pointed_thing)
local node_name = itemstack:get_name()
local param2 = minetest.dir_to_facedir(placer:get_look_dir())
local owner = placer:get_player_name()
-- Add node
if minecart.is_rail(pointed_thing.under) then
minecart.add_nodecart(pointed_thing.under, node_name, param2, {}, owner, 0)
placer:get_meta():set_string("cart_pos", P2S(pointed_thing.under))
minecart.show_formspec(pointed_thing.under, placer)
elseif minecart.is_rail(pointed_thing.above) then
minecart.add_nodecart(pointed_thing.above, node_name, param2, {}, owner, 0)
placer:get_meta():set_string("cart_pos", P2S(pointed_thing.above))
minecart.show_formspec(pointed_thing.above, placer)
else
return itemstack
end
minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
{pos = pointed_thing.above})
if not (creative and creative.is_enabled_for
and creative.is_enabled_for(placer:get_player_name())) then
itemstack:take_item()
end
return itemstack
end
-- Start the node cart (or dig by shift+leftclick)
function minecart.on_nodecart_punch(pos, node, puncher, pointed_thing)
--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
if puncher:get_player_control().sneak then
local ndef = minetest.registered_nodes[node.name]
if not ndef.has_cargo or not ndef.has_cargo(pos) then
minecart.remove_nodecart(pos)
minecart.add_node_to_player_inventory(pos, puncher, node.name)
minecart.monitoring_remove_cart(owner, userID)
end
else
minecart.start_nodecart(pos, node.name, puncher)
end
end
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "minecart:userID_node" then
if fields.exit or fields.key_enter == "true" then
local cart_pos = S2P(player:get_meta():get_string("cart_pos"))
local owner = M(cart_pos):get_string("owner")
if minecart.is_owner(player, owner) then
local userID = tonumber(fields.userID) or 0
if minecart.userID_available(owner, userID) then
M(cart_pos):set_int("userID", userID)
M(cart_pos):set_string("infotext",
minetest.get_color_escape_sequence("#FFFF00") ..
player:get_player_name() .. ": " .. userID)
local node = minetest.get_node(cart_pos)
local entity_name = minecart.tNodeNames[node.name]
minecart.monitoring_add_cart(owner, userID, cart_pos, node.name, entity_name)
end
end
end
return true
end
return false
end)

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

67
minecart/pusher.lua Normal file
View File

@ -0,0 +1,67 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local CYCLE_TIME = 4
local function node_timer(pos)
local node = minetest.get_node(pos)
local dir = minetest.facedir_to_dir(node.param2)
minecart.punch_cart({x = pos.x, y = pos.y + 1, z = pos.z}, nil, 1, dir)
return true
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
techage.remove_node(pos, oldnode, oldmetadata)
end
minetest.register_node("minecart:cart_pusher", {
description = S("Cart Pusher"),
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{-8/16,-8/16,-8/16, 8/16, 8/16, 8/16},
{-1/16, 8/16,-4/16, 1/16, 10/16, 4/16},
},
},
tiles = {
-- up, down, right, left, back, front
"default_steel_block.png^minecart_pusher_top.png",
"default_steel_block.png",
"default_steel_block.png^minecart_pusher.png",
"default_steel_block.png^minecart_pusher.png",
"default_steel_block.png^minecart_pusher.png",
"default_steel_block.png^minecart_pusher.png",
},
after_place_node = function(pos)
minetest.get_node_timer(pos):start(CYCLE_TIME)
end,
on_timer = node_timer,
paramtype2 = "facedir",
groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = true,
sounds = default.node_sound_metal_defaults(),
})
minetest.register_craft({
output = "minecart:cart_pusher",
recipe = {
{"dye:black", "default:steel_ingot", "dye:yellow"},
{"default:steel_ingot", "default:mese_crystal", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})

548
minecart/rails.lua Normal file
View File

@ -0,0 +1,548 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P2H = minetest.hash_node_position
local get_node_lvm = minecart.get_node_lvm
local MAX_SPEED = 8
local SLOWDOWN = 0.3
local MAX_NODES = 100
--waypoint = {
-- dot = travel direction,
-- pos = destination pos,
-- speed = 10 times the section speed (as int),
-- limit = 10 times the speed limit (as int),
--}
--
-- waypoints = {facedir = waypoint,...}
local tWaypoints = {} -- {pos_hash = waypoints, ...}
local tRailsPower = {
["carts:rail"] = 0,
["carts:powerrail"] = 1,
["minecart:rail"] = 0,
["minecart:powerrail"] = 1,
["carts:brakerail"] = 0,
}
-- Real rails from the mod carts
local tRails = {
["carts:rail"] = true,
["carts:powerrail"] = true,
["carts:brakerail"] = true,
["minecart:rail"] = true,
["minecart:powerrail"] = true,
}
-- Rails plus node carts. Used to find waypoints. Added via add_raillike_nodes
local tRailsExt = {
["carts:rail"] = true,
["carts:powerrail"] = true,
["carts:brakerail"] = true,
["minecart:rail"] = true,
["minecart:powerrail"] = true,
}
local tSigns = {
["minecart:speed1"] = 1,
["minecart:speed2"] = 2,
["minecart:speed4"] = 4,
["minecart:speed8"] = 8,
}
-- Real rails from the mod carts
local lRails = {"carts:rail", "carts:powerrail", "carts:brakerail", "minecart:rail", "minecart:powerrail"}
-- Rails plus node carts used to find waypoints, , added via add_raillike_nodes
local lRailsExt = {"carts:rail", "carts:powerrail", "carts:brakerail", "minecart:rail", "minecart:powerrail"}
minecart.MAX_SPEED = MAX_SPEED
minecart.lRails = lRails
minecart.tRails = tRails
minecart.tRailsExt = tRailsExt
minecart.lRailsExt = lRailsExt
local Dot2Dir = {}
local Dir2Dot = {}
local Facedir2Dir = {[0] =
{x= 0, y=0, z= 1},
{x= 1, y=0, z= 0},
{x= 0, y=0, z=-1},
{x=-1, y=0, z= 0},
{x= 0, y=-1, z= 0},
{x= 0, y=1, z= 0},
}
local flip = {
[0] = 2,
[1] = 3,
[2] = 0,
[3] = 1,
[4] = 5,
[5] = 4,
}
-- facedir = math.floor(dot / 4)
-- y = (dot % 4) - 1
-- Create helper tables
for facedir = 0,3 do
for y = -1,1 do
local dot = 1 + facedir * 4 + y
local dir = vector.new(Facedir2Dir[facedir])
dir.y = y
Dot2Dir[dot] = dir
Dir2Dot[P2H(dir)] = dot
end
end
local function dot2dir(dot) return vector.new(Dot2Dir[dot]) end
local function facedir2dir(fd) return vector.new(Facedir2Dir[fd]) end
minecart.dot2dir = dot2dir
minecart.facedir2dir = facedir2dir
-------------------------------------------------------------------------------
-- waypoint metadata
-------------------------------------------------------------------------------
local function has_metadata(pos)
return M(pos):contains("waypoints")
end
local function get_metadata(pos)
local hash = P2H(pos)
if tWaypoints[hash] then
return tWaypoints[hash]
end
local s = M(pos):get_string("waypoints")
if s ~= "" then
tWaypoints[hash] = minetest.deserialize(s)
return tWaypoints[hash]
end
end
local function get_oldmetadata(meta)
local s = meta:get_string("waypoints")
if s ~= "" then
return minetest.deserialize(s)
end
end
local function set_metadata(pos, t)
local hash = P2H(pos)
tWaypoints[hash] = t
local s = minetest.serialize(t)
M(pos):set_string("waypoints", s)
-- visualization
local name = get_node_lvm(pos).name
if name == "carts:rail" then
minetest.swap_node(pos, {name = "minecart:rail"})
elseif name == "carts:powerrail" then
minetest.swap_node(pos, {name = "minecart:powerrail"})
end
end
local function del_metadata(pos)
local hash = P2H(pos)
tWaypoints[hash] = nil
local meta = M(pos)
if meta:contains("waypoints") then
meta:set_string("waypoints", "")
-- visualization
local name = get_node_lvm(pos).name
if name == "minecart:rail" then
minetest.swap_node(pos, {name = "carts:rail"})
elseif name == "minecart:powerrail" then
minetest.swap_node(pos, {name = "carts:powerrail"})
end
end
end
-------------------------------------------------------------------------------
-- find_next_waypoint
-------------------------------------------------------------------------------
local function check_right(pos, facedir)
local fdr = (facedir + 1) % 4 -- right
local new_pos = vector.add(pos, facedir2dir(fdr))
local name = get_node_lvm(new_pos).name
if tRailsExt[name] or tSigns[name] then
return true
end
new_pos.y = new_pos.y - 1
if tRailsExt[get_node_lvm(new_pos).name] then
return true
end
end
local function check_left(pos, facedir)
local fdl = (facedir + 3) % 4 -- left
local new_pos = vector.add(pos, facedir2dir(fdl))
local name = get_node_lvm(new_pos).name
if tRailsExt[name] or tSigns[name] then
return true
end
new_pos.y = new_pos.y - 1
if tRailsExt[get_node_lvm(new_pos).name] then
return true
end
end
local function get_next_pos(pos, facedir, y)
local new_pos = vector.add(pos, facedir2dir(facedir))
new_pos.y = new_pos.y + y
local name = get_node_lvm(new_pos).name
return tRailsExt[name] ~= nil, new_pos, tRailsPower[name] or 0
end
local function is_ramp(pos)
return tRailsExt[get_node_lvm({x = pos.x, y = pos.y + 1, z = pos.z}).name] ~= nil
end
-- Check also the next position to detect a ramp
local function slope_detection(pos, facedir)
local is_rail, new_pos = get_next_pos(pos, facedir, 0)
if not is_rail then
return is_ramp(new_pos)
end
end
local function find_next_waypoint(pos, facedir, y)
local cnt = 0
local name = get_node_lvm(pos).name
local speed = tRailsPower[name] or 0
local is_rail, new_pos, _speed
while cnt < MAX_NODES do
is_rail, new_pos, _speed = get_next_pos(pos, facedir, y)
speed = speed + _speed
if not is_rail then
return pos, y == 0 and is_ramp(new_pos), speed
end
if y == 0 then -- no slope
if check_right(new_pos, facedir) then
return new_pos, slope_detection(new_pos, facedir), speed
elseif check_left(new_pos, facedir) then
return new_pos, slope_detection(new_pos, facedir), speed
end
end
pos = new_pos
cnt = cnt + 1
end
return new_pos, false, speed
end
-------------------------------------------------------------------------------
-- find_all_next_waypoints
-------------------------------------------------------------------------------
local function check_front_up_down(pos, facedir)
local new_pos = vector.add(pos, facedir2dir(facedir))
if tRailsExt[get_node_lvm(new_pos).name] then
return 0
end
new_pos.y = new_pos.y - 1
if tRailsExt[get_node_lvm(new_pos).name] then
return -1
end
new_pos.y = new_pos.y + 2
if tRailsExt[get_node_lvm(new_pos).name] then
return 1
end
end
-- Search for rails in all 4 directions
local function find_all_rails_nearby(pos)
--print("find_all_rails_nearby")
local tbl = {}
for fd = 0, 3 do
tbl[#tbl + 1] = check_front_up_down(pos, fd, true)
end
return tbl
end
-- Recalc the value based on waypoint length and slope
local function recalc_speed(num_pow_rails, pos1, pos2, y)
local num_norm_rails = vector.distance(pos1, pos2) - num_pow_rails
local ratio, speed
if y ~= 0 then
num_norm_rails = math.floor(num_norm_rails / 1.41 + 0.5)
end
if y ~= -1 then
if num_pow_rails == 0 then
return num_norm_rails * -SLOWDOWN
else
ratio = math.floor(num_norm_rails / num_pow_rails)
ratio = minecart.range(ratio, 0, 11)
end
else
ratio = 3 + num_norm_rails * SLOWDOWN + num_pow_rails
end
if y == 1 then
speed = 7 - ratio
elseif y == -1 then
speed = 15 - ratio
else
speed = 11 - ratio
end
return minecart.range(speed, 0, 8)
end
local function find_all_next_waypoints(pos)
local wp = {}
local dots = {}
for facedir = 0,3 do
local y = check_front_up_down(pos, facedir)
if y then
local new_pos, is_ramp, speed = find_next_waypoint(pos, facedir, y)
--print("find_all_next_waypoints", P2S(new_pos), is_ramp, speed)
local dot = 1 + facedir * 4 + y
speed = recalc_speed(speed, pos, new_pos, y) * 10
wp[facedir] = {dot = dot, pos = new_pos, speed = speed, is_ramp = is_ramp}
end
end
return wp
end
-------------------------------------------------------------------------------
-- get_waypoint
-------------------------------------------------------------------------------
-- If ramp, stop 0.5 nodes earlier or later
local function ramp_correction(pos, wp, facedir)
if wp.is_ramp or pos.y < wp.pos.y then -- ramp detection
local dir = facedir2dir(facedir)
local pos = wp.pos
wp.cart_pos = {
x = pos.x - dir.x / 2,
y = pos.y,
z = pos.z - dir.z / 2}
elseif pos.y > wp.pos.y then
local dir = facedir2dir(facedir)
local pos = wp.pos
wp.cart_pos = {
x = pos.x + dir.x / 2,
y = pos.y,
z = pos.z + dir.z / 2}
end
return wp
end
-- Returns waypoint and is_junction
function minecart.get_waypoint(pos, facedir, ctrl, uturn)
local t = get_metadata(pos)
if not t then
t = find_all_next_waypoints(pos)
set_metadata(pos, t)
end
local left = (facedir + 3) % 4
local right = (facedir + 1) % 4
local back = (facedir + 2) % 4
if ctrl.right and t[right] then return t[right], t[facedir] ~= nil or t[left] ~= nil end
if ctrl.left and t[left] then return t[left] , t[facedir] ~= nil or t[right] ~= nil end
if t[facedir] then return ramp_correction(pos, t[facedir], facedir), false end
if t[right] then return ramp_correction(pos, t[right], right), false end
if t[left] then return ramp_correction(pos, t[left], left), false end
if uturn and t[back] then return t[back], false end
end
-------------------------------------------------------------------------------
-- delete waypoints
-------------------------------------------------------------------------------
local function delete_counterpart_metadata(pos, wp)
for facedir = 0,3 do
if wp[facedir] then
del_metadata(wp[facedir].pos)
end
end
del_metadata(pos)
end
local function delete_next_metadata(pos, facedir, y)
local cnt = 0
while cnt <= MAX_NODES do
local is_rail, new_pos = get_next_pos(pos, facedir, y)
if not is_rail then
return
end
if has_metadata(new_pos) then
del_metadata(new_pos)
end
pos = new_pos
cnt = cnt + 1
end
if has_metadata(pos) then
del_metadata(pos)
end
end
function minecart.delete_waypoint(pos)
if has_metadata(pos) then
local wp = get_metadata(pos)
delete_counterpart_metadata(pos, wp)
return
end
for facedir = 0,3 do
local y = check_front_up_down(pos, facedir)
if y then
local new_pos = vector.add(pos, facedir2dir(facedir))
new_pos.y = new_pos.y + y
if has_metadata(new_pos) then
local wp = get_metadata(new_pos)
delete_counterpart_metadata(new_pos, wp)
else
delete_next_metadata(pos, facedir, y)
end
end
end
end
carts:register_rail("minecart:rail", {
description = "Rail",
tiles = {
"carts_rail_straight.png^minecart_waypoint.png", "carts_rail_curved.png^minecart_waypoint.png",
"carts_rail_t_junction.png^minecart_waypoint.png", "carts_rail_crossing.png^minecart_waypoint.png"
},
inventory_image = "carts_rail_straight.png",
wield_image = "carts_rail_straight.png",
groups = carts:get_rail_groups({not_in_creative_inventory = 1}),
drop = "carts:rail",
}, {})
carts:register_rail("minecart:powerrail", {
description = "Powered Rail",
tiles = {
"carts_rail_straight_pwr.png^minecart_waypoint.png", "carts_rail_curved_pwr.png^minecart_waypoint.png",
"carts_rail_t_junction_pwr.png^minecart_waypoint.png", "carts_rail_crossing_pwr.png^minecart_waypoint.png"
},
inventory_image = "carts_rail_straight.png",
wield_image = "carts_rail_straight.png",
groups = carts:get_rail_groups({not_in_creative_inventory = 1}),
drop = "carts:powerrail",
}, {})
for name,_ in pairs(tRails) do
minetest.override_item(name, {
after_destruct = minecart.delete_waypoint,
after_place_node = minecart.delete_waypoint,
})
end
-------------------------------------------------------------------------------
-- API functions
-------------------------------------------------------------------------------
-- Return new cart pos and if an extra move cycle is needed
function minecart.get_current_cart_pos_correction(curr_pos, curr_fd, curr_y, new_dot)
if new_dot then
local new_y = (new_dot % 4) - 1
local new_fd = math.floor(new_dot / 4)
if curr_y == -1 or new_y == -1 then
local new_fd = math.floor(new_dot / 4)
local dir = facedir2dir(new_fd)
return {
x = curr_pos.x + dir.x / 2,
y = curr_pos.y,
z = curr_pos.z + dir.z / 2}, new_y == -1
elseif curr_y == 1 and curr_fd ~= new_fd then
local dir = facedir2dir(new_fd)
return {
x = curr_pos.x + dir.x / 2,
y = curr_pos.y,
z = curr_pos.z + dir.z / 2}, true
elseif curr_y == 1 or new_y == 1 then
local dir = facedir2dir(curr_fd)
return {
x = curr_pos.x - dir.x / 2,
y = curr_pos.y,
z = curr_pos.z - dir.z / 2}, false
end
end
return curr_pos, false
end
-- Called by carts, returns the speed value or nil
function minecart.get_speedlimit(pos, facedir)
local fd = (facedir + 1) % 4 -- right
local new_pos = vector.add(pos, facedir2dir(fd))
local node = get_node_lvm(new_pos)
if tSigns[node.name] and node.param2 == facedir then
return tSigns[node.name]
end
fd = (facedir + 3) % 4 -- left
new_pos = vector.add(pos, facedir2dir(fd))
node = get_node_lvm(new_pos)
if tSigns[node.name] and node.param2 == facedir then
return tSigns[node.name]
end
end
-- Called by carts, to delete temporarily created waypoints
function minecart.delete_cart_waypoint(pos)
del_metadata(pos)
end
-- Called by signs, to delete the rail waypoints nearby
function minecart.delete_signs_waypoint(pos)
local node = minetest.get_node(pos)
local facedir = (node.param2 + 1) % 4 -- right
local new_pos = vector.add(pos, facedir2dir(facedir))
if tRailsExt[get_node_lvm(new_pos).name] then
minecart.delete_waypoint(new_pos)
end
facedir = (node.param2 + 3) % 4 -- left
new_pos = vector.add(pos, facedir2dir(facedir))
if tRailsExt[get_node_lvm(new_pos).name] then
minecart.delete_waypoint(new_pos)
end
end
function minecart.is_rail(pos)
return tRails[get_node_lvm(pos).name] ~= nil
end
-- To register node cart names
function minecart.add_raillike_nodes(name)
tRailsExt[name] = true
lRailsExt[#lRailsExt + 1] = name
end
--minetest.register_lbm({
-- label = "Delete waypoints",
-- name = "minecart:del_meta",
-- nodenames = {"carts:brakerail"},
-- run_at_every_load = true,
-- action = function(pos, node)
-- del_metadata(pos)
-- 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
@ -14,76 +14,172 @@
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local S = minecart.S
local MP = minetest.get_modpath("minecart")
local lib = dofile(MP.."/cart_lib3.lua")
local CartsOnRail = minecart.CartsOnRail -- from storage.lua
local get_route = minecart.get_route -- from storage.lua
local function dashboard_destroy(self)
if self.driver and self.hud_id then
local player = minetest.get_player_by_name(self.driver)
if player then
player:hud_remove(self.hud_id)
self.hud_id = nil
end
end
end
local function dashboard_create(self)
if self.driver then
local player = minetest.get_player_by_name(self.driver)
if player then
dashboard_destroy(self)
self.hud_id = player:hud_add({
name = "minecart",
hud_elem_type = "text",
position = {x = 0.4, y = 0.25},
scale = {x=100, y=100},
text = "Recording:",
number = 0xFFFFFF,
size = {x = 1},
})
end
end
end
local function dashboard_update(self)
if self.driver and self.hud_id then
local player = minetest.get_player_by_name(self.driver)
if player then
local time = self.runtime or 0
local dir = (self.ctrl and self.ctrl.left and S("left")) or
(self.ctrl and self.ctrl.right and S("right")) or S("straight")
local speed = math.floor((self.curr_speed or 0) + 0.5)
local s = string.format(S("Recording") ..
" | " .. S("speed") ..
": %.1f | " .. S("next junction") ..
": %-8s | " .. S("Travel time") .. ": %.1f s",
speed, dir, time)
player:hud_change(self.hud_id, "text", s)
end
end
end
local function check_waypoint(self, pos)
-- If next waypoint already reached but not handled
-- determine next waypoint
if vector.equals(pos, self.waypoint.pos) then
local rot = self.object:get_rotation()
local dir = minetest.yaw_to_dir(rot.y)
dir.y = math.floor((rot.x / (math.pi/4)) + 0.5)
local facedir = minetest.dir_to_facedir(dir)
local waypoint = minecart.get_waypoint(pos, facedir, self.ctrl or {}, false)
if waypoint then
return waypoint.pos
end
end
return self.waypoint.pos
end
--
-- Route recording
--
function minecart.start_recording(self, pos)
self.start_key = lib.get_route_key(pos, self.driver)
if self.start_key then
self.waypoints = {}
--print("start_recording")
if self.driver then
self.start_pos = minecart.get_buffer_pos(pos, self.driver)
if self.start_pos then
self.checkpoints = {} -- {cart_pos, next_waypoint_pos, speed, dot}
self.junctions = {}
self.recording = true
self.next_time = minetest.get_us_time() + 1000000
minetest.chat_send_player(self.driver, S("[minecart] Start route recording!"))
self.is_recording = true
self.rec_time = self.timebase
self.hud_time = self.timebase
self.runtime = 0
self.num_sections = 0
self.sum_speed = 0
self.ctrl = {}
dashboard_create(self)
dashboard_update(self, 0)
end
end
end
function minecart.store_next_waypoint(self, pos, vel)
if self.start_key and self.recording and self.driver and
self.next_time < minetest.get_us_time() then
self.next_time = minetest.get_us_time() + 1000000
self.waypoints[#self.waypoints+1] = {P2S(vector.round(pos)), P2S(vector.round(vel))}
elseif self.recording and not self.driver then
self.recording = false
self.waypoints = nil
self.junctions = nil
end
end
-- destination reached(speed == 0)
function minecart.stop_recording(self, pos, vel, puncher)
local dest_pos = lib.get_route_key(pos, self.driver)
if dest_pos then
if self.start_key and self.start_key ~= dest_pos then
function minecart.stop_recording(self, pos)
--print("stop_recording")
if self.driver and self.is_recording then
local dest_pos = minecart.get_buffer_pos(pos, self.driver)
local player = minetest.get_player_by_name(self.driver)
if dest_pos and player and #self.checkpoints > 3 then
-- Remove last checkpoint, because it is potentially too close to the dest_pos
table.remove(self.checkpoints)
if self.start_pos then
local route = {
waypoints = self.waypoints,
dest_pos = dest_pos,
checkpoints = self.checkpoints,
junctions = self.junctions,
}
minecart.store_route(self.start_key, route)
minecart.store_route(self.start_pos, route)
minetest.chat_send_player(self.driver, S("[minecart] Route stored!"))
else
minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!"))
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
else
minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!"))
end
self.recording = false
dashboard_destroy(self)
end
self.is_recording = false
self.checkpoints = nil
self.waypoints = nil
self.junctions = nil
end
function minecart.set_junction(self, pos, dir, switch_keys)
if self.junctions then
self.junctions[P2S(vector.round(pos))] = {dir, switch_keys}
function minecart.recording_waypoints(self)
local pos = vector.round(self.object:get_pos())
-- hier müsste überprüfung dest_pos rein
self.sum_speed = self.sum_speed + self.curr_speed
local wp_pos = check_waypoint(self, pos)
self.checkpoints[#self.checkpoints+1] = {
-- cart_pos, next_waypoint_pos, speed, dot
P2H(pos),
P2H(wp_pos),
math.floor(self.curr_speed + 0.5),
self.waypoint.dot
}
end
function minecart.recording_junctions(self)
local player = minetest.get_player_by_name(self.driver)
if player then
local ctrl = player:get_player_control()
if ctrl.left then
self.ctrl = {left = true}
elseif ctrl.right then
self.ctrl = {right = true}
elseif ctrl.up or ctrl.down then
self.ctrl = nil
end
end
if self.hud_time <= self.timebase then
dashboard_update(self)
self.hud_time = self.timebase + 0.5
self.runtime = self.runtime + 0.5
end
end
function minecart.get_junction(self, pos, dir)
local junctions = CartsOnRail[self.myID] and CartsOnRail[self.myID].junctions
if junctions then
local data = junctions[P2S(vector.round(pos))]
if data then
return data[1], data[2]
function minecart.set_junctions(self, wayp_pos)
if self.ctrl then
self.junctions[P2H(wayp_pos)] = self.ctrl
end
end
return dir
end
function minecart.player_ctrl(self)
local player = minetest.get_player_by_name(self.driver)
if player then
local ctrl = player:get_player_control()
if ctrl.left then
self.ctrl = {left = true}
elseif ctrl.right then
self.ctrl = {right = true}
end
end
end

107
minecart/signs.lua Normal file
View File

@ -0,0 +1,107 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg 4
MIT
See license.txt for more information
]]--
local S = minecart.S
local function register_sign(def)
minetest.register_node("minecart:"..def.name, {
description = def.description,
inventory_image = def.image,
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -1/16, -8/16, -1/16, 1/16,-2/16, 1/16},
{ -5/16, -2/16, -1/32, 5/16, 8/16, 1/32},
},
},
paramtype2 = "facedir",
tiles = {
"default_steel_block.png",
"default_steel_block.png",
"default_steel_block.png",
"default_steel_block.png",
"default_steel_block.png",
"default_steel_block.png^"..def.image,
},
after_place_node = minecart.delete_signs_waypoint,
preserve_metadata = minecart.delete_signs_waypoint,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = minecart.CLIP,
sunlight_propagates = true,
is_ground_content = false,
groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, minecart_sign = 1},
sounds = default.node_sound_wood_defaults(),
})
end
register_sign({
name = "speed1",
description = S('Speed "1"'),
image = "minecart_sign1.png",
})
register_sign({
name = "speed2",
description = S('Speed "2"'),
image = "minecart_sign2.png",
})
register_sign({
name = "speed4",
description = S('Speed "4"'),
image = "minecart_sign4.png",
})
register_sign({
name = "speed8",
description = S('No speed limit'),
image = "minecart_sign8.png",
})
minetest.register_craft({
output = "minecart:speed8 8",
recipe = {
{"default:tin_ingot", "dye:red", "default:tin_ingot"},
{"", "default:steel_ingot", ""},
{"", "default:steel_ingot", ""}
}
})
minetest.register_craft({
type = "shapeless",
output = "minecart:speed4",
recipe = {"minecart:speed8"}
})
minetest.register_craft({
type = "shapeless",
output = "minecart:speed2",
recipe = {"minecart:speed4"}
})
minetest.register_craft({
type = "shapeless",
output = "minecart:speed1",
recipe = {"minecart:speed2"}
})
minetest.register_craft({
type = "shapeless",
output = "minecart:speed8",
recipe = {"minecart:speed1"}
})

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
@ -14,24 +14,75 @@
local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local S = minecart.S
local storage = minetest.get_mod_storage()
local function place_carts(t)
local Carts = {
["minecart:cart"] = "minecart:cart",
["techage:tank_cart_entity"] = "techage:tank_cart",
["techage:chest_cart_entity"] = "techage:chest_cart",
}
for id, item in pairs(t) do
local pos = vector.round((item.start_pos or item.last_pos))
local name = Carts[item.entity_name] or "minecart:cart"
--print(P2S(pos), name, item.owner, item.userID)
if minetest.registered_nodes[name] then
minecart.add_nodecart(pos, name, 0, {}, item.owner or "", item.userID or 0)
end
end
end
-------------------------------------------------------------------------------
-- Store data of running carts
-------------------------------------------------------------------------------
minecart.CartsOnRail = {}
minetest.register_on_mods_loaded(function()
for key,val in pairs(minetest.deserialize(storage:get_string("CartsOnRail")) or {}) do
-- use invalid keys to force the cart spawning
minecart.CartsOnRail[-key] = val
local version = storage:get_int("version")
if version < 2 then
local t = minetest.deserialize(storage:get_string("CartsOnRail")) or {}
minetest.after(5, place_carts, t)
storage:set_int("version", 2)
else
local t = minetest.deserialize(storage:get_string("CartsOnRail")) or {}
for owner, carts in pairs(t) do
minecart.CartsOnRail[owner] = {}
for userID, cart in pairs(carts) do
print("reload cart", owner, userID, cart.objID)
minecart.CartsOnRail[owner][userID] = cart
-- mark all entity carts as zombified
if cart.objID and cart.objID ~= 0 then
cart.objID = -1
minecart.push(1, cart)
end
end
end
end
end)
minetest.after(10, function()
for owner, carts in pairs(minecart.CartsOnRail) do
for userID, cart in pairs(carts) do
-- Remove node carts that are not available anymore
if cart.objID == 0 or not cart.objID then
local node = minecart.get_node_lvm(cart.pos)
if not minecart.tNodeNames[node.name] then
-- Mark as "to be deleted"
print("Node cart deleted", owner, userID)
minecart.CartsOnRail[owner][userID] = nil
end
end
end
end
end)
minetest.register_on_shutdown(function()
storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail))
print("minecart shutdown finished!!!")
end)
function minecart.store_carts()
@ -39,69 +90,31 @@ function minecart.store_carts()
end
-------------------------------------------------------------------------------
-- Store routes
-- Store routes (in buffers)
-------------------------------------------------------------------------------
-- All positions as "pos_to_string" string
--Routes = {
-- start_pos = {
-- waypoints = {{spos, svel}, {spos, svel}, ...},
-- dest_pos = spos,
-- junctions = {
-- {spos = num},
-- {spos = num},
-- },
-- },
-- start_pos = {...},
--}
local Routes = {}
local NEW_ROUTE = {waypoints = {}, junctions = {}}
function minecart.store_route(key, route)
if key and route then
Routes[key] = route
local meta = M(S2P(key))
if meta then
meta:set_string("route", minetest.serialize(route))
function minecart.store_route(pos, route)
if pos and route then
M(pos):set_string("route", minetest.serialize(route))
return true
end
end
return false
end
function minecart.get_route(key)
if not Routes[key] then
local s = M(S2P(key)):get_string("route")
function minecart.get_route(pos)
if pos then
local s = M(pos):get_string("route")
if s ~= "" then
Routes[key] = minetest.deserialize(s) or NEW_ROUTE
else
Routes[key] = NEW_ROUTE
end
end
return Routes[key]
end
function minecart.del_route(key)
Routes[key] = nil -- remove from memory
M(S2P(key)):set_string("route", "") -- and as metadata
end
-------------------------------------------------------------------------------
-- Convert data to v2
-------------------------------------------------------------------------------
minetest.after(5, function()
local tbl = storage:to_table()
for key,s in pairs(tbl.fields) do
if key ~= "CartsOnRail" then
local route = minetest.deserialize(s)
if route.waypoints and route.junctions then
if minecart.store_route(key, route) then
storage:set_string(key, "")
if route.waypoints then
M(pos):set_string("route", "")
M(pos):set_int("time", 0)
return
end
else
storage:set_string(key, "")
return minetest.deserialize(s)
end
end
end
end)
end
function minecart.del_route(pos)
M(pos):set_string("route", "")
end

90
minecart/terminal.lua Normal file
View File

@ -0,0 +1,90 @@
--[[
Minecart
========
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local function is_player_nearby(pos)
for _, object in pairs(minetest.get_objects_inside_radius(pos, 6)) do
if object:is_player() then
return true
end
end
end
local function formspec(pos, text)
text = minetest.formspec_escape(text)
text = text:gsub("\n", ",")
return "size[11,9]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"box[0,-0.1;10.8,0.5;#c6e8ff]"..
"label[4.5,-0.1;"..minetest.colorize( "#000000", S("Cart List")).."]"..
"style_type[table,field;font=mono]"..
"table[0,0.5;10.8,8.6;output;"..text..";200]"
end
minetest.register_node("minecart:terminal", {
description = S("Cart Terminal"),
inventory_image = "minecart_terminal_front.png",
tiles = {
"minecart_terminal_top.png",
"minecart_terminal_top.png",
"minecart_terminal_side.png",
"minecart_terminal_side.png",
"minecart_terminal_back.png",
"minecart_terminal_front.png",
},
drawtype = "nodebox",
node_box = {
type = "fixed",
fixed = {
{ -8/16, -8/16, 0/16, 8/16, 8/16, 8/16},
},
},
after_place_node = function(pos, placer)
local meta = M(pos)
meta:set_string("owner", placer:get_player_name())
meta:set_string("formspec", formspec(pos, ""))
minetest.get_node_timer(pos):start(2)
end,
on_timer = function(pos, elapsed)
if is_player_nearby(pos) then
local text = minecart.get_cart_list(pos, M(pos):get_string("owner"))
M(pos):set_string("formspec", formspec(pos, text))
end
return true
end,
paramtype2 = "facedir",
paramtype = "light",
use_texture_alpha = minecart.CLIP,
on_rotate = screwdriver.disallow,
sunlight_propagates = true,
is_ground_content = false,
groups = {cracky = 2, level = 2},
sounds = default.node_sound_metal_defaults(),
})
minetest.register_craft({
output = "minecart:terminal",
recipe = {
{"", "default:obsidian_glass", "default:steel_ingot"},
{"", "default:obsidian_glass", "default:copper_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
},
})

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 B

After

Width:  |  Height:  |  Size: 138 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 169 B

After

Width:  |  Height:  |  Size: 148 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 203 B

After

Width:  |  Height:  |  Size: 182 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 572 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 457 B

After

Width:  |  Height:  |  Size: 222 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 285 B

After

Width:  |  Height:  |  Size: 264 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 263 B

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 195 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 B

After

Width:  |  Height:  |  Size: 128 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 154 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 151 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 174 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 123 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 166 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 126 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 202 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 111 B

View File

@ -1,16 +0,0 @@
import os, fnmatch
print ">>> Convert"
for filename in os.listdir("./"):
if fnmatch.fnmatch(filename, "*.png"):
print(filename)
os.system("pngquant --skip-if-larger --quality=8-32 --output ./%s.new ./%s" % (filename, filename))
print "\n>>> Copy"
for filename in os.listdir("./"):
if fnmatch.fnmatch(filename, "*.new"):
print(filename)
os.remove("./" + filename[:-4])
os.rename("./" + filename, "./" + filename[:-4])

2
minecart/textures/shrink.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
pngquant --skip-if-larger --quality=80 --strip *.png --ext .png --force

101
minecart/tool.lua Normal file
View File

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

View File

@ -96,6 +96,7 @@ For all Inventory commands applies: If the inventory stack specified by <slot> i
pattern - save the blocks behind the shield (up to 5x3x3) as template
copy <size> - make a copy of "pattern". Size is e.g. 3x3 (see ingame help)
punch_cart - Punch a rail cart to start it
print <text> - Output chat message for debug purposes
#### Flow control commands
@ -169,4 +170,7 @@ optional: farming redo, node_io, doc, techage, minecart
- 2020-06-21 v1.03 * Interpreter bugfixes, node and crop sensors changed
- 2020-10-01 v1.04 * Many improvements and bugfixes (Thanks to Thomas-S)
- 2021-01-30 v1.05 * Many improvements and bugfixes
- 2021-03-14 v1.06 * Switch translation from intllib to minetest.translator
- 2021-04-24 v1.07 * Adapted to minecart v2.0
- 2021-05-04 v1.08 * Add print command, improve error msg

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,13 +13,10 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
@ -112,16 +109,33 @@ local function preassigned_slots(pos)
return table.concat(tbl, "")
end
local function status(mem)
if mem.error then
if type(mem.error) == "string" then
return mem.error
else
return dump(mem.error)
end
end
if mem.running then
return S("running")
end
if mem.charging then
return S("charging")
end
return S("stopped")
end
local function formspec(pos, mem)
mem.running = mem.running or false
local cmnd = mem.running and "stop;"..I("Off") or "start;"..I("On")
local cmnd = mem.running and "stop;"..S("Off") or "start;"..S("On")
local bot = not mem.running and "image[0.6,0;1,1;signs_bot_bot_inv.png]" or ""
local current_capa = mem.capa or (signs_bot.MAX_CAPA * 0.9)
return "size[9,7.6]"..
return "size[9,8.2]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"label[2.1,0;"..I("Signs").."]label[5.3,0;"..I("Other items").."]"..
"label[2.1,0;"..S("Signs").."]label[5.3,0;"..S("Other items").."]"..
"image[0.6,0;1,1;signs_bot_form_mask.png]"..
bot..
preassigned_slots(pos)..
@ -132,24 +146,27 @@ local function formspec(pos, mem)
"label[5.3,0.5;1]label[6.3,0.5;2]label[7.3,0.5;3]label[8.3,0.5;4]"..
"list[context;main;5,1;4,2;]"..
"label[5.3,3;5]label[6.3,3;6]label[7.3,3;7]label[8.3,3;8]"..
"button[0.2,1;1.5,1;config;"..I("Config").."]"..
"button[0.2,1;1.5,1;config;"..S("Config").."]"..
"button[0.2,2;1.5,1;"..cmnd.."]"..
"list[current_player;main;0.5,3.8;8,4;]"..
"label[1,3.6;"..status(mem).."]"..
"list[current_player;main;0.5,4.4;8,4;]"..
"listring[context;main]"..
"listring[current_player;main]"
end
local function formspec_cfg(pos, mem)
return "size[9,7.6]"..
local function formspec_cfg()
return "size[9,8.2]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"label[5.3,0;"..I("Preassign slots items").."]"..
"label[5.3,0;"..S("Preassign slots items").."]"..
"label[5.3,0.5;1]label[6.3,0.5;2]label[7.3,0.5;3]label[8.3,0.5;4]"..
"list[context;filter;5,1;4,2;]"..
"label[5.3,3;5]label[6.3,3;6]label[7.3,3;7]label[8.3,3;8]"..
"button[0.2,1;1.5,1;back;"..I("Back").."]"..
"list[current_player;main;0.5,3.8;8,4;]"
"button[0.2,1;1.5,1;back;"..S("Back").."]"..
"list[current_player;main;0.5,4.4;8,4;]"..
"listring[context;filter]"..
"listring[current_player;main]"
end
local function get_capa(itemstack)
@ -166,7 +183,7 @@ local function set_capa(pos, oldnode, digger, capa)
capa = techage.power.percent(signs_bot.MAX_CAPA, capa)
capa = (math.floor((capa or 0) / 5)) * 5
meta:set_int("capa", capa)
local text = I("Robot Box ").." ("..capa.." %)"
local text = S("Robot Box").." ("..capa.." %)"
meta:set_string("description", text)
local inv = minetest.get_inventory({type="player", name=digger:get_player_name()})
local left_over = inv:add_item("main", node)
@ -179,7 +196,7 @@ function signs_bot.infotext(pos, state)
local meta = M(pos)
local number = meta:get_string("number")
state = state or "<unknown>"
meta:set_string("infotext", I("Robot Box ")..number..": "..state)
meta:set_string("infotext", S("Robot Box").." "..number..": "..state)
end
local function reset_robot(pos, mem)
@ -205,7 +222,7 @@ function signs_bot.start_robot(base_pos)
mem.capa = nil
end
meta:set_string("formspec", formspec(base_pos, mem))
signs_bot.infotext(base_pos, I("running"))
signs_bot.infotext(base_pos, S("running"))
reset_robot(base_pos, mem)
minetest.get_node_timer(base_pos):start(CYCLE_TIME)
return true
@ -224,9 +241,9 @@ function signs_bot.stop_robot(base_pos, mem)
mem.charging = false
end
if mem.power_available then
signs_bot.infotext(base_pos, I("charging"))
signs_bot.infotext(base_pos, S("charging"))
else
signs_bot.infotext(base_pos, I("stopped"))
signs_bot.infotext(base_pos, S("stopped"))
end
meta:set_string("formspec", formspec(base_pos, mem))
signs_bot.remove_robot(mem)
@ -285,7 +302,7 @@ local function on_receive_fields(pos, formname, fields, player)
if fields.update then
meta:set_string("formspec", formspec(pos, mem))
elseif fields.config then
meta:set_string("formspec", formspec_cfg(pos, mem))
meta:set_string("formspec", formspec_cfg())
elseif fields.back then
meta:set_string("formspec", formspec(pos, mem))
elseif fields.start then
@ -373,6 +390,7 @@ end
local function on_power(pos)
local mem = tubelib2.get_mem(pos)
mem.power_available = true
mem.charging = true
signs_bot.infotext(pos, S("charging"))
end
@ -383,7 +401,7 @@ local function on_nopower(pos)
end
minetest.register_node("signs_bot:box", {
description = I("Signs Bot Box"),
description = S("Signs Bot Box"),
stack_max = 1,
tiles = {
-- up, down, right, left, back, front
@ -417,7 +435,7 @@ minetest.register_node("signs_bot:box", {
meta:set_string("formspec", formspec(pos, mem))
meta:set_string("signs_bot_cmnd", "turn_off")
meta:set_int("err_code", 0)
signs_bot.infotext(pos, I("stopped"))
signs_bot.infotext(pos, S("stopped"))
if minetest.global_exists("techage") then
techage.ElectricCable:after_place_node(pos)
mem.capa = get_capa(itemstack)
@ -473,12 +491,12 @@ minetest.register_node("signs_bot:box", {
on_power = function(pos)
local mem = tubelib2.get_mem(pos)
mem.power_available = true
signs_bot.infotext(pos, I("charging"))
signs_bot.infotext(pos, S("charging"))
end,
on_nopower = function(pos)
local mem = tubelib2.get_mem(pos)
mem.power_available = false
signs_bot.infotext(pos, I("no power"))
signs_bot.infotext(pos, S("no power"))
end,
nominal = PWR_NEEDED,
}
@ -516,22 +534,22 @@ end
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "box", {
name = I("Signs Bot Box"),
name = S("Signs Bot Box"),
data = {
item = "signs_bot:box",
text = table.concat({
I("The Box is the housing of the bot."),
I("Place the box and start the bot by means of the 'On' button."),
I("If the mod techage is installed, the bot needs electrical power."),
S("The Box is the housing of the bot."),
S("Place the box and start the bot by means of the 'On' button."),
S("If the mod techage is installed, the bot needs electrical power."),
"",
I("The bot leaves the box on the right side."),
I("It will not start, if this position is blocked."),
S("The bot leaves the box on the right side."),
S("It will not start, if this position is blocked."),
"",
I("To stop and remove the bot, press the 'Off' button."),
S("To stop and remove the bot, press the 'Off' button."),
"",
I("The box inventory simulates the inventory of the bot."),
I("You will not be able to access the inventory, if the bot is running."),
I("The bot can carry up to 8 stacks and 6 signs with it."),
S("The box inventory simulates the inventory of the bot."),
S("You will not be able to access the inventory, if the bot is running."),
S("The bot can carry up to 8 stacks and 6 signs with it."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPLv3
See LICENSE.txt for more information
@ -12,14 +12,8 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local function formspec(cmnd)
cmnd = minetest.formspec_escape(cmnd)
@ -28,7 +22,7 @@ local function formspec(cmnd)
default.gui_bg_img..
default.gui_slots..
"label[0.3,0.3;"..cmnd.."]"..
"button_exit[2.5,5.5;2,1;exit;"..I("Exit").."]"
"button_exit[2.5,5.5;2,1;exit;"..S("Exit").."]"
end
local commands = [[dig_sign 6
@ -37,7 +31,7 @@ place_sign_behind 6
]]
minetest.register_node("signs_bot:bot_flap", {
description = "Bot Flap",
description = S("Bot Flap"),
paramtype2 = "facedir",
tiles = {
"signs_bot_bot_flap_top.png",
@ -69,13 +63,13 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "bot_flap", {
name = I("Bot Flap"),
name = S("Bot Flap"),
data = {
item = "signs_bot:bot_flap",
text = table.concat({
I("The flap is a simple block used as door for the bot."),
I("Place the flap in any wall, and the bot will automatically open"),
I("and close the flap as it passes through it."),
S("The flap is a simple block used as door for the bot."),
S("Place the flap in any wall, and the bot will automatically open"),
S("and close the flap as it passes through it."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,22 +13,19 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
local lib = signs_bot.lib
-- Load support for I18n.
local S = signs_bot.S
local function update_infotext(pos, dest_pos, cmnd)
M(pos):set_string("infotext", I("Bot Sensor: Connected with ")..S(dest_pos).." / "..cmnd)
M(pos):set_string("infotext", S("Bot Sensor: Connected with").." "..P2S(dest_pos).." / "..cmnd)
end
minetest.register_node("signs_bot:bot_sensor", {
description = I("Bot Sensor"),
description = S("Bot Sensor"),
inventory_image = "signs_bot_sensor_bot_inv.png",
drawtype = "nodebox",
node_box = {
@ -49,7 +46,7 @@ minetest.register_node("signs_bot:bot_sensor", {
after_place_node = function(pos, placer)
local meta = M(pos)
meta:set_string("infotext", I("Bot Sensor: Not connected"))
meta:set_string("infotext", S("Bot Sensor: Not connected"))
end,
update_infotext = update_infotext,
@ -57,13 +54,14 @@ minetest.register_node("signs_bot:bot_sensor", {
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = signs_bot.CLIP,
is_ground_content = false,
groups = {sign_bot_sensor = 1, cracky = 1},
sounds = default.node_sound_metal_defaults(),
})
minetest.register_node("signs_bot:bot_sensor_on", {
description = I("Bot Sensor"),
description = S("Bot Sensor"),
drawtype = "nodebox",
node_box = {
type = "fixed",
@ -100,6 +98,7 @@ minetest.register_node("signs_bot:bot_sensor_on", {
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = signs_bot.CLIP,
is_ground_content = false,
diggable = false,
groups = {sign_bot_sensor = 1, not_in_creative_inventory = 1},
@ -118,13 +117,13 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "bot_sensor", {
name = I("Bot Sensor"),
name = S("Bot Sensor"),
data = {
item = "signs_bot:bot_sensor",
text = table.concat({
I("The Bot Sensor detects any bot and sends a signal, if a bot is nearby."),
I("the sensor range is one node/meter."),
I("The sensor direction does not care."),
S("The Bot Sensor detects any bot and sends a signal, if a bot is nearby."),
S("the sensor range is one node/meter."),
S("The sensor direction does not care."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,20 +13,19 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
local CYCLE_TIME = 2
local function update_infotext(pos, dest_pos, dest_idx)
M(pos):set_string("infotext", I("Cart Sensor: Connected with ")..S(dest_pos).." / "..dest_idx)
M(pos):set_string("infotext", S("Cart Sensor: Connected with").." "..P2S(dest_pos).." / "..dest_idx)
end
local function swap_node(pos, name)
@ -39,18 +38,8 @@ local function swap_node(pos, name)
return true
end
local function check_cart(pos)
for _, object in pairs(minetest.get_objects_inside_radius(pos, 1)) do
if object:get_entity_name() == "minecart:cart" then
return true
end
end
return false
end
local function node_timer(pos)
local pos1 = lib.next_pos(pos, M(pos):get_int("param2"))
if check_cart(pos1) then
if minecart.is_cart_available(pos, M(pos):get_int("param2"), 1) then
if swap_node(pos, "signs_bot:cart_sensor_on") then
signs_bot.send_signal(pos)
signs_bot.lib.activate_extender_nodes(pos, true)
@ -62,7 +51,7 @@ local function node_timer(pos)
end
minetest.register_node("signs_bot:cart_sensor", {
description = I("Cart Sensor"),
description = S("Cart Sensor"),
inventory_image = "signs_bot_sensor_cart_inv.png",
drawtype = "nodebox",
node_box = {
@ -83,7 +72,7 @@ minetest.register_node("signs_bot:cart_sensor", {
after_place_node = function(pos, placer)
local meta = M(pos)
meta:set_string("infotext", "Cart Sensor: Not connected")
meta:set_string("infotext", S("Cart Sensor: Not connected"))
minetest.get_node_timer(pos):start(CYCLE_TIME)
local node = minetest.get_node(pos)
meta:set_int("param2", (node.param2 + 2) % 4)
@ -93,6 +82,7 @@ minetest.register_node("signs_bot:cart_sensor", {
update_infotext = update_infotext,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -101,7 +91,7 @@ minetest.register_node("signs_bot:cart_sensor", {
})
minetest.register_node("signs_bot:cart_sensor_on", {
description = I("Cart Sensor"),
description = S("Cart Sensor"),
drawtype = "nodebox",
node_box = {
type = "fixed",
@ -123,6 +113,7 @@ minetest.register_node("signs_bot:cart_sensor_on", {
update_infotext = update_infotext,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -156,13 +147,13 @@ minetest.register_lbm({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "cart_sensor", {
name = I("Cart Sensor"),
name = S("Cart Sensor"),
data = {
item = "signs_bot:cart_sensor",
text = table.concat({
I("The Cart Sensor detects and sends a signal, if a cart (Minecart) is nearby."),
I("the sensor range is one node/meter."),
I("The sensor has an active side (red) that must point to the rail/cart."),
S("The Cart Sensor detects and sends a signal, if a cart (Minecart) is nearby."),
S("the sensor range is one node/meter."),
S("The sensor has an active side (red) that must point to the rail/cart."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,13 +13,10 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
@ -34,7 +31,7 @@ local formspec = "size[8,7]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"label[1,1.3;"..I("Signs:").."]"..
"label[1,1.3;"..S("Signs:").."]"..
"label[2.6,0.7;1]label[5.1,0.7;2]"..
"list[context;sign;3,0.5;2,2;]"..
"label[2.6,1.7;3]label[5.1,1.7;4]"..
@ -100,10 +97,15 @@ local function allow_metadata_inventory()
return 0
end
local function can_dig(pos)
local inv = minetest.get_inventory({type="node", pos=pos})
return inv:is_empty("sign")
end
for idx = 1,4 do
local not_in_inv = idx == 1 and 0 or 1
minetest.register_node("signs_bot:changer"..idx, {
description = I("Bot Control Unit"),
description = S("Bot Control Unit"),
inventory_image = "signs_bot_ctrl_unit_inv.png",
drawtype = "nodebox",
node_box = {
@ -139,9 +141,11 @@ for idx = 1,4 do
allow_metadata_inventory_put = allow_metadata_inventory,
allow_metadata_inventory_take = allow_metadata_inventory,
on_punch = swap_node,
can_dig = can_dig,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -162,18 +166,18 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "changer", {
name = I("Bot Control Unit"),
name = S("Bot Control Unit"),
data = {
item = "signs_bot:changer1",
text = table.concat({
I("The Bot Control Unit is used to lead the bot by means of signs."),
I("The unit can be loaded with up to 4 different signs and can be programmed by means of sensors."),
S("The Bot Control Unit is used to lead the bot by means of signs."),
S("The unit can be loaded with up to 4 different signs and can be programmed by means of sensors."),
"",
I("To load the unit, place a sign on the red side of the unit and click on the unit."),
I("The sign disappears / is moved to the inventory of the unit."),
I("This can be repeated 3 times."),
S("To load the unit, place a sign on the red side of the unit and click on the unit."),
S("The sign disappears / is moved to the inventory of the unit."),
S("This can be repeated 3 times."),
"",
I("Use the connection tool to connect up to 4 sensors with the Bot Control Unit."),
S("Use the connection tool to connect up to 4 sensors with the Bot Control Unit."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-0221 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,13 +13,12 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local NODE_IO = minetest.global_exists("node_io")
@ -50,7 +49,7 @@ end
local function update_infotext(pos, dest_pos, cmnd)
local meta = M(pos)
local state = get_inv_state(pos)
meta:set_string("infotext", I("Bot Chest: Sends signal to ")..S(dest_pos).." / "..cmnd..", if "..state)
meta:set_string("infotext", S("Bot Chest: Sends signal to").." "..P2S(dest_pos).." / "..cmnd..", if "..state)
meta:set_string("state", state)
end
@ -68,7 +67,7 @@ end
if NODE_IO then
minetest.register_node("signs_bot:chest", {
description = I("Signs Bot Chest"),
description = S("Signs Bot Chest"),
tiles = {
-- up, down, right, left, back, front
'signs_bot_chest_top.png',
@ -91,7 +90,7 @@ if NODE_IO then
local meta = minetest.get_meta(pos)
meta:set_string("owner", placer:get_player_name())
meta:set_string("formspec", formspec(pos, mem))
meta:set_string("infotext", "Bot Chest: Not connected")
meta:set_string("infotext", S("Bot Chest: Not connected"))
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
@ -158,7 +157,7 @@ if NODE_IO then
})
else
minetest.register_node("signs_bot:chest", {
description = I("Signs Bot Chest"),
description = S("Signs Bot Chest"),
tiles = {
-- up, down, right, left, back, front
'signs_bot_chest_top.png',
@ -181,7 +180,7 @@ else
local meta = minetest.get_meta(pos)
meta:set_string("owner", placer:get_player_name())
meta:set_string("formspec", formspec(pos, mem))
meta:set_string("infotext", "Bot Chest: Not connected")
meta:set_string("infotext", S("Bot Chest: Not connected"))
end,
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
@ -245,15 +244,15 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "chest", {
name = I("Signs Bot Chest"),
name = S("Signs Bot Chest"),
data = {
item = "signs_bot:chest",
text = table.concat({
I("The Signs Bot Chest is a special chest with sensor function."),
I("It sends a signal depending on the chest state."),
I("Possible states are 'empty', 'not empty', 'almost full'"),
S("The Signs Bot Chest is a special chest with sensor function."),
S("It sends a signal depending on the chest state."),
S("Possible states are 'empty', 'not empty', 'almost full'"),
"",
I("A typical use case is to turn off the bot, when the chest is almost full or empty."),
S("A typical use case is to turn off the bot, when the chest is almost full or empty."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -11,14 +11,8 @@
Bot farming commands
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
@ -65,7 +59,7 @@ signs_bot.register_botcommand("sow_seed", {
mod = "farming",
params = "<slot>",
num_param = 1,
description = I("Sow farming seeds\nin front of the robot"),
description = S("Sow farming seeds\nin front of the robot"),
check = function(slot)
slot = tonumber(slot)
return slot and slot > 0 and slot < 9
@ -97,7 +91,10 @@ local function harvesting(base_pos, mem)
-- Do not cache the result of get_node_drops; it is a probabilistic function!
local drops = minetest.get_node_drops(node.name)
for _,itemstring in ipairs(drops) do
bot_inv_put_item(base_pos, 0, ItemStack(itemstring))
local leftover = bot_inv_put_item(base_pos, 0, ItemStack(itemstring))
if leftover and leftover:get_count() > 0 then
signs_bot.lib.drop_items(mem.robot_pos, leftover)
end
end
end
end
@ -107,7 +104,7 @@ signs_bot.register_botcommand("harvest", {
mod = "farming",
params = "",
num_param = 0,
description = I("Harvest farming products\nin front of the robot\non a 3x3 field."),
description = S("Harvest farming products\nin front of the robot\non a 3x3 field."),
cmnd = function(base_pos, mem)
if not mem.steps then
mem.pos_tbl = signs_bot.lib.gen_position_table(mem.robot_pos, mem.robot_param2, 3, 3, 0)
@ -145,7 +142,7 @@ signs_bot.register_botcommand("plant_sapling", {
mod = "farming",
params = "<slot>",
num_param = 1,
description = I("Plant a sapling\nin front of the robot"),
description = S("Plant a sapling\nin front of the robot"),
check = function(slot)
slot = tonumber(slot)
return slot and slot > 0 and slot < 9
@ -168,7 +165,7 @@ turn_around]]
signs_bot.register_sign({
name = "farming",
description = I('Sign "farming"'),
description = S('Sign "farming"'),
commands = CMD,
image = "signs_bot_sign_farming.png",
})
@ -184,14 +181,14 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "farming", {
name = I("Sign 'farming'"),
name = S("Sign 'farming'"),
data = {
item = "signs_bot:farming",
text = table.concat({
I("Used to harvest and seed a 3x3 field."),
I("Place the sign in front of the field."),
I("The seed to be placed has to be in the first inventory slot of the bot."),
I("When finished, the bot turns."),
S("Used to harvest and seed a 3x3 field."),
S("Place the sign in front of the field."),
S("The seed to be placed has to be in the first inventory slot of the bot."),
S("When finished, the bot turns."),
}, "\n")
},
})

View File

@ -11,14 +11,8 @@
Bot flower cutting command
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
@ -48,16 +42,28 @@ minetest.after(1, function()
end
end)
local function is_tree(node)
if minetest.get_item_group(node.name, "tree") == 1 then
return signs_bot.handle_drop_like_a_player(node)
end
if minetest.get_item_group(node.name, "leaves") == 1 then
return signs_bot.handle_drop_like_a_player(node)
end
end
local function harvesting(base_pos, mem)
local pos = mem.pos_tbl and mem.pos_tbl[mem.steps]
mem.steps = (mem.steps or 1) + 1
if pos and lib.not_protected(base_pos, pos) then
local node = minetest.get_node_or_nil(pos)
local drop = Flowers[node.name]
local drop = Flowers[node.name] or is_tree(node)
if drop then
minetest.remove_node(pos)
bot_inv_put_item(base_pos, 0, ItemStack(drop))
local leftover = bot_inv_put_item(base_pos, 0, ItemStack(drop))
if leftover and leftover:get_count() > 0 then
signs_bot.lib.drop_items(mem.robot_pos, leftover)
end
end
end
end
@ -66,7 +72,7 @@ signs_bot.register_botcommand("cutting", {
mod = "farming",
params = "",
num_param = 0,
description = I("Cutting flowers\nin front of the robot\non a 3x3 field."),
description = S("Cutting flowers, leaves and tree blocks\nin front of the robot\non a 3x3 field."),
cmnd = function(base_pos, mem)
if not mem.steps then
mem.pos_tbl = signs_bot.lib.gen_position_table(mem.robot_pos, mem.robot_param2, 3, 3, 0)
@ -91,7 +97,7 @@ turn_around]]
signs_bot.register_sign({
name = "flowers",
description = I('Sign "flowers"'),
description = S('Sign "flowers"'),
commands = CMD,
image = "signs_bot_sign_flowers.png",
})
@ -107,13 +113,13 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "flowers", {
name = I("Sign 'flowers'"),
name = S("Sign 'flowers'"),
data = {
item = "signs_bot:flowers",
text = table.concat({
I("Used to cut flowers on a 3x3 field."),
I("Place the sign in front of the field."),
I("When finished, the bot turns."),
S("Used to cut flowers on a 3x3 field."),
S("Place the sign in front of the field."),
S("When finished, the bot turns."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,13 +13,10 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
@ -90,7 +87,7 @@ signs_bot.register_botcommand("take_item", {
mod = "item",
params = "<num> <slot>",
num_param = 2,
description = I("Take <num> items from a chest like node\nand put it into the item inventory.\n"..
description = S("Take <num> items from a chest like node\nand put it into the item inventory.\n"..
"<slot> is the inventory slot (1..8) or 0 for any one"),
check = function(num, slot)
num = tonumber(num) or 1
@ -115,7 +112,7 @@ signs_bot.register_botcommand("add_item", {
mod = "item",
params = "<num> <slot>",
num_param = 2,
description = I("Add <num> items to a chest like node\ntaken from the item inventory.\n"..
description = S("Add <num> items to a chest like node\ntaken from the item inventory.\n"..
"<slot> is the inventory slot (1..8) or 0 for any one"),
check = function(num, slot)
num = tonumber(num) or 1
@ -140,7 +137,7 @@ signs_bot.register_botcommand("add_fuel", {
mod = "item",
params = "<num> <slot>",
num_param = 2,
description = I("Add <num> fuel to a furnace like node\ntaken from the item inventory.\n"..
description = S("Add <num> fuel to a furnace like node\ntaken from the item inventory.\n"..
"<slot> is the inventory slot (1..8) or 0 for any one"),
check = function(num, slot)
num = tonumber(num) or 1
@ -165,7 +162,7 @@ signs_bot.register_botcommand("cond_take_item", {
mod = "item",
params = "<num> <slot>",
num_param = 2,
description = I("deprecated, use bot inventory configuration instead"),
description = S("deprecated, use bot inventory configuration instead"),
check = function(num, slot)
return false
end,
@ -178,7 +175,7 @@ signs_bot.register_botcommand("cond_add_item", {
mod = "item",
params = "<num> <slot>",
num_param = 2,
description = I("deprecated, use bot inventory configuration instead"),
description = S("deprecated, use bot inventory configuration instead"),
check = function(num, slot)
return false
end,
@ -191,7 +188,7 @@ signs_bot.register_botcommand("pickup_items", {
mod = "item",
params = "<slot>",
num_param = 1,
description = I("Pick up all objects\n"..
description = S("Pick up all objects\n"..
"in a 3x3 field.\n"..
"<slot> is the inventory slot (1..8) or 0 for any one"),
check = function(slot)
@ -219,7 +216,7 @@ signs_bot.register_botcommand("drop_items", {
mod = "item",
params = "<num> <slot>",
num_param = 2,
description = I("Drop items in front of the bot.\n"..
description = S("Drop items in front of the bot.\n"..
"<slot> is the inventory slot (1..8) or 0 for any one"),
check = function(num, slot)
num = tonumber(num) or 1
@ -246,18 +243,10 @@ signs_bot.register_botcommand("punch_cart", {
mod = "item",
params = "",
num_param = 0,
description = I("Punch a rail cart to start it"),
description = S("Punch a rail cart to start it"),
cmnd = function(base_pos, mem)
local pos = lib.dest_pos(mem.robot_pos, mem.robot_param2, {0})
for _, object in pairs(minetest.get_objects_inside_radius(pos, 2)) do
if object:get_entity_name() == "minecart:cart" then
object:punch(object, 1.0, {
full_punch_interval = 1.0,
damage_groups = {fleshy = 1},
}, minetest.facedir_to_dir(mem.robot_param2))
break -- start only one cart
end
end
local punch_dir = minetest.facedir_to_dir(mem.robot_param2)
minecart.punch_cart(mem.robot_pos, mem.robot_param2, 1, punch_dir)
return signs_bot.DONE
end,
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -12,14 +12,8 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
local get_node_lvm = tubelib2.get_node_lvm
@ -135,7 +129,7 @@ signs_bot.register_botcommand("backward", {
mod = "move",
params = "",
num_param = 0,
description = I("Move the robot one step back"),
description = S("Move the robot one step back"),
cmnd = function(base_pos, mem)
local new_pos = backward_robot(mem)
if new_pos then -- not blocked?
@ -160,7 +154,7 @@ signs_bot.register_botcommand("turn_left", {
mod = "move",
params = "",
num_param = 0,
description = I("Turn the robot to the left"),
description = S("Turn the robot to the left"),
cmnd = function(base_pos, mem)
mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "L")
return signs_bot.DONE
@ -171,7 +165,7 @@ signs_bot.register_botcommand("turn_right", {
mod = "move",
params = "",
num_param = 0,
description = I("Turn the robot to the right"),
description = S("Turn the robot to the right"),
cmnd = function(base_pos, mem)
mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R")
return signs_bot.DONE
@ -182,7 +176,7 @@ signs_bot.register_botcommand("turn_around", {
mod = "move",
params = "",
num_param = 0,
description = I("Turn the robot around"),
description = S("Turn the robot around"),
cmnd = function(base_pos, mem)
mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R")
mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R")
@ -217,7 +211,7 @@ signs_bot.register_botcommand("move_up", {
mod = "move",
params = "",
num_param = 0,
description = I("Move the robot upwards"),
description = S("Move the robot upwards"),
cmnd = function(base_pos, mem)
local new_pos = robot_up(mem.robot_pos, mem.robot_param2)
if new_pos then -- not blocked?
@ -251,7 +245,7 @@ signs_bot.register_botcommand("move_down", {
mod = "move",
params = "",
num_param = 0,
description = I("Move the robot down"),
description = S("Move the robot down"),
cmnd = function(base_pos, mem)
local new_pos = robot_down(mem.robot_pos, mem.robot_param2)
if new_pos then -- not blocked?
@ -265,14 +259,14 @@ signs_bot.register_botcommand("pause", {
mod = "move",
params = "<sec>",
num_param = 1,
description = I("Stop the robot for <sec> seconds\n(1..9999)"),
description = S("Stop the robot for <sec> seconds\n(1..9999)"),
check = function(sec)
sec = tonumber(sec) or 1
return sec and sec > 0 and sec < 10000
end,
cmnd = function(base_pos, mem, sec)
if not mem.steps then
mem.steps = tonumber(sec or 1)
mem.steps = tonumber(sec) or 1
end
mem.steps = mem.steps - 1
if mem.steps == 0 then
@ -290,7 +284,7 @@ signs_bot.register_botcommand("stop", {
mod = "move",
params = "",
num_param = 0,
description = I("Stop the robot."),
description = S("Stop the robot."),
cmnd = function(base_pos, mem, slot)
if mem.capa then
mem.capa = mem.capa + 2
@ -303,7 +297,7 @@ signs_bot.register_botcommand("turn_off", {
mod = "move",
params = "",
num_param = 0,
description = I("Turn the robot off\n"..
description = S("Turn the robot off\n"..
"and put it back in the box."),
cmnd = function(base_pos, mem)
signs_bot.stop_robot(base_pos, mem)

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -12,14 +12,8 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
@ -117,7 +111,7 @@ signs_bot.register_botcommand("pattern", {
mod = "copy",
params = "",
num_param = 0,
description = I("Store pattern to be cloned."),
description = S("Store pattern to be cloned."),
cmnd = function(base_pos, mem)
mem.pttrn_pos = lib.next_pos(mem.robot_pos, mem.robot_param2)
mem.pttrn_param2 = mem.robot_param2
@ -129,7 +123,7 @@ signs_bot.register_botcommand("copy", {
mod = "copy",
params = "<size> <lvl>",
num_param = 2,
description = I("Copy the nodes from\n"..
description = S("Copy the nodes from\n"..
"the stored pattern position\n"..
"<size> is: 3x1, 3x2, 3x3,\n"..
"5x1, 5x2, 5x3 (wide x deep)\n"..
@ -178,7 +172,7 @@ minetest.register_node("signs_bot:missing", {
signs_bot.register_sign({
name = "pattern",
description = I('Sign "pattern"'),
description = S('Sign "pattern"'),
commands = "pattern\nturn_around",
image = "signs_bot_sign_pattern.png",
})
@ -207,7 +201,7 @@ turn_around]]
signs_bot.register_sign({
name = "copy3x3x3",
description = I('Sign "copy 3x3x3"'),
description = S('Sign "copy 3x3x3"'),
commands = CMND,
image = "signs_bot_sign_copy3x3x3.png",
})
@ -223,14 +217,14 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "pattern", {
name = I("Sign 'pattern'"),
name = S("Sign 'pattern'"),
data = {
item = "signs_bot:pattern",
text = table.concat({
I("Used to make a copy of a 3x3x3 cube."),
I("Place the sign in front of the pattern to be copied."),
I("Use the copy sign to make the copy of this pattern on a different location."),
I("The bot must first reach the pattern sign, then the copy sign."),
S("Used to make a copy of a 3x3x3 cube."),
S("Place the sign in front of the pattern to be copied."),
S("Use the copy sign to make the copy of this pattern on a different location."),
S("The bot must first reach the pattern sign, then the copy sign."),
}, "\n")
},
})
@ -238,13 +232,13 @@ end
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "copy3x3x3", {
name = I("Sign 'copy3x3x3'"),
name = S("Sign 'copy3x3x3'"),
data = {
item = "signs_bot:copy3x3x3",
text = table.concat({
I("Used to make a copy of a 3x3x3 cube."),
I("Place the sign in front of the location, where the copy should be made."),
I("Use the pattern sign to mark the pattern."),
S("Used to make a copy of a 3x3x3 cube."),
S("Place the sign in front of the location, where the copy should be made."),
S("Use the pattern sign to mark the pattern."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -12,14 +12,8 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
local bot_inv_take_item = signs_bot.bot_inv_take_item
@ -32,13 +26,23 @@ end
local tValidLevels = {[-1] = -1, [0] = 0, [1] = 1}
-- for items with paramtype2 = "facedir"
local tRotations = {
[0] = {8,20,4},
[1] = {16,20,12},
[2] = {4,20,8},
[3] = {12,20,16},
local tRotations = {}
local Rotations = {
{0,8,22,4},
{1,17,21,13},
{2,6,20,10},
{3,15,23,19},
}
for _,v in ipairs(Rotations) do
local t = table.copy(v)
for i = 1,4 do
table.insert(t, 1, table.remove(t))
tRotations[t[1]] = {t[2], t[3], t[4]}
end
end
--
-- Place/dig items
--
@ -46,7 +50,7 @@ local function place_item(base_pos, robot_pos, param2, slot, route, level)
local pos1, p2 = lib.dest_pos(robot_pos, param2, route)
pos1.y = pos1.y + level
if not lib.not_protected(base_pos, pos1) then
return signs_bot.ERROR, I("Error: Position protected")
return signs_bot.ERROR, S("Error: Position protected")
end
if lib.is_air_like(pos1) then
local taken = signs_bot.bot_inv_take_item(base_pos, slot, 1)
@ -74,7 +78,7 @@ signs_bot.register_botcommand("place_front", {
mod = "place",
params = "<slot> <lvl>",
num_param = 2,
description = I("Place a block in front of the robot\n"..
description = S("Place a block in front of the robot\n"..
"<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"),
check = function(slot, lvl)
@ -95,7 +99,7 @@ signs_bot.register_botcommand("place_left", {
mod = "place",
params = "<slot> <lvl>",
num_param = 2,
description = I("Place a block on the left side\n"..
description = S("Place a block on the left side\n"..
"<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"),
check = function(slot, lvl)
@ -116,7 +120,7 @@ signs_bot.register_botcommand("place_right", {
mod = "place",
params = "<slot> <lvl>",
num_param = 2,
description = I("Place a block on the right side\n"..
description = S("Place a block on the right side\n"..
"<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"),
check = function(slot, lvl)
@ -136,7 +140,7 @@ signs_bot.register_botcommand("place_right", {
local function place_item_below(base_pos, robot_pos, param2, slot)
local pos1 = {x=robot_pos.x,y=robot_pos.y-1,z=robot_pos.z}
if not lib.not_protected(base_pos, pos1) then
return signs_bot.ERROR, I("Error: Position protected")
return signs_bot.ERROR, S("Error: Position protected")
end
local node = tubelib2.get_node_lvm(pos1)
if node.name == "signs_bot:robot_foot" then
@ -155,7 +159,7 @@ signs_bot.register_botcommand("place_below", {
mod = "place",
params = "<slot>",
num_param = 1,
description = I("Place a block under the robot.\n"..
description = S("Place a block under the robot.\n"..
"Hint: use 'move_up' first.\n"..
"<slot> is the inventory slot (1..8)"),
check = function(slot)
@ -171,7 +175,7 @@ signs_bot.register_botcommand("place_below", {
local function place_item_above(base_pos, robot_pos, param2, slot)
local pos1 = {x=robot_pos.x,y=robot_pos.y+1,z=robot_pos.z}
if not lib.not_protected(base_pos, pos1) then
return signs_bot.ERROR, I("Error: Position protected")
return signs_bot.ERROR, S("Error: Position protected")
end
if lib.is_air_like(pos1) then
local taken = bot_inv_take_item(base_pos, slot, 1)
@ -189,7 +193,7 @@ signs_bot.register_botcommand("place_above", {
mod = "place",
params = "<slot>",
num_param = 1,
description = I("Place a block above the robot.\n"..
description = S("Place a block above the robot.\n"..
"<slot> is the inventory slot (1..8)"),
check = function(slot)
slot = tonumber(slot) or 0
@ -207,13 +211,13 @@ local function dig_item(base_pos, robot_pos, param2, slot, route, level)
local node = tubelib2.get_node_lvm(pos1)
local dug_name = lib.is_simple_node(node)
if not lib.not_protected(base_pos, pos1) then
return signs_bot.ERROR, I("Error: Position protected")
return signs_bot.ERROR, S("Error: Position protected")
end
if dug_name then
if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then
minetest.remove_node(pos1)
else
return signs_bot.ERROR, I("Error: No free inventory space")
return signs_bot.ERROR, S("Error: No free inventory space")
end
end
return signs_bot.DONE
@ -223,7 +227,7 @@ signs_bot.register_botcommand("dig_front", {
mod = "place",
params = "<slot> <lvl>",
num_param = 2,
description = I("Dig the block in front of the robot\n"..
description = S("Dig the block in front of the robot\n"..
"<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"),
check = function(slot, lvl)
@ -245,7 +249,7 @@ signs_bot.register_botcommand("dig_left", {
mod = "place",
params = "<slot> <lvl>",
num_param = 2,
description = I("Dig the block on the left side\n"..
description = S("Dig the block on the left side\n"..
"<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"),
check = function(slot, lvl)
@ -267,7 +271,7 @@ signs_bot.register_botcommand("dig_right", {
mod = "place",
params = "<slot> <lvl>",
num_param = 2,
description = I("Dig the block on the right side\n"..
description = S("Dig the block on the right side\n"..
"<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"),
check = function(slot, lvl)
@ -290,13 +294,13 @@ local function dig_item_below(base_pos, robot_pos, param2, slot)
local node = tubelib2.get_node_lvm(pos1)
local dug_name = lib.is_simple_node(node)
if not lib.not_protected(base_pos, pos1) then
return signs_bot.ERROR, I("Error: Position protected")
return signs_bot.ERROR, S("Error: Position protected")
end
if dug_name then
if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then
minetest.set_node(pos1, {name="signs_bot:robot_foot"})
else
return signs_bot.ERROR, I("Error: No free inventory space")
return signs_bot.ERROR, S("Error: No free inventory space")
end
end
return signs_bot.DONE
@ -306,7 +310,7 @@ signs_bot.register_botcommand("dig_below", {
mod = "place",
params = "<slot>",
num_param = 1,
description = I("Dig the block under the robot.\n"..
description = S("Dig the block under the robot.\n"..
"<slot> is the inventory slot (1..8)"),
check = function(slot)
slot = tonumber(slot) or 0
@ -324,13 +328,13 @@ local function dig_item_above(base_pos, robot_pos, param2, slot)
local node = tubelib2.get_node_lvm(pos1)
local dug_name = lib.is_simple_node(node)
if not lib.not_protected(base_pos, pos1) then
return signs_bot.ERROR, I("Error: Position protected")
return signs_bot.ERROR, S("Error: Position protected")
end
if dug_name then
if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then
minetest.remove_node(pos1)
else
return signs_bot.ERROR, I("Error: No free inventory space")
return signs_bot.ERROR, S("Error: No free inventory space")
end
end
return signs_bot.DONE
@ -340,7 +344,7 @@ signs_bot.register_botcommand("dig_above", {
mod = "place",
params = "<slot>",
num_param = 1,
description = I("Dig the block above the robot.\n"..
description = S("Dig the block above the robot.\n"..
"<slot> is the inventory slot (1..8)"),
check = function(slot)
slot = tonumber(slot) or 0
@ -358,7 +362,7 @@ local function rotate_item(base_pos, robot_pos, param2, route, level, steps)
pos1.y = pos1.y + level
local node = tubelib2.get_node_lvm(pos1)
if not lib.not_protected(base_pos, pos1) then
return signs_bot.ERROR, I("Error: Position protected")
return signs_bot.ERROR, S("Error: Position protected")
end
if lib.is_simple_node(node) then
local p2 = tRotations[node.param2] and tRotations[node.param2][steps]
@ -372,7 +376,8 @@ end
signs_bot.register_botcommand("rotate_item", {
mod = "place",
params = "<lvl> <steps>",
description = I("Rotate the block in front of the robot\n"..
num_param = 2,
description = S("Rotate the block in front of the robot\n"..
"<lvl> is one of: -1 0 +1\n"..
"<steps> is one of: 1 2 3"),
check = function(lvl, steps)
@ -391,7 +396,7 @@ signs_bot.register_botcommand("rotate_item", {
-- Simplified torch which can be placed w/o a fake player
minetest.register_node("signs_bot:torch", {
description = "Bot torch",
description = S("Bot torch"),
inventory_image = "default_torch_on_floor.png",
wield_image = "default_torch_on_floor.png",
drawtype = "nodebox",
@ -428,6 +433,7 @@ minetest.register_node("signs_bot:torch", {
"group:bakedclay", "group:soil"},
paramtype = "light",
paramtype2 = "facedir",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
walkable = false,
liquids_pointable = false,

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,13 +13,10 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
@ -48,12 +45,12 @@ local function formspec1(meta)
default.gui_bg_img..
default.gui_slots..
"style_type[textarea,table;font=mono]"..
"tabheader[0,0;tab;"..I("Commands,Help")..";1;;true]"..
"field[0.3,0.5;9,1;name;"..I("Sign name:")..";"..name.."]"..
"tabheader[0,0;tab;"..S("Commands,Help")..";1;;true]"..
"field[0.3,0.5;9,1;name;"..S("Sign name:")..";"..name.."]"..
"textarea[0.3,1.2;9,7.2;cmnd;;"..cmnd.."]"..
"label[0.3,7.5;"..err_msg.."]"..
"button_exit[5,7.5;2,1;cancel;"..I("Cancel").."]"..
"button[7,7.5;2,1;check;"..I("Check").."]"
"button_exit[5,7.5;2,1;cancel;"..S("Cancel").."]"..
"button[7,7.5;2,1;check;"..S("Check").."]"
end
local function formspec2(pos, text)
@ -62,10 +59,10 @@ local function formspec2(pos, text)
default.gui_bg_img..
default.gui_slots..
"style_type[textarea,table;font=mono]"..
"tabheader[0,0;tab;"..I("Commands,Help")..";2;;true]"..
"tabheader[0,0;tab;"..S("Commands,Help")..";2;;true]"..
"table[0.1,0;8.6,4;command;"..sCmnds..";"..pos.."]"..
"textarea[0.3,4.5;9,3.5;help;Help:;"..text.."]"..
"button[3,7.5;3,1;copy;"..I("Copy Cmnd").."]"
"textarea[0.3,4.5;9,3.5;help;"..S("Help")..":;"..text.."]"..
"button[3,7.5;3,1;copy;"..S("Copy Cmnd").."]"
end
local function add_arrow(text, line_num)
@ -94,7 +91,7 @@ local function append_line(pos, meta, line)
local text = meta:get_string("signs_bot_cmnd").."\n"..line
meta:set_string("signs_bot_cmnd", text)
meta:set_int("err_code", 1) -- zero means OK
meta:set_string("err_msg", "please check the added line(s)")
meta:set_string("err_msg", S("please check the added line(s)"))
end
local function check_and_store(pos, meta, fields)
@ -106,7 +103,7 @@ local function check_and_store(pos, meta, fields)
end
minetest.register_node("signs_bot:sign_cmnd", {
description = I('Sign "command"'),
description = S('Sign "command"'),
drawtype = "nodebox",
inventory_image = "signs_bot_sign_cmnd.png",
node_box = {
@ -134,8 +131,8 @@ minetest.register_node("signs_bot:sign_cmnd", {
nmeta:set_string("err_msg", imeta:get_string("err_msg"))
nmeta:set_int("err_code", imeta:get_int("err_code"))
else
nmeta:set_string("sign_name", I('Sign "command"'))
nmeta:set_string("signs_bot_cmnd", I("-- enter or copy commands from help page"))
nmeta:set_string("sign_name", S('Sign "command"'))
nmeta:set_string("signs_bot_cmnd", S("-- enter or copy commands from help page"))
nmeta:set_int("err_code", 0)
end
nmeta:set_string("infotext", nmeta:get_string("sign_name"))
@ -175,6 +172,7 @@ minetest.register_node("signs_bot:sign_cmnd", {
after_dig_node = lib.after_dig_sign_node,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
is_ground_content = false,
drop = "",
@ -215,18 +213,18 @@ local function place_sign(base_pos, robot_pos, param2, slot)
lib.place_sign(pos1, sign, param2)
return signs_bot.DONE
else
return signs_bot.ERROR, I("Error: Signs inventory empty")
return signs_bot.ERROR, S("Error: Signs inventory empty")
end
end
end
return signs_bot.ERROR, I("Error: Position protected or occupied")
return signs_bot.ERROR, S("Error: Position protected or occupied")
end
signs_bot.register_botcommand("place_sign", {
mod = "sign",
params = "<slot>",
num_param = 1,
description = I("Place a sign in front of the robot\ntaken from the signs inventory\n"..
description = S("Place a sign in front of the robot\ntaken from the signs inventory\n"..
"<slot> is the inventory slot (1..6)"),
check = function(slot)
slot = tonumber(slot) or 1
@ -247,18 +245,18 @@ local function place_sign_behind(base_pos, robot_pos, param2, slot)
lib.place_sign(pos1, sign, param2)
return signs_bot.DONE
else
return signs_bot.ERROR, I("Error: Signs inventory empty")
return signs_bot.ERROR, S("Error: Signs inventory empty")
end
end
end
return signs_bot.ERROR, I("Error: Position protected or occupied")
return signs_bot.ERROR, S("Error: Position protected or occupied")
end
signs_bot.register_botcommand("place_sign_behind", {
mod = "sign",
params = "<slot>",
num_param = 1,
description = I("Place a sign behind the robot\ntaken from the signs inventory\n"..
description = S("Place a sign behind the robot\ntaken from the signs inventory\n"..
"<slot> is the inventory slot (1..6)"),
check = function(slot)
slot = tonumber(slot) or 1
@ -277,7 +275,7 @@ local function dig_sign(base_pos, robot_pos, param2, slot)
local err_code = meta:get_int("err_code")
local name = meta:get_string("sign_name")
if cmnd == "" then
return signs_bot.ERROR, I("Error: No sign available")
return signs_bot.ERROR, S("Error: No sign available")
end
if lib.not_protected(base_pos, pos1) then
local node = tubelib2.get_node_lvm(pos1)
@ -289,18 +287,18 @@ local function dig_sign(base_pos, robot_pos, param2, slot)
minetest.remove_node(pos1)
if not put_inv_sign(base_pos, slot, sign) then
signs_bot.lib.drop_items(robot_pos, sign)
return signs_bot.ERROR, I("Error: Signs inventory slot is occupied")
return signs_bot.ERROR, S("Error: Signs inventory slot is occupied")
end
return signs_bot.DONE
end
return signs_bot.ERROR, I("Error: Position is protected")
return signs_bot.ERROR, S("Error: Position is protected")
end
signs_bot.register_botcommand("dig_sign", {
mod = "sign",
params = "<slot>",
num_param = 1,
description = I("Dig the sign in front of the robot\n"..
description = S("Dig the sign in front of the robot\n"..
"and add it to the signs inventory.\n"..
"<slot> is the inventory slot (1..6)"),
check = function(slot)
@ -317,23 +315,26 @@ local function trash_sign(base_pos, robot_pos, param2, slot)
local pos1 = lib.dest_pos(robot_pos, param2, {0})
local cmnd = M(pos1):get_string("signs_bot_cmnd")
if cmnd == "" then
return signs_bot.ERROR, I("Error: No sign available")
return signs_bot.ERROR, S("Error: No sign available")
end
if lib.not_protected(base_pos, pos1) then
local node = tubelib2.get_node_lvm(pos1)
local sign = ItemStack("signs_bot:sign_cmnd")
minetest.remove_node(pos1)
signs_bot.bot_inv_put_item(base_pos, slot, sign)
local leftover = signs_bot.bot_inv_put_item(base_pos, slot, sign)
if leftover and leftover:get_count() > 0 then
signs_bot.lib.drop_items(robot_pos, leftover)
end
return signs_bot.DONE
end
return signs_bot.ERROR, I("Error: Position is protected")
return signs_bot.ERROR, S("Error: Position is protected")
end
signs_bot.register_botcommand("trash_sign", {
mod = "sign",
params = "<slot>",
num_param = 1,
description = I("Dig the sign in front of the robot\n"..
description = S("Dig the sign in front of the robot\n"..
"and add the cleared sign to\nthe item iventory.\n"..
"<slot> is the inventory slot (1..8)"),
check = function(slot)
@ -358,14 +359,14 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "sign_cmnd", {
name = I("Sign 'command'"),
name = S("Sign 'command'"),
data = {
item = "signs_bot:sign_cmnd",
text = table.concat({
I("The 'command' sign can be programmed by the player."),
I("Place the sign in front of you and use the node menu to program your sequence of bot commands."),
I("The menu has an edit field for your commands and a help page with all available commands."),
I("The help page has a copy button to simplify the programming."),
S("The 'command' sign can be programmed by the player."),
S("Place the sign in front of you and use the node menu to program your sequence of bot commands."),
S("The menu has an edit field for your commands and a help page with all available commands."),
S("The help page has a copy button to simplify the programming."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,13 +13,12 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
-- Load support for I18n.
local S = signs_bot.S
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
local ci = dofile(MP.."/interpreter.lua")
local lib = signs_bot.lib
@ -73,7 +72,7 @@ end
function signs_bot.get_commands()
local tbl = {}
for _,mod in ipairs(SortedMods) do
tbl[#tbl+1] = mod.." "..I("commands:")
tbl[#tbl+1] = mod.." "..S("commands:")
for _,cmnd in ipairs(SortedKeys[mod]) do
local item = tCommands[cmnd]
tbl[#tbl+1] = " "..item.name.." "..item.params
@ -90,7 +89,7 @@ function signs_bot.get_help_text(cmnd)
return item.description
end
end
return I("unknown command")
return S("unknown command")
end
function signs_bot.check_commands(pos, text)
@ -154,12 +153,16 @@ local function activate_sensor(pos, param2)
end
end
local function bot_error(base_pos, mem, err)
local function bot_error(base_pos, mem, err, cmd)
minetest.sound_play('signs_bot_error', {pos = base_pos})
minetest.sound_play('signs_bot_error', {pos = mem.robot_pos})
print(err)
if cmd then
signs_bot.infotext(base_pos, err .. ":\n'" .. cmd .. "'")
mem.error = err .. ": '" .. cmd .. "'"
else
signs_bot.infotext(base_pos, err)
mem.error = true
mem.error = err
end
return false
end
@ -176,9 +179,9 @@ local function power_consumption(mem, cmnd)
end
function signs_bot.run_next_command(base_pos, mem)
local res, err = ci.run_script(base_pos, mem)
local res, err, cmd = ci.run_script(base_pos, mem)
if res == ci.ERROR then
return bot_error(base_pos, mem, err)
return bot_error(base_pos, mem, err, cmd)
elseif res == ci.EXIT then
signs_bot.stop_robot(base_pos, mem)
return false
@ -198,31 +201,31 @@ end
signs_bot.register_botcommand("repeat", {
mod = "core",
params = "<num>",
description = I("start of a 'repeat..end' block"),
description = S("start of a 'repeat..end' block"),
})
signs_bot.register_botcommand("end", {
mod = "core",
params = "",
description = I("end command of a 'repeat..end' block"),
description = S("end command of a 'repeat..end' block"),
})
signs_bot.register_botcommand("call", {
mod = "core",
params = "<label>",
description = I("call a subroutine (with 'return' statement)"),
description = S("call a subroutine (with 'return' statement)"),
})
signs_bot.register_botcommand("return", {
mod = "core",
params = "",
description = I("return from a subroutine"),
description = S("return from a subroutine"),
})
signs_bot.register_botcommand("jump", {
mod = "core",
params = "<label>",
description = I("jump to a label"),
description = S("jump to a label"),
})
local function move(mem, any_sensor)
@ -242,7 +245,7 @@ signs_bot.register_botcommand("move", {
mod = "move",
params = "<steps>",
num_param = 1,
description = I([[Move the robot 1..999 steps forward
description = S([[Move the robot 1..999 steps forward
without paying attention to any signs.
Up and down movements also become
counted as steps.]]),
@ -262,7 +265,7 @@ signs_bot.register_botcommand("cond_move", {
mod = "move",
params = "",
num_param = 0,
description = I([[Walk until a sign or obstacle is
description = S([[Walk until a sign or obstacle is
reached. Then continue with the next command.
When a sign has been reached,
the current program is ended
@ -280,4 +283,22 @@ new program from the sign]]),
end,
})
signs_bot.register_botcommand("print", {
mod = "debug",
params = "<text>",
num_param = 1,
description = S([[Print given text as chat message.
For two or more words, use the '*' character
instead of spaces, like "Hello*world"]]),
check = function(text)
return text ~= ""
end,
cmnd = function(base_pos, mem, text)
text = text:gsub("*", " ")
local owner = M(base_pos):get_string("owner")
if owner ~= "" and text ~= "" then
minetest.chat_send_player(owner, "Bot: " .. text)
end
return signs_bot.DONE
end,
})

101
signs_bot/compost.lua Normal file
View File

@ -0,0 +1,101 @@
--[[
Signs Bot
=========
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
Signs Bot: Commands for the compost mod
]]--
-- Load support for I18n.
local S = signs_bot.S
local NUM_LEAVES = 2
-- we reuse the minecart hopper API here
local function additem(mem, stack)
local pos = signs_bot.lib.next_pos(mem.robot_pos, mem.robot_param2)
local node = minetest.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
if ndef.minecart_hopper_additem then
return ndef.minecart_hopper_additem(pos, stack)
end
pos = {x = pos.x, y = pos.y - 1, z = pos.z}
node = minetest.get_node(pos)
ndef = minetest.registered_nodes[node.name]
if ndef.minecart_hopper_additem then
return ndef.minecart_hopper_additem(pos, stack)
end
return stack
end
local function takeitem(mem)
local pos = signs_bot.lib.next_pos(mem.robot_pos, mem.robot_param2)
local node = minetest.get_node(pos)
local ndef = minetest.registered_nodes[node.name]
if ndef.minecart_hopper_takeitem then
return ndef.minecart_hopper_takeitem(pos, 1)
end
pos = {x = pos.x, y = pos.y - 1, z = pos.z}
node = minetest.get_node(pos)
ndef = minetest.registered_nodes[node.name]
if ndef.minecart_hopper_takeitem then
return ndef.minecart_hopper_takeitem(pos, 1)
end
end
if minetest.global_exists("signs_bot") then
signs_bot.register_botcommand("add_compost", {
mod = "compost",
params = "<slot>",
num_param = 1,
description = S("Put 2 leaves into the compost barrel\n"..
"<slot> is the bot inventory slot (1..8)\n"..
"with the leaves."),
check = function(slot)
slot = tonumber(slot) or 0
return slot > 0 and slot < 9
end,
cmnd = function(base_pos, mem, slot)
slot = tonumber(slot) or 0
local taken = signs_bot.bot_inv_take_item(base_pos, slot, NUM_LEAVES)
local leftover = additem(mem, taken)
if leftover and leftover:get_count() > 0 then
signs_bot.bot_inv_put_item(base_pos, slot, leftover)
end
return signs_bot.DONE
end,
})
signs_bot.register_botcommand("take_compost", {
mod = "compost",
params = "<slot>",
num_param = 1,
description = S("Take a compost item from the barrel.\n"..
"<slot> (1..8 or 0 for the first free slot) is the bot\n"..
"slot for the compost item."),
check = function(num, slot)
slot = tonumber(slot) or 0
return slot >= 0 and slot < 9
end,
cmnd = function(base_pos, mem, slot)
slot = tonumber(slot) or 0
local taken = takeitem(mem)
local leftover = signs_bot.bot_inv_put_item(base_pos, slot, taken)
if leftover and leftover:get_count() > 0 then
signs_bot.lib.drop_items(mem.robot_pos, leftover)
end
return signs_bot.DONE
end,
})
end

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,20 +13,18 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
local CYCLE_TIME = 4
local function update_infotext(pos, dest_pos, dest_idx)
M(pos):set_string("infotext", I("Crop Sensor: Connected with ")..S(dest_pos).." / "..dest_idx)
M(pos):set_string("infotext", S("Crop Sensor: Connected with").." "..P2S(dest_pos).." / "..dest_idx)
end
local function swap_node(pos, name)
@ -56,7 +54,7 @@ local function node_timer(pos)
end
minetest.register_node("signs_bot:crop_sensor", {
description = I("Crop Sensor"),
description = S("Crop Sensor"),
inventory_image = "signs_bot_sensor_crop_inv.png",
drawtype = "nodebox",
node_box = {
@ -77,7 +75,7 @@ minetest.register_node("signs_bot:crop_sensor", {
after_place_node = function(pos, placer)
local meta = M(pos)
meta:set_string("infotext", "Crop Sensor: Not connected")
meta:set_string("infotext", S("Crop Sensor: Not connected"))
minetest.get_node_timer(pos):start(CYCLE_TIME)
local node = minetest.get_node(pos)
meta:set_int("param2", (node.param2 + 2) % 4)
@ -87,6 +85,7 @@ minetest.register_node("signs_bot:crop_sensor", {
update_infotext = update_infotext,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -95,7 +94,7 @@ minetest.register_node("signs_bot:crop_sensor", {
})
minetest.register_node("signs_bot:crop_sensor_on", {
description = I("Crop Sensor"),
description = S("Crop Sensor"),
drawtype = "nodebox",
node_box = {
type = "fixed",
@ -117,6 +116,7 @@ minetest.register_node("signs_bot:crop_sensor_on", {
update_infotext = update_infotext,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -150,13 +150,13 @@ minetest.register_lbm({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "crop_sensor", {
name = I("Crop Sensor"),
name = S("Crop Sensor"),
data = {
item = "signs_bot:crop_sensor",
text = table.concat({
I("The Crop Sensor sends cyclical signals when, for example, wheat is fully grown."),
I("The sensor range is one node/meter."),
I("The sensor has an active side (red) that must point to the crop/field."),
S("The Crop Sensor sends cyclical signals when, for example, wheat is fully grown."),
S("The sensor range is one node/meter."),
S("The sensor has an active side (red) that must point to the crop/field."),
}, "\n")
},

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,20 +13,17 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local CYCLE_TIME = 2
local lib = signs_bot.lib
local function update_infotext(pos, dest_pos, cmnd)
M(pos):set_string("infotext", I("Signal Delayer: Connected with ")..S(dest_pos).." / "..cmnd)
M(pos):set_string("infotext", S("Signal Delayer: Connected with").." "..P2S(dest_pos).." / "..cmnd)
end
local function infotext(pos)
@ -34,19 +31,19 @@ local function infotext(pos)
local dest_pos = meta:get_string("signal_pos")
local signal = meta:get_string("signal_data")
if dest_pos ~= "" and signal ~= "" then
update_infotext(pos, P(dest_pos), signal)
update_infotext(pos, S2P(dest_pos), signal)
end
end
local function formspec(meta)
local label = minetest.formspec_escape(I("Delay time [sec]:"))
local label = minetest.formspec_escape(S("Delay time [sec]:"))
local value = minetest.formspec_escape(meta:get_int("time"))
return "size[4,3]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"field[0.3,1;4,1;time;"..label..";"..value.."]"..
"button_exit[1,2.2;2,1;start;"..I("Start").."]"
"button_exit[1,2.2;2,1;start;"..S("Start").."]"
end
-- Used by the pairing tool
@ -141,7 +138,7 @@ local function on_receive_fields(pos, formname, fields, player)
end
minetest.register_node("signs_bot:delayer", {
description = I("Signal Delayer"),
description = S("Signal Delayer"),
inventory_image = "signs_bot_delayer_inv.png",
drawtype = "nodebox",
node_box = {
@ -172,6 +169,7 @@ minetest.register_node("signs_bot:delayer", {
update_infotext = update_infotext,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -180,7 +178,7 @@ minetest.register_node("signs_bot:delayer", {
})
minetest.register_node("signs_bot:delayer_loaded", {
description = I("Signal Delayer"),
description = S("Signal Delayer"),
drawtype = "nodebox",
node_box = {
type = "fixed",
@ -203,6 +201,7 @@ minetest.register_node("signs_bot:delayer_loaded", {
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = signs_bot.CLIP,
is_ground_content = false,
diggable = false,
groups = {sign_bot_sensor = 1, not_in_creative_inventory = 1},
@ -210,7 +209,7 @@ minetest.register_node("signs_bot:delayer_loaded", {
})
minetest.register_node("signs_bot:delayer_on", {
description = I("Signal Delayer"),
description = S("Signal Delayer"),
drawtype = "nodebox",
node_box = {
type = "fixed",
@ -230,6 +229,7 @@ minetest.register_node("signs_bot:delayer_on", {
paramtype = "light",
sunlight_propagates = true,
paramtype2 = "facedir",
use_texture_alpha = signs_bot.CLIP,
is_ground_content = false,
diggable = false,
groups = {sign_bot_sensor = 1, not_in_creative_inventory = 1},
@ -261,12 +261,12 @@ minetest.register_lbm({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "delayer", {
name = I("Signal Delayer"),
name = S("Signal Delayer"),
data = {
item = "signs_bot:delayer",
text = table.concat({
I("Signals are forwarded delayed. Subsequent signals are queued."),
I("The delay time can be configured."),
S("Signals are forwarded delayed. Subsequent signals are queued."),
S("The delay time can be configured."),
}, "\n")
},
})

View File

@ -1,13 +1,26 @@
--[[
Signs Bot
=========
Copyright (C) 2019-2021 Joachim Stolberg
GPLv3
See LICENSE.txt for more information
Signs Bot: Bot Flap
]]--
-- Load support for I18n.
local S = signs_bot.S
signs_bot.doc = {}
if not minetest.get_modpath("doc") then
return
end
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
local function formspec(data)
if data.image then
local image = "image["..(doc.FORMSPEC.ENTRY_WIDTH - 3)..",0;3,2;"..data.image.."]"
@ -24,112 +37,113 @@ local function formspec(data)
end
local start_doc = table.concat({
I("After you have placed the Signs Bot Box, you can start the bot by means of the 'On' button in the box menu."),
I("The bot then runs straight up until it reaches an obstacle (a step with two or more blocks up or down or a sign.)"),
I("If the bot first reaches a sign it will execute the commands on the sign."),
I("If the command(s) on the sign is e.g. 'turn_around', the bot turns and goes back."),
I("In this case, the bot reaches his box again and turns off."),
S("After you have placed the Signs Bot Box, you can start the bot by means of the 'On' button in the box menu."),
S("If the bot returns to its box right away, you will likely need to charge it with electrical energy (techage) first."),
S("The bot then runs straight up until it reaches an obstacle (a step with two or more blocks up or down or a sign.)"),
S("If the bot first reaches a sign it will execute the commands on the sign."),
S("If the command(s) on the sign is e.g. 'turn_around', the bot turns and goes back."),
S("In this case, the bot reaches his box again and turns off."),
"",
I("The Signs Bot Box has an inventory with 6 stacks for signs and 8 stacks for other items (to be placed/dug by the bot)."),
I("This inventory simulates the bot internal inventory."),
I("That means you will only have access to the inventory if the bot is turned off ('sitting' in his box)."),
S("The Signs Bot Box has an inventory with 6 stacks for signs and 8 stacks for other items (to be placed/dug by the bot)."),
S("This inventory simulates the bot internal inventory."),
S("That means you will only have access to the inventory if the bot is turned off ('sitting' in his box)."),
}, "\n")
local control_doc = table.concat({
I("You simply control the direction of the bot by means of the 'turn left' and 'turn right' signs (signs with the arrow)."),
I("The bot can run over steps (one block up/down). But there are also commands to move the bot up and down."),
S("You simply control the direction of the bot by means of the 'turn left' and 'turn right' signs (signs with the arrow)."),
S("The bot can run over steps (one block up/down). But there are also commands to move the bot up and down."),
"",
I("It is not necessary to mark a way back to the box."),
I("With the command 'turn_off' the bot will turn off and be back in his box from every position."),
I("The same applies if you turn off the bot by the box menu."),
I("If the bot reaches a sign from the wrong direction (from back or sides) the sign will be ignored."),
I("The bot will walk over."),
S("It is not necessary to mark a way back to the box."),
S("With the command 'turn_off' the bot will turn off and be back in his box from every position."),
S("The same applies if you turn off the bot by the box menu."),
S("If the bot reaches a sign from the wrong direction (from back or sides) the sign will be ignored."),
S("The bot will walk over."),
"",
I("All predefined signs have a menu with a list of the bot commands."),
I("These signs can't be changed, but you can craft and program your own signs."),
I("For this you have to use the 'command' sign."),
I("This sign has an edit field for your commands and a help page with all available commands."),
I("The help page has a copy button to simplify the programming."),
S("All predefined signs have a menu with a list of the bot commands."),
S("These signs can't be changed, but you can craft and program your own signs."),
S("For this you have to use the 'command' sign."),
S("This sign has an edit field for your commands and a help page with all available commands."),
S("The help page has a copy button to simplify the programming."),
"",
I("Also for your own signs it is important to know:"),
I("After the execution of the last command of the sign, the bot falls back into its default behaviour and runs in its taken direction."),
S("Also for your own signs it is important to know:"),
S("After the execution of the last command of the sign, the bot falls back into its default behaviour and runs in its taken direction."),
"",
I("A standard job for the bot is to move items from one chest to another"),
I("(chest or node with a chest like inventory)."),
I("This can be done by means of the two signs 'take item' and 'add item'."),
I("These signs have to be placed on top of chest nodes."),
S("A standard job for the bot is to move items from one chest to another"),
S("(chest or node with a chest like inventory)."),
S("This can be done by means of the two signs 'take item' and 'add item'."),
S("These signs have to be placed on top of chest nodes."),
}, "\n")
local sensor_doc = table.concat({
I("In addition to the signs the bot can be controlled by means of sensors."),
I("Sensors like the Bot Sensor have two states: on and off."),
I("If the Bot Sensor detects a bot it will switch to the state 'on' and"),
I("sends a signal to a connected block, called an actuator."),
S("In addition to the signs the bot can be controlled by means of sensors."),
S("Sensors like the Bot Sensor have two states: on and off."),
S("If the Bot Sensor detects a bot it will switch to the state 'on' and"),
S("sends a signal to a connected block, called an actuator."),
"",
I("Sensors are:"),
I("- Bot Sensor: Sends a signal when the robot passes by"),
I("- Node Sensor: Sends a signal when it detects any node"),
I("- Crop Sensor: Sends a signal when, for example wheat is fully grown"),
I("- Bot Chest: Sends a signal depending on the chest state (empty, full)"),
S("Sensors are:"),
S("- Bot Sensor: Sends a signal when the robot passes by"),
S("- Node Sensor: Sends a signal when it detects any node"),
S("- Crop Sensor: Sends a signal when, for example wheat is fully grown"),
S("- Bot Chest: Sends a signal depending on the chest state (empty, full)"),
"",
I("Actuators are:"),
I("- Signs Bot Box: Can turn the bot off and on"),
I("- Control Unit: Can be used to exchange the sign to lead the bot"),
S("Actuators are:"),
S("- Signs Bot Box: Can turn the bot off and on"),
S("- Control Unit: Can be used to exchange the sign to lead the bot"),
"",
I("Additional sensors and actuator can be added by other mods."),
S("Additional sensors and actuator can be added by other mods."),
}, "\n")
local tool_doc = table.concat({
I("To send a signal from a sensor to an actuator, the sensor has to be connected (paired) with actuator."),
I("To connect sensor and actuator, the Sensor Connection Tool has to be used."),
I("Simply click with the tool on both blocks and the sensor will be connected with the actuator."),
I("A successful connection is indicated by a ping/pong noise."),
S("To send a signal from a sensor to an actuator, the sensor has to be connected (paired) with actuator."),
S("To connect sensor and actuator, the Sensor Connection Tool has to be used."),
S("Simply click with the tool on both blocks and the sensor will be connected with the actuator."),
S("A successful connection is indicated by a ping/pong noise."),
"",
I("Before you connect sensor with actuator, take care that the actuator is in the requested state."),
I("For example: If you want to start the Bot with a sensor, connect the sensor with the Bot Box,"),
I("when the Bot is in the state 'on'. Otherwise the sensor signal will stop the Bot,"),
I("instead of starting it."),
S("Before you connect sensor with actuator, take care that the actuator is in the requested state."),
S("For example: If you want to start the Bot with a sensor, connect the sensor with the Bot Box,"),
S("when the Bot is in the state 'on'. Otherwise the sensor signal will stop the Bot,"),
S("instead of starting it."),
}, "\n")
local inventory_doc = table.concat({
I("The following applies to all commands that are used to place items in the bot inventory, like:"),
S("The following applies to all commands that are used to place items in the bot inventory, like:"),
"",
I("- take_item <num> <slot>"),
I("- pickup_items <slot>"),
I("- trash_sign <slot>"),
I("- harvest <slot>"),
I("- dig_front <slot> <lvl>"),
I("- dig_left <slot> <lvl>"),
I("- dig_right <slot> <lvl>"),
I("- dig_below <slot> <lvl>"),
I("- dig_above <slot> <lvl>"),
S("- take_item <num> <slot>"),
S("- pickup_items <slot>"),
S("- trash_sign <slot>"),
S("- harvest <slot>"),
S("- dig_front <slot> <lvl>"),
S("- dig_left <slot> <lvl>"),
S("- dig_right <slot> <lvl>"),
S("- dig_below <slot> <lvl>"),
S("- dig_above <slot> <lvl>"),
"",
I("If no slot or slot 0 was specified with the command (case A), all 8 slots of the bot inventory "),
I("are checked one after the other. If a slot was specified (case B), only this slot is checked."),
I("In both cases the following applies: If the slot is preconfigured and fits the item, "),
I("or if the slot is not configured and empty, or is only partially filled with the item type "),
I("(which should be added), then the items are added."),
I("If not all items can be added, the remaining slots will be tried out in case A."),
I("Anything that could not be added to your own inventory goes back."),
S("If no slot or slot 0 was specified with the command (case A), all 8 slots of the bot inventory "),
S("are checked one after the other. If a slot was specified (case B), only this slot is checked."),
S("In both cases the following applies: If the slot is preconfigured and fits the item, "),
S("or if the slot is not configured and empty, or is only partially filled with the item type "),
S("(which should be added), then the items are added."),
S("If not all items can be added, the remaining slots will be tried out in case A."),
S("Anything that could not be added to your own inventory goes back."),
"",
I("The following applies to all commands that are used to take items from the bot inventory, like:"),
S("The following applies to all commands that are used to take items from the bot inventory, like:"),
"",
I("- add_item <num> <slot>"),
S("- add_item <num> <slot>"),
"",
I("It doesn't matter whether a slot is configured or not. The bot takes the first stack that "),
I("it can find from its own inventory and tries to use it."),
I("If a slot is specified, it only takes this, if no slot has been specified, it checks all of "),
I("them one after the other, starting from slot 1 until it finds something."),
I("If the number found is smaller than requested, he tries to take the rest out of any slot."),
S("It doesn't matter whether a slot is configured or not. The bot takes the first stack that "),
S("it can find from its own inventory and tries to use it."),
S("If a slot is specified, it only takes this, if no slot has been specified, it checks all of "),
S("them one after the other, starting from slot 1 until it finds something."),
S("If the number found is smaller than requested, he tries to take the rest out of any slot."),
}, "\n")
doc.add_category("signs_bot",
{
name = I("Signs Bot"),
description = I("A robot controlled by signs, used for automated work"),
name = S("Signs Bot"),
description = S("A robot controlled by signs, used for automated work"),
sorting = "custom",
sorting_data = {"start", "control", "sensor_doc", "tool",
"box", "bot_flap", "duplicator",
@ -141,26 +155,26 @@ doc.add_category("signs_bot",
})
doc.add_entry("signs_bot", "start", {
name = I("Start the Bot"),
name = S("Start the Bot"),
data = {text = start_doc, image = "signs_bot_doc_image.png"},
})
doc.add_entry("signs_bot", "control", {
name = I("Control the Bot"),
name = S("Control the Bot"),
data = {text = control_doc, image = "signs_bot_doc_image.png"},
})
doc.add_entry("signs_bot", "sensor_doc", {
name = I("Sensors and Actuators"),
name = S("Sensors and Actuators"),
data = {text = sensor_doc, image = "signs_bot_doc_image.png"},
})
doc.add_entry("signs_bot", "tool", {
name = I("Connecting sensors and actuator"),
name = S("Connecting sensors and actuator"),
data = {text = tool_doc, image = "signs_bot_doc_image.png"},
})
doc.add_entry("signs_bot", "tool", {
name = I("Bot inventory behavior"),
name = S("Bot inventory behavior"),
data = {text = inventory_doc, image = "signs_bot_doc_image.png"},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,13 +13,10 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
@ -27,15 +24,15 @@ local formspec = "size[8,7.3]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"label[0.3,0;"..I("Input:").."]"..
"list[context;inp;3,0;1,1;]"..
"label[0.3,1;"..I("Template:").."]"..
"list[context;temp;3,1;1,1;]"..
"label[0.3,2;"..I("Output:").."]"..
"list[context;outp;3,2;1,1;]"..
"label[4,0;"..I("1. Place one 'cmnd' sign to be\n used as template.\n")..
I("2. Add 'blank signs' to\n the input inventory.\n")..
I("3. Take the copies\n from the output inventory.").."]"..
"label[0.1,0.2;"..S("Template:").."]"..
"list[context;temp;2,0;1,1;]"..
"label[0.1,1.2;"..S("Input:").."]"..
"list[context;inp;2,1;1,1;]"..
"label[0.1,2.2;"..S("Output:").."]"..
"list[context;outp;2,2;1,1;]"..
"label[3,0.2;"..S("1. Place one 'cmnd' sign").."]"..
"label[3,1.2;"..S("2. Add 'blank signs'").."]"..
"label[3,2.2;"..S("3. Take the copies").."]"..
"list[current_player;main;0,3.5;8,4;]"..
"listring[context;inp]"..
"listring[current_player;main]"..
@ -108,7 +105,7 @@ local function on_metadata_inventory_put(pos, listname, index, stack, player)
end
minetest.register_node("signs_bot:duplicator", {
description = I("Signs Duplicator"),
description = S("Signs Duplicator"),
stack_max = 1,
tiles = {
-- up, down, right, left, back, front
@ -167,7 +164,7 @@ local function formspec_user(cmnd)
end
minetest.register_node("signs_bot:sign_user", {
description = I('Sign "user"'),
description = S('Sign "user"'),
drawtype = "nodebox",
inventory_image = "signs_bot_sign_user.png",
node_box = {
@ -210,7 +207,7 @@ minetest.register_node("signs_bot:sign_user", {
signs_bot.register_sign({
name = "sign_blank",
description = I('Sign "blank"'),
description = S('Sign "blank"'),
commands = "",
image = "signs_bot_sign_blank.png",
})
@ -226,17 +223,17 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "duplicator", {
name = I("Signs Duplicator"),
name = S("Signs Duplicator"),
data = {
item = "signs_bot:duplicator",
text = table.concat({
I("The Duplicator can be used to make copies of signs."),
I("1. Put one 'cmnd' sign to be used as template into the 'Template' inventory"),
I("2. Add one or several 'blank signs' to the 'Input' inventory."),
I("3. Take the copies from the 'Output' inventory."),
S("The Duplicator can be used to make copies of signs."),
S("1. Put one 'cmnd' sign to be used as template into the 'Template' inventory"),
S("2. Add one or several 'blank signs' to the 'Input' inventory."),
S("3. Take the copies from the 'Output' inventory."),
"",
I("Written books [default:book_written] can alternatively be used as template"),
I("Already written signs can be used as input, too."),
S("Written books [default:book_written] can alternatively be used as template"),
S("Already written signs can be used as input, too."),
}, "\n")
},
})
@ -244,10 +241,10 @@ end
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "sign_blank", {
name = I('Sign "blank"'),
name = S('Sign "blank"'),
data = {
item = "signs_bot:sign_blank",
text = I("Needed as input for the Duplicator.")
text = S("Needed as input for the Duplicator.")
},
})
end

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,22 +13,18 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
local lib = signs_bot.lib
-- Load support for I18n.
local S = signs_bot.S
local function update_infotext(pos, dest_pos, cmnd)
M(pos):set_string("infotext", I("Sensor Extender: Connected with ")..S(dest_pos).." / "..cmnd)
M(pos):set_string("infotext", S("Sensor Extender: Connected with").." "..P2S(dest_pos).." / "..cmnd)
end
minetest.register_node("signs_bot:sensor_extender", {
description = I("Sensor Extender"),
description = S("Sensor Extender"),
inventory_image = "signs_bot_extender_inv.png",
drawtype = "nodebox",
node_box = {
@ -54,12 +50,13 @@ minetest.register_node("signs_bot:sensor_extender", {
after_place_node = function(pos, placer)
local meta = M(pos)
meta:set_string("infotext", I("Sensor Extender: Not connected"))
meta:set_string("infotext", S("Sensor Extender: Not connected"))
end,
update_infotext = update_infotext,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -68,7 +65,7 @@ minetest.register_node("signs_bot:sensor_extender", {
})
minetest.register_node("signs_bot:sensor_extender_on", {
description = I("Sensor Extender"),
description = S("Sensor Extender"),
drawtype = "nodebox",
node_box = {
type = "connected",
@ -108,6 +105,7 @@ minetest.register_node("signs_bot:sensor_extender_on", {
update_infotext = update_infotext,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -127,13 +125,13 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "sensor_extender", {
name = I("Sensor Extender"),
name = S("Sensor Extender"),
data = {
item = "signs_bot:sensor_extender",
text = table.concat({
I("With the Sensor Extender, sensor signals can be sent to more than one actuator."),
I("Place one or more extender nearby the sensor and connect each extender"),
I("with one further actuator by means of the Connection Tool."),
S("With the Sensor Extender, sensor signals can be sent to more than one actuator."),
S("Place one or more extender nearby the sensor and connect each extender"),
S("with one further actuator by means of the Connection Tool."),
}, "\n")
},
})

458
signs_bot/i18n.py Normal file
View File

@ -0,0 +1,458 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Script to generate the template file and update the translation files.
# Copy the script into the mod or modpack root folder and run it there.
#
# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer
# LGPLv2.1+
#
# See https://github.com/minetest-tools/update_translations for
# potential future updates to this script.
from __future__ import print_function
import os, fnmatch, re, shutil, errno
from sys import argv as _argv
from sys import stderr as _stderr
# Running params
params = {"recursive": False,
"help": False,
"mods": False,
"verbose": False,
"folders": [],
"no-old-file": False,
"break-long-lines": False,
"sort": False
}
# 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']
}
# 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.abspath('~/.minetest/mods/')}")
run_all_subfolders("~/.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. 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:
pass
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 = 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}\n"]
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()
lOut.append("")
lOut.append(source)
lOut.append("")
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:
lOut.append(comment)
lOut.append(f"{localizedString}={translation}")
if params["break-long-lines"] and len(localizedString) > doublespace_threshold:
lOut.append("")
unusedExist = False
for key in dOld:
if key not in dkeyStrings:
val = dOld[key]
translation = val.get("translation")
comment = val.get("comment")
# only keep an unused translation if there was translated
# text or a comment associated with it
if translation != None and (translation != "" or comment):
if not unusedExist:
unusedExist = True
lOut.append("\n\n##### not used anymore #####\n")
if params["break-long-lines"] and len(key) > doublespace_threshold and not lOut[-1] == "":
lOut.append("")
if comment != None:
lOut.append(comment)
lOut.append(f"{key}={translation}")
if params["break-long-lines"] and len(key) > doublespace_threshold:
lOut.append("")
return "\n".join(lOut) + '\n'
# Writes a template.txt file
# dkeyStrings is the dictionary returned by generate_template
def write_template(templ_file, dkeyStrings, mod_name):
# read existing template file to preserve comments
existing_template = import_tr_file(templ_file)
text = strings_to_text(dkeyStrings, existing_template[0], mod_name, existing_template[2])
mkdir_p(os.path.dirname(templ_file))
with open(templ_file, "wt", encoding='utf-8') as template_file:
template_file.write(text)
# Gets all translatable strings from a lua file
def read_lua_file_strings(lua_file):
lOut = []
with open(lua_file, encoding='utf-8') as text_file:
text = text_file.read()
#TODO remove comments here
text = re.sub(pattern_concat, "", text)
strings = []
for s in pattern_lua_s.findall(text):
strings.append(s[1])
for s in pattern_lua_bracketed_s.findall(text):
strings.append(s)
for s in pattern_lua_fs.findall(text):
strings.append(s[1])
for s in pattern_lua_bracketed_fs.findall(text):
strings.append(s)
for s in strings:
s = re.sub(r'"\.\.\s+"', "", s)
s = re.sub("@[^@=0-9]", "@@", s)
s = s.replace('\\"', '"')
s = s.replace("\\'", "'")
s = s.replace("\n", "@n")
s = s.replace("\\n", "@n")
s = s.replace("=", "@=")
lOut.append(s)
return lOut
# Gets strings from an existing translation file
# returns both a dictionary of translations
# and the full original source text so that the new text
# can be compared to it for changes.
# Returns also header comments in the third return value.
def import_tr_file(tr_file):
dOut = {}
text = None
header_comment = None
if os.path.exists(tr_file):
with open(tr_file, "r", encoding='utf-8') as existing_file :
# save the full text to allow for comparison
# of the old version with the new output
text = existing_file.read()
existing_file.seek(0)
# a running record of the current comment block
# we're inside, to allow preceeding multi-line comments
# to be retained for a translation line
latest_comment_block = None
for line in existing_file.readlines():
line = line.rstrip('\n')
if line[:3] == "###":
if header_comment is None:
# Save header comments
header_comment = latest_comment_block
# Stip textdomain line
tmp_h_c = ""
for l in header_comment.split('\n'):
if not l.startswith("# textdomain:"):
tmp_h_c += l + '\n'
header_comment = tmp_h_c
# Reset comment block if we hit a header
latest_comment_block = None
continue
if line[:1] == "#":
# Save the comment we're inside
if not latest_comment_block:
latest_comment_block = line
else:
latest_comment_block = latest_comment_block + "\n" + line
continue
match = pattern_tr.match(line)
if match:
# this line is a translated line
outval = {}
outval["translation"] = match.group(2)
if latest_comment_block:
# if there was a comment, record that.
outval["comment"] = latest_comment_block
latest_comment_block = None
dOut[match.group(1)] = outval
return (dOut, text, header_comment)
# Walks all lua files in the mod folder, collects translatable strings,
# and writes it to a template.txt file
# Returns a dictionary of localized strings to source file sets
# that can be used with the strings_to_text function.
def generate_template(folder, mod_name):
dOut = {}
for root, dirs, files in os.walk(folder):
for name in files:
if fnmatch.fnmatch(name, "*.lua"):
fname = os.path.join(root, name)
found = read_lua_file_strings(fname)
if params["verbose"]:
print(f"{fname}: {str(len(found))} translatable strings")
for s in found:
sources = dOut.get(s, set())
sources.add(f"### {os.path.basename(fname)} ###")
dOut[s] = sources
if len(dOut) == 0:
return None
templ_file = os.path.join(folder, "locale/template.txt")
write_template(templ_file, dOut, mod_name)
return dOut
# Updates an existing .tr file, copying the old one to a ".old" file
# if any changes have happened
# dNew is the data used to generate the template, it has all the
# currently-existing localized strings
def update_tr_file(dNew, mod_name, tr_file):
if params["verbose"]:
print(f"updating {tr_file}")
tr_import = import_tr_file(tr_file)
dOld = tr_import[0]
textOld = tr_import[1]
textNew = strings_to_text(dNew, dOld, mod_name, tr_import[2])
if textOld and textOld != textNew:
print(f"{tr_file} has changed.")
if not params["no-old-file"]:
shutil.copyfile(tr_file, f"{tr_file}.old")
with open(tr_file, "w", encoding='utf-8') as new_tr_file:
new_tr_file.write(textNew)
# Updates translation files for the mod in the given folder
def update_mod(folder):
modname = get_modname(folder)
if modname is not None:
process_po_files(folder, modname)
print(f"Updating translations for {modname}")
data = generate_template(folder, modname)
if data == None:
print(f"No translatable strings found in {modname}")
else:
for tr_file in get_existing_tr_files(folder):
update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file))
else:
print(f"\033[31mUnable to find modname in folder {folder}.\033[0m", file=_stderr)
exit(1)
# Determines if the folder being pointed to is a mod or a mod pack
# and then runs update_mod accordingly
def update_folder(folder):
is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf"))
if is_modpack:
subfolders = [f.path for f in os.scandir(folder) if f.is_dir()]
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()]:
update_folder(modfolder + "/")
main()

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -15,7 +15,10 @@
signs_bot = {}
-- Version for compatibility checks, see readme.md/history
signs_bot.version = 1.05
signs_bot.version = 1.08
-- Test for MT 5.4 new string mode
signs_bot.CLIP = minetest.features.use_texture_alpha_string_modes and "clip" or true
if minetest.global_exists("techage") and techage.version < 0.25 then
error("[signs_bot] Signs Bot requires techage version 0.25 or newer!")
@ -25,8 +28,15 @@ if tubelib2.version < 1.9 then
error("[signs_bot] Signs Bot requires tubelib2 version 1.9 or newer!")
end
if minetest.global_exists("minecart") and minecart.version < 2.0 then
error("[signs_bot] Signs Bot requires minecart version 2.0 or newer!")
end
-- Load support for I18n.
signs_bot.S = minetest.get_translator("signs_bot")
local MP = minetest.get_modpath("signs_bot")
dofile(MP.."/doc.lua")
dofile(MP.."/random.lua")
dofile(MP.."/lib.lua")
@ -60,5 +70,6 @@ dofile(MP.."/techage.lua")
dofile(MP.."/timer.lua")
dofile(MP.."/delayer.lua")
dofile(MP.."/logic_and.lua")
dofile(MP.."/compost.lua")
dofile(MP.."/tool.lua")

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019-2020 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -12,9 +12,8 @@
]]--
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local MAX_SIZE = 1000 -- max number of tokens
@ -128,6 +127,19 @@ local function compile(script)
return pass2(tokens)
end
local function gen_string_cmnd(code, pc, num_param, script)
local tokens = tokenizer(script)
if num_param == 0 then
return tokens[pc]
elseif num_param == 1 then
return tokens[pc] .. " " .. (tokens[pc+1] or "")
elseif num_param == 2 then
return tokens[pc] .. " " .. (tokens[pc+1] or "") .. " " .. (tokens[pc+2] or "")
else
return tokens[pc] .. " " .. (tokens[pc+1] or "") .. " " .. (tokens[pc+2] or "") .. " " .. (tokens[pc+3] or "")
end
end
-------------------------------------------------------------------------------
-- Commands
-------------------------------------------------------------------------------
@ -235,29 +247,29 @@ function api.check_script(script)
if tCmdDef[cmnd] then
num_token = num_token + 1 + tCmdDef[cmnd].num_param
if num_token > MAX_SIZE then
return false, I("Maximum programm size exceeded"), idx
return false, S("Maximum programm size exceeded"), idx
end
param1 = tonumber(param1) or param1
param2 = tonumber(param2) or param2
param3 = tonumber(param3) or param3
local num_param = (param1 and 1 or 0) + (param2 and 1 or 0) + (param3 and 1 or 0)
if tCmdDef[cmnd].num_param < num_param then
return false, I("Too many parameters"), idx
return false, S("Too many parameters"), idx
end
if tCmdDef[cmnd].num_param > 0 and not tCmdDef[cmnd].check(param1, param2, param3) then
return false, I("Parameter error"), idx
return false, S("Parameter error"), idx
end
elseif not cmnd:find("%w+:") then
return false, I("Command error"), idx
return false, S("Command error"), idx
end
tbl[cmnd] = (tbl[cmnd] or 0) + 1
end
if (tbl["end"] or 0) > (tbl["repeat"] or 0) then
return false, I("'repeat' missing"), 0
return false, S("'repeat' missing"), 0
elseif (tbl["end"] or 0) < (tbl["repeat"] or 0) then
return false, I("'end' missing"), 0
return false, S("'end' missing"), 0
end
return true, I("Checked and approved"), 0
return true, S("Checked and approved"), 0
end
-- function returns: true/false, error-string
@ -281,7 +293,7 @@ function api.run_script(base_pos, mem)
mem.pc = 1
mem.Stack = {}
end
return res, err
return res, err, gen_string_cmnd(code, mem.pc, num_param, mem.script)
end
return api.EXIT
end

View File

@ -1,45 +0,0 @@
-- Fallback functions for when `intllib` is not installed.
-- Code released under Unlicense <http://unlicense.org>.
-- Get the latest version of this file at:
-- https://raw.githubusercontent.com/minetest-mods/intllib/master/lib/intllib.lua
local function format(str, ...)
local args = { ... }
local function repl(escape, open, num, close)
if escape == "" then
local replacement = tostring(args[tonumber(num)])
if open == "" then
replacement = replacement..close
end
return replacement
else
return "@"..open..num..close
end
end
return (str:gsub("(@?)@(%(?)(%d+)(%)?)", repl))
end
local gettext, ngettext
if minetest.get_modpath("intllib") then
if intllib.make_gettext_pair then
-- New method using gettext.
gettext, ngettext = intllib.make_gettext_pair()
else
-- Old method using text files.
gettext = intllib.Getter()
end
end
-- Fill in missing functions.
gettext = gettext or function(msgid, ...)
return format(msgid, ...)
end
ngettext = ngettext or function(msgid, msgid_plural, n, ...)
return format(n==1 and msgid or msgid_plural, ...)
end
return gettext, ngettext

View File

@ -1,4 +1,4 @@
#!/bin/bash
../intllib/tools/xgettext.sh ./basis.lua ./bot_flap.lua ./bot_sensor.lua ./cart_sensor.lua ./changer.lua ./chest.lua ./cmd_farming.lua ./cmd_flowers.lua ./cmd_item.lua ./cmd_move.lua ./cmd_pattern.lua ./cmd_place.lua ./cmd_sign.lua ./commands.lua ./crop_sensor.lua ./doc.lua ./duplicator.lua ./extender.lua ./init.lua ./lib.lua ./node_sensor.lua ./nodes.lua ./robot.lua ./signal.lua ./signs.lua ./tool.lua ./timer.lua ./delayer.lua ./logic_and.lua ./interpreter.lua
../intllib/tools/xgettext.sh ./basis.lua ./bot_flap.lua ./bot_sensor.lua ./cart_sensor.lua ./changer.lua ./chest.lua ./cmd_farming.lua ./cmd_flowers.lua ./cmd_item.lua ./cmd_move.lua ./cmd_pattern.lua ./cmd_place.lua ./cmd_sign.lua ./commands.lua ./crop_sensor.lua ./doc.lua ./duplicator.lua ./extender.lua ./init.lua ./lib.lua ./node_sensor.lua ./nodes.lua ./robot.lua ./signal.lua ./signs.lua ./tool.lua ./timer.lua ./delayer.lua ./logic_and.lua ./interpreter.lua ./compost.lua

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,8 +13,6 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
signs_bot.register_inventory({"default:chest", "default:chest_open"}, {
@ -63,3 +61,21 @@ signs_bot.register_inventory({"default:furnace", "default:furnace_active"}, {
},
})
signs_bot.register_inventory({"mobs:beehive"}, {
put = {
listname = "beehive",
},
take = {
listname = "beehive",
},
})
signs_bot.register_inventory({"xdecor:hive"}, {
put = {
listname = "honey",
},
take = {
listname = "honey",
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,8 +13,6 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta
signs_bot.lib = {}
@ -91,6 +89,13 @@ local function handle_drop(drop)
end
return name
end
end
function signs_bot.handle_drop_like_a_player(node)
local drops = minetest.get_node_drops(node)
if #drops >= 1 then
return drops[1]
end
return false
end
@ -213,7 +218,6 @@ function signs_bot.lib.after_dig_sign_node(pos, oldnode, oldmetadata, digger)
smeta:set_string("err_msg", oldmetadata.fields.err_msg or "")
end
local player_name = digger:get_player_name()
-- See https://github.com/minetest/minetest/blob/34e3ede8eeb05e193e64ba3d055fc67959d87d86/doc/lua_api.txt#L6222
if player_name == "" then
minetest.add_item(pos, sign)
else

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,413 @@
# textdomain: signs_bot
### basis.lua ###
running=läuft
charging=aufladen
stopped=gestoppt
Off=Aus
On=An
Signs=Zeichen
Other items=Andere Gegenstände
Config=Konfig.
Preassign slots items=Vorbelegungen
Back=Zurück
Robot Box=Roboterbox
no power=kein Strom
Signs Bot Box=Roboter Box
The Box is the housing of the bot.=Die Box ist das Gehäuse des Roboters.
Place the box and start the bot by means of the 'On' button.=Platziere die Box und starte den Roboter über den "An" Button.
If the mod techage is installed, the bot needs electrical power.=Wenn die Mod techage installiert ist, benötigt der Roboter elektrischen Strom.
The bot leaves the box on the right side.=Der Roboter verlässt die Box auf der rechten Seite.
It will not start, if this position is blocked.=Er startet nicht, wenn diese Position belegt ist.
To stop and remove the bot, press the 'Off' button.=Um den Roboter zu stoppen bzw. zu entfernen, drücke den "Aus" Button.
The box inventory simulates the inventory of the bot.=Das Inventar der Box simuliert das Roboter Inventar.
You will not be able to access the inventory, if the bot is running.=Du hast keinen Zugriff auf das Inventar, sofern der Roboter unterwegs ist.
The bot can carry up to 8 stacks and 6 signs with it.=Der Roboter kann 8 Stapel von Blöcken und 6 Zeichen transportieren.
### bot_flap.lua ###
Exit=Beenden
Bot Flap=Roboterklappe
The flap is a simple block used as door for the bot.=Die Klappe ist ein einfacher Block, welcher vom Roboter als Tür genutzt wird.
Place the flap in any wall, and the bot will automatically open=Platziere die Klappe in eine Mauer und der Roboter wird die Klappe automatisch öffnen
and close the flap as it passes through it.=und wieder schließen, wenn er durchgegangen ist.
### bot_sensor.lua ###
Bot Sensor: Connected with=Bot Sensor: Verbunden mit
Bot Sensor=Bot Sensor
Bot Sensor: Not connected=Bot Sensor: Nicht verbunden
The Bot Sensor detects any bot and sends a signal, if a bot is nearby.=Der Roboter Sensor entdeckt jeden Roboter und sendet ein Signal, sofern ein Roboter in der Nähe ist.
The sensor direction does not care.=Die Ausrichtung des Sensor spielt keine Rolle.
### cart_sensor.lua ###
Cart Sensor: Connected with=Wagen Sensor: Verbunden mit
Cart Sensor=Wagen Sensor
Cart Sensor: Not connected=Wagen Sensor: Nicht verbunden
The Cart Sensor detects and sends a signal, if a cart (Minecart) is nearby.=Der Wagen Sensor sendet ein Signal, sofern ein Wagen in der Nähe ist.
The sensor has an active side (red) that must point to the rail/cart.=Der Sensor hat eine aktive Seite (rot), welche zu den Schienen zeigen muss.
### cart_sensor.lua ###
### bot_sensor.lua ###
the sensor range is one node/meter.=Der Sensorbereich ist einen Block/Meter groß.
### changer.lua ###
Signs:=Zeichen:
Bot Control Unit=Roboter Steuerungseinheit
The Bot Control Unit is used to lead the bot by means of signs.=Die Roboter Steuerungseinheit dient zur Steuerung des Roboters über Zeichen.
The unit can be loaded with up to 4 different signs and can be programmed by means of sensors.=Die Einheit kann mit bis zu 4 verschiedenen Zeichen geladen und über Sensoren programmiert werden.
To load the unit, place a sign on the red side of the unit and click on the unit.=Um die Steuerungseinheit zu laden, platziere ein Zeichen auf die rote Seite der Einheit und klicke auf die Einheit.
The sign disappears / is moved to the inventory of the unit.=Das Zeichen verschwindet/ist in die Steuerungseinheit verschoben.
This can be repeated 3 times.=Dies kann bis zu 3 mal wiederholt werden.
Use the connection tool to connect up to 4 sensors with the Bot Control Unit.=Benutze das Verbinde-Werkzeug um die bis zu 4 Sensoren mit der Steuerungseinheit zu verbinden.
### chest.lua ###
Bot Chest: Sends signal to=Bot Kiste: Sende Signal zu
Signs Bot Chest=Signs Bot Kiste
Bot Chest: Not connected=Bot Kiste: nicht verbunden
The Signs Bot Chest is a special chest with sensor function.=Die Roboter Kiste ist eine spezielle Kiste mit zusätzlicher Sensor-Funktion.
It sends a signal depending on the chest state.=Sie sendet ein Signal abhängig vom Zustand der Kiste.
Possible states are 'empty', 'not empty', 'almost full'=Mögliche Zustände sind "leer", "nicht leer" und "fast voll"
A typical use case is to turn off the bot, when the chest is almost full or empty.=Ein typischer Anwendungsfall ist den Roboter zu stoppen, wenn die Kiste fast voll oder leer ist.
### cmd_farming.lua ###
Sow farming seeds@nin front of the robot=Sähe Saatgut@nvor dem Roboter
Harvest farming products@nin front of the robot@non a 3x3 field.=Ernte Früchte/Getreide@nin einem 3x3 großem Feld@nvor dem Roboter.
Plant a sapling@nin front of the robot=Pflanze einen Setzling@nvor den Roboter
Sign "farming"=Zeichen "Farming"
Sign 'farming'=Zeichen 'Farming'
Used to harvest and seed a 3x3 field.=Benötigt um ein 3x3 Feld zu ernten und wieder zu sähen.
The seed to be placed has to be in the first inventory slot of the bot.=Das Saatgut, dass gesät werden soll, muss sich an der 1. Position im Inventar befinden.
### cmd_farming.lua ###
### cmd_flowers.lua ###
Place the sign in front of the field.=Platziere das Zeichen vor das Feld.
When finished, the bot turns.=Der Roboter dreht um, wenn er fertig ist.
### cmd_flowers.lua ###
Cutting flowers, leaves and tree blocks@nin front of the robot@non a 3x3 field.=Schneide Blumen, Blätter und Baumblöcke@nin einem 3x3 großem Feld@nvor dem Roboter.
Sign "flowers"=Zeichen "Blumen"
Sign 'flowers'=Zeichen 'Blumen'
Used to cut flowers on a 3x3 field.=Benötigt um ein 3x3 Blumenfeld zu ernten.
### cmd_item.lua ###
Take <num> items from a chest like node@nand put it into the item inventory.@n<slot> is the inventory slot (1..8) or 0 for any one=Nehme <num> Gegenstände aus der@nKiste oder dem Kisten-ähnlichen Block@nund tue diese in das eigene Inventar@nan der Position <slot>. Slot = (1..8)@noder 0 für irgend eine Position
Add <num> items to a chest like node@ntaken from the item inventory.@n<slot> is the inventory slot (1..8) or 0 for any one=Lege <num> Gegenstände aus dem@neigenen Inventar in die andere Kiste.@n<slot> ist die Position im@neigenen Inventar (1--8).@noder 0 für irgend eine Position
Add <num> fuel to a furnace like node@ntaken from the item inventory.@n<slot> is the inventory slot (1..8) or 0 for any one=Lege <num> Brennstoffe aus dem@neigenen Inventar in den anderen Block.@n<slot> ist die Position im@neigenen Inventar (1--8).@noder 0 für irgend eine Position
deprecated, use bot inventory configuration instead=veraltet, benutze stattdessen die Inventar Konfigurationsmöglichkeit
Pick up all objects@nin a 3x3 field.@n<slot> is the inventory slot (1..8) or 0 for any one=Hebe alle Objekte in einem@n3x3 Blöcke großen Feld auf@nund lege diese in das eigene@nInventar an Position <slot> (1-8)@noder 0 für irgend eine Position
Drop items in front of the bot.@n<slot> is the inventory slot (1..8) or 0 for any one=Lasse ein Objekt aus dem eigenen@nInventar vor dem Roboter fallen.@n<slot> ist die Position im@neigenen Inventar (1--8).@noder 0 für irgend eine Position
Punch a rail cart to start it=Schlage den Wagen um ihn zu starten
### cmd_move.lua ###
Move the robot one step back=Bewege den Roboter@neinen Schritt zurück
Turn the robot to the left=Drehe den Roboter@nnach links
Turn the robot to the right=Drehe den Roboter@nnach rechts
Turn the robot around=Drehe den Roboter@num (180 Grad)
Move the robot upwards=Bewege den Roboter@nnach oben
Move the robot down=Bewege den Roboter@nnach unten
Stop the robot for <sec> seconds@n(1..9999)=Stoppe den Roboter@nfür <sec> Sekunden (1.9999)
Stop the robot.=Stoppe den Roboter.
Turn the robot off@nand put it back in the box.=Schalte den Roboter aus und@nsetze ihn damit zurück in@nseine Box.
### cmd_pattern.lua ###
Store pattern to be cloned.=Speichere die Vorlage@ndie kopiert werden soll.
Copy the nodes from@nthe stored pattern position@n<size> is: 3x1, 3x2, 3x3,@n5x1, 5x2, 5x3 (wide x deep)@n<lvl> pattern level offset (0..4)=Kopiere die Blöcke von der@n"Vorlage" Position.@n<size> ist: 3x1, 3x2, 3x3,@n5x1, 5x2, 5x3 (Breite x Tiefe)@n<lvl> Vorlagenebene (0..4)
Sign "pattern"=Zeichen "Vorlage"
Sign "copy 3x3x3"=Zeichen "kopiere 3x3x3"
Sign 'pattern'=Zeichen 'Vorlage'
Used to make a copy of a 3x3x3 cube.=Benötigt um eine Kopie eines 3x3x3 Quadrats zu machen.
Place the sign in front of the pattern to be copied.=Platziere das Zeichen vor die Vorlage, die kopiert werden soll.
Use the copy sign to make the copy of this pattern on a different location.=Benutze das Kopier-Zeichen, um eine Kopie dieser Vorlage an einer anderen Stelle zu machen.
The bot must first reach the pattern sign, then the copy sign.=Der Roboter muss zuerst das Vorlage-Zeichen und dann das Kopier-Zeichen erreichen.
Sign 'copy3x3x3'=Zeichen 'kopiere 3x3x3'
Place the sign in front of the location, where the copy should be made.=Platziere das Zeichen vor die Stelle, wo die Kopie hergestellt werden soll.
Use the pattern sign to mark the pattern.=Benutze das Vorlage-Zeichen und die Vorlage zu markieren.
### cmd_place.lua ###
Error: Position protected=Fehler: Position geschützt
Place a block in front of the robot@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=Setze einen Block vor den Roboter.@n<slot> ist die Position im@neigenen Inventar (1--8).@nFür <lvl> ist zulässig: -1 0 +1
Place a block on the left side@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=Setze einen Block links vorne.@n<slot> ist die Position im@neigenen Inventar (1--8).@nFür <lvl> ist zulässig: -1 0 +1
Place a block on the right side@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=Setze einen Block rechts vorne.@n<slot> ist die Position im@neigenen Inventar (1--8).@nFür <lvl> ist zulässig: -1 0 +1
Place a block under the robot.@nHint: use 'move_up' first.@n<slot> is the inventory slot (1..8)=Setze einen Block unter den Roboter.@n<slot> ist die Position im@neigenen Inventar (1--8).
Place a block above the robot.@n<slot> is the inventory slot (1..8)=Setze einen Block über den Roboter.@n<slot> ist die Position im@neigenen Inventar (1--8).
Error: No free inventory space=Fehler: Kein freier Inventarplatz
Dig the block in front of the robot@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=Entferne einen Block vor den Roboter.@n<slot> ist die Position im@neigenen Inventar (1--8).@nFür <lvl> ist zulässig: -1 0 +1
Dig the block on the left side@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=Entferne einen links vorne.@n<slot> ist die Position im@neigenen Inventar (1--8).@nFür <lvl> ist zulässig: -1 0 +1
Dig the block on the right side@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=Entferne einen rechts vorne.@n<slot> ist die Position im@neigenen Inventar (1--8).@nFür <lvl> ist zulässig: -1 0 +1
Dig the block under the robot.@n<slot> is the inventory slot (1..8)=Entferne einen Block unter dem@nRoboter.@n<slot> ist die Position im@neigenen Inventar (1--8).
Dig the block above the robot.@n<slot> is the inventory slot (1..8)=Entferne einen Block über dem@nRoboter.@n<slot> ist die Position im@neigenen Inventar (1--8).
Rotate the block in front of the robot@n<lvl> is one of: -1 0 +1@n<steps> is one of: 1 2 3=Rotiere den Block vor dem Roboter.@nFür <lvl> ist zulässig: -1 0 +1@nFür <steps> ist zulässig: 1 2 3
Bot torch=Bot Fackel
### cmd_sign.lua ###
Commands,Help=Kommandos,Hilfe
Sign name:=Zeichenname:
Cancel=Abbruch
Check=Prüfen
Help=Hilfe
Copy Cmnd=Kopiere Kommando
please check the added line(s)=bitte prüfe die neue(n) Zeile(n)
Sign "command"=Zeichen "Kommando"
-- enter or copy commands from help page=-- Kommandos eingeben oder von der Hilfeseite kopieren
Error: Signs inventory empty=Fehler: Zeichen Inventar ist leer
Error: Position protected or occupied=Fehler: Position ist geschützt oder belegt
Place a sign in front of the robot@ntaken from the signs inventory@n<slot> is the inventory slot (1..6)=Setze ein Zeichen vor den Roboter.@n<slot> ist die Position im@neigenen Inventar (1--6).
Place a sign behind the robot@ntaken from the signs inventory@n<slot> is the inventory slot (1..6)=Setze ein Zeichen hinter den Roboter.@n<slot> ist die Position im@neigenen Inventar (1--6).
Error: No sign available=Fehler: Kein Zeichen verfügar
Error: Signs inventory slot is occupied=Fehler: Die Zeicheninventar Position ist belegt
Error: Position is protected=Fehler: Die Position ist geschützt
Dig the sign in front of the robot@nand add it to the signs inventory.@n<slot> is the inventory slot (1..6)=Entferne das Zeichen vor den Roboter.@n<slot> ist die Position im@neigenen Inventar (1--6).
Dig the sign in front of the robot@nand add the cleared sign to@nthe item iventory.@n<slot> is the inventory slot (1..8)=Entferne das Zeichen vor den Roboter.@n<slot> ist die Position im@neigenen Inventar (1--6).
Sign 'command'=Zeichen 'Kommando'
The 'command' sign can be programmed by the player.=Das 'Kommando' Zeichen kann vom Spieler programmiert werden.
Place the sign in front of you and use the node menu to program your sequence of bot commands.=Platziere das Zeichen vor dir und nutze das Zeichen-Menü, um die Kommando-Sequenz zu programmieren.
The menu has an edit field for your commands and a help page with all available commands.=Das Menü hat ein Eingabefeld für deine Kommandos und eine Hilfeseite zu allen Kommandos.
### commands.lua ###
commands:=Kommandos:
unknown command=unbekanntes Kommando
start of a 'repeat..end' block=Anfang eines 'repeat..end' Blocks
end command of a 'repeat..end' block=Ende Kommando eines 'repeat..end' Blocks
call a subroutine (with 'return' statement)=Aufruf einer Unterfunktion (mit 'return' Anweisung)
return from a subroutine=Rückkehr von einer Unterfunktion
jump to a label=Sprung zu einer Marke
Move the robot 1..999 steps forward@nwithout paying attention to any signs.@nUp and down movements also become@ncounted as steps.=Bewege den Roboter 1..999 Schritte@nvorwärts ohne auf Zeichen zu achten.@nAuf- und Ab-Bewegungen werden auch@nals Schritte gezählt.
Walk until a sign or obstacle is@nreached. Then continue with the next command.@nWhen a sign has been reached, @nthe current program is ended@nand the bot executes the@nnew program from the sign=Gehe bis ein Zeichen oder Hindernis@nerreicht wurde. Führe dann das nächste@nKommando aus. @nWurde ein Zeichen erreicht, so arbeite@ndie Kommandos des Zeichens als@nUnter-Prozess ab
Print given text as chat message.@nFor two or more words, use the '*' character @ninstead of spaces, like "Hello*world"=Gebe den angegebenen Text als Chat-Nachricht aus.@nFür zwei oder mehr Wörter verwende das Zeichen '*' @nanstelle von Leerzeichen, z. B. "Hallo*Welt".
### compost.lua ###
Put 2 leaves into the compost barrel@n<slot> is the bot inventory slot (1..8)@nwith the leaves.=Lege 2 Blätter in den Kompostbehälter@n<slot> ist die Position im@nBot Inventar (1..8)@nmit den Blättern.
Take a compost item from the barrel.@n<slot> (1..8 or 0 for the first free slot) is the bot@nslot for the compost item.=Nimm einen Kompostblock aus dem@nKompostbehälter. <slot> ist die Position im@nBot Inventar für den Block.@nWerte für <slot>: 0..8, oder 0 für die erste@nfreie Inventarposition.
### crop_sensor.lua ###
Crop Sensor: Connected with=Ernte Sensor: Verbunden mit
Crop Sensor=Ernte Sensor
Crop Sensor: Not connected=Ernte Sensor: Nicht verbunden
The Crop Sensor sends cyclical signals when, for example, wheat is fully grown.=Der Ernte Sensor sendet zyklisch ein Signal, wenn bspw. der Weizen voll ausgewachsen ist.
The sensor range is one node/meter.=Der Sensorbereich beträgt einen Block/Meter.
The sensor has an active side (red) that must point to the crop/field.=Der Sensor hat eine aktive Seite (rot), welche zur der Pflanze zeigen muss.
### delayer.lua ###
Signal Delayer: Connected with=Signal Verzögerer: Verbunden mit
Delay time [sec]:=Verzögerungszeit [s]:
Signal Delayer=Signal Verzögerer
Signals are forwarded delayed. Subsequent signals are queued.=Signale werden verzögert weitergeleitet. Nachfolgende Signale werden in die Warteschlange gestellt.
The delay time can be configured.=Die Verzögerungszeit kann eingestellt werden.
### doc.lua ###
After you have placed the Signs Bot Box, you can start the bot by means of the 'On' button in the box menu.=Nachdem du die Roboter-Kiste platziert hast, kannst du den Roboter über den "An" Button im Kistenmenü starten.
If the bot returns to its box right away, you will likely need to charge it with electrical energy (techage) first.=Wenn der Bot sofort in seine Box zurückkehrt, musst du ihn wahrscheinlich zuerst mit elektrischer Energie (Techage) aufladen.
The bot then runs straight up until it reaches an obstacle (a step with two or more blocks up or down or a sign.)=Der Roboter läuft dann geradeaus, bis er ein Hindernis erreicht (eine Stufe mit zwei oder mehr Blöcken, auf oder ab).
If the bot first reaches a sign it will execute the commands on the sign.=Falls der Roboter zuerst ein Zeichen erreicht, wird er die Kommandos auf dem Zeichen ausführen.
If the command(s) on the sign is e.g. 'turn_around', the bot turns and goes back.=Falls das Kommandos auf dem Zeichen bspw. ein 'turn_around' ist, dreht der Roboter um und geht zurück.
In this case, the bot reaches his box again and turns off.=In diesem Fall erreicht der Roboter wieder seine Box und schaltet sich ab.
The Signs Bot Box has an inventory with 6 stacks for signs and 8 stacks for other items (to be placed/dug by the bot).=Die Roboter Box hat ein Inventar für 6 Stapel Zeichen und 8 Stapel für andere Gegenstände (welche platziert oder eingesammelt werden können).
This inventory simulates the bot internal inventory.=Das Inventar der Box simuliert das Roboter Inventar.
That means you will only have access to the inventory if the bot is turned off ('sitting' in his box).=Das bedeutet, du hast nur Zugriff auf das Inventar, sofern der Roboter ausgeschaltet ist.
You simply control the direction of the bot by means of the 'turn left' and 'turn right' signs (signs with the arrow).=Du kannst die Richtung des Roboters ganz einfach über 'turn_left' (drehe nach links) oder 'turn_right' (drehe nach rechts) Zeichen verändern.
The bot can run over steps (one block up/down). But there are also commands to move the bot up and down.=Der Roboter kann auch Stufen überwinden (eine Block hoch oder runter). Aber es gibt auch Kommandos, um den Roboter nach oben oder unten zu bewegen.
It is not necessary to mark a way back to the box.=Es ist nicht notwendig, dem Roboter einen Weg zurück zu seiner Box zu markieren.
With the command 'turn_off' the bot will turn off and be back in his box from every position.=Mit dem Kommando "turn_off" wird der Roboter ausgeschaltet und befindet sich danach wieder in seiner Box, egal wo er war.
The same applies if you turn off the bot by the box menu.=Das gleiche gilt, wenn du den Roboter über den 'Aus' Button ausschaltest.
If the bot reaches a sign from the wrong direction (from back or sides) the sign will be ignored.=Wenn der Roboter ein Zeichen von der falschen Richtung erreicht (von der Seite oder von hinten), wird das Zeichen ignoriert.
The bot will walk over.=Der Roboter läuft einfach hinüber.
All predefined signs have a menu with a list of the bot commands.=Alle vordefinierten Zeichen haben ein Menü mit der Liste der Kommandos.
These signs can't be changed, but you can craft and program your own signs.=Diese Zeichen können nicht geändert werden, aber du kannst eigene Zeichen herstellen und programmieren.
For this you have to use the 'command' sign.=Dafür kannst du das 'command' Zeichen verwenden.
This sign has an edit field for your commands and a help page with all available commands.=Das Zeichen hat ein Eingabefeld für deine Kommandos und eine Hilfeseite zu allen Kommandos.
Also for your own signs it is important to know:=Es ist auch für eigene Zeichen wichtig zu wissen:
After the execution of the last command of the sign, the bot falls back into its default behaviour and runs in its taken direction.=Nach der Ausführung des letzten Kommandos eines Zeichens fällt der Roboter immer zurück in sein Standardverhalten und läuft in die eingeschlagene Richtung weiter.
A standard job for the bot is to move items from one chest to another=Eine Standardaufgabe für den Roboter ist, Gegenstände von einer Kiste in eine andere zu tun
(chest or node with a chest like inventory).=(Kiste oder Block mit Kisten-ähnlichem Inventar).
This can be done by means of the two signs 'take item' and 'add item'.=Das kann mit Hilfe von zwei Zeichen realisiert werden: 'take_item' und 'add_item'.
These signs have to be placed on top of chest nodes.=Diese Zeichen müssen auf den Kisten platziert werden.
In addition to the signs the bot can be controlled by means of sensors.=Zusätzlich zu den Zeichen kann der Roboter auch über Sensoren gesteuert werden.
Sensors like the Bot Sensor have two states: on and off.=Sensoren wie der Roboter-Sensor haben zwei Zustande: on' (an) und 'off' (aus).
If the Bot Sensor detects a bot it will switch to the state 'on' and=Wenn der Roboter-Sensor einen Roboter erkennt geht er in den Zustand 'on' und
sends a signal to a connected block, called an actuator.=sendet ein Signal zu einem verbundenen Block, einem Aktor.
Sensors are:=Sensoren sind:
- Bot Sensor: Sends a signal when the robot passes by=- Roboter Sensor: Sendet ein Signal wenn ein Roboter vorbei geht
- Node Sensor: Sends a signal when it detects any node=- Block Sensor: Sendet ein Signal bei einer Veränderung von Blocken vor sich
- Crop Sensor: Sends a signal when, for example wheat is fully grown=- Ernte Sensor: Sendet ein Signal, wenn bspw. der Weizen voll ausgewachsen ist
- Bot Chest: Sends a signal depending on the chest state (empty, full)=- Roboter-Kiste: Sendet ein Signal abhängig vom Zustand der Kiste (voll, leer)
Actuators are:=Aktoren sind:
- Signs Bot Box: Can turn the bot off and on=- Roboter-Box: Kann den Roboter ein- und ausschalten
- Control Unit: Can be used to exchange the sign to lead the bot=- Steuerungseinheit: Wird genutzt um Zeichen auszutauschen und damit den Roboter zu steuern
Additional sensors and actuator can be added by other mods.=Weitere Sensoren und Aktoren können von weiteren Mods hinzugefügt werden.
To send a signal from a sensor to an actuator, the sensor has to be connected (paired) with actuator.=Um ein Signal von einem Sensor zu einem Aktor senden zu können, muss der Sensor mit dem Aktor verbunden werden (Pairing).
To connect sensor and actuator, the Sensor Connection Tool has to be used.=Das Sensor Verbinde-Werkzeug wir genutzt, um Sensoren mit Aktoren zu verbinden.
Simply click with the tool on both blocks and the sensor will be connected with the actuator.=Klicke einfach mit dem Werkzeug nacheinander auf beide Blöcke und der Sensor wird mit dem Aktor verbunden.
A successful connection is indicated by a ping/pong noise.=Eine erfolgreiche Verbindung wird über ein Ping/Pong-Geräusch angezeigt.
Before you connect sensor with actuator, take care that the actuator is in the requested state.=Bevor du einen Sensor mit einem Aktor verbindest, achte darauf, dass sich der Aktor im richtigen Zustand befindet.
For example: If you want to start the Bot with a sensor, connect the sensor with the Bot Box,=Zum Beispiel: Wenn du den Roboter über einen Sensor starten willst, verbinde Sensor und Roboter-Kiste nur,
when the Bot is in the state 'on'. Otherwise the sensor signal will stop the Bot,=wenn der Roboter an ist (im Zustand 'on'). Anderenfalls würde der Sensor den Roboter ausschalten,
instead of starting it.=anstatt ihn zu starten.
The following applies to all commands that are used to place items in the bot inventory, like:=Das folgende gilt für alle Kommandos, um Items in das Roboter Inventar zu legen, wie:
- take_item <num> <slot>=- take_item <num> <slot>
- pickup_items <slot>=- pickup_items <slot>
- trash_sign <slot>=- trash_sign <slot>
- harvest <slot>=- harvest <slot>
- dig_front <slot> <lvl>=- dig_front <slot> <lvl>
- dig_left <slot> <lvl>=- dig_left <slot> <lvl>
- dig_right <slot> <lvl>=- dig_right <slot> <lvl>
- dig_below <slot> <lvl>=- dig_below <slot> <lvl>
- dig_above <slot> <lvl>=- dig_above <slot> <lvl>
If no slot or slot 0 was specified with the command (case A), all 8 slots of the bot inventory =Wurde beim Kommando kein Slot oder Slot 0 angegeben (Fall A), werden nacheinander alle
are checked one after the other. If a slot was specified (case B), only this slot is checked.=8 Slots des Bot Inventars geprüft, Wurde ein Slot angegeben (Fall B), wird nur dieser geprüft.
In both cases the following applies: If the slot is preconfigured and fits the item, =In beiden Fällen gilt: Ist der Slot vorkonfiguriert und passt das Item dazu, oder ist der Slot
or if the slot is not configured and empty, or is only partially filled with the item type =nicht konfiguriert und leer, oder mit dem Item-Typ (das hinzu gefügt werden soll) nur
(which should be added), then the items are added.=teilweise gefüllt, dann werden die Items hinzugefügt.
If not all items can be added, the remaining slots will be tried out in case A.=Können nicht alle Items hinzugefügt werden, werden im Falle A die restlichen Slots weiter durchprobiert.
Anything that could not be added to your own inventory goes back.=Was nicht dem eigenen Inventar hinzugefügt werden konnte, geht zurück.
The following applies to all commands that are used to take items from the bot inventory, like:=Das folgende gilt für alle Kommandos, um Items aus dem Roboter Inventar zu nehmen, wie:
- add_item <num> <slot>=- add_item <num> <slot>
It doesn't matter whether a slot is configured or not. The bot takes the first stack that =Hier ist es egal, ob ein Slot konfiguriert ist, oder nicht. Der Bot nimmt den ersten Stack, den
it can find from its own inventory and tries to use it.=er finden kann, aus dem eigenen Inventar und versucht diesen zu nutzen.
If a slot is specified, it only takes this, if no slot has been specified, it checks all of =Ist ein Slot angegeben, nimmt er nur diesen, wurde kein Slot angegeben, prüft er alle
them one after the other, starting from slot 1 until it finds something.=nacheinander, von Slot 1 beginnend, bis er etwas findet. Ist die gefundene Anzahl kleiner als
If the number found is smaller than requested, he tries to take the rest out of any slot.=gefordert, versucht er den Rest aus irgend einem Slot zu nehmen.
Signs Bot=Signs Bot
A robot controlled by signs, used for automated work=Ein Roboter, gesteuert über Zeichen, für Automatisierungsaufgaben
Start the Bot=Starte den Roboter
Control the Bot=Steuere den Roboter
Sensors and Actuators=Sensoren und Aktoren
Connecting sensors and actuator=Verbinde Sensor mit Aktor
Bot inventory behavior=Verhalten beim Roboter Inventar
### doc.lua ###
### cmd_sign.lua ###
The help page has a copy button to simplify the programming.=Die Hilfeseite hat einen Kopier-Button um die Programmierung zu erleichtern.
### duplicator.lua ###
Template:=Vorlage:
Input:=Eingabe:
Output:=Ausgabe:
1. Place one 'cmnd' sign= 1. Kommando Zeichen einlegen
2. Add 'blank signs'=2. Füge 'leere Zeichen' hinzu
3. Take the copies=3. Entnehme die Kopien
Signs Duplicator=Zeichen Kopierer
Sign "user"=Zeichen "Benutzer"
Sign "blank"="Leeres" Zeichen
The Duplicator can be used to make copies of signs.=Der Zeichen Kopierer kann zur Herstellung von Kopien eines Zeichens genutzt werden.
1. Put one 'cmnd' sign to be used as template into the 'Template' inventory=1. Lege ein Kommando-Zeichen als Vorlage in das 'Vorlage' Inventar
2. Add one or several 'blank signs' to the 'Input' inventory.=2. Füge ein oder mehrere 'leere Zeichen' als Eingabe hinzu.
3. Take the copies from the 'Output' inventory.=3. Entnehme die Kopieren aus der Ausgabe.
Written books [default:book_written] can alternatively be used as template=Alternativ können auch beschriebene Bücher [default:book_written] als Vorlage verwendet werden
Already written signs can be used as input, too.=Bereits beschriebene Zeichen können auch als Eingabe-Zeichen genutzt werden.
Needed as input for the Duplicator.=Wird als Eingabe für den Zeichen Kopierer benötigt.
### extender.lua ###
Sensor Extender: Connected with=Sensor Erweiterung: Verbunden mit
Sensor Extender=Sensor Erweiterung
Sensor Extender: Not connected=Sensor Erweiterung: Nicht verbunden
With the Sensor Extender, sensor signals can be sent to more than one actuator.=Mit Hilfe der Sensor-Erweiterung können weitere Aktoren mit dem Sensor verbunden werden.
Place one or more extender nearby the sensor and connect each extender=Platziere ein oder mehrere Sensor-Erweiterungen neben einen Sensor und verbinde diese
with one further actuator by means of the Connection Tool.=mit weiteren Aktoren mit Hilfe des Verbinde-Werkzeuges.
### interpreter.lua ###
Maximum programm size exceeded=Maximale Programmlänge überschritten
Too many parameters=Zu viele Parameter
Parameter error=Parameter Fehler
Command error=Kommandozeilen Fehler
'repeat' missing=Es fehlt ein 'repeat'
'end' missing=Es fehlt ein 'end'
Checked and approved=Geprüft und genehmigt
### logic_and.lua ###
Signal AND with=Signal UND mit
inputs=Eingängen
Signal AND=Signal UND
Signal is sent, if all input signals are received.=Signal wird gesendet, wenn all Eingangssignale empfangen wurden.
### node_sensor.lua ###
Node Sensor: Connected with =Block Sensor: Verbunden mit
added=hinzukommen
removed=fehlen
added or removed=hinzukommen oder fehlen
Send signal if nodes have been:=Sende ein Signal wenn Blöcke:
accept=übernehmen
Node Sensor=Block Sensor
Node Sensor: Not connected=Block Sensor: Nicht verbunden
The node sensor sends cyclical signals when it detects that nodes have appeared or disappeared,=Der Block Sensor sendet zyklisch ein Signal, wenn er eine Veränderung von Blöcken vor sich entdeckt (ein Block erscheint oder verschwindet),
but has to be configured accordingly.=aber muss entsprechend konfiguriert werden.
Valid nodes are all kind of blocks and plants.=Gültig sind alle Arten von Blöcken oder Pflanzen.
The sensor range is 3 nodes/meters in one direction.=Die Sensor-Reichweite beträgt 3 Blöcke/Meter in eine Richtung.
The sensor has an active side (red) that must point to the observed area.=Der Sensor hat eine aktive Seite (rot) welche zu dem zu überwachenden Bereich zeigen muss.
### signs.lua ###
Sign "turn right"=Zeichen "rechts drehen"
Sign "turn left"=Zeichen "links drehen"
Sign "take item"=Zeichen "Nehme Gegenstand"
Sign "add item"=Zeichen "Lege Gegenstand"
Sign "stop"=Zeichen "Stopp"
Sign "add to cart"=Zeichen "Lege in den Wagen"
Sign "take from cart"=Zeichen "Nehme aus dem Wagen"
The Bot turns right when it detects this sign in front of it.=Der Roboter dreht nach rechts, wenn er dieses Zeichen vor sich hat.
The Bot turns left when it detects this sign in front of it.=Der Roboter dreht nach links, wenn er dieses Zeichen vor sich hat.
The Bot takes items out of a chest in front of it and then turns around.=Der Roboter nimmt Gegenstände aus einer Kiste vor sich und dreht dann um.
This sign has to be placed on top of the chest.=Das Zeichen muss auf der Kiste platziert werden.
The Bot puts items into a chest in front of it and then turns around.=Der Roboter legt Gegenstände in eine Kiste vor sich und dreht dann um.
The Bot will stop in front of this sign until the sign is removed or the bot is turned off.=Der Roboter stoppt vor diesem Zeichen, bis das Zeichen entfernt, oder der Roboter ausgeschaltet wird.
The Bot puts items into a minecart in front of it, pushes the cart and then turns around.=Der Roboter legt Gegenstände in den Wagen vor sich, startet den Wagen und dreht dann um.
This sign has to be placed on top of the rail at the cart end position.=Das Zeichen muss auf dem Gleis und damit über dem Wagen platziert werden.
The Bot takes items out of a minecart in front of it, pushes the cart and then turns around.=Der Roboter nimmt Gegenstände aus dem Wagen vor sich, startet den Wagen und dreht dann um.
### techage.lua ###
Ignite the techage charcoal lighter=Zünde den Holzkohle-Anzünder an
Turns the bot off if the@nbattery power is below the@ngiven value in percent (1..99)=Schalte den Bot aus,@nwenn die Batterieladung kleiner@nist als der angegebene Wert@nin Prozent (1.99)
fully charged=voll geladen
Sends a techage command@nto a given node. @nReceiver is addressed by@nthe techage node number.@nFor commands with two or more @nwords, use the '*' character @ninstead of spaces, e.g.: @nsend_cmnd 3465 pull*default:dirt*2=Sende ein techage Kommando@nan einen Block mit der@nangegebenen Blocknummer.@nFür Kommandos mit zwei oder mehr @nWörtern verwende das Zeichen '*' @nanstelle des Leerzeichens, wie bspw.:@nsend_cmnd 3465 pull*default:dirt*2
### timer.lua ###
Bot Timer=Roboter Timer
Cycle time [min]:=Zykluszeit [min]:
Bot Timer: Not connected=Roboter Timer: Nicht verbunden
Special kind of sensor.=Spezielle Form eines Sensors.
Can be programmed with a time in seconds, e.g. to start the bot cyclically.=Kann mit einer Zeit in Sekunden programmiert werden, um bspw. den Roboter zyklisch zu starten.
### timer.lua ###
### delayer.lua ###
Start=Start
### timer.lua ###
### logic_and.lua ###
Connected with=Verbunden mit
### tool.lua ###
Sensor Connection Tool=Sensor Verbindungswerkzeug
##### not used anymore #####

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,410 @@
# textdomain: signs_bot
### basis.lua ###
running=
charging=
stopped=
Off=
On=
Signs=
Other items=
Config=
Preassign slots items=
Back=
Robot Box=
no power=
Signs Bot Box=
The Box is the housing of the bot.=
Place the box and start the bot by means of the 'On' button.=
If the mod techage is installed, the bot needs electrical power.=
The bot leaves the box on the right side.=
It will not start, if this position is blocked.=
To stop and remove the bot, press the 'Off' button.=
The box inventory simulates the inventory of the bot.=
You will not be able to access the inventory, if the bot is running.=
The bot can carry up to 8 stacks and 6 signs with it.=
### bot_flap.lua ###
Exit=
Bot Flap=
The flap is a simple block used as door for the bot.=
Place the flap in any wall, and the bot will automatically open=
and close the flap as it passes through it.=
### bot_sensor.lua ###
Bot Sensor: Connected with=
Bot Sensor=
Bot Sensor: Not connected=
The Bot Sensor detects any bot and sends a signal, if a bot is nearby.=
The sensor direction does not care.=
### cart_sensor.lua ###
Cart Sensor: Connected with=
Cart Sensor=
Cart Sensor: Not connected=
The Cart Sensor detects and sends a signal, if a cart (Minecart) is nearby.=
The sensor has an active side (red) that must point to the rail/cart.=
### cart_sensor.lua ###
### bot_sensor.lua ###
the sensor range is one node/meter.=
### changer.lua ###
Signs:=
Bot Control Unit=
The Bot Control Unit is used to lead the bot by means of signs.=
The unit can be loaded with up to 4 different signs and can be programmed by means of sensors.=
To load the unit, place a sign on the red side of the unit and click on the unit.=
The sign disappears / is moved to the inventory of the unit.=
This can be repeated 3 times.=
Use the connection tool to connect up to 4 sensors with the Bot Control Unit.=
### chest.lua ###
Bot Chest: Sends signal to=
Signs Bot Chest=
Bot Chest: Not connected=
The Signs Bot Chest is a special chest with sensor function.=
It sends a signal depending on the chest state.=
Possible states are 'empty', 'not empty', 'almost full'=
A typical use case is to turn off the bot, when the chest is almost full or empty.=
### cmd_farming.lua ###
Sow farming seeds@nin front of the robot=
Harvest farming products@nin front of the robot@non a 3x3 field.=
Plant a sapling@nin front of the robot=
Sign "farming"=
Sign 'farming'=
Used to harvest and seed a 3x3 field.=
The seed to be placed has to be in the first inventory slot of the bot.=
### cmd_farming.lua ###
### cmd_flowers.lua ###
Place the sign in front of the field.=
When finished, the bot turns.=
### cmd_flowers.lua ###
Cutting flowers, leaves and tree blocks@nin front of the robot@non a 3x3 field.=
Sign "flowers"=
Sign 'flowers'=
Used to cut flowers on a 3x3 field.=
### cmd_item.lua ###
Take <num> items from a chest like node@nand put it into the item inventory.@n<slot> is the inventory slot (1..8) or 0 for any one=
Add <num> items to a chest like node@ntaken from the item inventory.@n<slot> is the inventory slot (1..8) or 0 for any one=
Add <num> fuel to a furnace like node@ntaken from the item inventory.@n<slot> is the inventory slot (1..8) or 0 for any one=
deprecated, use bot inventory configuration instead=
Pick up all objects@nin a 3x3 field.@n<slot> is the inventory slot (1..8) or 0 for any one=
Drop items in front of the bot.@n<slot> is the inventory slot (1..8) or 0 for any one=
Punch a rail cart to start it=
### cmd_move.lua ###
Move the robot one step back=
Turn the robot to the left=
Turn the robot to the right=
Turn the robot around=
Move the robot upwards=
Move the robot down=
Stop the robot for <sec> seconds@n(1..9999)=
Stop the robot.=
Turn the robot off@nand put it back in the box.=
### cmd_pattern.lua ###
Store pattern to be cloned.=
Copy the nodes from@nthe stored pattern position@n<size> is: 3x1, 3x2, 3x3,@n5x1, 5x2, 5x3 (wide x deep)@n<lvl> pattern level offset (0..4)=
Sign "pattern"=
Sign "copy 3x3x3"=
Sign 'pattern'=
Used to make a copy of a 3x3x3 cube.=
Place the sign in front of the pattern to be copied.=
Use the copy sign to make the copy of this pattern on a different location.=
The bot must first reach the pattern sign, then the copy sign.=
Sign 'copy3x3x3'=
Place the sign in front of the location, where the copy should be made.=
Use the pattern sign to mark the pattern.=
### cmd_place.lua ###
Error: Position protected=
Place a block in front of the robot@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=
Place a block on the left side@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=
Place a block on the right side@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=
Place a block under the robot.@nHint: use 'move_up' first.@n<slot> is the inventory slot (1..8)=
Place a block above the robot.@n<slot> is the inventory slot (1..8)=
Error: No free inventory space=
Dig the block in front of the robot@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=
Dig the block on the left side@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=
Dig the block on the right side@n<slot> is the inventory slot (1..8)@n<lvl> is one of: -1 0 +1=
Dig the block under the robot.@n<slot> is the inventory slot (1..8)=
Dig the block above the robot.@n<slot> is the inventory slot (1..8)=
Rotate the block in front of the robot@n<lvl> is one of: -1 0 +1@n<steps> is one of: 1 2 3=
Bot torch=
### cmd_sign.lua ###
Commands,Help=
Sign name:=
Cancel=
Check=
Help=
Copy Cmnd=
please check the added line(s)=
Sign "command"=
-- enter or copy commands from help page=
Error: Signs inventory empty=
Error: Position protected or occupied=
Place a sign in front of the robot@ntaken from the signs inventory@n<slot> is the inventory slot (1..6)=
Place a sign behind the robot@ntaken from the signs inventory@n<slot> is the inventory slot (1..6)=
Error: No sign available=
Error: Signs inventory slot is occupied=
Error: Position is protected=
Dig the sign in front of the robot@nand add it to the signs inventory.@n<slot> is the inventory slot (1..6)=
Dig the sign in front of the robot@nand add the cleared sign to@nthe item iventory.@n<slot> is the inventory slot (1..8)=
Sign 'command'=
The 'command' sign can be programmed by the player.=
Place the sign in front of you and use the node menu to program your sequence of bot commands.=
The menu has an edit field for your commands and a help page with all available commands.=
### commands.lua ###
commands:=
unknown command=
start of a 'repeat..end' block=
end command of a 'repeat..end' block=
call a subroutine (with 'return' statement)=
return from a subroutine=
jump to a label=
Move the robot 1..999 steps forward@nwithout paying attention to any signs.@nUp and down movements also become@ncounted as steps.=
Walk until a sign or obstacle is@nreached. Then continue with the next command.@nWhen a sign has been reached, @nthe current program is ended@nand the bot executes the@nnew program from the sign=
Print given text as chat message.@nFor two or more words, use the '*' character @ninstead of spaces, like "Hello*world"=
### compost.lua ###
Put 2 leaves into the compost barrel@n<slot> is the bot inventory slot (1..8)@nwith the leaves.=
Take a compost item from the barrel.@n<slot> (1..8 or 0 for the first free slot) is the bot@nslot for the compost item.=
### crop_sensor.lua ###
Crop Sensor: Connected with=
Crop Sensor=
Crop Sensor: Not connected=
The Crop Sensor sends cyclical signals when, for example, wheat is fully grown.=
The sensor range is one node/meter.=
The sensor has an active side (red) that must point to the crop/field.=
### delayer.lua ###
Signal Delayer: Connected with=
Delay time [sec]:=
Signal Delayer=
Signals are forwarded delayed. Subsequent signals are queued.=
The delay time can be configured.=
### doc.lua ###
After you have placed the Signs Bot Box, you can start the bot by means of the 'On' button in the box menu.=
If the bot returns to its box right away, you will likely need to charge it with electrical energy (techage) first.=
The bot then runs straight up until it reaches an obstacle (a step with two or more blocks up or down or a sign.)=
If the bot first reaches a sign it will execute the commands on the sign.=
If the command(s) on the sign is e.g. 'turn_around', the bot turns and goes back.=
In this case, the bot reaches his box again and turns off.=
The Signs Bot Box has an inventory with 6 stacks for signs and 8 stacks for other items (to be placed/dug by the bot).=
This inventory simulates the bot internal inventory.=
That means you will only have access to the inventory if the bot is turned off ('sitting' in his box).=
You simply control the direction of the bot by means of the 'turn left' and 'turn right' signs (signs with the arrow).=
The bot can run over steps (one block up/down). But there are also commands to move the bot up and down.=
It is not necessary to mark a way back to the box.=
With the command 'turn_off' the bot will turn off and be back in his box from every position.=
The same applies if you turn off the bot by the box menu.=
If the bot reaches a sign from the wrong direction (from back or sides) the sign will be ignored.=
The bot will walk over.=
All predefined signs have a menu with a list of the bot commands.=
These signs can't be changed, but you can craft and program your own signs.=
For this you have to use the 'command' sign.=
This sign has an edit field for your commands and a help page with all available commands.=
Also for your own signs it is important to know:=
After the execution of the last command of the sign, the bot falls back into its default behaviour and runs in its taken direction.=
A standard job for the bot is to move items from one chest to another=
(chest or node with a chest like inventory).=
This can be done by means of the two signs 'take item' and 'add item'.=
These signs have to be placed on top of chest nodes.=
In addition to the signs the bot can be controlled by means of sensors.=
Sensors like the Bot Sensor have two states: on and off.=
If the Bot Sensor detects a bot it will switch to the state 'on' and=
sends a signal to a connected block, called an actuator.=
Sensors are:=
- Bot Sensor: Sends a signal when the robot passes by=
- Node Sensor: Sends a signal when it detects any node=
- Crop Sensor: Sends a signal when, for example wheat is fully grown=
- Bot Chest: Sends a signal depending on the chest state (empty, full)=
Actuators are:=
- Signs Bot Box: Can turn the bot off and on=
- Control Unit: Can be used to exchange the sign to lead the bot=
Additional sensors and actuator can be added by other mods.=
To send a signal from a sensor to an actuator, the sensor has to be connected (paired) with actuator.=
To connect sensor and actuator, the Sensor Connection Tool has to be used.=
Simply click with the tool on both blocks and the sensor will be connected with the actuator.=
A successful connection is indicated by a ping/pong noise.=
Before you connect sensor with actuator, take care that the actuator is in the requested state.=
For example: If you want to start the Bot with a sensor, connect the sensor with the Bot Box,=
when the Bot is in the state 'on'. Otherwise the sensor signal will stop the Bot,=
instead of starting it.=
The following applies to all commands that are used to place items in the bot inventory, like:=
- take_item <num> <slot>=
- pickup_items <slot>=
- trash_sign <slot>=
- harvest <slot>=
- dig_front <slot> <lvl>=
- dig_left <slot> <lvl>=
- dig_right <slot> <lvl>=
- dig_below <slot> <lvl>=
- dig_above <slot> <lvl>=
If no slot or slot 0 was specified with the command (case A), all 8 slots of the bot inventory =
are checked one after the other. If a slot was specified (case B), only this slot is checked.=
In both cases the following applies: If the slot is preconfigured and fits the item, =
or if the slot is not configured and empty, or is only partially filled with the item type =
(which should be added), then the items are added.=
If not all items can be added, the remaining slots will be tried out in case A.=
Anything that could not be added to your own inventory goes back.=
The following applies to all commands that are used to take items from the bot inventory, like:=
- add_item <num> <slot>=
It doesn't matter whether a slot is configured or not. The bot takes the first stack that =
it can find from its own inventory and tries to use it.=
If a slot is specified, it only takes this, if no slot has been specified, it checks all of =
them one after the other, starting from slot 1 until it finds something.=
If the number found is smaller than requested, he tries to take the rest out of any slot.=
Signs Bot=
A robot controlled by signs, used for automated work=
Start the Bot=
Control the Bot=
Sensors and Actuators=
Connecting sensors and actuator=
Bot inventory behavior=
### doc.lua ###
### cmd_sign.lua ###
The help page has a copy button to simplify the programming.=
### duplicator.lua ###
Template:=
Input:=
Output:=
1. Place one 'cmnd' sign=
2. Add 'blank signs'=
3. Take the copies=
Signs Duplicator=
Sign "user"=
Sign "blank"=
The Duplicator can be used to make copies of signs.=
1. Put one 'cmnd' sign to be used as template into the 'Template' inventory=
2. Add one or several 'blank signs' to the 'Input' inventory.=
3. Take the copies from the 'Output' inventory.=
Written books [default:book_written] can alternatively be used as template=
Already written signs can be used as input, too.=
Needed as input for the Duplicator.=
### extender.lua ###
Sensor Extender: Connected with=
Sensor Extender=
Sensor Extender: Not connected=
With the Sensor Extender, sensor signals can be sent to more than one actuator.=
Place one or more extender nearby the sensor and connect each extender=
with one further actuator by means of the Connection Tool.=
### interpreter.lua ###
Maximum programm size exceeded=
Too many parameters=
Parameter error=
Command error=
'repeat' missing=
'end' missing=
Checked and approved=
### logic_and.lua ###
Signal AND with=
inputs=
Signal AND=
Signal is sent, if all input signals are received.=
### node_sensor.lua ###
Node Sensor: Connected with =
added=
removed=
added or removed=
Send signal if nodes have been:=
accept=
Node Sensor=
Node Sensor: Not connected=
The node sensor sends cyclical signals when it detects that nodes have appeared or disappeared,=
but has to be configured accordingly.=
Valid nodes are all kind of blocks and plants.=
The sensor range is 3 nodes/meters in one direction.=
The sensor has an active side (red) that must point to the observed area.=
### signs.lua ###
Sign "turn right"=
Sign "turn left"=
Sign "take item"=
Sign "add item"=
Sign "stop"=
Sign "add to cart"=
Sign "take from cart"=
The Bot turns right when it detects this sign in front of it.=
The Bot turns left when it detects this sign in front of it.=
The Bot takes items out of a chest in front of it and then turns around.=
This sign has to be placed on top of the chest.=
The Bot puts items into a chest in front of it and then turns around.=
The Bot will stop in front of this sign until the sign is removed or the bot is turned off.=
The Bot puts items into a minecart in front of it, pushes the cart and then turns around.=
This sign has to be placed on top of the rail at the cart end position.=
The Bot takes items out of a minecart in front of it, pushes the cart and then turns around.=
### techage.lua ###
Ignite the techage charcoal lighter=
Turns the bot off if the@nbattery power is below the@ngiven value in percent (1..99)=
fully charged=
Sends a techage command@nto a given node. @nReceiver is addressed by@nthe techage node number.@nFor commands with two or more @nwords, use the '*' character @ninstead of spaces, e.g.: @nsend_cmnd 3465 pull*default:dirt*2=
### timer.lua ###
Bot Timer=
Cycle time [min]:=
Bot Timer: Not connected=
Special kind of sensor.=
Can be programmed with a time in seconds, e.g. to start the bot cyclically.=
### timer.lua ###
### delayer.lua ###
Start=
### timer.lua ###
### logic_and.lua ###
Connected with=
### tool.lua ###
Sensor Connection Tool=

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,13 +13,12 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
@ -38,12 +37,12 @@ end
local function update_infotext(pos, dest_pos, cmnd)
local mem = tubelib2.get_mem(pos)
local text = table.concat({
I("Signal AND with"),
S("Signal AND with"),
#mem.inputs or 0,
I("inputs"),
S("inputs"),
":",
I("Connected with"),
S(dest_pos),
S("Connected with"),
P2S(dest_pos),
"/",
cmnd,
":",
@ -80,7 +79,7 @@ local function infotext(pos)
local dest_pos = meta:get_string("signal_pos")
local signal = meta:get_string("signal_data")
if dest_pos ~= "" and signal ~= "" then
update_infotext(pos, P(dest_pos), signal)
update_infotext(pos, S2P(dest_pos), signal)
end
end
@ -143,7 +142,7 @@ local function signs_bot_on_signal(pos, node, signal)
end
minetest.register_node("signs_bot:and1", {
description = I("Signal AND"),
description = S("Signal AND"),
inventory_image = "signs_bot_and_inv.png",
drawtype = "nodebox",
node_box = {
@ -170,6 +169,7 @@ minetest.register_node("signs_bot:and1", {
update_infotext = update_infotext,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -178,7 +178,7 @@ minetest.register_node("signs_bot:and1", {
})
minetest.register_node("signs_bot:and2", {
description = I("Signal AND"),
description = S("Signal AND"),
drawtype = "nodebox",
node_box = {
type = "fixed",
@ -197,6 +197,7 @@ minetest.register_node("signs_bot:and2", {
update_infotext = update_infotext,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -206,7 +207,7 @@ minetest.register_node("signs_bot:and2", {
})
minetest.register_node("signs_bot:and3", {
description = I("Signal AND"),
description = S("Signal AND"),
drawtype = "nodebox",
node_box = {
type = "fixed",
@ -223,6 +224,7 @@ minetest.register_node("signs_bot:and3", {
update_infotext = update_infotext,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -242,11 +244,11 @@ minetest.register_craft({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "and", {
name = I("Signal AND"),
name = S("Signal AND"),
data = {
item = "signs_bot:and1",
text = table.concat({
I("Signal is sent, if all input signals are received."),
S("Signal is sent, if all input signals are received."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -13,20 +13,18 @@
]]--
-- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local M = minetest.get_meta
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
-- Load support for I18n.
local S = signs_bot.S
local lib = signs_bot.lib
local CYCLE_TIME = 2
local function update_infotext(pos, dest_pos, cmnd)
M(pos):set_string("infotext", I("Node Sensor: Connected with ")..S(dest_pos).." / "..cmnd)
M(pos):set_string("infotext", S("Node Sensor: Connected with ")..P2S(dest_pos).." / "..cmnd)
end
local function swap_node(pos, name)
@ -44,20 +42,20 @@ end
local DropdownValues = {
[I("added")] = 1,
[I("removed")] = 2,
[I("added or removed")] = 3,
[S("added")] = 1,
[S("removed")] = 2,
[S("added or removed")] = 3,
}
local function formspec(mem)
local label = I("added")..","..I("removed")..","..I("added or removed")
local label = S("added")..","..S("removed")..","..S("added or removed")
return "size[6,3]"..
default.gui_bg..
default.gui_bg_img..
default.gui_slots..
"label[0.2,0.4;"..I("Send signal if nodes have been:").."]"..
"label[0.2,0.4;"..S("Send signal if nodes have been:").."]"..
"dropdown[0.2,1;6,1;mode;"..label..";"..(mem.mode or 3).."]"..
"button_exit[1.5,2.2;3,1;accept;"..I("accept").."]"
"button_exit[1.5,2.2;3,1;accept;"..S("accept").."]"
end
local function any_node_changed(pos)
@ -115,7 +113,7 @@ local function node_timer(pos)
end
minetest.register_node("signs_bot:node_sensor", {
description = I("Node Sensor"),
description = S("Node Sensor"),
inventory_image = "signs_bot_sensor_node_inv.png",
drawtype = "nodebox",
node_box = {
@ -137,7 +135,7 @@ minetest.register_node("signs_bot:node_sensor", {
after_place_node = function(pos, placer)
local meta = M(pos)
local mem = tubelib2.init_mem(pos)
meta:set_string("infotext", "Node Sensor: Not connected")
meta:set_string("infotext", S("Node Sensor: Not connected"))
mem.mode = 3 -- default legacy mode
meta:set_string("formspec", formspec(mem))
minetest.get_node_timer(pos):start(CYCLE_TIME)
@ -149,6 +147,7 @@ minetest.register_node("signs_bot:node_sensor", {
on_receive_fields = on_receive_fields,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -157,7 +156,7 @@ minetest.register_node("signs_bot:node_sensor", {
})
minetest.register_node("signs_bot:node_sensor_on", {
description = I("Node Sensor"),
description = S("Node Sensor"),
drawtype = "nodebox",
node_box = {
type = "fixed",
@ -180,6 +179,7 @@ minetest.register_node("signs_bot:node_sensor_on", {
on_receive_fields = on_receive_fields,
on_rotate = screwdriver.disallow,
paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true,
paramtype2 = "facedir",
is_ground_content = false,
@ -213,15 +213,15 @@ minetest.register_lbm({
if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "node_sensor", {
name = I("Node Sensor"),
name = S("Node Sensor"),
data = {
item = "signs_bot:node_sensor",
text = table.concat({
I("The node sensor sends cyclical signals when it detects that nodes have appeared or disappeared,"),
I("but has to be configured accordingly."),
I("Valid nodes are all kind of blocks and plants."),
I("The sensor range is 3 nodes/meters in one direction."),
I("The sensor has an active side (red) that must point to the observed area."),
S("The node sensor sends cyclical signals when it detects that nodes have appeared or disappeared,"),
S("but has to be configured accordingly."),
S("Valid nodes are all kind of blocks and plants."),
S("The sensor range is 3 nodes/meters in one direction."),
S("The sensor has an active side (red) that must point to the observed area."),
}, "\n")
},
})

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information
@ -69,7 +69,7 @@ if farming.mod == "redo" then
fp("farming:cocoa_beans", "farming:cocoa_1", "farming:cocoa_4")
fp("farming:garlic_clove", "farming:garlic_1", "farming:garlic_5")
fp("farming:onion", "farming:onion_1", "farming:onion_5")
fp("farming:peas", "farming:pea_1", "farming:pea_5")
fp("farming:pea_pod", "farming:pea_1", "farming:pea_5")
fp("farming:peppercorn", "farming:pepper_1", "farming:pepper_5")
fp("farming:pineapple_top", "farming:pineapple_1", "farming:pineapple_8")
end

View File

@ -3,7 +3,7 @@
Signs Bot
=========
Copyright (C) 2019 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
GPL v3
See LICENSE.txt for more information

Some files were not shown because too many files have changed in this diff Show More