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) nnode = minetest.get_node(npos)
if NodeTbl1[nnode.name] and NodeTbl3[node.name] then if NodeTbl1[nnode.name] and NodeTbl3[node.name] then
node.name = node.name .. "1" node.name = node.name .. "1"
minetest.swap_node(pos, node) if minetest.registered_nodes[node.name] then
minetest.swap_node(pos, node)
end
return return
end end
-- check case 2 -- check case 2
@ -192,7 +194,9 @@ local function update_node(pos)
nnode = minetest.get_node(npos) nnode = minetest.get_node(npos)
if NodeTbl2[nnode.name] then if NodeTbl2[nnode.name] then
node.name = string.sub(node.name,1,-1) .. "2" node.name = string.sub(node.name,1,-1) .. "2"
minetest.swap_node(pos, node) if minetest.registered_nodes[node.name] then
minetest.swap_node(pos, node)
end
return return
end end
-- check case 3 -- check case 3
@ -203,7 +207,9 @@ local function update_node(pos)
if NodeTbl1[nnode.name] and NodeTbl3[node.name] then if NodeTbl1[nnode.name] and NodeTbl3[node.name] then
node.name = node.name .. "1" node.name = node.name .. "1"
node.param2 = 3 node.param2 = 3
minetest.swap_node(pos, node) if minetest.registered_nodes[node.name] then
minetest.swap_node(pos, node)
end
return return
end end
-- check case 4 -- check case 4
@ -212,7 +218,9 @@ local function update_node(pos)
if NodeTbl2[nnode.name] then if NodeTbl2[nnode.name] then
node.name = string.sub(node.name,1,-1) .. "2" node.name = string.sub(node.name,1,-1) .. "2"
node.param2 = 3 node.param2 = 3
minetest.swap_node(pos, node) if minetest.registered_nodes[node.name] then
minetest.swap_node(pos, node)
end
return return
end end
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 = {} compost = {}
local CYCLE_TIME = 10 local CYCLE_TIME = 30
-- Version for compatibility checks -- Version for compatibility checks
compost.version = 1.0 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 * 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) * 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) ![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. ..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 ## Migration from v1 to v2
@ -59,7 +58,7 @@ has some risks. Therefore:
## Introduction ## 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 ## 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) * "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) * "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. 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': Example for 'minetest.conf':
```LUA ```LUA
hyperloop_wifi_enabled = true hyperloop_wifi_enabled = true -- WiFi block enabled
hyperloop_wifi_crafting_enabled = false hyperloop_wifi_crafting_enabled = false -- WiFi block crafting enabled
hyperloop_free_tube_placement_enabled = true hyperloop_free_tube_placement_enabled = true -- free tube placement enabled
hyperloop_subnet_enabled = true -- enable building of subnets
``` ```
## Dependencies ## Dependencies
tubelib2 (![GitHub](https://github.com/joe7575/tubelib2)) tubelib2 ([GitHub](https://github.com/joe7575/tubelib2))
default default
intllib intllib
optional: worldedit, techage optional: worldedit, techage
# License # 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 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 Textures: CC0
Display: Derived from the work of kaeza, sofar and others (digilines) LGPLv2.1+ 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 return tbl
end end
local function station_list_as_string(pos) local function filter_subnet(sortedList, subnet)
-- Generate a distance sorted list of all connected stations if hyperloop.subnet_enabled then
local sortedList = Stations:station_list(pos, pos, "dist") if subnet == "" then
-- Delete the own station from list subnet = nil
table.remove(sortedList, 1) 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 -- remove all junctions from the list
sortedList = remove_junctions(sortedList) sortedList = remove_junctions(sortedList)
-- use subnet pattern to reduce the list
sortedList = filter_subnet(sortedList, subnet)
-- store the list for later use -- store the list for later use
store_station_list(pos, sortedList) store_station_list(pos, sortedList)
-- Generate the formspec string -- Generate the formspec string
return generate_string(sortedList) return generate_string(sortedList)
end end
local naming_formspec = nil
local function naming_formspec(pos) if hyperloop.subnet_enabled then
local meta = minetest.get_meta(pos) naming_formspec = function(pos)
local formspec = "size[6,4]".. local meta = M(pos)
default.gui_bg.. local formspec = "size[7,5.4]"..
default.gui_bg_img.. default.gui_bg..
default.gui_slots.. default.gui_bg_img..
"label[0,0;"..S("Please enter the station name to\nwhich this booking machine belongs.").."]" .. default.gui_slots..
"field[0.5,1.5;5,1;name;"..S("Station name")..";MyTown]" .. "label[0,0;"..S("Please enter the station name to\nwhich this booking machine belongs.").."]" ..
"field[0.5,2.7;5,1;info;"..S("Additional station information")..";]" .. "field[0.2,1.5;7.1,1;name;"..S("Station name")..";MyTown]" ..
"button_exit[2,3.6;2,1;exit;Save]" "field[0.2,2.7;7.1,1;info;"..S("Additional station information")..";]" ..
meta:set_string("formspec", formspec) "field[0.2,3.9;7.1,1;subnet;"..S("Subnet name (optional)")..";]" ..
meta:set_int("change_counter", 0) "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 end
local function booking_machine_update(pos) local function booking_machine_update(pos)
local meta = M(pos) local meta = M(pos)
local sStationPos = meta:get_string("sStationPos") local sStationPos = meta:get_string("sStationPos")
@ -111,25 +148,19 @@ local function booking_machine_update(pos)
local station_pos = P(sStationPos) local station_pos = P(sStationPos)
local counter = meta:get_int("change_counter") or 0 local counter = meta:get_int("change_counter") or 0
local changed, newcounter = Stations:changed(counter) local changed, newcounter = Stations:changed(counter)
if changed then if changed or not tStationList[sStationPos] then
meta:set_string("formspec", station_list_as_string(station_pos)) local subnet = meta:get_string("subnet")
meta:set_string("formspec", station_list_as_string(station_pos, subnet))
meta:set_int("change_counter", newcounter) meta:set_int("change_counter", newcounter)
end 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
end end
local function on_rightclick(pos)
booking_machine_update(pos)
end
local function on_receive_fields(pos, formname, fields, player) local function on_receive_fields(pos, formname, fields, player)
booking_machine_update(pos)
-- station name entered? -- station name entered?
if fields.name ~= nil then if fields.name ~= nil then
local station_name = string.trim(fields.name) 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!")) hyperloop.chat(player, S("Station has already a booking machine!"))
return return
end 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 -- store meta and generate station formspec
Stations:update(stationPos, { Stations:update(stationPos, {
name = station_name, name = station_name,
booking_pos = pos, booking_pos = pos,
booking_info = string.trim(fields.info), booking_info = string.trim(fields.info),
subnet = subnet,
}) })
local meta = M(pos) local meta = M(pos)
meta:set_string("sStationPos", SP(stationPos)) meta:set_string("sStationPos", SP(stationPos))
meta:set_string("infotext", "Station: "..station_name) 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 else
hyperloop.chat(player, S("Invalid station name!")) hyperloop.chat(player, S("Invalid station name!"))
end end
@ -233,6 +272,7 @@ minetest.register_node("hyperloop:booking", {
on_rotate = screwdriver.disallow, on_rotate = screwdriver.disallow,
on_receive_fields = on_receive_fields, on_receive_fields = on_receive_fields,
on_destruct = on_destruct, on_destruct = on_destruct,
on_rightclick = on_rightclick,
paramtype = 'light', paramtype = 'light',
light_source = 2, 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 v2.06 by JoSt
Copyright (C) 2017-2019 Joachim Stolberg Copyright (C) 2017-2021 Joachim Stolberg
LGPLv2.1+ LGPLv2.1+
See LICENSE.txt for more information See LICENSE.txt for more information
@ -34,6 +34,7 @@
2020-01-03 v2.04 Elevator door bugfix (MT 5+) 2020-01-03 v2.04 Elevator door bugfix (MT 5+)
2020-03-12 v2.05 minetest translator added (thanks to acmgit/Clyde) 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 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_enabled = minetest.settings:get_bool("hyperloop_wifi_enabled")
hyperloop.wifi_crafting_enabled = minetest.settings:get_bool("hyperloop_wifi_crafting_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.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") .. "/network.lua")
dofile(minetest.get_modpath("hyperloop") .. "/data_base.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 return lStations
end 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 -- Class Network
@ -233,8 +242,12 @@ function Network:station_list(pos, station_pos, sorted)
end end
if sorted == "dist" then if sorted == "dist" then
lStations = sort_based_on_distance(tStations, pos) lStations = sort_based_on_distance(tStations, pos)
else elseif sorted == "level" then
lStations = sort_based_on_level(tStations) 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 end
return lStations return lStations
end 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, # If disabled, connected stations have to be on one level,
# typically underground. # typically underground.
hyperloop_free_tube_placement_enabled (free tube placement enabled) bool false 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** **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) ![minecart](https://github.com/joe7575/minecart/blob/master/screenshot.png)
@ -26,21 +26,15 @@ license).
3. https://github.com/stujones11/railcart/ 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 Minecart Features
----------------- -----------------
The mod Minecart has its own cart (called Minecart) in addition to the standard cart. 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. Minecarts are used for automated item transport on private and public rail networks.
The mod features are: 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 - configurable timetables and routes for Minecarts
- automated loading/unloading of Minecarts by means of a Minecart Hopper - automated loading/unloading of Minecarts by means of a Minecart Hopper
- rail network protection based on protection blocks called Land Marks - 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 5. Drive from buffer to buffer in both directions using the Minecart(!) to record the
routes (use 'right-left' keys to control the Minecart) routes (use 'right-left' keys to control the Minecart)
6. Punch the buffers to check the connection data (e.g. "Oxford: connected to Cambridge") 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 will then start automatically after the configured time
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark 8. Optional: Protect your rail network with the Protection Landmarks (one Landmark
at least every 16 nodes/meters) at least every 16 nodes/meters)
9. Place a Minecart in front of the buffer and check whether it starts after the 9. Place a Minecart in front of the buffer and check whether it starts after the
configured time configured time
10. Check the cart state via the chat command: /mycart <num> 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 11. Drop items into the Minecart and punch the cart to start it, or "sneak+click" the
Minecart to get the items back Minecart to get cart and items back
12. Dig the empty cart with a second "sneak+click" (as usual)
Hopper 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. 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 History
------- -------
@ -118,3 +148,5 @@ History
2020-07-24 V1.08 Adapted to new techage ICTA style 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-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 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 Minecart
======== ========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
MIT MIT
See license.txt for more information See license.txt for more information
@ -23,21 +23,17 @@ local StopTime = {}
local function formspec(pos) local function formspec(pos)
local name = M(pos):get_string("name") local name = M(pos):get_string("name")
local time = M(pos):get_int("time") local time = M(pos):get_int("time")
local s = "size[4,4.2]" .. return "size[4,4.2]" ..
"label[0,0;Configuration]" .. "label[0,0;Configuration]" ..
"field[0.5,1.2;3.6,1;name;"..S("Station name")..":;"..name.."]".. "field[0.5,1.2;3.6,1;name;"..S("Station name")..":;"..name.."]"..
"button_exit[1,3.4;2,1;exit;Save]" "button_exit[1,3.4;2,1;exit;Save]"..
if minecart.hopper_enabled then "field[0.5,2.5;3.6,1;time;"..S("Waiting time/sec")..":;"..time.."]"
return s.."field[0.5,2.5;3.6,1;time;"..S("Stop time/sec")..":;"..time.."]"
end
return s
end end
local function remote_station_name(pos) 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 if route and route.dest_pos then
local pos2 = S2P(route.dest_pos) return M(route.dest_pos):get_string("name")
return M(pos2):get_string("name")
end end
return "none" return "none"
end end
@ -46,23 +42,21 @@ local function on_punch(pos, node, puncher)
local name = M(pos):get_string("name") local name = M(pos):get_string("name")
M(pos):set_string("infotext", name..": "..S("connected to").." "..remote_station_name(pos)) M(pos):set_string("infotext", name..": "..S("connected to").." "..remote_station_name(pos))
M(pos):set_string("formspec", formspec(pos)) M(pos):set_string("formspec", formspec(pos))
if minecart.hopper_enabled then minetest.get_node_timer(pos):start(CYCLE_TIME)
minetest.get_node_timer(pos):start(CYCLE_TIME)
end
-- Optional Teleport function -- Optional Teleport function
if not minecart.teleport_enabled then return end 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 if route and route.dest_pos and puncher and puncher:is_player() then
-- only teleport if the user is not pressing shift -- only teleport if the user is not pressing shift
if not puncher:get_player_control()['sneak'] then if not puncher:get_player_control()['sneak'] then
local playername = puncher:get_player_name() local playername = puncher:get_player_name()
local pos = S2P(route.dest_pos)
local teleport = function() local teleport = function()
-- Make sure the player object still exists -- Make sure the player object still exists
local player = minetest.get_player_by_name(playername) 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 end
minetest.after(0.25, teleport) minetest.after(0.25, teleport)
end end
@ -95,24 +89,21 @@ minetest.register_node("minecart:buffer", {
}, },
after_place_node = function(pos, placer) after_place_node = function(pos, placer)
M(pos):set_string("owner", placer:get_player_name()) 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)) M(pos):set_string("formspec", formspec(pos))
if minecart.hopper_enabled then minetest.get_node_timer(pos):start(CYCLE_TIME)
minetest.get_node_timer(pos):start(CYCLE_TIME)
end
end, end,
on_timer = function(pos, elapsed) on_timer = function(pos, elapsed)
local time = M(pos):get_int("time") local time = M(pos):get_int("time")
if time > 0 then if time > 0 then
local hash = minetest.hash_node_position(pos) local hash = minetest.hash_node_position(pos)
local param2 = (minetest.get_node(pos).param2 + 2) % 4 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] then
if StopTime[hash] < minetest.get_gametime() then if StopTime[hash] < minetest.get_gametime() then
StopTime[hash] = nil StopTime[hash] = nil
local node = minetest.get_node(pos) local dir = minetest.facedir_to_dir(param2)
local dir = minetest.facedir_to_dir(node.param2) minecart.punch_cart(pos, param2, 0.5, dir)
minecart.punch_cart(pos, param2, 0, dir)
end end
else else
StopTime[hash] = minetest.get_gametime() + time StopTime[hash] = minetest.get_gametime() + time
@ -124,7 +115,7 @@ minetest.register_node("minecart:buffer", {
return true return true
end, end,
after_dig_node = function(pos) after_dig_node = function(pos)
minecart.del_route(minetest.pos_to_string(pos)) minecart.del_route(pos)
local hash = minetest.hash_node_position(pos) local hash = minetest.hash_node_position(pos)
StopTime[hash] = nil StopTime[hash] = nil
end, end,
@ -137,6 +128,7 @@ minetest.register_node("minecart:buffer", {
M(pos):set_int("time", tonumber(fields.time) or 0) M(pos):set_int("time", tonumber(fields.time) or 0)
M(pos):set_string("formspec", formspec(pos)) M(pos):set_string("formspec", formspec(pos))
M(pos):set_string("infotext", fields.name.." "..S("connected to").." "..remote_station_name(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
end, end,
on_punch = on_punch, on_punch = on_punch,
@ -156,3 +148,15 @@ minetest.register_craft({
{"default:steel_ingot", "default:junglewood", "default:steel_ingot"}, {"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 Minecart
======== ========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
MIT MIT
See license.txt for more information 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("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("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("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("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("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("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("11. Drop items into the Minecart and punch the cart to start it."),
S("12. Dig the empty cart with a second 'sneak+click' (as usual)."), S("12. Dig the cart with 'sneak+click' (as usual). The items will be drop down."),
}, "\n") }, "\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") 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 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) local function formspec(data)
if data.image then if data.image then
@ -90,6 +113,16 @@ doc.add_entry("minecart", "landmark", {
data = {text = landmark_doc, item="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 if minecart.hopper_enabled then
doc.add_entry("minecart", "hopper", { doc.add_entry("minecart", "hopper", {
name = S("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 Minecart
======== ========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
MIT MIT
See license.txt for more information See license.txt for more information
@ -155,6 +155,7 @@ minetest.register_node("minecart:hopper", {
paramtype = "light", paramtype = "light",
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
use_texture_alpha = minecart.CLIP,
groups = {choppy=2, cracky=2, crumbly=2}, groups = {choppy=2, cracky=2, crumbly=2},
is_ground_content = false, is_ground_content = false,
sounds = default.node_sound_wood_defaults(), 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 -*- # -*- coding: utf-8 -*-
# #
# Script to generate the template file and update the translation files. # 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+ # 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 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) # Running params
pattern_tr = re.compile(r'(.+?[^@])=(.+)') 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): # Strings longer than this will have extra space added between
lOut = [] # them in the translation files to make it easier to distinguish their
lkeyStrings.sort() # beginnings and endings at a glance
for s in lkeyStrings: doublespace_threshold = 80
lOut.append("%s=" % s)
open(templ_file, "wt").write("\n".join(lOut))
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): def read_lua_file_strings(lua_file):
lOut = [] lOut = []
text = open(lua_file).read() with open(lua_file, encoding='utf-8') as text_file:
for s in pattern_lua.findall(text): text = text_file.read()
s = re.sub(r'"\.\.\s+"', "", s) #TODO remove comments here
s = re.sub("@[^@=n]", "@@", s)
s = s.replace("\n", "@n") text = re.sub(pattern_concat, "", text)
s = s.replace("\\n", "@n")
s = s.replace("=", "@=") strings = []
lOut.append(s) 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 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 = {} dOut = {}
text = None
header_comment = None
if os.path.exists(tr_file): if os.path.exists(tr_file):
for line in open(tr_file, "r").readlines(): with open(tr_file, "r", encoding='utf-8') as existing_file :
s = line.strip() # save the full text to allow for comparison
if s == "" or s[0] == "#": # of the old version with the new output
continue text = existing_file.read()
match = pattern_tr.match(s) existing_file.seek(0)
if match: # a running record of the current comment block
dOut[match.group(1)] = match.group(2) # we're inside, to allow preceeding multi-line comments
return dOut # 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): # Reset comment block if we hit a header
lOut = [] latest_comment_block = None
for root, dirs, files in os.walk('./'): 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: for name in files:
if fnmatch.fnmatch(name, "*.lua"): if fnmatch.fnmatch(name, "*.lua"):
fname = os.path.join(root, name) fname = os.path.join(root, name)
found = read_lua_file_strings(fname) found = read_lua_file_strings(fname)
print(fname, len(found)) if params["verbose"]:
lOut.extend(found) print(f"{fname}: {str(len(found))} translatable strings")
lOut = list(set(lOut))
lOut.sort()
gen_template(templ_file, lOut)
return lOut
def update_tr_file(lNew, mod_name, tr_file): for s in found:
lOut = ["# textdomain: %s\n" % mod_name] sources = dOut.get(s, set())
if os.path.exists(tr_file): sources.add(f"### {os.path.basename(fname)} ###")
shutil.copyfile(tr_file, tr_file+".old") dOut[s] = sources
dOld = inport_tr_file(tr_file)
for key in lNew:
val = dOld.get(key, "")
lOut.append("%s=%s" % (key, val))
lOut.append("##### not used anymore #####")
for key in dOld:
if key not in lNew:
lOut.append("%s=%s" % (key, dOld[key]))
open(tr_file, "w").write("\n".join(lOut))
data = generate_template("./locale/template.txt") if len(dOut) == 0:
update_tr_file(data, "minecart", "./locale/minecart.de.tr") return None
print("Done.\n") 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 Minecart
======== ========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
MIT MIT
See license.txt for more information See license.txt for more information
@ -13,24 +13,37 @@
minecart = {} minecart = {}
-- Version for compatibility checks, see readme.md/history -- 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.hopper_enabled = minetest.settings:get_bool("minecart_hopper_enabled") ~= false
minecart.teleport_enabled = minetest.settings:get_bool("minecart_teleport_enabled") == true 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") minecart.S = minetest.get_translator("minecart")
local MP = minetest.get_modpath("minecart") local MP = minetest.get_modpath("minecart")
dofile(MP.."/storage.lua") dofile(MP .. "/baselib.lua")
dofile(MP.."/lib.lua") dofile(MP .. "/storage.lua")
dofile(MP.."/monitoring.lua") dofile(MP .. "/rails.lua")
dofile(MP.."/recording.lua") dofile(MP .. "/monitoring.lua")
dofile(MP.."/minecart.lua") dofile(MP .. "/recording.lua")
dofile(MP.."/buffer.lua") dofile(MP .. "/hopperlib.lua")
dofile(MP.."/protection.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 if minecart.hopper_enabled then
dofile(MP.."/hopper.lua") dofile(MP .. "/hopper.lua")
dofile(MP.."/mods_support.lua") dofile(MP .. "/mods_support.lua")
end end
dofile(MP.."/doc.lua")
dofile(MP .. "/doc.lua")
minetest.log("info", "[MOD] Minecart loaded") 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 # 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. 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). 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. 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) 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. 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') 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). 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. 9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=9. Platziere einen Wagen direkt vor einem Prellbock und prüfe, ob er nach der konfigurierten Zeit startet.
Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen 10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=Prüfe den Status des Wagen mit dem Chat Kommando: /mycart <num>@n <num> ist die Wagennummer
Minecart=Minecart 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.
Minecart (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts) 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.
Minecart Cart=Wagen 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
Minecart Hopper=Minecart Hopper
Minecart Landmark=Minecart Meilenstein
Minecart Railway Buffer=Minecart Prellbock
Minecart, the lean railway transportation automation system=Minecart, das schlanke Schienentransport Automatisierungssystem
Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get the items back=Primär für den Transport von Gegenständen genutzt. Du kannst Gegenstände in ein Cart legen (Taste Q) und dann den Wagen durch Anklicken starten. Klicke mit gedrückter Shift-Taste auf den Wagen, um die Gegenstände wieder auszuladen
Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke der Strecke entlang)
Station name=Stationsname
Stop time/sec=Haltezeit/s
Summary=Zusammenfassung
Used as buffer on both rail ends. Needed to be able to record the cart routes=Preckblöcke müssen an beiden Schienenenden platziert sein, so dass Aufzeichnungen der Strecke gemacht werden können. Used 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. 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] Area is protected!=[minecart] Bereich ist geschützt!
[minecart] Cart is protected by = Wagen ist geschützt durch Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen
[minecart] Recording canceled!=[minecart] Aufzeichnung abgebrochen! 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] Route stored!=[minecart] Strecke gespeichert
[minecart] Start route recording!=[minecart] Starte die Streckenaufzeichnung! [minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m=[minecart] Geschw. @= %u m/s, Zeit @= %u s, Routenlänge @= %u m
connected to=verbunden mit 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 ##### ##### 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.= 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).= 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.= 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)= 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).= 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').= 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).= 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.= 9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=
Allow to dig/place rails in Minecart Landmark areas= 10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=
Minecart= 11. Drop items into the Minecart and punch the cart to start it.=
Minecart (Sneak+Click to pick up)= 12. Dig the cart with 'sneak+click' (as usual). The items will be drop down.=
Minecart Cart= Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back=
Minecart Hopper=
Minecart Landmark=
Minecart Railway Buffer=
Minecart, the lean railway transportation automation system=
Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get the items back=
Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=
Station name=
Stop time/sec=
Summary=
Used as buffer on both rail ends. Needed to be able to record the cart routes= Used 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.= 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] Area is protected!=
[minecart] Cart is protected by = Allow to dig/place rails in Minecart Landmark areas=
[minecart] Recording canceled!= Minecart Landmark=
Cart Pusher=
left=
right=
straight=
Recording=
speed=
next junction=
Travel time=
[minecart] Route stored!= [minecart] Route stored!=
[minecart] Start route recording!= [minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m=
connected to= Speed "1"=
Speed "2"=
Speed "4"=
No speed limit=
Cart List=
Cart Terminal=

View File

@ -3,7 +3,7 @@
Minecart Minecart
======== ========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
MIT MIT
See license.txt for more information See license.txt for more information
@ -11,69 +11,92 @@
]]-- ]]--
local S = minecart.S local S = minecart.S
local MP = minetest.get_modpath("minecart") local M = minetest.get_meta
local lib = dofile(MP.."/cart_lib1.lua")
lib:init(false) minetest.register_node("minecart:cart", {
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", {
description = S("Minecart (Sneak+Click to pick up)"), 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"), tiles = {
wield_image = "carts_cart_side.png", -- up, down, right, left, back, front
on_place = function(itemstack, placer, pointed_thing) "carts_cart_top.png^minecart_appl_cart_top.png",
-- use cart as tool "carts_cart_top.png",
local under = pointed_thing.under "carts_cart_side.png^minecart_logo.png",
local node = minetest.get_node(under) "carts_cart_side.png^minecart_logo.png",
local udef = minetest.registered_nodes[node.name] "carts_cart_side.png^minecart_logo.png",
if udef and udef.on_rightclick and "carts_cart_side.png^minecart_logo.png",
not (placer and placer:is_player() and },
placer:get_player_control().sneak) then drawtype = "nodebox",
return udef.on_rightclick(under, node, placer, itemstack, node_box = {
pointed_thing) or itemstack type = "fixed",
end 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 on_place = minecart.on_nodecart_place,
return on_punch = minecart.on_nodecart_punch,
end
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, 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({ minetest.register_craft({
output = "minecart:cart", output = "minecart:cart",
recipe = { recipe = {

View File

@ -3,7 +3,7 @@
Minecart Minecart
======== ========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
MIT MIT
See license.txt for more information See license.txt for more information

View File

@ -3,274 +3,271 @@
Minecart Minecart
======== ========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
MIT MIT
See license.txt for more information 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 -- for lazy programmers
local M = minetest.get_meta local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local S = minecart.S 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 tCartsOnRail = minecart.CartsOnRail
local get_route = minecart.get_route -- from storage.lua local Queue = {}
local NodesAtStation = {} local first = 0
local last = -1
-- local function push(cycle, item)
-- Helper functions last = last + 1
-- item.cycle = cycle
local function get_pos_vel_pitch_yaw(item) Queue[last] = 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
end end
-- local function pop(cycle)
-- Monitoring of cart entities if first > last then return end
-- local item = Queue[first]
function minecart.add_to_monitoring(obj, myID, owner, userID) if item.cycle < cycle then
local pos = vector.round(obj:get_pos()) Queue[first] = nil -- to allow garbage collection
CartsOnRail[myID] = { first = first + 1
start_key = lib.get_route_key(pos), return item
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
end end
end end
-- When cart entity is removed local function is_player_nearby(pos)
function minecart.remove_from_monitoring(myID) for _, object in pairs(minetest.get_objects_inside_radius(pos, 64)) do
if myID then if object:is_player() 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()
return true return true
end end
end end
return false
end end
function minecart.stop_cart(pos, myID) local function zombie_to_entity(pos, cart, checkpoint)
local item = CartsOnRail[myID] local vel = {x = 0, y = 0, z = 0}
if item and not item.stopped then local obj = minecart.add_entitycart(pos, cart.node_name, cart.entity_name,
item.start_time = nil vel, cart.cargo, cart.owner, cart.userID)
item.start_key = nil if obj then
item.start_pos = nil local entity = obj:get_luaentity()
item.junctions = nil entity.reenter = checkpoint
item.stopped = true entity.junctions = cart.junctions
minecart.store_carts() entity.is_running = true
return true entity.arrival_time = 0
cart.objID = entity.objID
end end
return false
end end
local function monitoring() local function get_checkpoint(cart)
local to_be_added = {} local cp = cart.checkpoints[cart.idx]
for key, item in pairs(CartsOnRail) do if not cp then
local entity = minetest.luaentities[key] cart.idx = #cart.checkpoints
--print("Cart:", key, item.owner, item.userID, item.stopped) cp = cart.checkpoints[cart.idx]
if entity then -- cart entity running end
local pos = entity.object:get_pos() local pos = H2P(cp[1])
local vel = entity.object:get_velocity() -- if M(pos):contains("waypoints") then
local rot = entity.object:get_rotation() -- print("get_checkpoint", P2S(H2P(cp[1])), P2S(H2P(cp[2])))
if pos and vel and rot then -- end
if not minetest.get_node_or_nil(pos) then -- unloaded area return cp, cart.idx == #cart.checkpoints
lib.unload_cart(pos, vel, entity, item) end
item.stopped = minecart.stopped(vel)
end -- Function returns the cart state ("running" / "stopped") and
-- store last pos from cart -- the station name or position string, or if cart is running,
item.last_pos, item.last_vel, item.last_pitch, item.last_yaw = pos, vel, rot.x, rot.y -- the distance to the query_pos.
end local function get_cart_state_and_loc(name, userID, query_pos)
else -- no cart running if tCartsOnRail[name] and tCartsOnRail[name][userID] then
local pos, vel, pitch, yaw = get_pos_vel_pitch_yaw(item) local cart = tCartsOnRail[name][userID]
if pos and vel then local pos = cart.last_pos or cart.pos
if minetest.get_node_or_nil(pos) then -- loaded area local loc = minecart.get_buffer_name(cart.pos) or
if pitch > 0 then math.floor(vector.distance(pos, query_pos))
pos.y = pos.y + 0.5 if cart.objID == 0 then
end return "stopped", minecart.get_buffer_name(cart.pos) or
local myID = lib.load_cart(pos, vel, pitch, yaw, item) math.floor(vector.distance(pos, query_pos)), cart.node_name
if myID then else
item.stopped = minecart.stopped(vel) return "running", math.floor(vector.distance(pos, query_pos)), cart.node_name
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
else
-- should never happen
minetest.log("error", "[minecart] Cart of owner "..(item.owner or "nil").." got lost")
CartsOnRail[key] = nil
end
end end
end end
-- table maintenance return "unknown", 0, "unknown"
local is_changed = false end
for key,val in pairs(to_be_added) do
CartsOnRail[key] = val local function get_cart_info(owner, userID, query_pos)
is_changed = true 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
if is_changed then 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()
if pos then
cart.last_pos = vector.round(pos)
--print("entity card " .. cart.userID .. " at " .. P2S(cart.last_pos))
else
print("entity card without pos!")
end
push(cycle, cart)
elseif cart.checkpoints then
local cp, 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
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
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() minecart.store_carts()
end end
minetest.after(1, monitoring)
end end
-- delay the start to prevent cart disappear into nirvana
minetest.register_on_mods_loaded(function() function minecart.stop_monitoring(owner, userID, pos)
minetest.after(10, monitoring) --print("stop_monitoring", owner, userID)
end) 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 -- API functions
-- --
-- Return a list of carts with current position and speed. -- Needed by storage to re-construct the queue after server start
function minecart.get_cart_list() minecart.push = push
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
minetest.register_chatcommand("mycart", { minetest.register_chatcommand("mycart", {
params = "<cart-num>", params = "<cart-num>",
description = "Output cart state and position, or a list of carts, if no cart number is given.", description = S("Output cart state and position, or a list of carts, if no cart number is given."),
func = function(name, param) func = function(owner, param)
local userID = tonumber(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 if userID then
-- First check if it is a node cart at a station return true, get_cart_info(owner, userID, query_pos)
local cart_pos = NodesAtStation[name] and NodesAtStation[name][userID] elseif tCartsOnRail[owner] then
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
-- Output a list with all numbers -- Output a list with all numbers
local tbl = {} local tbl = {}
for userID, pos in pairs(NodesAtStation[name] or {}) do for userID, cart in pairs(tCartsOnRail[owner]) do
tbl[#tbl + 1] = userID tbl[#tbl + 1] = userID
end end
for id, item in pairs(CartsOnRail) do return true, S("List of carts") .. ": "..table.concat(tbl, ", ").." "
if item.owner == name then
tbl[#tbl + 1] = item.userID
end
end
return true, "List of carts: "..table.concat(tbl, ", ").." "
end end
end end
}) })
function minecart.cmnd_cart_state(name, userID) function minecart.cmnd_cart_state(name, userID)
-- First check if it is a node cart at a station local state, loc = get_cart_state_and_loc(name, userID, {x=0, y=0, z=0})
local pos = NodesAtStation[name] and NodesAtStation[name][userID] return state
if pos then
return "stopped"
end
return get_cart_state(name, userID)
end end
function minecart.cmnd_cart_location(name, userID, query_pos) function minecart.cmnd_cart_location(name, userID, query_pos)
-- First check if it is a node cart at a station local state, loc = get_cart_state_and_loc(name, userID, query_pos)
local station = NodesAtStation[name] and NodesAtStation[name][userID] return loc
if station then end
return station
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 end
local state, cart_pos = get_cart_state(name, userID)
if state then table.sort(userIDs, function(a,b) return a < b end)
return get_cart_pos(query_pos, cart_pos)
for _, userID in ipairs(userIDs) do
carts[#carts + 1] = get_cart_info(name, userID, pos)
end end
return table.concat(carts, "\n")
end end
minetest.register_on_mods_loaded(function() minetest.register_on_mods_loaded(function()
@ -357,4 +354,3 @@ minetest.register_on_mods_loaded(function()
}) })
end end
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 Minecart
======== ========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
MIT MIT
See license.txt for more information 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")
minecart.register_protected_node("minecart:ballast_slope") minecart.register_protected_node("minecart:ballast_slope")
minecart.register_protected_node("minecart:ballast_ramp") 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 Minecart
======== ========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
MIT MIT
See license.txt for more information See license.txt for more information
@ -14,76 +14,172 @@
local M = minetest.get_meta local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local S = minecart.S 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 function dashboard_destroy(self)
local get_route = minecart.get_route -- from storage.lua 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 -- Route recording
-- --
function minecart.start_recording(self, pos) function minecart.start_recording(self, pos)
self.start_key = lib.get_route_key(pos, self.driver) --print("start_recording")
if self.start_key then if self.driver then
self.waypoints = {} self.start_pos = minecart.get_buffer_pos(pos, self.driver)
self.junctions = {} if self.start_pos then
self.recording = true self.checkpoints = {} -- {cart_pos, next_waypoint_pos, speed, dot}
self.next_time = minetest.get_us_time() + 1000000 self.junctions = {}
minetest.chat_send_player(self.driver, S("[minecart] Start route recording!")) self.is_recording = true
end self.rec_time = self.timebase
end self.hud_time = self.timebase
self.runtime = 0
function minecart.store_next_waypoint(self, pos, vel) self.num_sections = 0
if self.start_key and self.recording and self.driver and self.sum_speed = 0
self.next_time < minetest.get_us_time() then self.ctrl = {}
self.next_time = minetest.get_us_time() + 1000000 dashboard_create(self)
self.waypoints[#self.waypoints+1] = {P2S(vector.round(pos)), P2S(vector.round(vel))} dashboard_update(self, 0)
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
local route = {
waypoints = self.waypoints,
dest_pos = dest_pos,
junctions = self.junctions,
}
minecart.store_route(self.start_key, route)
minetest.chat_send_player(self.driver, S("[minecart] Route stored!"))
else
minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!"))
end end
else
minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!"))
end end
self.recording = false end
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 = {
dest_pos = dest_pos,
checkpoints = self.checkpoints,
junctions = self.junctions,
}
minecart.store_route(self.start_pos, route)
minetest.chat_send_player(self.driver, S("[minecart] Route stored!"))
local speed = self.sum_speed / #self.checkpoints
local length = speed * self.runtime
local fmt = S("[minecart] Speed = %u m/s, Time = %u s, Route length = %u m")
minetest.chat_send_player(self.driver, string.format(fmt, speed, self.runtime, length))
end
end
dashboard_destroy(self)
end
self.is_recording = false
self.checkpoints = nil
self.waypoints = nil self.waypoints = nil
self.junctions = nil self.junctions = nil
end end
function minecart.set_junction(self, pos, dir, switch_keys) function minecart.recording_waypoints(self)
if self.junctions then local pos = vector.round(self.object:get_pos())
self.junctions[P2S(vector.round(pos))] = {dir, switch_keys} -- hier müsste überprüfung dest_pos rein
end 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 end
function minecart.get_junction(self, pos, dir) function minecart.recording_junctions(self)
local junctions = CartsOnRail[self.myID] and CartsOnRail[self.myID].junctions local player = minetest.get_player_by_name(self.driver)
if junctions then if player then
local data = junctions[P2S(vector.round(pos))] local ctrl = player:get_player_control()
if data then if ctrl.left then
return data[1], data[2] self.ctrl = {left = true}
elseif ctrl.right then
self.ctrl = {right = true}
elseif ctrl.up or ctrl.down then
self.ctrl = nil
end end
end end
return dir 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 end
function minecart.set_junctions(self, wayp_pos)
if self.ctrl then
self.junctions[P2H(wayp_pos)] = self.ctrl
end
end
function minecart.player_ctrl(self)
local player = minetest.get_player_by_name(self.driver)
if player then
local ctrl = player:get_player_control()
if ctrl.left then
self.ctrl = {left = true}
elseif ctrl.right then
self.ctrl = {right = true}
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 Minecart
======== ========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
MIT MIT
See license.txt for more information See license.txt for more information
@ -14,24 +14,75 @@
local M = minetest.get_meta local M = minetest.get_meta
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos local S2P = minetest.string_to_pos
local P2H = minetest.hash_node_position
local H2P = minetest.get_position_from_hash
local S = minecart.S local S = minecart.S
local storage = minetest.get_mod_storage() 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 -- Store data of running carts
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
minecart.CartsOnRail = {} minecart.CartsOnRail = {}
minetest.register_on_mods_loaded(function() minetest.register_on_mods_loaded(function()
for key,val in pairs(minetest.deserialize(storage:get_string("CartsOnRail")) or {}) do local version = storage:get_int("version")
-- use invalid keys to force the cart spawning if version < 2 then
minecart.CartsOnRail[-key] = val 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
end) end)
minetest.register_on_shutdown(function() minetest.register_on_shutdown(function()
storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail)) storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail))
print("minecart shutdown finished!!!")
end) end)
function minecart.store_carts() function minecart.store_carts()
@ -39,69 +90,31 @@ function minecart.store_carts()
end end
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- Store routes -- Store routes (in buffers)
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
-- All positions as "pos_to_string" string function minecart.store_route(pos, route)
--Routes = { if pos and route then
-- start_pos = { M(pos):set_string("route", minetest.serialize(route))
-- waypoints = {{spos, svel}, {spos, svel}, ...}, return true
-- 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))
return true
end
end end
return false return false
end end
function minecart.get_route(key) function minecart.get_route(pos)
if not Routes[key] then if pos then
local s = M(S2P(key)):get_string("route") local s = M(pos):get_string("route")
if s ~= "" then 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) local route = minetest.deserialize(s)
if route.waypoints and route.junctions then if route.waypoints then
if minecart.store_route(key, route) then M(pos):set_string("route", "")
storage:set_string(key, "") M(pos):set_int("time", 0)
end return
else
storage:set_string(key, "")
end end
return minetest.deserialize(s)
end 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 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) copy <size> - make a copy of "pattern". Size is e.g. 3x3 (see ingame help)
punch_cart - Punch a rail cart to start it punch_cart - Punch a rail cart to start it
print <text> - Output chat message for debug purposes
#### Flow control commands #### 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-06-21 v1.03 * Interpreter bugfixes, node and crop sensors changed
- 2020-10-01 v1.04 * Many improvements and bugfixes (Thanks to Thomas-S) - 2020-10-01 v1.04 * Many improvements and bugfixes (Thanks to Thomas-S)
- 2021-01-30 v1.05 * Many improvements and bugfixes - 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 Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -13,13 +13,10 @@
]]-- ]]--
-- for lazy programmers -- 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 local M = minetest.get_meta
-- Load support for intllib. -- Load support for I18n.
local MP = minetest.get_modpath("signs_bot") local S = signs_bot.S
local I,_ = dofile(MP.."/intllib.lua")
local lib = signs_bot.lib local lib = signs_bot.lib
@ -112,16 +109,33 @@ local function preassigned_slots(pos)
return table.concat(tbl, "") return table.concat(tbl, "")
end 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) local function formspec(pos, mem)
mem.running = mem.running or false 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 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) 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..
default.gui_bg_img.. default.gui_bg_img..
default.gui_slots.. 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]".. "image[0.6,0;1,1;signs_bot_form_mask.png]"..
bot.. bot..
preassigned_slots(pos).. 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]".. "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;]".. "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]".. "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.."]".. "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[context;main]"..
"listring[current_player;main]" "listring[current_player;main]"
end end
local function formspec_cfg(pos, mem) local function formspec_cfg()
return "size[9,7.6]".. return "size[9,8.2]"..
default.gui_bg.. default.gui_bg..
default.gui_bg_img.. default.gui_bg_img..
default.gui_slots.. 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]".. "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;]".. "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]".. "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").."]".. "button[0.2,1;1.5,1;back;"..S("Back").."]"..
"list[current_player;main;0.5,3.8;8,4;]" "list[current_player;main;0.5,4.4;8,4;]"..
"listring[context;filter]"..
"listring[current_player;main]"
end end
local function get_capa(itemstack) 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 = techage.power.percent(signs_bot.MAX_CAPA, capa)
capa = (math.floor((capa or 0) / 5)) * 5 capa = (math.floor((capa or 0) / 5)) * 5
meta:set_int("capa", capa) meta:set_int("capa", capa)
local text = I("Robot Box ").." ("..capa.." %)" local text = S("Robot Box").." ("..capa.." %)"
meta:set_string("description", text) meta:set_string("description", text)
local inv = minetest.get_inventory({type="player", name=digger:get_player_name()}) local inv = minetest.get_inventory({type="player", name=digger:get_player_name()})
local left_over = inv:add_item("main", node) local left_over = inv:add_item("main", node)
@ -179,7 +196,7 @@ function signs_bot.infotext(pos, state)
local meta = M(pos) local meta = M(pos)
local number = meta:get_string("number") local number = meta:get_string("number")
state = state or "<unknown>" state = state or "<unknown>"
meta:set_string("infotext", I("Robot Box ")..number..": "..state) meta:set_string("infotext", S("Robot Box").." "..number..": "..state)
end end
local function reset_robot(pos, mem) local function reset_robot(pos, mem)
@ -205,7 +222,7 @@ function signs_bot.start_robot(base_pos)
mem.capa = nil mem.capa = nil
end end
meta:set_string("formspec", formspec(base_pos, mem)) 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) reset_robot(base_pos, mem)
minetest.get_node_timer(base_pos):start(CYCLE_TIME) minetest.get_node_timer(base_pos):start(CYCLE_TIME)
return true return true
@ -224,9 +241,9 @@ function signs_bot.stop_robot(base_pos, mem)
mem.charging = false mem.charging = false
end end
if mem.power_available then if mem.power_available then
signs_bot.infotext(base_pos, I("charging")) signs_bot.infotext(base_pos, S("charging"))
else else
signs_bot.infotext(base_pos, I("stopped")) signs_bot.infotext(base_pos, S("stopped"))
end end
meta:set_string("formspec", formspec(base_pos, mem)) meta:set_string("formspec", formspec(base_pos, mem))
signs_bot.remove_robot(mem) signs_bot.remove_robot(mem)
@ -285,7 +302,7 @@ local function on_receive_fields(pos, formname, fields, player)
if fields.update then if fields.update then
meta:set_string("formspec", formspec(pos, mem)) meta:set_string("formspec", formspec(pos, mem))
elseif fields.config then elseif fields.config then
meta:set_string("formspec", formspec_cfg(pos, mem)) meta:set_string("formspec", formspec_cfg())
elseif fields.back then elseif fields.back then
meta:set_string("formspec", formspec(pos, mem)) meta:set_string("formspec", formspec(pos, mem))
elseif fields.start then elseif fields.start then
@ -373,6 +390,7 @@ end
local function on_power(pos) local function on_power(pos)
local mem = tubelib2.get_mem(pos) local mem = tubelib2.get_mem(pos)
mem.power_available = true mem.power_available = true
mem.charging = true
signs_bot.infotext(pos, S("charging")) signs_bot.infotext(pos, S("charging"))
end end
@ -383,7 +401,7 @@ local function on_nopower(pos)
end end
minetest.register_node("signs_bot:box", { minetest.register_node("signs_bot:box", {
description = I("Signs Bot Box"), description = S("Signs Bot Box"),
stack_max = 1, stack_max = 1,
tiles = { tiles = {
-- up, down, right, left, back, front -- 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("formspec", formspec(pos, mem))
meta:set_string("signs_bot_cmnd", "turn_off") meta:set_string("signs_bot_cmnd", "turn_off")
meta:set_int("err_code", 0) meta:set_int("err_code", 0)
signs_bot.infotext(pos, I("stopped")) signs_bot.infotext(pos, S("stopped"))
if minetest.global_exists("techage") then if minetest.global_exists("techage") then
techage.ElectricCable:after_place_node(pos) techage.ElectricCable:after_place_node(pos)
mem.capa = get_capa(itemstack) mem.capa = get_capa(itemstack)
@ -473,12 +491,12 @@ minetest.register_node("signs_bot:box", {
on_power = function(pos) on_power = function(pos)
local mem = tubelib2.get_mem(pos) local mem = tubelib2.get_mem(pos)
mem.power_available = true mem.power_available = true
signs_bot.infotext(pos, I("charging")) signs_bot.infotext(pos, S("charging"))
end, end,
on_nopower = function(pos) on_nopower = function(pos)
local mem = tubelib2.get_mem(pos) local mem = tubelib2.get_mem(pos)
mem.power_available = false mem.power_available = false
signs_bot.infotext(pos, I("no power")) signs_bot.infotext(pos, S("no power"))
end, end,
nominal = PWR_NEEDED, nominal = PWR_NEEDED,
} }
@ -516,22 +534,22 @@ end
if minetest.get_modpath("doc") then if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "box", { doc.add_entry("signs_bot", "box", {
name = I("Signs Bot Box"), name = S("Signs Bot Box"),
data = { data = {
item = "signs_bot:box", item = "signs_bot:box",
text = table.concat({ text = table.concat({
I("The Box is the housing of the bot."), S("The Box is the housing of the bot."),
I("Place the box and start the bot by means of the 'On' button."), S("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("If the mod techage is installed, the bot needs electrical power."),
"", "",
I("The bot leaves the box on the right side."), S("The bot leaves the box on the right side."),
I("It will not start, if this position is blocked."), 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."), S("The box inventory simulates the inventory of the bot."),
I("You will not be able to access the inventory, if the bot is running."), S("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 bot can carry up to 8 stacks and 6 signs with it."),
}, "\n") }, "\n")
}, },
}) })

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -11,14 +11,8 @@
Bot flower cutting command Bot flower cutting command
]]-- ]]--
-- for lazy programmers -- Load support for I18n.
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S = signs_bot.S
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")
local lib = signs_bot.lib local lib = signs_bot.lib
@ -48,16 +42,28 @@ minetest.after(1, function()
end end
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 function harvesting(base_pos, mem)
local pos = mem.pos_tbl and mem.pos_tbl[mem.steps] local pos = mem.pos_tbl and mem.pos_tbl[mem.steps]
mem.steps = (mem.steps or 1) + 1 mem.steps = (mem.steps or 1) + 1
if pos and lib.not_protected(base_pos, pos) then if pos and lib.not_protected(base_pos, pos) then
local node = minetest.get_node_or_nil(pos) 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 if drop then
minetest.remove_node(pos) 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 end
end end
@ -66,7 +72,7 @@ signs_bot.register_botcommand("cutting", {
mod = "farming", mod = "farming",
params = "", params = "",
num_param = 0, 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) cmnd = function(base_pos, mem)
if not mem.steps then if not mem.steps then
mem.pos_tbl = signs_bot.lib.gen_position_table(mem.robot_pos, mem.robot_param2, 3, 3, 0) 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({ signs_bot.register_sign({
name = "flowers", name = "flowers",
description = I('Sign "flowers"'), description = S('Sign "flowers"'),
commands = CMD, commands = CMD,
image = "signs_bot_sign_flowers.png", image = "signs_bot_sign_flowers.png",
}) })
@ -107,13 +113,13 @@ minetest.register_craft({
if minetest.get_modpath("doc") then if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "flowers", { doc.add_entry("signs_bot", "flowers", {
name = I("Sign 'flowers'"), name = S("Sign 'flowers'"),
data = { data = {
item = "signs_bot:flowers", item = "signs_bot:flowers",
text = table.concat({ text = table.concat({
I("Used to cut flowers on a 3x3 field."), S("Used to cut flowers on a 3x3 field."),
I("Place the sign in front of the field."), S("Place the sign in front of the field."),
I("When finished, the bot turns."), S("When finished, the bot turns."),
}, "\n") }, "\n")
}, },
}) })

View File

@ -3,7 +3,7 @@
Signs Bot Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -13,13 +13,10 @@
]]-- ]]--
-- for lazy programmers -- 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 local M = minetest.get_meta
-- Load support for intllib. -- Load support for I18n.
local MP = minetest.get_modpath("signs_bot") local S = signs_bot.S
local I,_ = dofile(MP.."/intllib.lua")
local lib = signs_bot.lib local lib = signs_bot.lib
@ -90,7 +87,7 @@ signs_bot.register_botcommand("take_item", {
mod = "item", mod = "item",
params = "<num> <slot>", params = "<num> <slot>",
num_param = 2, 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"), "<slot> is the inventory slot (1..8) or 0 for any one"),
check = function(num, slot) check = function(num, slot)
num = tonumber(num) or 1 num = tonumber(num) or 1
@ -115,7 +112,7 @@ signs_bot.register_botcommand("add_item", {
mod = "item", mod = "item",
params = "<num> <slot>", params = "<num> <slot>",
num_param = 2, 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"), "<slot> is the inventory slot (1..8) or 0 for any one"),
check = function(num, slot) check = function(num, slot)
num = tonumber(num) or 1 num = tonumber(num) or 1
@ -140,7 +137,7 @@ signs_bot.register_botcommand("add_fuel", {
mod = "item", mod = "item",
params = "<num> <slot>", params = "<num> <slot>",
num_param = 2, 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"), "<slot> is the inventory slot (1..8) or 0 for any one"),
check = function(num, slot) check = function(num, slot)
num = tonumber(num) or 1 num = tonumber(num) or 1
@ -165,7 +162,7 @@ signs_bot.register_botcommand("cond_take_item", {
mod = "item", mod = "item",
params = "<num> <slot>", params = "<num> <slot>",
num_param = 2, num_param = 2,
description = I("deprecated, use bot inventory configuration instead"), description = S("deprecated, use bot inventory configuration instead"),
check = function(num, slot) check = function(num, slot)
return false return false
end, end,
@ -178,7 +175,7 @@ signs_bot.register_botcommand("cond_add_item", {
mod = "item", mod = "item",
params = "<num> <slot>", params = "<num> <slot>",
num_param = 2, num_param = 2,
description = I("deprecated, use bot inventory configuration instead"), description = S("deprecated, use bot inventory configuration instead"),
check = function(num, slot) check = function(num, slot)
return false return false
end, end,
@ -191,7 +188,7 @@ signs_bot.register_botcommand("pickup_items", {
mod = "item", mod = "item",
params = "<slot>", params = "<slot>",
num_param = 1, num_param = 1,
description = I("Pick up all objects\n".. description = S("Pick up all objects\n"..
"in a 3x3 field.\n".. "in a 3x3 field.\n"..
"<slot> is the inventory slot (1..8) or 0 for any one"), "<slot> is the inventory slot (1..8) or 0 for any one"),
check = function(slot) check = function(slot)
@ -219,7 +216,7 @@ signs_bot.register_botcommand("drop_items", {
mod = "item", mod = "item",
params = "<num> <slot>", params = "<num> <slot>",
num_param = 2, 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"), "<slot> is the inventory slot (1..8) or 0 for any one"),
check = function(num, slot) check = function(num, slot)
num = tonumber(num) or 1 num = tonumber(num) or 1
@ -246,18 +243,10 @@ signs_bot.register_botcommand("punch_cart", {
mod = "item", mod = "item",
params = "", params = "",
num_param = 0, 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) cmnd = function(base_pos, mem)
local pos = lib.dest_pos(mem.robot_pos, mem.robot_param2, {0}) local punch_dir = minetest.facedir_to_dir(mem.robot_param2)
for _, object in pairs(minetest.get_objects_inside_radius(pos, 2)) do minecart.punch_cart(mem.robot_pos, mem.robot_param2, 1, punch_dir)
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
return signs_bot.DONE return signs_bot.DONE
end, end,
}) })

View File

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

View File

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

View File

@ -3,7 +3,7 @@
Signs Bot Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -12,14 +12,8 @@
]]-- ]]--
-- for lazy programmers -- Load support for I18n.
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S = signs_bot.S
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")
local lib = signs_bot.lib local lib = signs_bot.lib
local bot_inv_take_item = signs_bot.bot_inv_take_item local bot_inv_take_item = signs_bot.bot_inv_take_item
@ -32,13 +26,23 @@ end
local tValidLevels = {[-1] = -1, [0] = 0, [1] = 1} local tValidLevels = {[-1] = -1, [0] = 0, [1] = 1}
-- for items with paramtype2 = "facedir" -- for items with paramtype2 = "facedir"
local tRotations = { local tRotations = {}
[0] = {8,20,4},
[1] = {16,20,12}, local Rotations = {
[2] = {4,20,8}, {0,8,22,4},
[3] = {12,20,16}, {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 -- 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) local pos1, p2 = lib.dest_pos(robot_pos, param2, route)
pos1.y = pos1.y + level pos1.y = pos1.y + level
if not lib.not_protected(base_pos, pos1) then 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 end
if lib.is_air_like(pos1) then if lib.is_air_like(pos1) then
local taken = signs_bot.bot_inv_take_item(base_pos, slot, 1) local taken = signs_bot.bot_inv_take_item(base_pos, slot, 1)
@ -74,7 +78,7 @@ signs_bot.register_botcommand("place_front", {
mod = "place", mod = "place",
params = "<slot> <lvl>", params = "<slot> <lvl>",
num_param = 2, 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".. "<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"), "<lvl> is one of: -1 0 +1"),
check = function(slot, lvl) check = function(slot, lvl)
@ -95,7 +99,7 @@ signs_bot.register_botcommand("place_left", {
mod = "place", mod = "place",
params = "<slot> <lvl>", params = "<slot> <lvl>",
num_param = 2, 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".. "<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"), "<lvl> is one of: -1 0 +1"),
check = function(slot, lvl) check = function(slot, lvl)
@ -116,7 +120,7 @@ signs_bot.register_botcommand("place_right", {
mod = "place", mod = "place",
params = "<slot> <lvl>", params = "<slot> <lvl>",
num_param = 2, 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".. "<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"), "<lvl> is one of: -1 0 +1"),
check = function(slot, lvl) 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 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} local pos1 = {x=robot_pos.x,y=robot_pos.y-1,z=robot_pos.z}
if not lib.not_protected(base_pos, pos1) then 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 end
local node = tubelib2.get_node_lvm(pos1) local node = tubelib2.get_node_lvm(pos1)
if node.name == "signs_bot:robot_foot" then if node.name == "signs_bot:robot_foot" then
@ -155,7 +159,7 @@ signs_bot.register_botcommand("place_below", {
mod = "place", mod = "place",
params = "<slot>", params = "<slot>",
num_param = 1, 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".. "Hint: use 'move_up' first.\n"..
"<slot> is the inventory slot (1..8)"), "<slot> is the inventory slot (1..8)"),
check = function(slot) 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 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} local pos1 = {x=robot_pos.x,y=robot_pos.y+1,z=robot_pos.z}
if not lib.not_protected(base_pos, pos1) then 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 end
if lib.is_air_like(pos1) then if lib.is_air_like(pos1) then
local taken = bot_inv_take_item(base_pos, slot, 1) local taken = bot_inv_take_item(base_pos, slot, 1)
@ -189,7 +193,7 @@ signs_bot.register_botcommand("place_above", {
mod = "place", mod = "place",
params = "<slot>", params = "<slot>",
num_param = 1, 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)"), "<slot> is the inventory slot (1..8)"),
check = function(slot) check = function(slot)
slot = tonumber(slot) or 0 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 node = tubelib2.get_node_lvm(pos1)
local dug_name = lib.is_simple_node(node) local dug_name = lib.is_simple_node(node)
if not lib.not_protected(base_pos, pos1) then 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 end
if dug_name then if dug_name then
if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then
minetest.remove_node(pos1) minetest.remove_node(pos1)
else else
return signs_bot.ERROR, I("Error: No free inventory space") return signs_bot.ERROR, S("Error: No free inventory space")
end end
end end
return signs_bot.DONE return signs_bot.DONE
@ -223,7 +227,7 @@ signs_bot.register_botcommand("dig_front", {
mod = "place", mod = "place",
params = "<slot> <lvl>", params = "<slot> <lvl>",
num_param = 2, 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".. "<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"), "<lvl> is one of: -1 0 +1"),
check = function(slot, lvl) check = function(slot, lvl)
@ -245,7 +249,7 @@ signs_bot.register_botcommand("dig_left", {
mod = "place", mod = "place",
params = "<slot> <lvl>", params = "<slot> <lvl>",
num_param = 2, 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".. "<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"), "<lvl> is one of: -1 0 +1"),
check = function(slot, lvl) check = function(slot, lvl)
@ -267,7 +271,7 @@ signs_bot.register_botcommand("dig_right", {
mod = "place", mod = "place",
params = "<slot> <lvl>", params = "<slot> <lvl>",
num_param = 2, 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".. "<slot> is the inventory slot (1..8)\n"..
"<lvl> is one of: -1 0 +1"), "<lvl> is one of: -1 0 +1"),
check = function(slot, lvl) 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 node = tubelib2.get_node_lvm(pos1)
local dug_name = lib.is_simple_node(node) local dug_name = lib.is_simple_node(node)
if not lib.not_protected(base_pos, pos1) then 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 end
if dug_name then if dug_name then
if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then
minetest.set_node(pos1, {name="signs_bot:robot_foot"}) minetest.set_node(pos1, {name="signs_bot:robot_foot"})
else else
return signs_bot.ERROR, I("Error: No free inventory space") return signs_bot.ERROR, S("Error: No free inventory space")
end end
end end
return signs_bot.DONE return signs_bot.DONE
@ -306,7 +310,7 @@ signs_bot.register_botcommand("dig_below", {
mod = "place", mod = "place",
params = "<slot>", params = "<slot>",
num_param = 1, 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)"), "<slot> is the inventory slot (1..8)"),
check = function(slot) check = function(slot)
slot = tonumber(slot) or 0 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 node = tubelib2.get_node_lvm(pos1)
local dug_name = lib.is_simple_node(node) local dug_name = lib.is_simple_node(node)
if not lib.not_protected(base_pos, pos1) then 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 end
if dug_name then if dug_name then
if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then
minetest.remove_node(pos1) minetest.remove_node(pos1)
else else
return signs_bot.ERROR, I("Error: No free inventory space") return signs_bot.ERROR, S("Error: No free inventory space")
end end
end end
return signs_bot.DONE return signs_bot.DONE
@ -340,7 +344,7 @@ signs_bot.register_botcommand("dig_above", {
mod = "place", mod = "place",
params = "<slot>", params = "<slot>",
num_param = 1, 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)"), "<slot> is the inventory slot (1..8)"),
check = function(slot) check = function(slot)
slot = tonumber(slot) or 0 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 pos1.y = pos1.y + level
local node = tubelib2.get_node_lvm(pos1) local node = tubelib2.get_node_lvm(pos1)
if not lib.not_protected(base_pos, pos1) then 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 end
if lib.is_simple_node(node) then if lib.is_simple_node(node) then
local p2 = tRotations[node.param2] and tRotations[node.param2][steps] local p2 = tRotations[node.param2] and tRotations[node.param2][steps]
@ -372,7 +376,8 @@ end
signs_bot.register_botcommand("rotate_item", { signs_bot.register_botcommand("rotate_item", {
mod = "place", mod = "place",
params = "<lvl> <steps>", 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".. "<lvl> is one of: -1 0 +1\n"..
"<steps> is one of: 1 2 3"), "<steps> is one of: 1 2 3"),
check = function(lvl, steps) 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 -- Simplified torch which can be placed w/o a fake player
minetest.register_node("signs_bot:torch", { minetest.register_node("signs_bot:torch", {
description = "Bot torch", description = S("Bot torch"),
inventory_image = "default_torch_on_floor.png", inventory_image = "default_torch_on_floor.png",
wield_image = "default_torch_on_floor.png", wield_image = "default_torch_on_floor.png",
drawtype = "nodebox", drawtype = "nodebox",
@ -428,6 +433,7 @@ minetest.register_node("signs_bot:torch", {
"group:bakedclay", "group:soil"}, "group:bakedclay", "group:soil"},
paramtype = "light", paramtype = "light",
paramtype2 = "facedir", paramtype2 = "facedir",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true, sunlight_propagates = true,
walkable = false, walkable = false,
liquids_pointable = false, liquids_pointable = false,

View File

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

View File

@ -3,7 +3,7 @@
Signs Bot Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -13,13 +13,12 @@
]]-- ]]--
-- for lazy programmers -- 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 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 MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
local ci = dofile(MP.."/interpreter.lua") local ci = dofile(MP.."/interpreter.lua")
local lib = signs_bot.lib local lib = signs_bot.lib
@ -73,7 +72,7 @@ end
function signs_bot.get_commands() function signs_bot.get_commands()
local tbl = {} local tbl = {}
for _,mod in ipairs(SortedMods) do 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 for _,cmnd in ipairs(SortedKeys[mod]) do
local item = tCommands[cmnd] local item = tCommands[cmnd]
tbl[#tbl+1] = " "..item.name.." "..item.params tbl[#tbl+1] = " "..item.name.." "..item.params
@ -90,7 +89,7 @@ function signs_bot.get_help_text(cmnd)
return item.description return item.description
end end
end end
return I("unknown command") return S("unknown command")
end end
function signs_bot.check_commands(pos, text) function signs_bot.check_commands(pos, text)
@ -154,12 +153,16 @@ local function activate_sensor(pos, param2)
end end
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 = base_pos})
minetest.sound_play('signs_bot_error', {pos = mem.robot_pos}) minetest.sound_play('signs_bot_error', {pos = mem.robot_pos})
print(err) if cmd then
signs_bot.infotext(base_pos, err) signs_bot.infotext(base_pos, err .. ":\n'" .. cmd .. "'")
mem.error = true mem.error = err .. ": '" .. cmd .. "'"
else
signs_bot.infotext(base_pos, err)
mem.error = err
end
return false return false
end end
@ -176,9 +179,9 @@ local function power_consumption(mem, cmnd)
end end
function signs_bot.run_next_command(base_pos, mem) 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 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 elseif res == ci.EXIT then
signs_bot.stop_robot(base_pos, mem) signs_bot.stop_robot(base_pos, mem)
return false return false
@ -198,31 +201,31 @@ end
signs_bot.register_botcommand("repeat", { signs_bot.register_botcommand("repeat", {
mod = "core", mod = "core",
params = "<num>", params = "<num>",
description = I("start of a 'repeat..end' block"), description = S("start of a 'repeat..end' block"),
}) })
signs_bot.register_botcommand("end", { signs_bot.register_botcommand("end", {
mod = "core", mod = "core",
params = "", params = "",
description = I("end command of a 'repeat..end' block"), description = S("end command of a 'repeat..end' block"),
}) })
signs_bot.register_botcommand("call", { signs_bot.register_botcommand("call", {
mod = "core", mod = "core",
params = "<label>", params = "<label>",
description = I("call a subroutine (with 'return' statement)"), description = S("call a subroutine (with 'return' statement)"),
}) })
signs_bot.register_botcommand("return", { signs_bot.register_botcommand("return", {
mod = "core", mod = "core",
params = "", params = "",
description = I("return from a subroutine"), description = S("return from a subroutine"),
}) })
signs_bot.register_botcommand("jump", { signs_bot.register_botcommand("jump", {
mod = "core", mod = "core",
params = "<label>", params = "<label>",
description = I("jump to a label"), description = S("jump to a label"),
}) })
local function move(mem, any_sensor) local function move(mem, any_sensor)
@ -242,7 +245,7 @@ signs_bot.register_botcommand("move", {
mod = "move", mod = "move",
params = "<steps>", params = "<steps>",
num_param = 1, 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. without paying attention to any signs.
Up and down movements also become Up and down movements also become
counted as steps.]]), counted as steps.]]),
@ -262,7 +265,7 @@ signs_bot.register_botcommand("cond_move", {
mod = "move", mod = "move",
params = "", params = "",
num_param = 0, 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. reached. Then continue with the next command.
When a sign has been reached, When a sign has been reached,
the current program is ended the current program is ended
@ -280,4 +283,22 @@ new program from the sign]]),
end, 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 Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -13,20 +13,18 @@
]]-- ]]--
-- for lazy programmers -- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta local M = minetest.get_meta
-- Load support for intllib. -- Load support for I18n.
local MP = minetest.get_modpath("signs_bot") local S = signs_bot.S
local I,_ = dofile(MP.."/intllib.lua")
local lib = signs_bot.lib local lib = signs_bot.lib
local CYCLE_TIME = 4 local CYCLE_TIME = 4
local function update_infotext(pos, dest_pos, dest_idx) 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 end
local function swap_node(pos, name) local function swap_node(pos, name)
@ -56,7 +54,7 @@ local function node_timer(pos)
end end
minetest.register_node("signs_bot:crop_sensor", { minetest.register_node("signs_bot:crop_sensor", {
description = I("Crop Sensor"), description = S("Crop Sensor"),
inventory_image = "signs_bot_sensor_crop_inv.png", inventory_image = "signs_bot_sensor_crop_inv.png",
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
@ -77,7 +75,7 @@ minetest.register_node("signs_bot:crop_sensor", {
after_place_node = function(pos, placer) after_place_node = function(pos, placer)
local meta = M(pos) 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) minetest.get_node_timer(pos):start(CYCLE_TIME)
local node = minetest.get_node(pos) local node = minetest.get_node(pos)
meta:set_int("param2", (node.param2 + 2) % 4) meta:set_int("param2", (node.param2 + 2) % 4)
@ -87,6 +85,7 @@ minetest.register_node("signs_bot:crop_sensor", {
update_infotext = update_infotext, update_infotext = update_infotext,
on_rotate = screwdriver.disallow, on_rotate = screwdriver.disallow,
paramtype = "light", paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
is_ground_content = false, is_ground_content = false,
@ -95,7 +94,7 @@ minetest.register_node("signs_bot:crop_sensor", {
}) })
minetest.register_node("signs_bot:crop_sensor_on", { minetest.register_node("signs_bot:crop_sensor_on", {
description = I("Crop Sensor"), description = S("Crop Sensor"),
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
type = "fixed", type = "fixed",
@ -117,6 +116,7 @@ minetest.register_node("signs_bot:crop_sensor_on", {
update_infotext = update_infotext, update_infotext = update_infotext,
on_rotate = screwdriver.disallow, on_rotate = screwdriver.disallow,
paramtype = "light", paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
is_ground_content = false, is_ground_content = false,
@ -150,13 +150,13 @@ minetest.register_lbm({
if minetest.get_modpath("doc") then if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "crop_sensor", { doc.add_entry("signs_bot", "crop_sensor", {
name = I("Crop Sensor"), name = S("Crop Sensor"),
data = { data = {
item = "signs_bot:crop_sensor", item = "signs_bot:crop_sensor",
text = table.concat({ text = table.concat({
I("The Crop Sensor sends cyclical signals when, for example, wheat is fully grown."), S("The Crop Sensor sends cyclical signals when, for example, wheat is fully grown."),
I("The sensor range is one node/meter."), S("The sensor range is one node/meter."),
I("The sensor has an active side (red) that must point to the crop/field."), S("The sensor has an active side (red) that must point to the crop/field."),
}, "\n") }, "\n")
}, },

View File

@ -3,7 +3,7 @@
Signs Bot Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -13,20 +13,17 @@
]]-- ]]--
-- for lazy programmers -- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos local S2P = minetest.string_to_pos
local M = minetest.get_meta local M = minetest.get_meta
-- Load support for intllib. -- Load support for I18n.
local MP = minetest.get_modpath("signs_bot") local S = signs_bot.S
local I,_ = dofile(MP.."/intllib.lua")
local CYCLE_TIME = 2 local CYCLE_TIME = 2
local lib = signs_bot.lib
local function update_infotext(pos, dest_pos, cmnd) 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 end
local function infotext(pos) local function infotext(pos)
@ -34,19 +31,19 @@ local function infotext(pos)
local dest_pos = meta:get_string("signal_pos") local dest_pos = meta:get_string("signal_pos")
local signal = meta:get_string("signal_data") local signal = meta:get_string("signal_data")
if dest_pos ~= "" and signal ~= "" then if dest_pos ~= "" and signal ~= "" then
update_infotext(pos, P(dest_pos), signal) update_infotext(pos, S2P(dest_pos), signal)
end end
end end
local function formspec(meta) 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")) local value = minetest.formspec_escape(meta:get_int("time"))
return "size[4,3]".. return "size[4,3]"..
default.gui_bg.. default.gui_bg..
default.gui_bg_img.. default.gui_bg_img..
default.gui_slots.. default.gui_slots..
"field[0.3,1;4,1;time;"..label..";"..value.."]".. "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 end
-- Used by the pairing tool -- Used by the pairing tool
@ -141,7 +138,7 @@ local function on_receive_fields(pos, formname, fields, player)
end end
minetest.register_node("signs_bot:delayer", { minetest.register_node("signs_bot:delayer", {
description = I("Signal Delayer"), description = S("Signal Delayer"),
inventory_image = "signs_bot_delayer_inv.png", inventory_image = "signs_bot_delayer_inv.png",
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
@ -172,6 +169,7 @@ minetest.register_node("signs_bot:delayer", {
update_infotext = update_infotext, update_infotext = update_infotext,
on_rotate = screwdriver.disallow, on_rotate = screwdriver.disallow,
paramtype = "light", paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
is_ground_content = false, is_ground_content = false,
@ -180,7 +178,7 @@ minetest.register_node("signs_bot:delayer", {
}) })
minetest.register_node("signs_bot:delayer_loaded", { minetest.register_node("signs_bot:delayer_loaded", {
description = I("Signal Delayer"), description = S("Signal Delayer"),
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
type = "fixed", type = "fixed",
@ -203,6 +201,7 @@ minetest.register_node("signs_bot:delayer_loaded", {
paramtype = "light", paramtype = "light",
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
use_texture_alpha = signs_bot.CLIP,
is_ground_content = false, is_ground_content = false,
diggable = false, diggable = false,
groups = {sign_bot_sensor = 1, not_in_creative_inventory = 1}, 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", { minetest.register_node("signs_bot:delayer_on", {
description = I("Signal Delayer"), description = S("Signal Delayer"),
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
type = "fixed", type = "fixed",
@ -230,6 +229,7 @@ minetest.register_node("signs_bot:delayer_on", {
paramtype = "light", paramtype = "light",
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
use_texture_alpha = signs_bot.CLIP,
is_ground_content = false, is_ground_content = false,
diggable = false, diggable = false,
groups = {sign_bot_sensor = 1, not_in_creative_inventory = 1}, groups = {sign_bot_sensor = 1, not_in_creative_inventory = 1},
@ -261,12 +261,12 @@ minetest.register_lbm({
if minetest.get_modpath("doc") then if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "delayer", { doc.add_entry("signs_bot", "delayer", {
name = I("Signal Delayer"), name = S("Signal Delayer"),
data = { data = {
item = "signs_bot:delayer", item = "signs_bot:delayer",
text = table.concat({ text = table.concat({
I("Signals are forwarded delayed. Subsequent signals are queued."), S("Signals are forwarded delayed. Subsequent signals are queued."),
I("The delay time can be configured."), S("The delay time can be configured."),
}, "\n") }, "\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 = {} signs_bot.doc = {}
if not minetest.get_modpath("doc") then if not minetest.get_modpath("doc") then
return return
end end
-- Load support for intllib.
local MP = minetest.get_modpath("signs_bot")
local I,_ = dofile(MP.."/intllib.lua")
local function formspec(data) local function formspec(data)
if data.image then if data.image then
local image = "image["..(doc.FORMSPEC.ENTRY_WIDTH - 3)..",0;3,2;"..data.image.."]" local image = "image["..(doc.FORMSPEC.ENTRY_WIDTH - 3)..",0;3,2;"..data.image.."]"
@ -24,112 +37,113 @@ local function formspec(data)
end end
local start_doc = table.concat({ 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."), S("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.)"), S("If the bot returns to its box right away, you will likely need to charge it with electrical energy (techage) first."),
I("If the bot first reaches a sign it will execute the commands on the sign."), 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.)"),
I("If the command(s) on the sign is e.g. 'turn_around', the bot turns and goes back."), S("If the bot first reaches a sign it will execute the commands on the sign."),
I("In this case, the bot reaches his box again and turns off."), 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)."), 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)."),
I("This inventory simulates the bot internal inventory."), S("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("That means you will only have access to the inventory if the bot is turned off ('sitting' in his box)."),
}, "\n") }, "\n")
local control_doc = table.concat({ 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)."), S("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("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."), S("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."), S("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."), S("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."), S("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("The bot will walk over."),
"", "",
I("All predefined signs have a menu with a list of the bot commands."), S("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."), S("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."), S("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."), S("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("The help page has a copy button to simplify the programming."),
"", "",
I("Also for your own signs it is important to know:"), S("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("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"), S("A standard job for the bot is to move items from one chest to another"),
I("(chest or node with a chest like inventory)."), S("(chest or node with a chest like inventory)."),
I("This can be done by means of the two signs 'take item' and 'add item'."), S("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("These signs have to be placed on top of chest nodes."),
}, "\n") }, "\n")
local sensor_doc = table.concat({ local sensor_doc = table.concat({
I("In addition to the signs the bot can be controlled by means of sensors."), S("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."), S("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"), S("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("sends a signal to a connected block, called an actuator."),
"", "",
I("Sensors are:"), S("Sensors are:"),
I("- Bot Sensor: Sends a signal when the robot passes by"), S("- Bot Sensor: Sends a signal when the robot passes by"),
I("- Node Sensor: Sends a signal when it detects any node"), S("- Node Sensor: Sends a signal when it detects any node"),
I("- Crop Sensor: Sends a signal when, for example wheat is fully grown"), S("- 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("- Bot Chest: Sends a signal depending on the chest state (empty, full)"),
"", "",
I("Actuators are:"), S("Actuators are:"),
I("- Signs Bot Box: Can turn the bot off and on"), S("- 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("- 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") }, "\n")
local tool_doc = table.concat({ 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."), S("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."), S("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."), S("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("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."), S("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,"), S("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,"), S("when the Bot is in the state 'on'. Otherwise the sensor signal will stop the Bot,"),
I("instead of starting it."), S("instead of starting it."),
}, "\n") }, "\n")
local inventory_doc = table.concat({ 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>"), S("- take_item <num> <slot>"),
I("- pickup_items <slot>"), S("- pickup_items <slot>"),
I("- trash_sign <slot>"), S("- trash_sign <slot>"),
I("- harvest <slot>"), S("- harvest <slot>"),
I("- dig_front <slot> <lvl>"), S("- dig_front <slot> <lvl>"),
I("- dig_left <slot> <lvl>"), S("- dig_left <slot> <lvl>"),
I("- dig_right <slot> <lvl>"), S("- dig_right <slot> <lvl>"),
I("- dig_below <slot> <lvl>"), S("- dig_below <slot> <lvl>"),
I("- dig_above <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 "), S("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."), S("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, "), S("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 "), S("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."), S("(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."), S("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("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 "), S("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."), S("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 "), S("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."), S("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("If the number found is smaller than requested, he tries to take the rest out of any slot."),
}, "\n") }, "\n")
doc.add_category("signs_bot", doc.add_category("signs_bot",
{ {
name = I("Signs Bot"), name = S("Signs Bot"),
description = I("A robot controlled by signs, used for automated work"), description = S("A robot controlled by signs, used for automated work"),
sorting = "custom", sorting = "custom",
sorting_data = {"start", "control", "sensor_doc", "tool", sorting_data = {"start", "control", "sensor_doc", "tool",
"box", "bot_flap", "duplicator", "box", "bot_flap", "duplicator",
@ -141,26 +155,26 @@ doc.add_category("signs_bot",
}) })
doc.add_entry("signs_bot", "start", { 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"}, data = {text = start_doc, image = "signs_bot_doc_image.png"},
}) })
doc.add_entry("signs_bot", "control", { 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"}, data = {text = control_doc, image = "signs_bot_doc_image.png"},
}) })
doc.add_entry("signs_bot", "sensor_doc", { 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"}, data = {text = sensor_doc, image = "signs_bot_doc_image.png"},
}) })
doc.add_entry("signs_bot", "tool", { 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"}, data = {text = tool_doc, image = "signs_bot_doc_image.png"},
}) })
doc.add_entry("signs_bot", "tool", { 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"}, data = {text = inventory_doc, image = "signs_bot_doc_image.png"},
}) })

View File

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

View File

@ -3,7 +3,7 @@
Signs Bot Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -13,22 +13,18 @@
]]-- ]]--
-- for lazy programmers -- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos
local M = minetest.get_meta local M = minetest.get_meta
-- Load support for intllib. -- Load support for I18n.
local MP = minetest.get_modpath("signs_bot") local S = signs_bot.S
local I,_ = dofile(MP.."/intllib.lua")
local lib = signs_bot.lib
local function update_infotext(pos, dest_pos, cmnd) 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 end
minetest.register_node("signs_bot:sensor_extender", { minetest.register_node("signs_bot:sensor_extender", {
description = I("Sensor Extender"), description = S("Sensor Extender"),
inventory_image = "signs_bot_extender_inv.png", inventory_image = "signs_bot_extender_inv.png",
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
@ -54,12 +50,13 @@ minetest.register_node("signs_bot:sensor_extender", {
after_place_node = function(pos, placer) after_place_node = function(pos, placer)
local meta = M(pos) local meta = M(pos)
meta:set_string("infotext", I("Sensor Extender: Not connected")) meta:set_string("infotext", S("Sensor Extender: Not connected"))
end, end,
update_infotext = update_infotext, update_infotext = update_infotext,
on_rotate = screwdriver.disallow, on_rotate = screwdriver.disallow,
paramtype = "light", paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
is_ground_content = false, is_ground_content = false,
@ -68,7 +65,7 @@ minetest.register_node("signs_bot:sensor_extender", {
}) })
minetest.register_node("signs_bot:sensor_extender_on", { minetest.register_node("signs_bot:sensor_extender_on", {
description = I("Sensor Extender"), description = S("Sensor Extender"),
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
type = "connected", type = "connected",
@ -108,6 +105,7 @@ minetest.register_node("signs_bot:sensor_extender_on", {
update_infotext = update_infotext, update_infotext = update_infotext,
on_rotate = screwdriver.disallow, on_rotate = screwdriver.disallow,
paramtype = "light", paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
is_ground_content = false, is_ground_content = false,
@ -127,13 +125,13 @@ minetest.register_craft({
if minetest.get_modpath("doc") then if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "sensor_extender", { doc.add_entry("signs_bot", "sensor_extender", {
name = I("Sensor Extender"), name = S("Sensor Extender"),
data = { data = {
item = "signs_bot:sensor_extender", item = "signs_bot:sensor_extender",
text = table.concat({ text = table.concat({
I("With the Sensor Extender, sensor signals can be sent to more than one actuator."), S("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"), S("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 one further actuator by means of the Connection Tool."),
}, "\n") }, "\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 Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -15,7 +15,10 @@
signs_bot = {} signs_bot = {}
-- Version for compatibility checks, see readme.md/history -- 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 if minetest.global_exists("techage") and techage.version < 0.25 then
error("[signs_bot] Signs Bot requires techage version 0.25 or newer!") 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!") error("[signs_bot] Signs Bot requires tubelib2 version 1.9 or newer!")
end 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") signs_bot.S = minetest.get_translator("signs_bot")
local MP = minetest.get_modpath("signs_bot") local MP = minetest.get_modpath("signs_bot")
dofile(MP.."/doc.lua") dofile(MP.."/doc.lua")
dofile(MP.."/random.lua") dofile(MP.."/random.lua")
dofile(MP.."/lib.lua") dofile(MP.."/lib.lua")
@ -60,5 +70,6 @@ dofile(MP.."/techage.lua")
dofile(MP.."/timer.lua") dofile(MP.."/timer.lua")
dofile(MP.."/delayer.lua") dofile(MP.."/delayer.lua")
dofile(MP.."/logic_and.lua") dofile(MP.."/logic_and.lua")
dofile(MP.."/compost.lua")
dofile(MP.."/tool.lua") dofile(MP.."/tool.lua")

View File

@ -3,7 +3,7 @@
Signs Bot Signs Bot
========= =========
Copyright (C) 2019-2020 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -12,9 +12,8 @@
]]-- ]]--
-- Load support for intllib. -- Load support for I18n.
local MP = minetest.get_modpath("signs_bot") local S = signs_bot.S
local I,_ = dofile(MP.."/intllib.lua")
local MAX_SIZE = 1000 -- max number of tokens local MAX_SIZE = 1000 -- max number of tokens
@ -128,6 +127,19 @@ local function compile(script)
return pass2(tokens) return pass2(tokens)
end 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 -- Commands
------------------------------------------------------------------------------- -------------------------------------------------------------------------------
@ -235,29 +247,29 @@ function api.check_script(script)
if tCmdDef[cmnd] then if tCmdDef[cmnd] then
num_token = num_token + 1 + tCmdDef[cmnd].num_param num_token = num_token + 1 + tCmdDef[cmnd].num_param
if num_token > MAX_SIZE then if num_token > MAX_SIZE then
return false, I("Maximum programm size exceeded"), idx return false, S("Maximum programm size exceeded"), idx
end end
param1 = tonumber(param1) or param1 param1 = tonumber(param1) or param1
param2 = tonumber(param2) or param2 param2 = tonumber(param2) or param2
param3 = tonumber(param3) or param3 param3 = tonumber(param3) or param3
local num_param = (param1 and 1 or 0) + (param2 and 1 or 0) + (param3 and 1 or 0) 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 if tCmdDef[cmnd].num_param < num_param then
return false, I("Too many parameters"), idx return false, S("Too many parameters"), idx
end end
if tCmdDef[cmnd].num_param > 0 and not tCmdDef[cmnd].check(param1, param2, param3) then 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 end
elseif not cmnd:find("%w+:") then elseif not cmnd:find("%w+:") then
return false, I("Command error"), idx return false, S("Command error"), idx
end end
tbl[cmnd] = (tbl[cmnd] or 0) + 1 tbl[cmnd] = (tbl[cmnd] or 0) + 1
end end
if (tbl["end"] or 0) > (tbl["repeat"] or 0) then 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 elseif (tbl["end"] or 0) < (tbl["repeat"] or 0) then
return false, I("'end' missing"), 0 return false, S("'end' missing"), 0
end end
return true, I("Checked and approved"), 0 return true, S("Checked and approved"), 0
end end
-- function returns: true/false, error-string -- function returns: true/false, error-string
@ -281,7 +293,7 @@ function api.run_script(base_pos, mem)
mem.pc = 1 mem.pc = 1
mem.Stack = {} mem.Stack = {}
end end
return res, err return res, err, gen_string_cmnd(code, mem.pc, num_param, mem.script)
end end
return api.EXIT return api.EXIT
end 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 #!/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 Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -13,8 +13,6 @@
]]-- ]]--
-- for lazy programmers -- 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 local M = minetest.get_meta
signs_bot.register_inventory({"default:chest", "default:chest_open"}, { 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 Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -13,8 +13,6 @@
]]-- ]]--
-- for lazy programmers -- 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 local M = minetest.get_meta
signs_bot.lib = {} signs_bot.lib = {}
@ -90,6 +88,13 @@ local function handle_drop(drop)
end end
end end
return name 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 end
return false return false
end 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 "") smeta:set_string("err_msg", oldmetadata.fields.err_msg or "")
end end
local player_name = digger:get_player_name() local player_name = digger:get_player_name()
-- See https://github.com/minetest/minetest/blob/34e3ede8eeb05e193e64ba3d055fc67959d87d86/doc/lua_api.txt#L6222
if player_name == "" then if player_name == "" then
minetest.add_item(pos, sign) minetest.add_item(pos, sign)
else 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 Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information See LICENSE.txt for more information
@ -13,13 +13,12 @@
]]-- ]]--
-- for lazy programmers -- for lazy programmers
local S = function(pos) if pos then return minetest.pos_to_string(pos) end end local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local P = minetest.string_to_pos local S2P = minetest.string_to_pos
local M = minetest.get_meta local M = minetest.get_meta
-- Load support for intllib. -- Load support for I18n.
local MP = minetest.get_modpath("signs_bot") local S = signs_bot.S
local I,_ = dofile(MP.."/intllib.lua")
local lib = signs_bot.lib local lib = signs_bot.lib
@ -38,12 +37,12 @@ end
local function update_infotext(pos, dest_pos, cmnd) local function update_infotext(pos, dest_pos, cmnd)
local mem = tubelib2.get_mem(pos) local mem = tubelib2.get_mem(pos)
local text = table.concat({ local text = table.concat({
I("Signal AND with"), S("Signal AND with"),
#mem.inputs or 0, #mem.inputs or 0,
I("inputs"), S("inputs"),
":", ":",
I("Connected with"), S("Connected with"),
S(dest_pos), P2S(dest_pos),
"/", "/",
cmnd, cmnd,
":", ":",
@ -80,7 +79,7 @@ local function infotext(pos)
local dest_pos = meta:get_string("signal_pos") local dest_pos = meta:get_string("signal_pos")
local signal = meta:get_string("signal_data") local signal = meta:get_string("signal_data")
if dest_pos ~= "" and signal ~= "" then if dest_pos ~= "" and signal ~= "" then
update_infotext(pos, P(dest_pos), signal) update_infotext(pos, S2P(dest_pos), signal)
end end
end end
@ -143,7 +142,7 @@ local function signs_bot_on_signal(pos, node, signal)
end end
minetest.register_node("signs_bot:and1", { minetest.register_node("signs_bot:and1", {
description = I("Signal AND"), description = S("Signal AND"),
inventory_image = "signs_bot_and_inv.png", inventory_image = "signs_bot_and_inv.png",
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
@ -170,6 +169,7 @@ minetest.register_node("signs_bot:and1", {
update_infotext = update_infotext, update_infotext = update_infotext,
on_rotate = screwdriver.disallow, on_rotate = screwdriver.disallow,
paramtype = "light", paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
is_ground_content = false, is_ground_content = false,
@ -178,7 +178,7 @@ minetest.register_node("signs_bot:and1", {
}) })
minetest.register_node("signs_bot:and2", { minetest.register_node("signs_bot:and2", {
description = I("Signal AND"), description = S("Signal AND"),
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
type = "fixed", type = "fixed",
@ -197,6 +197,7 @@ minetest.register_node("signs_bot:and2", {
update_infotext = update_infotext, update_infotext = update_infotext,
on_rotate = screwdriver.disallow, on_rotate = screwdriver.disallow,
paramtype = "light", paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
is_ground_content = false, is_ground_content = false,
@ -206,7 +207,7 @@ minetest.register_node("signs_bot:and2", {
}) })
minetest.register_node("signs_bot:and3", { minetest.register_node("signs_bot:and3", {
description = I("Signal AND"), description = S("Signal AND"),
drawtype = "nodebox", drawtype = "nodebox",
node_box = { node_box = {
type = "fixed", type = "fixed",
@ -223,6 +224,7 @@ minetest.register_node("signs_bot:and3", {
update_infotext = update_infotext, update_infotext = update_infotext,
on_rotate = screwdriver.disallow, on_rotate = screwdriver.disallow,
paramtype = "light", paramtype = "light",
use_texture_alpha = signs_bot.CLIP,
sunlight_propagates = true, sunlight_propagates = true,
paramtype2 = "facedir", paramtype2 = "facedir",
is_ground_content = false, is_ground_content = false,
@ -242,11 +244,11 @@ minetest.register_craft({
if minetest.get_modpath("doc") then if minetest.get_modpath("doc") then
doc.add_entry("signs_bot", "and", { doc.add_entry("signs_bot", "and", {
name = I("Signal AND"), name = S("Signal AND"),
data = { data = {
item = "signs_bot:and1", item = "signs_bot:and1",
text = table.concat({ text = table.concat({
I("Signal is sent, if all input signals are received."), S("Signal is sent, if all input signals are received."),
}, "\n") }, "\n")
}, },
}) })

View File

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

View File

@ -3,7 +3,7 @@
Signs Bot Signs Bot
========= =========
Copyright (C) 2019 Joachim Stolberg Copyright (C) 2019-2021 Joachim Stolberg
GPL v3 GPL v3
See LICENSE.txt for more information 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:cocoa_beans", "farming:cocoa_1", "farming:cocoa_4")
fp("farming:garlic_clove", "farming:garlic_1", "farming:garlic_5") fp("farming:garlic_clove", "farming:garlic_1", "farming:garlic_5")
fp("farming:onion", "farming:onion_1", "farming:onion_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:peppercorn", "farming:pepper_1", "farming:pepper_5")
fp("farming:pineapple_top", "farming:pineapple_1", "farming:pineapple_8") fp("farming:pineapple_top", "farming:pineapple_1", "farming:pineapple_8")
end end

View File

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

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