diff --git a/autobahn/init.lua b/autobahn/init.lua index 5083365..a55b0ad 100644 --- a/autobahn/init.lua +++ b/autobahn/init.lua @@ -184,7 +184,9 @@ local function update_node(pos) nnode = minetest.get_node(npos) if NodeTbl1[nnode.name] and NodeTbl3[node.name] then node.name = node.name .. "1" - minetest.swap_node(pos, node) + if minetest.registered_nodes[node.name] then + minetest.swap_node(pos, node) + end return end -- check case 2 @@ -192,7 +194,9 @@ local function update_node(pos) nnode = minetest.get_node(npos) if NodeTbl2[nnode.name] then node.name = string.sub(node.name,1,-1) .. "2" - minetest.swap_node(pos, node) + if minetest.registered_nodes[node.name] then + minetest.swap_node(pos, node) + end return end -- check case 3 @@ -203,7 +207,9 @@ local function update_node(pos) if NodeTbl1[nnode.name] and NodeTbl3[node.name] then node.name = node.name .. "1" node.param2 = 3 - minetest.swap_node(pos, node) + if minetest.registered_nodes[node.name] then + minetest.swap_node(pos, node) + end return end -- check case 4 @@ -212,7 +218,9 @@ local function update_node(pos) if NodeTbl2[nnode.name] then node.name = string.sub(node.name,1,-1) .. "2" node.param2 = 3 - minetest.swap_node(pos, node) + if minetest.registered_nodes[node.name] then + minetest.swap_node(pos, node) + end return end end diff --git a/basic_materials/.gitlab-ci.yml b/basic_materials/.gitlab-ci.yml new file mode 100644 index 0000000..ff51e7e --- /dev/null +++ b/basic_materials/.gitlab-ci.yml @@ -0,0 +1,8 @@ +stages: + - test + +luacheck: + stage: test + image: pipelinecomponents/luacheck:latest + script: + - luacheck . diff --git a/compost/init.lua b/compost/init.lua index 561e1c9..b2b6395 100644 --- a/compost/init.lua +++ b/compost/init.lua @@ -2,7 +2,7 @@ local S = minetest.get_translator("compost") compost = {} -local CYCLE_TIME = 10 +local CYCLE_TIME = 30 -- Version for compatibility checks compost.version = 1.0 diff --git a/hyperloop/README.md b/hyperloop/README.md index fc20f34..f1cd423 100644 --- a/hyperloop/README.md +++ b/hyperloop/README.md @@ -11,8 +11,7 @@ It is the fast and modern way of travelling. * It can be used even on small servers without lagging * No configuration or programming of the tube network is necessary (only the station names have to be entered) - -**![See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)** +**[See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)** ![screenshot](https://github.com/joe7575/Minetest-Hyperloop/blob/master/screenshot.png) @@ -33,9 +32,9 @@ The mod includes many different kind of blocks: ..and more. -Browse on: ![GitHub](https://github.com/joe7575/Minetest-Hyperloop) +Browse on: [GitHub](https://github.com/joe7575/Minetest-Hyperloop) -Download: ![GitHub](https://github.com/joe7575/Minetest-Hyperloop/archive/master.zip) +Download: [GitHub](https://github.com/joe7575/Minetest-Hyperloop/archive/master.zip) ## Migration from v1 to v2 @@ -59,7 +58,7 @@ has some risks. Therefore: ## Introduction -**![See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)** +**[See Wiki Page for more info](https://github.com/joe7575/Minetest-Hyperloop/wiki)** ## Configuration @@ -68,23 +67,25 @@ The following can be changed in the minetest menu (Settings -> Advanced Settings * "WiFi block crafting enabled" - To enable the crafting of WiFi blocks (default: false) * "free tube placement enabled" - If enabled Hyperloop Tubes and Elevator Shafts can be build in all directions (default: true) When this option is disabled, Hyperloop tubes can only be built in the horizontal direction and elevator shafts in the vertical direction. +* "enable building of subnets" - If enabled the ticket block has an additional field for specifying a subnet name. Stations with the same subnet name (optional) represent an isolated subnet within the Hyperloop network. Example for 'minetest.conf': ```LUA -hyperloop_wifi_enabled = true -hyperloop_wifi_crafting_enabled = false -hyperloop_free_tube_placement_enabled = true +hyperloop_wifi_enabled = true -- WiFi block enabled +hyperloop_wifi_crafting_enabled = false -- WiFi block crafting enabled +hyperloop_free_tube_placement_enabled = true -- free tube placement enabled +hyperloop_subnet_enabled = true -- enable building of subnets ``` ## Dependencies -tubelib2 (![GitHub](https://github.com/joe7575/tubelib2)) +tubelib2 ([GitHub](https://github.com/joe7575/tubelib2)) default intllib optional: worldedit, techage # License -Copyright (C) 2017,2020 Joachim Stolberg +Copyright (C) 2017,2021 Joachim Stolberg Code: Licensed under the GNU LGPL version 2.1 or later. See LICENSE.txt and http://www.gnu.org/licenses/lgpl-2.1.txt Textures: CC0 Display: Derived from the work of kaeza, sofar and others (digilines) LGPLv2.1+ diff --git a/hyperloop/booking_node.lua b/hyperloop/booking_node.lua index 494d4cb..4b4809d 100644 --- a/hyperloop/booking_node.lua +++ b/hyperloop/booking_node.lua @@ -75,35 +75,72 @@ local function remove_junctions(sortedList) return tbl end -local function station_list_as_string(pos) - -- Generate a distance sorted list of all connected stations - local sortedList = Stations:station_list(pos, pos, "dist") - -- Delete the own station from list - table.remove(sortedList, 1) +local function filter_subnet(sortedList, subnet) + if hyperloop.subnet_enabled then + if subnet == "" then + subnet = nil + end + + local tbl = {} + for idx,item in ipairs(sortedList) do + if item.subnet == subnet then + tbl[#tbl+1] = item + end + end + return tbl + end + return sortedList +end + +-- Used to update the station list for booking machine +-- and teleport list. +local function station_list_as_string(pos, subnet) + local meta = M(pos) + -- Generate a name sorted list of all connected stations + local sortedList = Stations:station_list(pos, pos, "name") -- remove all junctions from the list sortedList = remove_junctions(sortedList) + -- use subnet pattern to reduce the list + sortedList = filter_subnet(sortedList, subnet) -- store the list for later use store_station_list(pos, sortedList) -- Generate the formspec string return generate_string(sortedList) end +local naming_formspec = nil -local function naming_formspec(pos) - local meta = minetest.get_meta(pos) - local formspec = "size[6,4]".. - default.gui_bg.. - default.gui_bg_img.. - default.gui_slots.. - "label[0,0;"..S("Please enter the station name to\nwhich this booking machine belongs.").."]" .. - "field[0.5,1.5;5,1;name;"..S("Station name")..";MyTown]" .. - "field[0.5,2.7;5,1;info;"..S("Additional station information")..";]" .. - "button_exit[2,3.6;2,1;exit;Save]" - meta:set_string("formspec", formspec) - meta:set_int("change_counter", 0) +if hyperloop.subnet_enabled then + naming_formspec = function(pos) + local meta = M(pos) + local formspec = "size[7,5.4]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "label[0,0;"..S("Please enter the station name to\nwhich this booking machine belongs.").."]" .. + "field[0.2,1.5;7.1,1;name;"..S("Station name")..";MyTown]" .. + "field[0.2,2.7;7.1,1;info;"..S("Additional station information")..";]" .. + "field[0.2,3.9;7.1,1;subnet;"..S("Subnet name (optional)")..";]" .. + "button_exit[2.5,4.7;2,1;exit;Save]" + meta:set_string("formspec", formspec) + meta:set_int("change_counter", 0) + end +else + naming_formspec = function(pos) + local meta = M(pos) + local formspec = "size[7,4.4]".. + default.gui_bg.. + default.gui_bg_img.. + default.gui_slots.. + "label[0,0;"..S("Please enter the station name to\nwhich this booking machine belongs.").."]" .. + "field[0.2,1.5;7.1,1;name;"..S("Station name")..";MyTown]" .. + "field[0.2,2.7;7.1,1;info;"..S("Additional station information")..";]" .. + "button_exit[2.5,3.7;2,1;exit;Save]" + meta:set_string("formspec", formspec) + meta:set_int("change_counter", 0) + end end - local function booking_machine_update(pos) local meta = M(pos) local sStationPos = meta:get_string("sStationPos") @@ -111,25 +148,19 @@ local function booking_machine_update(pos) local station_pos = P(sStationPos) local counter = meta:get_int("change_counter") or 0 local changed, newcounter = Stations:changed(counter) - if changed then - meta:set_string("formspec", station_list_as_string(station_pos)) + if changed or not tStationList[sStationPos] then + local subnet = meta:get_string("subnet") + meta:set_string("formspec", station_list_as_string(station_pos, subnet)) meta:set_int("change_counter", newcounter) end - if not tStationList[sStationPos] then - local sortedList = Stations:station_list(station_pos, station_pos, "dist") - -- Delete the own station from list - table.remove(sortedList, 1) - -- remove all junctions from the list - sortedList = remove_junctions(sortedList) - -- store the list for later use - store_station_list(station_pos, sortedList) - end end end +local function on_rightclick(pos) + booking_machine_update(pos) +end local function on_receive_fields(pos, formname, fields, player) - booking_machine_update(pos) -- station name entered? if fields.name ~= nil then local station_name = string.trim(fields.name) @@ -142,17 +173,25 @@ local function on_receive_fields(pos, formname, fields, player) hyperloop.chat(player, S("Station has already a booking machine!")) return end + -- add subnet name if available + local subnet = string.trim(fields.subnet or "") + if subnet == "" then + subnet = nil + end -- store meta and generate station formspec Stations:update(stationPos, { name = station_name, booking_pos = pos, booking_info = string.trim(fields.info), + subnet = subnet, }) local meta = M(pos) meta:set_string("sStationPos", SP(stationPos)) meta:set_string("infotext", "Station: "..station_name) - meta:set_string("formspec", station_list_as_string(stationPos)) + meta:set_string("subnet", string.trim(fields.subnet or "")) + meta:set_int("change_counter", 0) -- force update + booking_machine_update(pos) else hyperloop.chat(player, S("Invalid station name!")) end @@ -233,6 +272,7 @@ minetest.register_node("hyperloop:booking", { on_rotate = screwdriver.disallow, on_receive_fields = on_receive_fields, on_destruct = on_destruct, + on_rightclick = on_rightclick, paramtype = 'light', light_source = 2, @@ -279,10 +319,4 @@ minetest.register_node("hyperloop:booking_ground", { }) -minetest.register_lbm({ - label = "[Hyperloop] Booking machine update", - name = "hyperloop:update", - nodenames = {"hyperloop:booking", "hyperloop:booking_ground"}, - run_at_every_load = true, - action = booking_machine_update -}) + diff --git a/hyperloop/init.lua b/hyperloop/init.lua index 2d4cff9..8798e07 100644 --- a/hyperloop/init.lua +++ b/hyperloop/init.lua @@ -5,7 +5,7 @@ v2.06 by JoSt - Copyright (C) 2017-2019 Joachim Stolberg + Copyright (C) 2017-2021 Joachim Stolberg LGPLv2.1+ See LICENSE.txt for more information @@ -34,6 +34,7 @@ 2020-01-03 v2.04 Elevator door bugfix (MT 5+) 2020-03-12 v2.05 minetest translator added (thanks to acmgit/Clyde) 2020-06-14 v2.06 The default value for `hyperloop_free_tube_placement_enabled` is now true + 2021-02-07 v2.07 tube_crowbar: Add tube length check ]]-- @@ -65,7 +66,8 @@ else hyperloop.wifi_enabled = minetest.settings:get_bool("hyperloop_wifi_enabled") hyperloop.wifi_crafting_enabled = minetest.settings:get_bool("hyperloop_wifi_crafting_enabled") hyperloop.free_tube_placement_enabled = minetest.settings:get_bool("hyperloop_free_tube_placement_enabled", true) - + hyperloop.subnet_enabled = minetest.settings:get_bool("hyperloop_subnet_enabled") + dofile(minetest.get_modpath("hyperloop") .. "/network.lua") dofile(minetest.get_modpath("hyperloop") .. "/data_base.lua") dofile(minetest.get_modpath("hyperloop") .. "/booking.lua") diff --git a/hyperloop/network.lua b/hyperloop/network.lua index ddb8fa8..ea9611f 100644 --- a/hyperloop/network.lua +++ b/hyperloop/network.lua @@ -96,6 +96,15 @@ local function sort_based_on_distance(tStations, pos) return lStations end +-- Return a list with sorted stations +local function sort_based_on_name(tStations, pos) + local lStations = table_to_list(table.copy(tStations)) + -- Add distance + lStations = add_distance_to_list(lStations, pos) + table.sort(lStations, function(a,b) return a.name < b.name end) + return lStations +end + -- -- Class Network @@ -233,8 +242,12 @@ function Network:station_list(pos, station_pos, sorted) end if sorted == "dist" then lStations = sort_based_on_distance(tStations, pos) - else + elseif sorted == "level" then lStations = sort_based_on_level(tStations) + else + -- delete own station from list + tStations[S(station_pos)] = nil + lStations = sort_based_on_name(tStations, pos) end return lStations end @@ -263,4 +276,4 @@ end function Network:serialize() return minetest.serialize(self) end - \ No newline at end of file + diff --git a/hyperloop/settingtypes.txt b/hyperloop/settingtypes.txt index 1fc0431..2c2e90e 100644 --- a/hyperloop/settingtypes.txt +++ b/hyperloop/settingtypes.txt @@ -8,3 +8,8 @@ hyperloop_wifi_crafting_enabled (WiFi block crafting enabled) bool false # If disabled, connected stations have to be on one level, # typically underground. hyperloop_free_tube_placement_enabled (free tube placement enabled) bool false + +# The ticket block has an additional field for specifying a subnet name. +# Stations with the same subnet name (optional) represent an isolated +# subnet within the Hyperloop network. +hyperloop_subnet_enabled (enable building of subnets) bool false \ No newline at end of file diff --git a/minecart/README.md b/minecart/README.md index 27a267c..0a9f5c2 100644 --- a/minecart/README.md +++ b/minecart/README.md @@ -4,9 +4,9 @@ Minecart **Minecart, the lean railway transportation automation system** -Browse on: ![GitHub](https://github.com/joe7575/minecart) +Browse on: [GitHub](https://github.com/joe7575/minecart) -Download: ![GitHub](https://github.com/joe7575/minecart/archive/master.zip) +Download: [GitHub](https://github.com/joe7575/minecart/archive/master.zip) ![minecart](https://github.com/joe7575/minecart/blob/master/screenshot.png) @@ -26,21 +26,15 @@ license). 3. https://github.com/stujones11/railcart/ -Original Cart Features ----------------------- - -- A fast cart for your railway or roller coaster (up to 7 m/s!) -- Boost and brake rails -- Rail junction switching with the 'right-left' walking keys -- Handbrake with the 'back' key - - Minecart Features ----------------- The mod Minecart has its own cart (called Minecart) in addition to the standard cart. Minecarts are used for automated item transport on private and public rail networks. The mod features are: +- a fast cart for your railway or roller coaster (up to 8 m/s!) +- boost rails and speed limit signs +- rail junction switching with the 'right-left' walking keys - configurable timetables and routes for Minecarts - automated loading/unloading of Minecarts by means of a Minecart Hopper - rail network protection based on protection blocks called Land Marks @@ -73,17 +67,16 @@ Introduction 5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart) 6. Punch the buffers to check the connection data (e.g. "Oxford: connected to Cambridge") -7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart +7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time 8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters) 9. Place a Minecart in front of the buffer and check whether it starts after the configured time 10. Check the cart state via the chat command: /mycart - '' is the cart number + '' is the cart number, or get a list of carts with /mycart 11. Drop items into the Minecart and punch the cart to start it, or "sneak+click" the - Minecart to get the items back -12. Dig the empty cart with a second "sneak+click" (as usual) + Minecart to get cart and items back Hopper @@ -97,6 +90,43 @@ to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart. +Cart Pusher +----------- + +Used to push a cart if the cart does not stop directly at a buffer. +The block has to be placed below the rail. + + +Cart Speed / Speed Limit Signs +------------------------------ + +As before, the speed of the carts is also influenced by power rails. +Brake rails are irrelevant, the cart does not brake here. +The maximum speed is 8 m/s. This assumes a ratio of power rails +to normal rails of 1 to 4 on a flat section of rail. A rail section is a +series of rail nodes without a change of direction. After every curve / kink, +the speed for the next section of the route is newly determined, +taking into account the swing of the cart. This means that a cart can +roll over short rail sections without power rails. + +In order to additionally brake the cart at certain points +(at switches or in front of a buffer), speed limit signs can be placed +on the track. With these signs the speed can be reduced to 4, 2, or 1 m / s. +The "No speed limit" sign can be used to remove the speed limit. + +The speed limit signs must be placed next to the track so that they can +be read from the cart. This allows different speeds in each direction of travel. + + +Migration to v2 +--------------- + +The way how carts are monitored and the cart speed is calculated has changed. +Therefore, it is necessary that all carts are repositioned and the +recording is repeated. +Rails and buffers are not affected and can be kept unchanged. + + History ------- @@ -117,4 +147,6 @@ History 2020-06-27 v1.07 Route storage and cart command bugfixes 2020-07-24 V1.08 Adapted to new techage ICTA style 2020-08-14 V1.09 Hopper support for digtron, protector:chest and default:furnace added -2020-11-12 V1.10 Make carts more robust against server lag +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 diff --git a/minecart/api.lua b/minecart/api.lua new file mode 100644 index 0000000..d93e188 --- /dev/null +++ b/minecart/api.lua @@ -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 \ No newline at end of file diff --git a/minecart/baselib.lua b/minecart/baselib.lua new file mode 100644 index 0000000..d0c035a --- /dev/null +++ b/minecart/baselib.lua @@ -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 = {} -- [] = +minecart.tEntityNames = {} -- [] = true +minecart.lCartNodeNames = {} -- {, , ...} +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 diff --git a/minecart/buffer.lua b/minecart/buffer.lua index 89e642b..3c2100c 100644 --- a/minecart/buffer.lua +++ b/minecart/buffer.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -23,21 +23,17 @@ local StopTime = {} local function formspec(pos) local name = M(pos):get_string("name") local time = M(pos):get_int("time") - local s = "size[4,4.2]" .. + return "size[4,4.2]" .. "label[0,0;Configuration]" .. "field[0.5,1.2;3.6,1;name;"..S("Station name")..":;"..name.."]".. - "button_exit[1,3.4;2,1;exit;Save]" - if minecart.hopper_enabled then - return s.."field[0.5,2.5;3.6,1;time;"..S("Stop time/sec")..":;"..time.."]" - end - return s + "button_exit[1,3.4;2,1;exit;Save]".. + "field[0.5,2.5;3.6,1;time;"..S("Waiting time/sec")..":;"..time.."]" end local function remote_station_name(pos) - local route = minecart.get_route(P2S(pos)) + local route = minecart.get_route(pos) if route and route.dest_pos then - local pos2 = S2P(route.dest_pos) - return M(pos2):get_string("name") + return M(route.dest_pos):get_string("name") end return "none" end @@ -46,23 +42,21 @@ local function on_punch(pos, node, puncher) local name = M(pos):get_string("name") M(pos):set_string("infotext", name..": "..S("connected to").." "..remote_station_name(pos)) M(pos):set_string("formspec", formspec(pos)) - if minecart.hopper_enabled then - minetest.get_node_timer(pos):start(CYCLE_TIME) - end + minetest.get_node_timer(pos):start(CYCLE_TIME) + -- Optional Teleport function if not minecart.teleport_enabled then return end - local route = minecart.get_route(P2S(pos)) + local route = minecart.get_route(pos) if route and route.dest_pos and puncher and puncher:is_player() then -- only teleport if the user is not pressing shift if not puncher:get_player_control()['sneak'] then local playername = puncher:get_player_name() - local pos = S2P(route.dest_pos) local teleport = function() -- Make sure the player object still exists local player = minetest.get_player_by_name(playername) - if player and pos then player:set_pos(pos) end + if player then player:set_pos(route.dest_pos) end end minetest.after(0.25, teleport) end @@ -95,24 +89,21 @@ minetest.register_node("minecart:buffer", { }, after_place_node = function(pos, placer) M(pos):set_string("owner", placer:get_player_name()) - minecart.del_route(minetest.pos_to_string(pos)) + minecart.del_route(pos) M(pos):set_string("formspec", formspec(pos)) - if minecart.hopper_enabled then - minetest.get_node_timer(pos):start(CYCLE_TIME) - end + minetest.get_node_timer(pos):start(CYCLE_TIME) end, on_timer = function(pos, elapsed) local time = M(pos):get_int("time") if time > 0 then local hash = minetest.hash_node_position(pos) local param2 = (minetest.get_node(pos).param2 + 2) % 4 - if minecart.check_cart_for_pushing(pos, param2) then + if minecart.is_cart_available(pos, param2, 0.5) then if StopTime[hash] then if StopTime[hash] < minetest.get_gametime() then StopTime[hash] = nil - local node = minetest.get_node(pos) - local dir = minetest.facedir_to_dir(node.param2) - minecart.punch_cart(pos, param2, 0, dir) + local dir = minetest.facedir_to_dir(param2) + minecart.punch_cart(pos, param2, 0.5, dir) end else StopTime[hash] = minetest.get_gametime() + time @@ -124,7 +115,7 @@ minetest.register_node("minecart:buffer", { return true end, after_dig_node = function(pos) - minecart.del_route(minetest.pos_to_string(pos)) + minecart.del_route(pos) local hash = minetest.hash_node_position(pos) StopTime[hash] = nil end, @@ -137,6 +128,7 @@ minetest.register_node("minecart:buffer", { M(pos):set_int("time", tonumber(fields.time) or 0) M(pos):set_string("formspec", formspec(pos)) M(pos):set_string("infotext", fields.name.." "..S("connected to").." "..remote_station_name(pos)) + minetest.get_node_timer(pos):start(CYCLE_TIME) end end, on_punch = on_punch, @@ -156,3 +148,15 @@ minetest.register_craft({ {"default:steel_ingot", "default:junglewood", "default:steel_ingot"}, }, }) + +minetest.register_lbm({ + label = "Delete waiting times", + name = "minecart:del_time", + nodenames = {"minecart:buffer"}, + run_at_every_load = false, + action = function(pos, node) + -- delete old data + minecart.get_route(pos) + M(pos):set_string("formspec", formspec(pos)) + end, +}) diff --git a/minecart/cart_lib1.lua b/minecart/cart_lib1.lua deleted file mode 100644 index afde093..0000000 --- a/minecart/cart_lib1.lua +++ /dev/null @@ -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 diff --git a/minecart/cart_lib2e.lua b/minecart/cart_lib2e.lua deleted file mode 100644 index cea4700..0000000 --- a/minecart/cart_lib2e.lua +++ /dev/null @@ -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 diff --git a/minecart/cart_lib2n.lua b/minecart/cart_lib2n.lua deleted file mode 100644 index 4625491..0000000 --- a/minecart/cart_lib2n.lua +++ /dev/null @@ -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 \ No newline at end of file diff --git a/minecart/cart_lib3.lua b/minecart/cart_lib3.lua deleted file mode 100644 index 6cecc9c..0000000 --- a/minecart/cart_lib3.lua +++ /dev/null @@ -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 diff --git a/minecart/doc.lua b/minecart/doc.lua index 3bb7cb6..3f43b37 100644 --- a/minecart/doc.lua +++ b/minecart/doc.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -29,15 +29,15 @@ local summary_doc = table.concat({ S("4. Place a Minecart at a buffer and give it a cart number (1..999)"), S("5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart)."), S("6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge')."), - S("7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time."), + S("7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time."), S("8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters)."), S("9. Place a Minecart in front of the buffer and check whether it starts after the configured time."), S("10. Check the cart state via the chat command: /mycart \n '' is the cart number"), - S("11. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back."), - S("12. Dig the empty cart with a second 'sneak+click' (as usual)."), + S("11. Drop items into the Minecart and punch the cart to start it."), + S("12. Dig the cart with 'sneak+click' (as usual). The items will be drop down."), }, "\n") -local cart_doc = S("Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get the items back") +local cart_doc = S("Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back") local buffer_doc = S("Used as buffer on both rail ends. Needed to be able to record the cart routes") @@ -45,6 +45,29 @@ local landmark_doc = S("Protect your rails with the Landmarks (one Landmark at l local hopper_doc = S("Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.") +local pusher_doc = S([[If several carts are running on one route, +it can happen that a buffer position is already occupied and one cart therefore stops earlier. +In this case, the cart pusher is used to push the cart towards the buffer again. +This block must be placed under the rail at a distance of 2 m in front of the buffer.]]) + +local speed_doc = S([[Limit the cart speed with speed limit signs. + +As before, the speed of the carts is also influenced by power rails. +Brake rails are irrelevant, the cart does not brake here. +The maximum speed is 8 m/s. This assumes a ratio of power rails +to normal rails of 1 to 4 on a flat section of rail. A rail section is a +series of rail nodes without a change of direction. After every curve / kink, +the speed for the next section of the route is newly determined, +taking into account the swing of the cart. This means that a cart can +roll over short rail sections without power rails. + +In order to additionally brake the cart at certain points +(at switches or in front of a buffer), speed limit signs can be placed +on the track. With these signs the speed can be reduced to 4, 2, or 1 m / s. +The "No speed limit" sign can be used to remove the speed limit. + +The speed limit signs must be placed next to the track so that they can +be read from the cart. This allows different speeds in each direction of travel.]]) local function formspec(data) if data.image then @@ -90,6 +113,16 @@ doc.add_entry("minecart", "landmark", { data = {text = landmark_doc, item="minecart:landmark"}, }) +doc.add_entry("minecart", "speed signs", { + name = S("Minecart Speed Signs"), + data = {text = speed_doc, item="minecart:speed4"}, +}) + +doc.add_entry("minecart", "cart pusher", { + name = S("Cart Pusher"), + data = {text = pusher_doc, item="minecart:cart_pusher"}, +}) + if minecart.hopper_enabled then doc.add_entry("minecart", "hopper", { name = S("Minecart Hopper"), diff --git a/minecart/entitylib.lua b/minecart/entitylib.lua new file mode 100644 index 0000000..bf20953 --- /dev/null +++ b/minecart/entitylib.lua @@ -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 + diff --git a/minecart/hopper.lua b/minecart/hopper.lua index 49f045b..7b7fbbf 100644 --- a/minecart/hopper.lua +++ b/minecart/hopper.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -155,6 +155,7 @@ minetest.register_node("minecart:hopper", { paramtype = "light", sunlight_propagates = true, paramtype2 = "facedir", + use_texture_alpha = minecart.CLIP, groups = {choppy=2, cracky=2, crumbly=2}, is_ground_content = false, sounds = default.node_sound_wood_defaults(), diff --git a/minecart/hopperlib.lua b/minecart/hopperlib.lua new file mode 100644 index 0000000..6f2a583 --- /dev/null +++ b/minecart/hopperlib.lua @@ -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", + }, +}) diff --git a/minecart/i18n.py b/minecart/i18n.py index b218fcf..3c6ce1b 100755 --- a/minecart/i18n.py +++ b/minecart/i18n.py @@ -1,79 +1,469 @@ -#!/usr/bin/env python +#!/usr/bin/env python3 # -*- coding: utf-8 -*- # # Script to generate the template file and update the translation files. +# Copy the script into the mod or modpack root folder and run it there. # -# Copyright (C) 2019 Joachim Stolberg +# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer # LGPLv2.1+ -# -# Copy the script into the mod root folder and adapt the last code lines to you needs. +# +# See https://github.com/minetest-tools/update_translations for +# potential future updates to this script. from __future__ import print_function -import os, fnmatch, re, shutil +import os, fnmatch, re, shutil, errno +from sys import argv as _argv +from sys import stderr as _stderr -pattern_lua = re.compile(r'[ \.=^\t]S\("(.+?)"\)', re.DOTALL) -pattern_tr = re.compile(r'(.+?[^@])=(.+)') +# Running params +params = {"recursive": False, + "help": False, + "mods": False, + "verbose": False, + "folders": [], + "no-old-file": False, + "break-long-lines": False, + "sort": False, + "print-source": False +} +# Available CLI options +options = {"recursive": ['--recursive', '-r'], + "help": ['--help', '-h'], + "mods": ['--installed-mods', '-m'], + "verbose": ['--verbose', '-v'], + "no-old-file": ['--no-old-file', '-O'], + "break-long-lines": ['--break-long-lines', '-b'], + "sort": ['--sort', '-s'], + "print-source": ['--print-source', '-p'] +} -def gen_template(templ_file, lkeyStrings): - lOut = [] - lkeyStrings.sort() - for s in lkeyStrings: - lOut.append("%s=" % s) - open(templ_file, "wt").write("\n".join(lOut)) +# Strings longer than this will have extra space added between +# them in the translation files to make it easier to distinguish their +# beginnings and endings at a glance +doublespace_threshold = 80 +def set_params_folders(tab: list): + '''Initialize params["folders"] from CLI arguments.''' + # Discarding argument 0 (tool name) + for param in tab[1:]: + stop_param = False + for option in options: + if param in options[option]: + stop_param = True + break + if not stop_param: + params["folders"].append(os.path.abspath(param)) + +def set_params(tab: list): + '''Initialize params from CLI arguments.''' + for option in options: + for option_name in options[option]: + if option_name in tab: + params[option] = True + break + +def print_help(name): + '''Prints some help message.''' + print(f'''SYNOPSIS + {name} [OPTIONS] [PATHS...] +DESCRIPTION + {', '.join(options["help"])} + prints this help message + {', '.join(options["recursive"])} + run on all subfolders of paths given + {', '.join(options["mods"])} + run on locally installed modules + {', '.join(options["no-old-file"])} + do not create *.old files + {', '.join(options["sort"])} + sort output strings alphabetically + {', '.join(options["break-long-lines"])} + add extra line breaks before and after long strings + {', '.join(options["verbose"])} + add output information +''') + + +def main(): + '''Main function''' + set_params(_argv) + set_params_folders(_argv) + if params["help"]: + print_help(_argv[0]) + elif params["recursive"] and params["mods"]: + print("Option --installed-mods is incompatible with --recursive") + else: + # Add recursivity message + print("Running ", end='') + if params["recursive"]: + print("recursively ", end='') + # Running + if params["mods"]: + print(f"on all locally installed modules in {os.path.expanduser('~/.minetest/mods/')}") + run_all_subfolders(os.path.expanduser("~/.minetest/mods")) + elif len(params["folders"]) >= 2: + print("on folder list:", params["folders"]) + for f in params["folders"]: + if params["recursive"]: + run_all_subfolders(f) + else: + update_folder(f) + elif len(params["folders"]) == 1: + print("on folder", params["folders"][0]) + if params["recursive"]: + run_all_subfolders(params["folders"][0]) + else: + update_folder(params["folders"][0]) + else: + print("on folder", os.path.abspath("./")) + if params["recursive"]: + run_all_subfolders(os.path.abspath("./")) + else: + update_folder(os.path.abspath("./")) + +#group 2 will be the string, groups 1 and 3 will be the delimiters (" or ') +#See https://stackoverflow.com/questions/46967465/regex-match-text-in-either-single-or-double-quote +pattern_lua_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*(["\'])((?:\\\1|(?:(?!\1)).)*)(\1)[\s,\)]', re.DOTALL) +pattern_lua_bracketed_s = re.compile(r'[\.=^\t,{\(\s]N?S\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) +pattern_lua_bracketed_fs = re.compile(r'[\.=^\t,{\(\s]N?FS\(\s*\[\[(.*?)\]\][\s,\)]', re.DOTALL) + +# Handles "concatenation" .. " of strings" +pattern_concat = re.compile(r'["\'][\s]*\.\.[\s]*["\']', re.DOTALL) + +pattern_tr = re.compile(r'(.*?[^@])=(.*)') +pattern_name = re.compile(r'^name[ ]*=[ ]*([^ \n]*)') +pattern_tr_filename = re.compile(r'\.tr$') +pattern_po_language_code = re.compile(r'(.*)\.po$') + +#attempt to read the mod's name from the mod.conf file or folder name. Returns None on failure +def get_modname(folder): + try: + with open(os.path.join(folder, "mod.conf"), "r", encoding='utf-8') as mod_conf: + for line in mod_conf: + match = pattern_name.match(line) + if match: + return match.group(1) + except FileNotFoundError: + if not os.path.isfile(os.path.join(folder, "modpack.txt")): + folder_name = os.path.basename(folder) + # Special case when run in Minetest's builtin directory + if folder_name == "builtin": + return "__builtin" + else: + return folder_name + else: + return None + return None + +#If there are already .tr files in /locale, returns a list of their names +def get_existing_tr_files(folder): + out = [] + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + if pattern_tr_filename.search(name): + out.append(name) + return out + +# A series of search and replaces that massage a .po file's contents into +# a .tr file's equivalent +def process_po_file(text): + # The first three items are for unused matches + text = re.sub(r'#~ msgid "', "", text) + text = re.sub(r'"\n#~ msgstr ""\n"', "=", text) + text = re.sub(r'"\n#~ msgstr "', "=", text) + # comment lines + text = re.sub(r'#.*\n', "", text) + # converting msg pairs into "=" pairs + text = re.sub(r'msgid "', "", text) + text = re.sub(r'"\nmsgstr ""\n"', "=", text) + text = re.sub(r'"\nmsgstr "', "=", text) + # various line breaks and escape codes + text = re.sub(r'"\n"', "", text) + text = re.sub(r'"\n', "\n", text) + text = re.sub(r'\\"', '"', text) + text = re.sub(r'\\n', '@n', text) + # remove header text + text = re.sub(r'=Project-Id-Version:.*\n', "", text) + # remove double-spaced lines + text = re.sub(r'\n\n', '\n', text) + return text + +# Go through existing .po files and, if a .tr file for that language +# *doesn't* exist, convert it and create it. +# The .tr file that results will subsequently be reprocessed so +# any "no longer used" strings will be preserved. +# Note that "fuzzy" tags will be lost in this process. +def process_po_files(folder, modname): + for root, dirs, files in os.walk(os.path.join(folder, 'locale/')): + for name in files: + code_match = pattern_po_language_code.match(name) + if code_match == None: + continue + language_code = code_match.group(1) + tr_name = f'{modname}.{language_code}.tr' + tr_file = os.path.join(root, tr_name) + if os.path.exists(tr_file): + if params["verbose"]: + print(f"{tr_name} already exists, ignoring {name}") + continue + fname = os.path.join(root, name) + with open(fname, "r", encoding='utf-8') as po_file: + if params["verbose"]: + print(f"Importing translations from {name}") + text = process_po_file(po_file.read()) + with open(tr_file, "wt", encoding='utf-8') as tr_out: + tr_out.write(text) + +# from https://stackoverflow.com/questions/600268/mkdir-p-functionality-in-python/600612#600612 +# Creates a directory if it doesn't exist, silently does +# nothing if it already exists +def mkdir_p(path): + try: + os.makedirs(path) + except OSError as exc: # Python >2.5 + if exc.errno == errno.EEXIST and os.path.isdir(path): + pass + else: raise + +# Converts the template dictionary to a text to be written as a file +# dKeyStrings is a dictionary of localized string to source file sets +# dOld is a dictionary of existing translations and comments from +# the previous version of this text +def strings_to_text(dkeyStrings, dOld, mod_name, header_comments): + lOut = [f"# textdomain: {mod_name}"] + if header_comments is not None: + lOut.append(header_comments) + + dGroupedBySource = {} + + for key in dkeyStrings: + sourceList = list(dkeyStrings[key]) + if params["sort"]: + sourceList.sort() + sourceString = "\n".join(sourceList) + listForSource = dGroupedBySource.get(sourceString, []) + listForSource.append(key) + dGroupedBySource[sourceString] = listForSource + + lSourceKeys = list(dGroupedBySource.keys()) + lSourceKeys.sort() + for source in lSourceKeys: + localizedStrings = dGroupedBySource[source] + if params["sort"]: + localizedStrings.sort() + if params["print-source"]: + if lOut[-1] != "": + lOut.append("") + lOut.append(source) + for localizedString in localizedStrings: + val = dOld.get(localizedString, {}) + translation = val.get("translation", "") + comment = val.get("comment") + if params["break-long-lines"] and len(localizedString) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None and comment != "" and not comment.startswith("# textdomain:"): + lOut.append(comment) + lOut.append(f"{localizedString}={translation}") + if params["break-long-lines"] and len(localizedString) > doublespace_threshold: + lOut.append("") + + + unusedExist = False + for key in dOld: + if key not in dkeyStrings: + val = dOld[key] + translation = val.get("translation") + comment = val.get("comment") + # only keep an unused translation if there was translated + # text or a comment associated with it + if translation != None and (translation != "" or comment): + if not unusedExist: + unusedExist = True + lOut.append("\n\n##### not used anymore #####\n") + if params["break-long-lines"] and len(key) > doublespace_threshold and not lOut[-1] == "": + lOut.append("") + if comment != None: + lOut.append(comment) + lOut.append(f"{key}={translation}") + if params["break-long-lines"] and len(key) > doublespace_threshold: + lOut.append("") + return "\n".join(lOut) + '\n' + +# Writes a template.txt file +# dkeyStrings is the dictionary returned by generate_template +def write_template(templ_file, dkeyStrings, mod_name): + # read existing template file to preserve comments + existing_template = import_tr_file(templ_file) + + text = strings_to_text(dkeyStrings, existing_template[0], mod_name, existing_template[2]) + mkdir_p(os.path.dirname(templ_file)) + with open(templ_file, "wt", encoding='utf-8') as template_file: + template_file.write(text) + + +# Gets all translatable strings from a lua file def read_lua_file_strings(lua_file): lOut = [] - text = open(lua_file).read() - for s in pattern_lua.findall(text): - s = re.sub(r'"\.\.\s+"', "", s) - s = re.sub("@[^@=n]", "@@", s) - s = s.replace("\n", "@n") - s = s.replace("\\n", "@n") - s = s.replace("=", "@=") - lOut.append(s) + with open(lua_file, encoding='utf-8') as text_file: + text = text_file.read() + #TODO remove comments here + + text = re.sub(pattern_concat, "", text) + + strings = [] + for s in pattern_lua_s.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed_s.findall(text): + strings.append(s) + for s in pattern_lua_fs.findall(text): + strings.append(s[1]) + for s in pattern_lua_bracketed_fs.findall(text): + strings.append(s) + + for s in strings: + s = re.sub(r'"\.\.\s+"', "", s) + s = re.sub("@[^@=0-9]", "@@", s) + s = s.replace('\\"', '"') + s = s.replace("\\'", "'") + s = s.replace("\n", "@n") + s = s.replace("\\n", "@n") + s = s.replace("=", "@=") + lOut.append(s) return lOut -def inport_tr_file(tr_file): +# Gets strings from an existing translation file +# returns both a dictionary of translations +# and the full original source text so that the new text +# can be compared to it for changes. +# Returns also header comments in the third return value. +def import_tr_file(tr_file): dOut = {} + text = None + header_comment = None if os.path.exists(tr_file): - for line in open(tr_file, "r").readlines(): - s = line.strip() - if s == "" or s[0] == "#": - continue - match = pattern_tr.match(s) - if match: - dOut[match.group(1)] = match.group(2) - return dOut + with open(tr_file, "r", encoding='utf-8') as existing_file : + # save the full text to allow for comparison + # of the old version with the new output + text = existing_file.read() + existing_file.seek(0) + # a running record of the current comment block + # we're inside, to allow preceeding multi-line comments + # to be retained for a translation line + latest_comment_block = None + for line in existing_file.readlines(): + line = line.rstrip('\n') + if line.startswith("###"): + if header_comment is None and not latest_comment_block is None: + # Save header comments + header_comment = latest_comment_block + # Strip textdomain line + tmp_h_c = "" + for l in header_comment.split('\n'): + if not l.startswith("# textdomain:"): + tmp_h_c += l + '\n' + header_comment = tmp_h_c -def generate_template(templ_file): - lOut = [] - for root, dirs, files in os.walk('./'): + # Reset comment block if we hit a header + latest_comment_block = None + continue + elif line.startswith("#"): + # Save the comment we're inside + if not latest_comment_block: + latest_comment_block = line + else: + latest_comment_block = latest_comment_block + "\n" + line + continue + match = pattern_tr.match(line) + if match: + # this line is a translated line + outval = {} + outval["translation"] = match.group(2) + if latest_comment_block: + # if there was a comment, record that. + outval["comment"] = latest_comment_block + latest_comment_block = None + dOut[match.group(1)] = outval + return (dOut, text, header_comment) + +# Walks all lua files in the mod folder, collects translatable strings, +# and writes it to a template.txt file +# Returns a dictionary of localized strings to source file sets +# that can be used with the strings_to_text function. +def generate_template(folder, mod_name): + dOut = {} + for root, dirs, files in os.walk(folder): for name in files: if fnmatch.fnmatch(name, "*.lua"): fname = os.path.join(root, name) found = read_lua_file_strings(fname) - print(fname, len(found)) - lOut.extend(found) - lOut = list(set(lOut)) - lOut.sort() - gen_template(templ_file, lOut) - return lOut + if params["verbose"]: + print(f"{fname}: {str(len(found))} translatable strings") -def update_tr_file(lNew, mod_name, tr_file): - lOut = ["# textdomain: %s\n" % mod_name] - if os.path.exists(tr_file): - shutil.copyfile(tr_file, tr_file+".old") - dOld = inport_tr_file(tr_file) - for key in lNew: - val = dOld.get(key, "") - lOut.append("%s=%s" % (key, val)) - lOut.append("##### not used anymore #####") - for key in dOld: - if key not in lNew: - lOut.append("%s=%s" % (key, dOld[key])) - open(tr_file, "w").write("\n".join(lOut)) - -data = generate_template("./locale/template.txt") -update_tr_file(data, "minecart", "./locale/minecart.de.tr") -print("Done.\n") + for s in found: + sources = dOut.get(s, set()) + sources.add(f"### {os.path.basename(fname)} ###") + dOut[s] = sources + + if len(dOut) == 0: + return None + templ_file = os.path.join(folder, "locale/template.txt") + write_template(templ_file, dOut, mod_name) + return dOut +# Updates an existing .tr file, copying the old one to a ".old" file +# if any changes have happened +# dNew is the data used to generate the template, it has all the +# currently-existing localized strings +def update_tr_file(dNew, mod_name, tr_file): + if params["verbose"]: + print(f"updating {tr_file}") + + tr_import = import_tr_file(tr_file) + dOld = tr_import[0] + textOld = tr_import[1] + + textNew = strings_to_text(dNew, dOld, mod_name, tr_import[2]) + + if textOld and textOld != textNew: + print(f"{tr_file} has changed.") + if not params["no-old-file"]: + shutil.copyfile(tr_file, f"{tr_file}.old") + + with open(tr_file, "w", encoding='utf-8') as new_tr_file: + new_tr_file.write(textNew) + +# Updates translation files for the mod in the given folder +def update_mod(folder): + modname = get_modname(folder) + if modname is not None: + process_po_files(folder, modname) + print(f"Updating translations for {modname}") + data = generate_template(folder, modname) + if data == None: + print(f"No translatable strings found in {modname}") + else: + for tr_file in get_existing_tr_files(folder): + update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file)) + else: + print(f"\033[31mUnable to find modname in folder {folder}.\033[0m", file=_stderr) + exit(1) + +# Determines if the folder being pointed to is a mod or a mod pack +# and then runs update_mod accordingly +def update_folder(folder): + is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf")) + if is_modpack: + subfolders = [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')] + for subfolder in subfolders: + update_mod(subfolder) + else: + update_mod(folder) + print("Done.") + +def run_all_subfolders(folder): + for modfolder in [f.path for f in os.scandir(folder) if f.is_dir() and not f.name.startswith('.')]: + update_folder(modfolder) + + +main() diff --git a/minecart/init.lua b/minecart/init.lua index c484085..b1e66b1 100644 --- a/minecart/init.lua +++ b/minecart/init.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -13,24 +13,37 @@ minecart = {} -- Version for compatibility checks, see readme.md/history -minecart.version = 1.10 +minecart.version = 2.00 minecart.hopper_enabled = minetest.settings:get_bool("minecart_hopper_enabled") ~= false minecart.teleport_enabled = minetest.settings:get_bool("minecart_teleport_enabled") == true +-- Test for MT 5.4 new string mode +minecart.CLIP = minetest.features.use_texture_alpha_string_modes and "clip" or false + minecart.S = minetest.get_translator("minecart") local MP = minetest.get_modpath("minecart") -dofile(MP.."/storage.lua") -dofile(MP.."/lib.lua") -dofile(MP.."/monitoring.lua") -dofile(MP.."/recording.lua") -dofile(MP.."/minecart.lua") -dofile(MP.."/buffer.lua") -dofile(MP.."/protection.lua") +dofile(MP .. "/baselib.lua") +dofile(MP .. "/storage.lua") +dofile(MP .. "/rails.lua") +dofile(MP .. "/monitoring.lua") +dofile(MP .. "/recording.lua") +dofile(MP .. "/hopperlib.lua") +dofile(MP .. "/nodelib.lua") +dofile(MP .. "/entitylib.lua") +dofile(MP .. "/api.lua") +dofile(MP .. "/minecart.lua") +dofile(MP .. "/buffer.lua") +dofile(MP .. "/protection.lua") +--dofile(MP .. "/tool.lua") # for debugging only +dofile(MP .. "/signs.lua") +dofile(MP .. "/terminal.lua") +dofile(MP .. "/pusher.lua") if minecart.hopper_enabled then - dofile(MP.."/hopper.lua") - dofile(MP.."/mods_support.lua") + dofile(MP .. "/hopper.lua") + dofile(MP .. "/mods_support.lua") end -dofile(MP.."/doc.lua") + +dofile(MP .. "/doc.lua") minetest.log("info", "[MOD] Minecart loaded") diff --git a/minecart/lib.lua b/minecart/lib.lua deleted file mode 100644 index efdcf72..0000000 --- a/minecart/lib.lua +++ /dev/null @@ -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 = {} -- [] = -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", - }, -}) diff --git a/minecart/locale/minecart.de.tr b/minecart/locale/minecart.de.tr index 94954c5..615b0cc 100644 --- a/minecart/locale/minecart.de.tr +++ b/minecart/locale/minecart.de.tr @@ -1,37 +1,58 @@ # textdomain: minecart - +Station name=Stationsname +Waiting time/sec=Wartezeit/s +connected to=verbunden mit +Minecart Railway Buffer=Minecart Prellbock +Summary=Zusammenfassung 1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint.=1. Baue eine Schienenstrecke mit zwei Enden. Kreuzungen sind zulässig, solange jede Route ihre eigenen Start- und Endpunkte hat. -10. Check the cart state via the chat command: /mycart @n '' is the cart number=Prüfe den Status des Wagen mit dem Chat Kommando: /mycart @n ist die Wagennummer -11. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back.=11: Lege Gegenstände in ein Wagen (Taste Q) und starte dann den Wagen durch Anklicken. Klicke mit gedrückter Shift-Taste auf den Wagen, um Gegenstände wieder auszuladen. -12. Dig the empty cart with a second 'sneak+click' (as usual).=10. Klicke erneut mit gedrückter Shift-Taste auf den Wagen, um diesen zu entfernen. 2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information).=2. Platziere einen Prellbock an beide Schienenenden (Prellböcke sind zwingend notwendig, sie speichern die Routen- und Zeit-Informationen). 3. Give both Railway Buffers unique station names, like Oxford and Cambridge.=3. Gib beiden Prellböcken eindeutige Stationsnamen wie: Stuttgart und München. 4. Place a Minecart at a buffer and give it a cart number (1..999)=4. Platziere einen Minecart Wagen an einem Prellbock und gib dem Wagen eine Wagennummer (1..999) 5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).=5. Um eine Route aufzuzeichnen, fahre die Route in beide Richtungen von Prellbock zu Prellbock mit dem Minecart Wagen(!). Nutze 'links-rechts' Tasten zur Steuerung. 6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').=6. Schlage auf die Prellböcke um die Verbindungsdaten zu prüfen (bspw.: 'München: verbunden mit Stuttgart') -7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time.=7. Optional: Konfiguriere die Wagenwartezeit in einem oder in beiden Prellböcken. Der Wagen startet dann nach dieser Zeit automatisch. +7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time.=7. Optional: Konfiguriere die Wagenwartezeit in beiden Prellböcken. Der Wagen startet dann nach dieser Zeit automatisch. 8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=8. Optional: Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke). 9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=9. Platziere einen Wagen direkt vor einem Prellbock und prüfe, ob er nach der konfigurierten Zeit startet. -Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen -Minecart=Minecart -Minecart (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts) -Minecart Cart=Wagen -Minecart Hopper=Minecart Hopper -Minecart Landmark=Minecart Meilenstein -Minecart Railway Buffer=Minecart Prellbock -Minecart, the lean railway transportation automation system=Minecart, das schlanke Schienentransport Automatisierungssystem -Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get the items back=Primär für den Transport von Gegenständen genutzt. Du kannst Gegenstände in ein Cart legen (Taste Q) und dann den Wagen durch Anklicken starten. Klicke mit gedrückter Shift-Taste auf den Wagen, um die Gegenstände wieder auszuladen -Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke der Strecke entlang) -Station name=Stationsname -Stop time/sec=Haltezeit/s -Summary=Zusammenfassung +10. Check the cart state via the chat command: /mycart @n '' is the cart number=Prüfe den Status des Wagen mit dem Chat Kommando: /mycart @n ist die Wagennummer +11. Drop items into the Minecart and punch the cart to start it.=11: Lege Gegenstände in ein Wagen (Taste Q) und starte dann den Wagen durch Anklicken. +12. Dig the cart with 'sneak+click' (as usual). The items will be drop down.=10. Klicke mit gedrückter Shift-Taste auf den Wagen, um diesen zu entfernen. Die Gegenstände fallen dann zu Boden. +Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back=Primär für den Transport von Gegenständen genutzt. Du kannst Gegenstände in ein Cart legen (Taste Q) und dann den Wagen durch Anklicken starten. Klicke mit gedrückter Shift-Taste auf den Wagen, um Cart und Gegenstände zurückzuerhalten Used as buffer on both rail ends. Needed to be able to record the cart routes=Preckblöcke müssen an beiden Schienenenden platziert sein, so dass Aufzeichnungen der Strecke gemacht werden können. +Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke der Strecke entlang) Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.=Um Wagen zu be- und entladen. Der Hopper kann Gegenstände aus Kisten Holen und legen, sowie diese in Wagen fallen lassen bzw. aus Wagen entnehmen. Um einen Wagen zu entladen, muss der Hopper unter die Schiene platziert werden. Um einen Wagen zu beladen, muss der Hopper direkt neben die Schiene platziert werden. +Minecart=Minecart +Minecart, the lean railway transportation automation system=Minecart, das schlanke Schienentransport Automatisierungssystem +Minecart Cart=Wagen +Minecart Speed Signs=Geschwindigkeitsbegrenzungszeichen +If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.=Wenn mehrere Wagen auf einer Route fahren, kann es vorkommen,@ndass eine Prellbock Position bereits belegt ist und ein Wagen daher früher anhält.@nDer Cart Anschieber dient in diesem Fall dazu, die Wagen wieder in Richtung Prellbock anzuschieben.@nDieser Block muss unter der Schiene mit 2 m Abstand vor dem Prellbock platziert werden. +Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.=Begrenze die Geschwindigkeit der Wagen mit Geschwindigkeitsbegrenzungszeichen@n@nDie Geschwindigkeit der Carts wird wie bisher auch über "power rails" beeinflusst. "Brake rails" sind ohne Bedeutung, das Cart bremst hier nicht. Die maximale Geschwindigkeit beträgt 8 m/s. Dies setzt eine Verhältnis von "power rails" zu "normal rails" von 1 zu 4 auf einem ebenen Streckenabschnitt voraus. Ein Streckenabschnitt ist dabei ein Reihe von Schienenblöcken ohne Richtungsänderung. Nach jeder Kurve/Knick wird die Geschwindigkeit für den nächsten Streckenabschnitt neu bestimmt, wobei hier der Schwung des Carts mit berücksichtigt wird. So kann ein Cart auch über kurze Streckenabschnitt ohne "power rails" rollen.@n@nUm das Cart zusätzlich an bestimmten Stellen abzubremsen (an Weichen oder vor einen Puffer), können Geschwindigkeitsbegrenzungszeichen an der Strecke platziert werden. Durch diese Zeichen kann die Geschwindigkeit auf 4, 2, oder 1 m/s reduziert werden. Durch das Aufhebungszeichen kann die Geschwindigkeitsbegrenzung wieder aufgehoben werden.@n@nDie Geschwindigkeitsbegrenzungszeichen müssen so neben die Strecke platziert werden, dass sie vom Cart ablesbar sind. Dies erlaubt damit unterschiedliche Geschwindigkeiten pro Fahrtrichtung. +Minecart Hopper=Minecart Hopper +Minecart (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts) +Output cart state and position, or a list of carts, if no cart number is given.=Gibt Status und Position des Wagens, oder eine Liste aller Wagen aus, wenn keine Wagennummer angegeben ist. +List of carts=Liste aller Wagen +Enter cart number=Gebe Cart Nummer ein +Save=Speichern [minecart] Area is protected!=[minecart] Bereich ist geschützt! -[minecart] Cart is protected by = Wagen ist geschützt durch -[minecart] Recording canceled!=[minecart] Aufzeichnung abgebrochen! +Allow to dig/place rails in Minecart Landmark areas=Erlaubt dir, Schienen in Meilensteinbereichen zu setzen/zu entfernen +Minecart Landmark=Minecart Meilenstein +Cart Pusher=Wagen Anschieber +left=links +right=rechts +straight=geradeaus +Recording=Aufzeichnung +speed=Tempo +next junction=nächste Weiche +Travel time=Fahrzeit [minecart] Route stored!=[minecart] Strecke gespeichert -[minecart] Start route recording!=[minecart] Starte die Streckenaufzeichnung! -connected to=verbunden mit +[minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m=[minecart] Geschw. @= %u m/s, Zeit @= %u s, Routenlänge @= %u m +Speed "1"=Tempo "1" +Speed "2"=Tempo "2" +Speed "4"=Tempo "4" +No speed limit=Keine Geschwindigkeitsbegrenzung +Cart List=Cart Liste +Cart Terminal=Cart Terminal + + ##### not used anymore ##### +Used to push a cart if the cart does not stop directly at a buffer. Block has to be placed below the rail.=Wird verwendet, um einen Wagen anzuschieben, wenn der Wagen nicht direkt an einem Puffer anhält. Der Block muss unter der Schiene platziert werden. diff --git a/minecart/locale/template.txt b/minecart/locale/template.txt index 8d66077..b9a4ae5 100644 --- a/minecart/locale/template.txt +++ b/minecart/locale/template.txt @@ -1,33 +1,53 @@ +# textdomain: minecart +Station name= +Waiting time/sec= +connected to= +Minecart Railway Buffer= +Summary= 1. Place your rails and build a route with two endpoints. Junctions are allowed as long as each route has its own start and endpoint.= -10. Check the cart state via the chat command: /mycart @n '' is the cart number= -11. Drop items into the Minecart and punch the cart to start it, or 'sneak+click' the Minecart to get the items back.= -12. Dig the empty cart with a second 'sneak+click' (as usual).= 2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information).= 3. Give both Railway Buffers unique station names, like Oxford and Cambridge.= 4. Place a Minecart at a buffer and give it a cart number (1..999)= 5. Drive from buffer to buffer in both directions using the Minecart(!) to record the routes (use 'right-left' keys to control the Minecart).= 6. Punch the buffers to check the connection data (e.g. 'Oxford: connected to Cambridge').= -7. Optional: Configure the Minecart stop time in one or both buffers. The Minecart will then start automatically after the configured time.= +7. Optional: Configure the Minecart waiting time in both buffers. The Minecart will then start automatically after the configured time.= 8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).= 9. Place a Minecart in front of the buffer and check whether it starts after the configured time.= -Allow to dig/place rails in Minecart Landmark areas= -Minecart= -Minecart (Sneak+Click to pick up)= -Minecart Cart= -Minecart Hopper= -Minecart Landmark= -Minecart Railway Buffer= -Minecart, the lean railway transportation automation system= -Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get the items back= -Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)= -Station name= -Stop time/sec= -Summary= +10. Check the cart state via the chat command: /mycart @n '' is the cart number= +11. Drop items into the Minecart and punch the cart to start it.= +12. Dig the cart with 'sneak+click' (as usual). The items will be drop down.= +Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back= Used as buffer on both rail ends. Needed to be able to record the cart routes= +Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)= Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.= +Minecart= +Minecart, the lean railway transportation automation system= +Minecart Cart= +Minecart Speed Signs= +If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.= +Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.= +Minecart Hopper= +Minecart (Sneak+Click to pick up)= +Output cart state and position, or a list of carts, if no cart number is given.= +List of carts= +Enter cart number= +Save= [minecart] Area is protected!= -[minecart] Cart is protected by = -[minecart] Recording canceled!= +Allow to dig/place rails in Minecart Landmark areas= +Minecart Landmark= +Cart Pusher= +left= +right= +straight= +Recording= +speed= +next junction= +Travel time= [minecart] Route stored!= -[minecart] Start route recording!= -connected to= \ No newline at end of file +[minecart] Speed @= %u m/s, Time @= %u s, Route length @= %u m= +Speed "1"= +Speed "2"= +Speed "4"= +No speed limit= +Cart List= +Cart Terminal= diff --git a/minecart/minecart.lua b/minecart/minecart.lua index 6918d89..1237019 100644 --- a/minecart/minecart.lua +++ b/minecart/minecart.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -11,69 +11,92 @@ ]]-- local S = minecart.S -local MP = minetest.get_modpath("minecart") -local lib = dofile(MP.."/cart_lib1.lua") +local M = minetest.get_meta -lib:init(false) - -local cart_entity = { - initial_properties = { - physical = false, -- otherwise going uphill breaks - collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, - visual = "mesh", - mesh = "carts_cart.b3d", - visual_size = {x=1, y=1}, - textures = {"carts_cart.png^minecart_cart.png"}, - static_save = false, - }, - ------------------------------------ changed - owner = nil, - ------------------------------------ changed - driver = nil, - punched = false, -- used to re-send velocity and position - velocity = {x=0, y=0, z=0}, -- only used on punch - old_dir = {x=1, y=0, z=0}, -- random value to start the cart on punch - old_pos = nil, - old_switch = 0, - railtype = nil, - cargo = {}, - on_rightclick = lib.on_rightclick, - on_activate = lib.on_activate, - on_detach_child = lib.on_detach_child, - on_punch = lib.on_punch, - on_step = lib.on_step, -} - - -minetest.register_entity("minecart:cart", cart_entity) - -minecart.register_cart_names("minecart:cart", "minecart:cart") - - -minetest.register_craftitem("minecart:cart", { +minetest.register_node("minecart:cart", { description = S("Minecart (Sneak+Click to pick up)"), - inventory_image = minetest.inventorycube("carts_cart_top.png", "carts_cart_side.png^minecart_logo.png", "carts_cart_side.png^minecart_logo.png"), - wield_image = "carts_cart_side.png", - on_place = function(itemstack, placer, pointed_thing) - -- use cart as tool - local under = pointed_thing.under - local node = minetest.get_node(under) - local udef = minetest.registered_nodes[node.name] - if udef and udef.on_rightclick and - not (placer and placer:is_player() and - placer:get_player_control().sneak) then - return udef.on_rightclick(under, node, placer, itemstack, - pointed_thing) or itemstack - end + tiles = { + -- up, down, right, left, back, front + "carts_cart_top.png^minecart_appl_cart_top.png", + "carts_cart_top.png", + "carts_cart_side.png^minecart_logo.png", + "carts_cart_side.png^minecart_logo.png", + "carts_cart_side.png^minecart_logo.png", + "carts_cart_side.png^minecart_logo.png", + }, + drawtype = "nodebox", + node_box = { + type = "fixed", + fixed = { + {-8/16,-8/16,-8/16, 8/16, 8/16,-7/16}, + {-8/16,-8/16, 7/16, 8/16, 8/16, 8/16}, + {-8/16,-8/16,-8/16, -7/16, 8/16, 8/16}, + { 7/16,-8/16,-8/16, 8/16, 8/16, 8/16}, + {-8/16,-8/16,-8/16, 8/16,-6/16, 8/16}, + }, + }, +-- collision_box = { +-- type = "fixed", +-- fixed = { +-- {-8/16,-8/16,-8/16, 8/16,-4/16, 8/16}, +-- }, +-- }, + paramtype2 = "facedir", + paramtype = "light", + use_texture_alpha = minecart.CLIP, + sunlight_propagates = true, + is_ground_content = false, + groups = {cracky = 2, crumbly = 2, choppy = 2}, + node_placement_prediction = "", + diggable = false, + + on_place = minecart.on_nodecart_place, + on_punch = minecart.on_nodecart_punch, - if not pointed_thing.type == "node" then - return + 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 - - return lib.add_cart(itemstack, placer, pointed_thing, "minecart:cart") + end, + + set_cargo = function(pos, data) + for _,item in ipairs(data or {}) do + minetest.add_item(pos, ItemStack(item)) + end + end, + + get_cargo = function(pos) + local data = {} + for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do + local entity = obj:get_luaentity() + if not obj:is_player() and entity and entity.name == "__builtin:item" then + obj:remove() + data[#data + 1] = entity.itemstring + end + end + return data end, }) +minecart.register_cart_entity("minecart:cart_entity", "minecart:cart", "default", { + initial_properties = { + physical = false, + collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, + visual = "wielditem", + textures = {"minecart:cart"}, + visual_size = {x=0.66, y=0.66, z=0.66}, + static_save = false, + }, + driver_allowed = true, +}) + + minetest.register_craft({ output = "minecart:cart", recipe = { diff --git a/minecart/mods_support.lua b/minecart/mods_support.lua index 14aaaa7..cebb9ea 100644 --- a/minecart/mods_support.lua +++ b/minecart/mods_support.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information diff --git a/minecart/monitoring.lua b/minecart/monitoring.lua index a77d89e..4d66b5e 100644 --- a/minecart/monitoring.lua +++ b/minecart/monitoring.lua @@ -3,274 +3,271 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information ]]-- --- Some notes: --- 1) Entity IDs are volatile. For each server restart all carts get new IDs. --- 2) Monitoring is performed for entities only. Stopped carts in form of --- real nodes need no monitoring. --- 3) But nodes at stations have to call 'node_at_station' to be "visible" --- for the chat commands - - -- for lazy programmers local M = minetest.get_meta local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S2P = minetest.string_to_pos +local P2H = minetest.hash_node_position +local H2P = minetest.get_position_from_hash local S = minecart.S -local MP = minetest.get_modpath("minecart") -local lib = dofile(MP.."/cart_lib3.lua") -local CartsOnRail = minecart.CartsOnRail -- from storage.lua -local get_route = minecart.get_route -- from storage.lua -local NodesAtStation = {} +local tCartsOnRail = minecart.CartsOnRail +local Queue = {} +local first = 0 +local last = -1 --- --- Helper functions --- -local function get_pos_vel_pitch_yaw(item) - if item.start_time and item.start_key then -- cart on recorded route - local run_time = minetest.get_gametime() - item.start_time - local waypoints = get_route(item.start_key).waypoints - local waypoint = waypoints[run_time] - if waypoint then - return S2P(waypoint[1]), S2P(waypoint[2]), 0, 0 - end - end - if item.last_pos then - item.last_pos = vector.round(item.last_pos) - if carts:is_rail(item.last_pos, minetest.raillike_group("rail")) then - return item.last_pos, item.last_vel, item.last_pitch or 0, item.last_yaw or 0 - end - item.last_pos.y = item.last_pos.y - 1 - if carts:is_rail(item.last_pos, minetest.raillike_group("rail")) then - return item.last_pos, item.last_vel, item.last_pitch or 0, item.last_yaw or 0 - end - end - return item.start_pos, {x=0, y=0, z=0}, 0, 0 +local function push(cycle, item) + last = last + 1 + item.cycle = cycle + Queue[last] = item end --- --- Monitoring of cart entities --- -function minecart.add_to_monitoring(obj, myID, owner, userID) - local pos = vector.round(obj:get_pos()) - CartsOnRail[myID] = { - start_key = lib.get_route_key(pos), - start_pos = pos, - owner = owner, -- needed for query API - userID = userID, -- needed for query API - stopped = true, - entity_name = obj:get_entity_name() - } -end - --- Called after cart number formspec is closed -function minecart.update_userID(myID, userID) - if CartsOnRail[myID] then - CartsOnRail[myID].userID = userID +local function pop(cycle) + if first > last then return end + local item = Queue[first] + if item.cycle < cycle then + Queue[first] = nil -- to allow garbage collection + first = first + 1 + return item end end --- When cart entity is removed -function minecart.remove_from_monitoring(myID) - if myID then - CartsOnRail[myID] = nil - minecart.store_carts() - end -end - --- For node carts at stations -function minecart.node_at_station(owner, userID, pos) - NodesAtStation[owner] = NodesAtStation[owner] or {} - NodesAtStation[owner][userID] = pos -end - -function minecart.start_cart(pos, myID) - local item = CartsOnRail[myID] - if item and item.stopped then - item.stopped = false - item.start_pos = pos - item.start_time = nil - -- cart started from a buffer? - local start_key = lib.get_route_key(pos) - if start_key then - item.start_time = minetest.get_gametime() - item.start_key = start_key - item.junctions = minecart.get_route(start_key).junctions - minecart.store_carts() +local function is_player_nearby(pos) + for _, object in pairs(minetest.get_objects_inside_radius(pos, 64)) do + if object:is_player() then return true end end - return false end -function minecart.stop_cart(pos, myID) - local item = CartsOnRail[myID] - if item and not item.stopped then - item.start_time = nil - item.start_key = nil - item.start_pos = nil - item.junctions = nil - item.stopped = true - minecart.store_carts() - return true +local function zombie_to_entity(pos, cart, checkpoint) + local vel = {x = 0, y = 0, z = 0} + local obj = minecart.add_entitycart(pos, cart.node_name, cart.entity_name, + vel, cart.cargo, cart.owner, cart.userID) + if obj then + local entity = obj:get_luaentity() + entity.reenter = checkpoint + entity.junctions = cart.junctions + entity.is_running = true + entity.arrival_time = 0 + cart.objID = entity.objID end - return false end -local function monitoring() - local to_be_added = {} - for key, item in pairs(CartsOnRail) do - local entity = minetest.luaentities[key] - --print("Cart:", key, item.owner, item.userID, item.stopped) - if entity then -- cart entity running - local pos = entity.object:get_pos() - local vel = entity.object:get_velocity() - local rot = entity.object:get_rotation() - if pos and vel and rot then - if not minetest.get_node_or_nil(pos) then -- unloaded area - lib.unload_cart(pos, vel, entity, item) - item.stopped = minecart.stopped(vel) - end - -- store last pos from cart - item.last_pos, item.last_vel, item.last_pitch, item.last_yaw = pos, vel, rot.x, rot.y - end - else -- no cart running - local pos, vel, pitch, yaw = get_pos_vel_pitch_yaw(item) - if pos and vel then - if minetest.get_node_or_nil(pos) then -- loaded area - if pitch > 0 then - pos.y = pos.y + 0.5 - end - local myID = lib.load_cart(pos, vel, pitch, yaw, item) - if myID then - item.stopped = minecart.stopped(vel) - to_be_added[myID] = table.copy(item) - CartsOnRail[key] = nil -- invalid old ID - end - end - item.last_pos, item.last_vel, item.last_pitch, item.last_yaw = pos, vel, pitch, yaw - else - -- should never happen - minetest.log("error", "[minecart] Cart of owner "..(item.owner or "nil").." got lost") - CartsOnRail[key] = nil - end +local function get_checkpoint(cart) + local cp = cart.checkpoints[cart.idx] + if not cp then + cart.idx = #cart.checkpoints + cp = cart.checkpoints[cart.idx] + end + local pos = H2P(cp[1]) +-- if M(pos):contains("waypoints") then +-- print("get_checkpoint", P2S(H2P(cp[1])), P2S(H2P(cp[2]))) +-- end + return cp, cart.idx == #cart.checkpoints +end + +-- Function returns the cart state ("running" / "stopped") and +-- the station name or position string, or if cart is running, +-- the distance to the query_pos. +local function get_cart_state_and_loc(name, userID, query_pos) + if tCartsOnRail[name] and tCartsOnRail[name][userID] then + local cart = tCartsOnRail[name][userID] + local pos = cart.last_pos or cart.pos + local loc = minecart.get_buffer_name(cart.pos) or + math.floor(vector.distance(pos, query_pos)) + if cart.objID == 0 then + return "stopped", minecart.get_buffer_name(cart.pos) or + math.floor(vector.distance(pos, query_pos)), cart.node_name + else + return "running", math.floor(vector.distance(pos, query_pos)), cart.node_name end end - -- table maintenance - local is_changed = false - for key,val in pairs(to_be_added) do - CartsOnRail[key] = val - is_changed = true + return "unknown", 0, "unknown" +end + +local function get_cart_info(owner, userID, query_pos) + local state, loc, name = get_cart_state_and_loc(owner, userID, query_pos) + local cart_type = minecart.tCartTypes[name] or "unknown" + if type(loc) == "number" then + return "Cart #" .. userID .. " (" .. cart_type .. ") " .. state .. " " .. loc .. " m away " + else + return "Cart #" .. userID .. " (" .. cart_type .. ") " .. state .. " at ".. loc .. " " end - 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() end - minetest.after(1, monitoring) end --- delay the start to prevent cart disappear into nirvana -minetest.register_on_mods_loaded(function() - minetest.after(10, monitoring) -end) + +function minecart.stop_monitoring(owner, userID, pos) + --print("stop_monitoring", owner, userID) + if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then + tCartsOnRail[owner][userID].pos = pos + tCartsOnRail[owner][userID].objID = 0 + minecart.store_carts() + end +end + +function minecart.monitoring_remove_cart(owner, userID) + --print("monitoring_remove_cart", owner, userID) + if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then + tCartsOnRail[owner][userID].objID = nil + tCartsOnRail[owner][userID] = nil + minecart.store_carts() + end +end + +function minecart.monitoring_valid_cart(owner, userID, pos, node_name) + if tCartsOnRail[owner] and tCartsOnRail[owner][userID] then + return vector.equals(tCartsOnRail[owner][userID].pos, pos) and + tCartsOnRail[owner][userID].node_name == node_name + end +end + +function minecart.userID_available(owner, userID) + return not tCartsOnRail[owner] or tCartsOnRail[owner][userID] == nil +end + +function minecart.get_cart_monitoring_data(owner, userID) + if tCartsOnRail[owner] then + return tCartsOnRail[owner][userID] + end +end -- -- API functions -- --- Return a list of carts with current position and speed. -function minecart.get_cart_list() - local tbl = {} - for id, item in pairs(CartsOnRail) do - local pos, speed = calc_pos_and_vel(item) - tbl[#tbl+1] = {pos = pos, speed = speed, id = id} - end - return tbl -end - -local function get_cart_pos(query_pos, cart_pos) - local dist = math.floor(vector.distance(cart_pos, query_pos)) - local station = lib.get_station_name(cart_pos) - return station or dist -end - -local function get_cart_state(name, userID) - for id, item in pairs(CartsOnRail) do - if item.owner == name and item.userID == userID then - return item.stopped and "stopped" or "running", item.last_pos - end - end - return nil, nil -end +-- Needed by storage to re-construct the queue after server start +minecart.push = push minetest.register_chatcommand("mycart", { params = "", - description = "Output cart state and position, or a list of carts, if no cart number is given.", - func = function(name, param) + description = S("Output cart state and position, or a list of carts, if no cart number is given."), + func = function(owner, param) local userID = tonumber(param) - local query_pos = minetest.get_player_by_name(name):get_pos() + local query_pos = minetest.get_player_by_name(owner):get_pos() if userID then - -- First check if it is a node cart at a station - local cart_pos = NodesAtStation[name] and NodesAtStation[name][userID] - if cart_pos then - local pos = get_cart_pos(query_pos, cart_pos) - return true, "Cart #"..userID.." stopped at "..pos.." " - end - -- Check all running carts - local state, cart_pos = get_cart_state(name, userID) - if state and cart_pos then - local pos = get_cart_pos(query_pos, cart_pos) - if type(pos) == "string" then - return true, "Cart #"..userID.." stopped at "..pos.." " - elseif state == "running" then - return true, "Cart #"..userID.." running "..pos.." m away " - else - return true, "Cart #"..userID.." stopped "..pos.." m away " - end - end - return false, "Cart #"..userID.." is unknown" - else + return true, get_cart_info(owner, userID, query_pos) + elseif tCartsOnRail[owner] then -- Output a list with all numbers local tbl = {} - for userID, pos in pairs(NodesAtStation[name] or {}) do + for userID, cart in pairs(tCartsOnRail[owner]) do tbl[#tbl + 1] = userID end - for id, item in pairs(CartsOnRail) do - if item.owner == name then - tbl[#tbl + 1] = item.userID - end - end - return true, "List of carts: "..table.concat(tbl, ", ").." " + return true, S("List of carts") .. ": "..table.concat(tbl, ", ").." " end end }) function minecart.cmnd_cart_state(name, userID) - -- First check if it is a node cart at a station - local pos = NodesAtStation[name] and NodesAtStation[name][userID] - if pos then - return "stopped" - end - return get_cart_state(name, userID) + local state, loc = get_cart_state_and_loc(name, userID, {x=0, y=0, z=0}) + return state end function minecart.cmnd_cart_location(name, userID, query_pos) - -- First check if it is a node cart at a station - local station = NodesAtStation[name] and NodesAtStation[name][userID] - if station then - return station + local state, loc = get_cart_state_and_loc(name, userID, query_pos) + return loc +end + +function minecart.get_cart_list(pos, name) + local userIDs = {} + local carts = {} + + for userID, cart in pairs(tCartsOnRail[name] or {}) do + userIDs[#userIDs + 1] = userID end - local state, cart_pos = get_cart_state(name, userID) - if state then - return get_cart_pos(query_pos, cart_pos) + + table.sort(userIDs, function(a,b) return a < b end) + + for _, userID in ipairs(userIDs) do + carts[#carts + 1] = get_cart_info(name, userID, pos) end + + return table.concat(carts, "\n") end minetest.register_on_mods_loaded(function() @@ -357,4 +354,3 @@ minetest.register_on_mods_loaded(function() }) end end) - diff --git a/minecart/nodelib.lua b/minecart/nodelib.lua new file mode 100644 index 0000000..efb540c --- /dev/null +++ b/minecart/nodelib.lua @@ -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) diff --git a/minecart/protection.lua b/minecart/protection.lua index bc02bfc..6d4a542 100644 --- a/minecart/protection.lua +++ b/minecart/protection.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -198,3 +198,8 @@ minecart.register_protected_node("minecart:buffer") minecart.register_protected_node("minecart:ballast") minecart.register_protected_node("minecart:ballast_slope") minecart.register_protected_node("minecart:ballast_ramp") +minecart.register_protected_node("minecart:speed1") +minecart.register_protected_node("minecart:speed2") +minecart.register_protected_node("minecart:speed4") +minecart.register_protected_node("minecart:speed8") + diff --git a/minecart/pusher.lua b/minecart/pusher.lua new file mode 100644 index 0000000..bc49ad5 --- /dev/null +++ b/minecart/pusher.lua @@ -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"}, + }, +}) diff --git a/minecart/rails.lua b/minecart/rails.lua new file mode 100644 index 0000000..b7b5622 --- /dev/null +++ b/minecart/rails.lua @@ -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, +--}) + diff --git a/minecart/recording.lua b/minecart/recording.lua index a5d5996..d516a47 100644 --- a/minecart/recording.lua +++ b/minecart/recording.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -14,76 +14,172 @@ local M = minetest.get_meta local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S2P = minetest.string_to_pos +local P2H = minetest.hash_node_position +local H2P = minetest.get_position_from_hash local S = minecart.S -local MP = minetest.get_modpath("minecart") -local lib = dofile(MP.."/cart_lib3.lua") -local CartsOnRail = minecart.CartsOnRail -- from storage.lua -local get_route = minecart.get_route -- from storage.lua +local function dashboard_destroy(self) + if self.driver and self.hud_id then + local player = minetest.get_player_by_name(self.driver) + if player then + player:hud_remove(self.hud_id) + self.hud_id = nil + end + end +end + +local function dashboard_create(self) + if self.driver then + local player = minetest.get_player_by_name(self.driver) + if player then + dashboard_destroy(self) + self.hud_id = player:hud_add({ + name = "minecart", + hud_elem_type = "text", + position = {x = 0.4, y = 0.25}, + scale = {x=100, y=100}, + text = "Recording:", + number = 0xFFFFFF, + size = {x = 1}, + }) + end + end +end + +local function dashboard_update(self) + if self.driver and self.hud_id then + local player = minetest.get_player_by_name(self.driver) + if player then + local time = self.runtime or 0 + local dir = (self.ctrl and self.ctrl.left and S("left")) or + (self.ctrl and self.ctrl.right and S("right")) or S("straight") + local speed = math.floor((self.curr_speed or 0) + 0.5) + local s = string.format(S("Recording") .. + " | " .. S("speed") .. + ": %.1f | " .. S("next junction") .. + ": %-8s | " .. S("Travel time") .. ": %.1f s", + speed, dir, time) + player:hud_change(self.hud_id, "text", s) + end + end +end + +local function check_waypoint(self, pos) + -- If next waypoint already reached but not handled + -- determine next waypoint + if vector.equals(pos, self.waypoint.pos) then + local rot = self.object:get_rotation() + local dir = minetest.yaw_to_dir(rot.y) + dir.y = math.floor((rot.x / (math.pi/4)) + 0.5) + local facedir = minetest.dir_to_facedir(dir) + local waypoint = minecart.get_waypoint(pos, facedir, self.ctrl or {}, false) + if waypoint then + return waypoint.pos + end + end + return self.waypoint.pos +end -- -- Route recording -- function minecart.start_recording(self, pos) - self.start_key = lib.get_route_key(pos, self.driver) - if self.start_key then - self.waypoints = {} - self.junctions = {} - self.recording = true - self.next_time = minetest.get_us_time() + 1000000 - minetest.chat_send_player(self.driver, S("[minecart] Start route recording!")) - end -end - -function minecart.store_next_waypoint(self, pos, vel) - if self.start_key and self.recording and self.driver and - self.next_time < minetest.get_us_time() then - self.next_time = minetest.get_us_time() + 1000000 - self.waypoints[#self.waypoints+1] = {P2S(vector.round(pos)), P2S(vector.round(vel))} - elseif self.recording and not self.driver then - self.recording = false - self.waypoints = nil - self.junctions = nil - end -end - --- destination reached(speed == 0) -function minecart.stop_recording(self, pos, vel, puncher) - local dest_pos = lib.get_route_key(pos, self.driver) - if dest_pos then - if self.start_key and self.start_key ~= dest_pos then - 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!")) + --print("start_recording") + if self.driver then + self.start_pos = minecart.get_buffer_pos(pos, self.driver) + if self.start_pos then + self.checkpoints = {} -- {cart_pos, next_waypoint_pos, speed, dot} + self.junctions = {} + self.is_recording = true + self.rec_time = self.timebase + self.hud_time = self.timebase + self.runtime = 0 + self.num_sections = 0 + self.sum_speed = 0 + self.ctrl = {} + dashboard_create(self) + dashboard_update(self, 0) end - else - minetest.chat_send_player(self.driver, S("[minecart] Recording canceled!")) 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.junctions = nil end -function minecart.set_junction(self, pos, dir, switch_keys) - if self.junctions then - self.junctions[P2S(vector.round(pos))] = {dir, switch_keys} - end +function minecart.recording_waypoints(self) + local pos = vector.round(self.object:get_pos()) + -- hier müsste überprüfung dest_pos rein + self.sum_speed = self.sum_speed + self.curr_speed + local wp_pos = check_waypoint(self, pos) + self.checkpoints[#self.checkpoints+1] = { + -- cart_pos, next_waypoint_pos, speed, dot + P2H(pos), + P2H(wp_pos), + math.floor(self.curr_speed + 0.5), + self.waypoint.dot + } end -function minecart.get_junction(self, pos, dir) - local junctions = CartsOnRail[self.myID] and CartsOnRail[self.myID].junctions - if junctions then - local data = junctions[P2S(vector.round(pos))] - if data then - return data[1], data[2] +function minecart.recording_junctions(self) + local player = minetest.get_player_by_name(self.driver) + if player then + local ctrl = player:get_player_control() + if ctrl.left then + self.ctrl = {left = true} + elseif ctrl.right then + self.ctrl = {right = true} + elseif ctrl.up or ctrl.down then + self.ctrl = nil end end - 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 +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 diff --git a/minecart/signs.lua b/minecart/signs.lua new file mode 100644 index 0000000..266e161 --- /dev/null +++ b/minecart/signs.lua @@ -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"} +}) + diff --git a/minecart/storage.lua b/minecart/storage.lua index 34d3f02..04e471f 100644 --- a/minecart/storage.lua +++ b/minecart/storage.lua @@ -3,7 +3,7 @@ Minecart ======== - Copyright (C) 2019-2020 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg MIT See license.txt for more information @@ -14,24 +14,75 @@ local M = minetest.get_meta local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end local S2P = minetest.string_to_pos +local P2H = minetest.hash_node_position +local H2P = minetest.get_position_from_hash local S = minecart.S local storage = minetest.get_mod_storage() +local function place_carts(t) + local Carts = { + ["minecart:cart"] = "minecart:cart", + ["techage:tank_cart_entity"] = "techage:tank_cart", + ["techage:chest_cart_entity"] = "techage:chest_cart", + } + for id, item in pairs(t) do + local pos = vector.round((item.start_pos or item.last_pos)) + local name = Carts[item.entity_name] or "minecart:cart" + --print(P2S(pos), name, item.owner, item.userID) + if minetest.registered_nodes[name] then + minecart.add_nodecart(pos, name, 0, {}, item.owner or "", item.userID or 0) + end + end +end + ------------------------------------------------------------------------------- -- Store data of running carts ------------------------------------------------------------------------------- minecart.CartsOnRail = {} minetest.register_on_mods_loaded(function() - for key,val in pairs(minetest.deserialize(storage:get_string("CartsOnRail")) or {}) do - -- use invalid keys to force the cart spawning - minecart.CartsOnRail[-key] = val + local version = storage:get_int("version") + if version < 2 then + local t = minetest.deserialize(storage:get_string("CartsOnRail")) or {} + minetest.after(5, place_carts, t) + storage:set_int("version", 2) + else + local t = minetest.deserialize(storage:get_string("CartsOnRail")) or {} + for owner, carts in pairs(t) do + minecart.CartsOnRail[owner] = {} + for userID, cart in pairs(carts) do + print("reload cart", owner, userID, cart.objID) + minecart.CartsOnRail[owner][userID] = cart + -- mark all entity carts as zombified + if cart.objID and cart.objID ~= 0 then + cart.objID = -1 + minecart.push(1, cart) + end + end + end + end +end) + +minetest.after(10, function() + for owner, carts in pairs(minecart.CartsOnRail) do + for userID, cart in pairs(carts) do + -- Remove node carts that are not available anymore + if cart.objID == 0 or not cart.objID then + local node = minecart.get_node_lvm(cart.pos) + if not minecart.tNodeNames[node.name] then + -- Mark as "to be deleted" + print("Node cart deleted", owner, userID) + minecart.CartsOnRail[owner][userID] = nil + end + end + end end end) minetest.register_on_shutdown(function() storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail)) + print("minecart shutdown finished!!!") end) function minecart.store_carts() @@ -39,69 +90,31 @@ function minecart.store_carts() end ------------------------------------------------------------------------------- --- Store routes +-- Store routes (in buffers) ------------------------------------------------------------------------------- --- All positions as "pos_to_string" string ---Routes = { --- start_pos = { --- waypoints = {{spos, svel}, {spos, svel}, ...}, --- dest_pos = spos, --- junctions = { --- {spos = num}, --- {spos = num}, --- }, --- }, --- start_pos = {...}, ---} -local Routes = {} -local NEW_ROUTE = {waypoints = {}, junctions = {}} - -function minecart.store_route(key, route) - if key and route then - Routes[key] = route - local meta = M(S2P(key)) - if meta then - meta:set_string("route", minetest.serialize(route)) - return true - end +function minecart.store_route(pos, route) + if pos and route then + M(pos):set_string("route", minetest.serialize(route)) + return true end return false end -function minecart.get_route(key) - if not Routes[key] then - local s = M(S2P(key)):get_string("route") +function minecart.get_route(pos) + if pos then + local s = M(pos):get_string("route") if s ~= "" then - Routes[key] = minetest.deserialize(s) or NEW_ROUTE - else - Routes[key] = NEW_ROUTE - end - end - return Routes[key] -end - -function minecart.del_route(key) - Routes[key] = nil -- remove from memory - M(S2P(key)):set_string("route", "") -- and as metadata -end - -------------------------------------------------------------------------------- --- Convert data to v2 -------------------------------------------------------------------------------- -minetest.after(5, function() - local tbl = storage:to_table() - for key,s in pairs(tbl.fields) do - if key ~= "CartsOnRail" then local route = minetest.deserialize(s) - if route.waypoints and route.junctions then - if minecart.store_route(key, route) then - storage:set_string(key, "") - end - else - storage:set_string(key, "") + if route.waypoints then + M(pos):set_string("route", "") + M(pos):set_int("time", 0) + return end + return minetest.deserialize(s) end end -end) - +end +function minecart.del_route(pos) + M(pos):set_string("route", "") +end diff --git a/minecart/terminal.lua b/minecart/terminal.lua new file mode 100644 index 0000000..60420de --- /dev/null +++ b/minecart/terminal.lua @@ -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"}, + }, +}) diff --git a/minecart/textures/minecart_appl_cart_top.png b/minecart/textures/minecart_appl_cart_top.png new file mode 100644 index 0000000..9a11a0b Binary files /dev/null and b/minecart/textures/minecart_appl_cart_top.png differ diff --git a/minecart/textures/minecart_appl_hopper.png b/minecart/textures/minecart_appl_hopper.png index 7c703e2..7eaf8a2 100644 Binary files a/minecart/textures/minecart_appl_hopper.png and b/minecart/textures/minecart_appl_hopper.png differ diff --git a/minecart/textures/minecart_appl_hopper_right.png b/minecart/textures/minecart_appl_hopper_right.png index 5e8c302..48e1037 100644 Binary files a/minecart/textures/minecart_appl_hopper_right.png and b/minecart/textures/minecart_appl_hopper_right.png differ diff --git a/minecart/textures/minecart_appl_hopper_top.png b/minecart/textures/minecart_appl_hopper_top.png index 97ce3c2..4c2fb4d 100644 Binary files a/minecart/textures/minecart_appl_hopper_top.png and b/minecart/textures/minecart_appl_hopper_top.png differ diff --git a/minecart/textures/minecart_ballast.png b/minecart/textures/minecart_ballast.png index 9bca948..0c99602 100644 Binary files a/minecart/textures/minecart_ballast.png and b/minecart/textures/minecart_ballast.png differ diff --git a/minecart/textures/minecart_buffer.png b/minecart/textures/minecart_buffer.png index 1bb752e..19ff9a0 100644 Binary files a/minecart/textures/minecart_buffer.png and b/minecart/textures/minecart_buffer.png differ diff --git a/minecart/textures/minecart_cart.png b/minecart/textures/minecart_cart.png index 621b8d2..2d48b12 100644 Binary files a/minecart/textures/minecart_cart.png and b/minecart/textures/minecart_cart.png differ diff --git a/minecart/textures/minecart_doc_image.png b/minecart/textures/minecart_doc_image.png index 209b412..07e22db 100644 Binary files a/minecart/textures/minecart_doc_image.png and b/minecart/textures/minecart_doc_image.png differ diff --git a/minecart/textures/minecart_logo.png b/minecart/textures/minecart_logo.png index f7e2e6c..17ff7b2 100644 Binary files a/minecart/textures/minecart_logo.png and b/minecart/textures/minecart_logo.png differ diff --git a/minecart/textures/minecart_marker_cube.png b/minecart/textures/minecart_marker_cube.png new file mode 100644 index 0000000..1321451 Binary files /dev/null and b/minecart/textures/minecart_marker_cube.png differ diff --git a/minecart/textures/minecart_protect.png b/minecart/textures/minecart_protect.png index def72c7..6ca8308 100644 Binary files a/minecart/textures/minecart_protect.png and b/minecart/textures/minecart_protect.png differ diff --git a/minecart/textures/minecart_pusher.png b/minecart/textures/minecart_pusher.png new file mode 100644 index 0000000..12704ed Binary files /dev/null and b/minecart/textures/minecart_pusher.png differ diff --git a/minecart/textures/minecart_pusher_top.png b/minecart/textures/minecart_pusher_top.png new file mode 100644 index 0000000..f0c4179 Binary files /dev/null and b/minecart/textures/minecart_pusher_top.png differ diff --git a/minecart/textures/minecart_sign1.png b/minecart/textures/minecart_sign1.png new file mode 100644 index 0000000..240b44e Binary files /dev/null and b/minecart/textures/minecart_sign1.png differ diff --git a/minecart/textures/minecart_sign2.png b/minecart/textures/minecart_sign2.png new file mode 100644 index 0000000..adf879d Binary files /dev/null and b/minecart/textures/minecart_sign2.png differ diff --git a/minecart/textures/minecart_sign4.png b/minecart/textures/minecart_sign4.png new file mode 100644 index 0000000..f981cc7 Binary files /dev/null and b/minecart/textures/minecart_sign4.png differ diff --git a/minecart/textures/minecart_sign8.png b/minecart/textures/minecart_sign8.png new file mode 100644 index 0000000..7190be3 Binary files /dev/null and b/minecart/textures/minecart_sign8.png differ diff --git a/minecart/textures/minecart_terminal_back.png b/minecart/textures/minecart_terminal_back.png new file mode 100644 index 0000000..daeff71 Binary files /dev/null and b/minecart/textures/minecart_terminal_back.png differ diff --git a/minecart/textures/minecart_terminal_front.png b/minecart/textures/minecart_terminal_front.png new file mode 100644 index 0000000..c6bf658 Binary files /dev/null and b/minecart/textures/minecart_terminal_front.png differ diff --git a/minecart/textures/minecart_terminal_side.png b/minecart/textures/minecart_terminal_side.png new file mode 100644 index 0000000..6102404 Binary files /dev/null and b/minecart/textures/minecart_terminal_side.png differ diff --git a/minecart/textures/minecart_terminal_top.png b/minecart/textures/minecart_terminal_top.png new file mode 100644 index 0000000..5604b88 Binary files /dev/null and b/minecart/textures/minecart_terminal_top.png differ diff --git a/minecart/textures/minecart_tool.png b/minecart/textures/minecart_tool.png new file mode 100644 index 0000000..4fddeb2 Binary files /dev/null and b/minecart/textures/minecart_tool.png differ diff --git a/minecart/textures/minecart_waypoint.png b/minecart/textures/minecart_waypoint.png new file mode 100644 index 0000000..d607a53 Binary files /dev/null and b/minecart/textures/minecart_waypoint.png differ diff --git a/minecart/textures/shrink.py b/minecart/textures/shrink.py deleted file mode 100644 index 96863b9..0000000 --- a/minecart/textures/shrink.py +++ /dev/null @@ -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]) - diff --git a/minecart/textures/shrink.sh b/minecart/textures/shrink.sh new file mode 100755 index 0000000..36036c5 --- /dev/null +++ b/minecart/textures/shrink.sh @@ -0,0 +1,2 @@ +#!/bin/bash +pngquant --skip-if-larger --quality=80 --strip *.png --ext .png --force \ No newline at end of file diff --git a/minecart/tool.lua b/minecart/tool.lua new file mode 100644 index 0000000..c5b192c --- /dev/null +++ b/minecart/tool.lua @@ -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, +}) diff --git a/signs_bot/README.md b/signs_bot/README.md index b7831c5..fec834c 100644 --- a/signs_bot/README.md +++ b/signs_bot/README.md @@ -96,6 +96,7 @@ For all Inventory commands applies: If the inventory stack specified by i pattern - save the blocks behind the shield (up to 5x3x3) as template copy - make a copy of "pattern". Size is e.g. 3x3 (see ingame help) punch_cart - Punch a rail cart to start it + print - Output chat message for debug purposes #### Flow control commands @@ -169,4 +170,7 @@ optional: farming redo, node_io, doc, techage, minecart - 2020-06-21 v1.03 * Interpreter bugfixes, node and crop sensors changed - 2020-10-01 v1.04 * Many improvements and bugfixes (Thanks to Thomas-S) - 2021-01-30 v1.05 * Many improvements and bugfixes +- 2021-03-14 v1.06 * Switch translation from intllib to minetest.translator +- 2021-04-24 v1.07 * Adapted to minecart v2.0 +- 2021-05-04 v1.08 * Add print command, improve error msg diff --git a/signs_bot/basis.lua b/signs_bot/basis.lua index c5c0bf9..7f9ef00 100644 --- a/signs_bot/basis.lua +++ b/signs_bot/basis.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,10 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -112,16 +109,33 @@ local function preassigned_slots(pos) return table.concat(tbl, "") end +local function status(mem) + if mem.error then + if type(mem.error) == "string" then + return mem.error + else + return dump(mem.error) + end + end + if mem.running then + return S("running") + end + if mem.charging then + return S("charging") + end + return S("stopped") +end + local function formspec(pos, mem) mem.running = mem.running or false - local cmnd = mem.running and "stop;"..I("Off") or "start;"..I("On") + local cmnd = mem.running and "stop;"..S("Off") or "start;"..S("On") local bot = not mem.running and "image[0.6,0;1,1;signs_bot_bot_inv.png]" or "" local current_capa = mem.capa or (signs_bot.MAX_CAPA * 0.9) - return "size[9,7.6]".. + return "size[9,8.2]".. default.gui_bg.. default.gui_bg_img.. default.gui_slots.. - "label[2.1,0;"..I("Signs").."]label[5.3,0;"..I("Other items").."]".. + "label[2.1,0;"..S("Signs").."]label[5.3,0;"..S("Other items").."]".. "image[0.6,0;1,1;signs_bot_form_mask.png]".. bot.. preassigned_slots(pos).. @@ -132,24 +146,27 @@ local function formspec(pos, mem) "label[5.3,0.5;1]label[6.3,0.5;2]label[7.3,0.5;3]label[8.3,0.5;4]".. "list[context;main;5,1;4,2;]".. "label[5.3,3;5]label[6.3,3;6]label[7.3,3;7]label[8.3,3;8]".. - "button[0.2,1;1.5,1;config;"..I("Config").."]".. + "button[0.2,1;1.5,1;config;"..S("Config").."]".. "button[0.2,2;1.5,1;"..cmnd.."]".. - "list[current_player;main;0.5,3.8;8,4;]".. + "label[1,3.6;"..status(mem).."]".. + "list[current_player;main;0.5,4.4;8,4;]".. "listring[context;main]".. "listring[current_player;main]" end -local function formspec_cfg(pos, mem) - return "size[9,7.6]".. +local function formspec_cfg() + return "size[9,8.2]".. default.gui_bg.. default.gui_bg_img.. default.gui_slots.. - "label[5.3,0;"..I("Preassign slots items").."]".. + "label[5.3,0;"..S("Preassign slots items").."]".. "label[5.3,0.5;1]label[6.3,0.5;2]label[7.3,0.5;3]label[8.3,0.5;4]".. "list[context;filter;5,1;4,2;]".. "label[5.3,3;5]label[6.3,3;6]label[7.3,3;7]label[8.3,3;8]".. - "button[0.2,1;1.5,1;back;"..I("Back").."]".. - "list[current_player;main;0.5,3.8;8,4;]" + "button[0.2,1;1.5,1;back;"..S("Back").."]".. + "list[current_player;main;0.5,4.4;8,4;]".. + "listring[context;filter]".. + "listring[current_player;main]" end local function get_capa(itemstack) @@ -166,7 +183,7 @@ local function set_capa(pos, oldnode, digger, capa) capa = techage.power.percent(signs_bot.MAX_CAPA, capa) capa = (math.floor((capa or 0) / 5)) * 5 meta:set_int("capa", capa) - local text = I("Robot Box ").." ("..capa.." %)" + local text = S("Robot Box").." ("..capa.." %)" meta:set_string("description", text) local inv = minetest.get_inventory({type="player", name=digger:get_player_name()}) local left_over = inv:add_item("main", node) @@ -179,7 +196,7 @@ function signs_bot.infotext(pos, state) local meta = M(pos) local number = meta:get_string("number") state = state or "" - meta:set_string("infotext", I("Robot Box ")..number..": "..state) + meta:set_string("infotext", S("Robot Box").." "..number..": "..state) end local function reset_robot(pos, mem) @@ -205,7 +222,7 @@ function signs_bot.start_robot(base_pos) mem.capa = nil end meta:set_string("formspec", formspec(base_pos, mem)) - signs_bot.infotext(base_pos, I("running")) + signs_bot.infotext(base_pos, S("running")) reset_robot(base_pos, mem) minetest.get_node_timer(base_pos):start(CYCLE_TIME) return true @@ -224,9 +241,9 @@ function signs_bot.stop_robot(base_pos, mem) mem.charging = false end if mem.power_available then - signs_bot.infotext(base_pos, I("charging")) + signs_bot.infotext(base_pos, S("charging")) else - signs_bot.infotext(base_pos, I("stopped")) + signs_bot.infotext(base_pos, S("stopped")) end meta:set_string("formspec", formspec(base_pos, mem)) signs_bot.remove_robot(mem) @@ -285,7 +302,7 @@ local function on_receive_fields(pos, formname, fields, player) if fields.update then meta:set_string("formspec", formspec(pos, mem)) elseif fields.config then - meta:set_string("formspec", formspec_cfg(pos, mem)) + meta:set_string("formspec", formspec_cfg()) elseif fields.back then meta:set_string("formspec", formspec(pos, mem)) elseif fields.start then @@ -373,6 +390,7 @@ end local function on_power(pos) local mem = tubelib2.get_mem(pos) mem.power_available = true + mem.charging = true signs_bot.infotext(pos, S("charging")) end @@ -383,7 +401,7 @@ local function on_nopower(pos) end minetest.register_node("signs_bot:box", { - description = I("Signs Bot Box"), + description = S("Signs Bot Box"), stack_max = 1, tiles = { -- up, down, right, left, back, front @@ -417,7 +435,7 @@ minetest.register_node("signs_bot:box", { meta:set_string("formspec", formspec(pos, mem)) meta:set_string("signs_bot_cmnd", "turn_off") meta:set_int("err_code", 0) - signs_bot.infotext(pos, I("stopped")) + signs_bot.infotext(pos, S("stopped")) if minetest.global_exists("techage") then techage.ElectricCable:after_place_node(pos) mem.capa = get_capa(itemstack) @@ -473,12 +491,12 @@ minetest.register_node("signs_bot:box", { on_power = function(pos) local mem = tubelib2.get_mem(pos) mem.power_available = true - signs_bot.infotext(pos, I("charging")) + signs_bot.infotext(pos, S("charging")) end, on_nopower = function(pos) local mem = tubelib2.get_mem(pos) mem.power_available = false - signs_bot.infotext(pos, I("no power")) + signs_bot.infotext(pos, S("no power")) end, nominal = PWR_NEEDED, } @@ -516,22 +534,22 @@ end if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "box", { - name = I("Signs Bot Box"), + name = S("Signs Bot Box"), data = { item = "signs_bot:box", text = table.concat({ - I("The Box is the housing of the bot."), - I("Place the box and start the bot by means of the 'On' button."), - I("If the mod techage is installed, the bot needs electrical power."), + S("The Box is the housing of the bot."), + S("Place the box and start the bot by means of the 'On' button."), + S("If the mod techage is installed, the bot needs electrical power."), "", - I("The bot leaves the box on the right side."), - I("It will not start, if this position is blocked."), + S("The bot leaves the box on the right side."), + S("It will not start, if this position is blocked."), "", - I("To stop and remove the bot, press the 'Off' button."), + S("To stop and remove the bot, press the 'Off' button."), "", - I("The box inventory simulates the inventory of the bot."), - I("You will not be able to access the inventory, if the bot is running."), - I("The bot can carry up to 8 stacks and 6 signs with it."), + S("The box inventory simulates the inventory of the bot."), + S("You will not be able to access the inventory, if the bot is running."), + S("The bot can carry up to 8 stacks and 6 signs with it."), }, "\n") }, }) diff --git a/signs_bot/bot_flap.lua b/signs_bot/bot_flap.lua index e4b9347..ed2ecaa 100644 --- a/signs_bot/bot_flap.lua +++ b/signs_bot/bot_flap.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPLv3 See LICENSE.txt for more information @@ -12,14 +12,8 @@ ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local function formspec(cmnd) cmnd = minetest.formspec_escape(cmnd) @@ -28,7 +22,7 @@ local function formspec(cmnd) default.gui_bg_img.. default.gui_slots.. "label[0.3,0.3;"..cmnd.."]".. - "button_exit[2.5,5.5;2,1;exit;"..I("Exit").."]" + "button_exit[2.5,5.5;2,1;exit;"..S("Exit").."]" end local commands = [[dig_sign 6 @@ -37,7 +31,7 @@ place_sign_behind 6 ]] minetest.register_node("signs_bot:bot_flap", { - description = "Bot Flap", + description = S("Bot Flap"), paramtype2 = "facedir", tiles = { "signs_bot_bot_flap_top.png", @@ -69,13 +63,13 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "bot_flap", { - name = I("Bot Flap"), + name = S("Bot Flap"), data = { item = "signs_bot:bot_flap", text = table.concat({ - I("The flap is a simple block used as door for the bot."), - I("Place the flap in any wall, and the bot will automatically open"), - I("and close the flap as it passes through it."), + S("The flap is a simple block used as door for the bot."), + S("Place the flap in any wall, and the bot will automatically open"), + S("and close the flap as it passes through it."), }, "\n") }, }) diff --git a/signs_bot/bot_sensor.lua b/signs_bot/bot_sensor.lua index db8c037..0d2e90f 100644 --- a/signs_bot/bot_sensor.lua +++ b/signs_bot/bot_sensor.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,22 +13,19 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") - -local lib = signs_bot.lib +-- Load support for I18n. +local S = signs_bot.S local function update_infotext(pos, dest_pos, cmnd) - M(pos):set_string("infotext", I("Bot Sensor: Connected with ")..S(dest_pos).." / "..cmnd) + M(pos):set_string("infotext", S("Bot Sensor: Connected with").." "..P2S(dest_pos).." / "..cmnd) end minetest.register_node("signs_bot:bot_sensor", { - description = I("Bot Sensor"), + description = S("Bot Sensor"), inventory_image = "signs_bot_sensor_bot_inv.png", drawtype = "nodebox", node_box = { @@ -49,7 +46,7 @@ minetest.register_node("signs_bot:bot_sensor", { after_place_node = function(pos, placer) local meta = M(pos) - meta:set_string("infotext", I("Bot Sensor: Not connected")) + meta:set_string("infotext", S("Bot Sensor: Not connected")) end, update_infotext = update_infotext, @@ -57,13 +54,14 @@ minetest.register_node("signs_bot:bot_sensor", { paramtype = "light", sunlight_propagates = true, paramtype2 = "facedir", + use_texture_alpha = signs_bot.CLIP, is_ground_content = false, groups = {sign_bot_sensor = 1, cracky = 1}, sounds = default.node_sound_metal_defaults(), }) minetest.register_node("signs_bot:bot_sensor_on", { - description = I("Bot Sensor"), + description = S("Bot Sensor"), drawtype = "nodebox", node_box = { type = "fixed", @@ -100,6 +98,7 @@ minetest.register_node("signs_bot:bot_sensor_on", { paramtype = "light", sunlight_propagates = true, paramtype2 = "facedir", + use_texture_alpha = signs_bot.CLIP, is_ground_content = false, diggable = false, groups = {sign_bot_sensor = 1, not_in_creative_inventory = 1}, @@ -118,13 +117,13 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "bot_sensor", { - name = I("Bot Sensor"), + name = S("Bot Sensor"), data = { item = "signs_bot:bot_sensor", text = table.concat({ - I("The Bot Sensor detects any bot and sends a signal, if a bot is nearby."), - I("the sensor range is one node/meter."), - I("The sensor direction does not care."), + S("The Bot Sensor detects any bot and sends a signal, if a bot is nearby."), + S("the sensor range is one node/meter."), + S("The sensor direction does not care."), }, "\n") }, }) diff --git a/signs_bot/cart_sensor.lua b/signs_bot/cart_sensor.lua index 4aa4420..350e035 100644 --- a/signs_bot/cart_sensor.lua +++ b/signs_bot/cart_sensor.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,20 +13,19 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib local CYCLE_TIME = 2 local function update_infotext(pos, dest_pos, dest_idx) - M(pos):set_string("infotext", I("Cart Sensor: Connected with ")..S(dest_pos).." / "..dest_idx) + M(pos):set_string("infotext", S("Cart Sensor: Connected with").." "..P2S(dest_pos).." / "..dest_idx) end local function swap_node(pos, name) @@ -39,18 +38,8 @@ local function swap_node(pos, name) return true end -local function check_cart(pos) - for _, object in pairs(minetest.get_objects_inside_radius(pos, 1)) do - if object:get_entity_name() == "minecart:cart" then - return true - end - end - return false -end - local function node_timer(pos) - local pos1 = lib.next_pos(pos, M(pos):get_int("param2")) - if check_cart(pos1) then + if minecart.is_cart_available(pos, M(pos):get_int("param2"), 1) then if swap_node(pos, "signs_bot:cart_sensor_on") then signs_bot.send_signal(pos) signs_bot.lib.activate_extender_nodes(pos, true) @@ -62,7 +51,7 @@ local function node_timer(pos) end minetest.register_node("signs_bot:cart_sensor", { - description = I("Cart Sensor"), + description = S("Cart Sensor"), inventory_image = "signs_bot_sensor_cart_inv.png", drawtype = "nodebox", node_box = { @@ -83,7 +72,7 @@ minetest.register_node("signs_bot:cart_sensor", { after_place_node = function(pos, placer) local meta = M(pos) - meta:set_string("infotext", "Cart Sensor: Not connected") + meta:set_string("infotext", S("Cart Sensor: Not connected")) minetest.get_node_timer(pos):start(CYCLE_TIME) local node = minetest.get_node(pos) meta:set_int("param2", (node.param2 + 2) % 4) @@ -93,6 +82,7 @@ minetest.register_node("signs_bot:cart_sensor", { update_infotext = update_infotext, on_rotate = screwdriver.disallow, paramtype = "light", + use_texture_alpha = signs_bot.CLIP, sunlight_propagates = true, paramtype2 = "facedir", is_ground_content = false, @@ -101,7 +91,7 @@ minetest.register_node("signs_bot:cart_sensor", { }) minetest.register_node("signs_bot:cart_sensor_on", { - description = I("Cart Sensor"), + description = S("Cart Sensor"), drawtype = "nodebox", node_box = { type = "fixed", @@ -123,6 +113,7 @@ minetest.register_node("signs_bot:cart_sensor_on", { update_infotext = update_infotext, on_rotate = screwdriver.disallow, paramtype = "light", + use_texture_alpha = signs_bot.CLIP, sunlight_propagates = true, paramtype2 = "facedir", is_ground_content = false, @@ -156,13 +147,13 @@ minetest.register_lbm({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "cart_sensor", { - name = I("Cart Sensor"), + name = S("Cart Sensor"), data = { item = "signs_bot:cart_sensor", text = table.concat({ - I("The Cart Sensor detects and sends a signal, if a cart (Minecart) is nearby."), - I("the sensor range is one node/meter."), - I("The sensor has an active side (red) that must point to the rail/cart."), + S("The Cart Sensor detects and sends a signal, if a cart (Minecart) is nearby."), + S("the sensor range is one node/meter."), + S("The sensor has an active side (red) that must point to the rail/cart."), }, "\n") }, }) diff --git a/signs_bot/changer.lua b/signs_bot/changer.lua index c299ede..6baa9c8 100644 --- a/signs_bot/changer.lua +++ b/signs_bot/changer.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,10 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -34,7 +31,7 @@ local formspec = "size[8,7]".. default.gui_bg.. default.gui_bg_img.. default.gui_slots.. - "label[1,1.3;"..I("Signs:").."]".. + "label[1,1.3;"..S("Signs:").."]".. "label[2.6,0.7;1]label[5.1,0.7;2]".. "list[context;sign;3,0.5;2,2;]".. "label[2.6,1.7;3]label[5.1,1.7;4]".. @@ -100,10 +97,15 @@ local function allow_metadata_inventory() return 0 end +local function can_dig(pos) + local inv = minetest.get_inventory({type="node", pos=pos}) + return inv:is_empty("sign") +end + for idx = 1,4 do local not_in_inv = idx == 1 and 0 or 1 minetest.register_node("signs_bot:changer"..idx, { - description = I("Bot Control Unit"), + description = S("Bot Control Unit"), inventory_image = "signs_bot_ctrl_unit_inv.png", drawtype = "nodebox", node_box = { @@ -139,9 +141,11 @@ for idx = 1,4 do allow_metadata_inventory_put = allow_metadata_inventory, allow_metadata_inventory_take = allow_metadata_inventory, on_punch = swap_node, + can_dig = can_dig, on_rotate = screwdriver.disallow, paramtype = "light", + use_texture_alpha = signs_bot.CLIP, sunlight_propagates = true, paramtype2 = "facedir", is_ground_content = false, @@ -162,18 +166,18 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "changer", { - name = I("Bot Control Unit"), + name = S("Bot Control Unit"), data = { item = "signs_bot:changer1", text = table.concat({ - I("The Bot Control Unit is used to lead the bot by means of signs."), - I("The unit can be loaded with up to 4 different signs and can be programmed by means of sensors."), + S("The Bot Control Unit is used to lead the bot by means of signs."), + S("The unit can be loaded with up to 4 different signs and can be programmed by means of sensors."), "", - I("To load the unit, place a sign on the red side of the unit and click on the unit."), - I("The sign disappears / is moved to the inventory of the unit."), - I("This can be repeated 3 times."), + S("To load the unit, place a sign on the red side of the unit and click on the unit."), + S("The sign disappears / is moved to the inventory of the unit."), + S("This can be repeated 3 times."), "", - I("Use the connection tool to connect up to 4 sensors with the Bot Control Unit."), + S("Use the connection tool to connect up to 4 sensors with the Bot Control Unit."), }, "\n") }, }) diff --git a/signs_bot/chest.lua b/signs_bot/chest.lua index e7f7f8b..024f977 100644 --- a/signs_bot/chest.lua +++ b/signs_bot/chest.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-0221 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,12 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos +local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end +local S2P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local NODE_IO = minetest.global_exists("node_io") @@ -50,7 +49,7 @@ end local function update_infotext(pos, dest_pos, cmnd) local meta = M(pos) local state = get_inv_state(pos) - meta:set_string("infotext", I("Bot Chest: Sends signal to ")..S(dest_pos).." / "..cmnd..", if "..state) + meta:set_string("infotext", S("Bot Chest: Sends signal to").." "..P2S(dest_pos).." / "..cmnd..", if "..state) meta:set_string("state", state) end @@ -68,7 +67,7 @@ end if NODE_IO then minetest.register_node("signs_bot:chest", { - description = I("Signs Bot Chest"), + description = S("Signs Bot Chest"), tiles = { -- up, down, right, left, back, front 'signs_bot_chest_top.png', @@ -91,7 +90,7 @@ if NODE_IO then local meta = minetest.get_meta(pos) meta:set_string("owner", placer:get_player_name()) meta:set_string("formspec", formspec(pos, mem)) - meta:set_string("infotext", "Bot Chest: Not connected") + meta:set_string("infotext", S("Bot Chest: Not connected")) end, allow_metadata_inventory_put = function(pos, listname, index, stack, player) @@ -158,7 +157,7 @@ if NODE_IO then }) else minetest.register_node("signs_bot:chest", { - description = I("Signs Bot Chest"), + description = S("Signs Bot Chest"), tiles = { -- up, down, right, left, back, front 'signs_bot_chest_top.png', @@ -181,7 +180,7 @@ else local meta = minetest.get_meta(pos) meta:set_string("owner", placer:get_player_name()) meta:set_string("formspec", formspec(pos, mem)) - meta:set_string("infotext", "Bot Chest: Not connected") + meta:set_string("infotext", S("Bot Chest: Not connected")) end, allow_metadata_inventory_put = function(pos, listname, index, stack, player) @@ -245,15 +244,15 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "chest", { - name = I("Signs Bot Chest"), + name = S("Signs Bot Chest"), data = { item = "signs_bot:chest", text = table.concat({ - I("The Signs Bot Chest is a special chest with sensor function."), - I("It sends a signal depending on the chest state."), - I("Possible states are 'empty', 'not empty', 'almost full'"), + S("The Signs Bot Chest is a special chest with sensor function."), + S("It sends a signal depending on the chest state."), + S("Possible states are 'empty', 'not empty', 'almost full'"), "", - I("A typical use case is to turn off the bot, when the chest is almost full or empty."), + S("A typical use case is to turn off the bot, when the chest is almost full or empty."), }, "\n") }, }) diff --git a/signs_bot/cmd_farming.lua b/signs_bot/cmd_farming.lua index 56dffff..905be0e 100644 --- a/signs_bot/cmd_farming.lua +++ b/signs_bot/cmd_farming.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -11,14 +11,8 @@ Bot farming commands ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -65,7 +59,7 @@ signs_bot.register_botcommand("sow_seed", { mod = "farming", params = "", num_param = 1, - description = I("Sow farming seeds\nin front of the robot"), + description = S("Sow farming seeds\nin front of the robot"), check = function(slot) slot = tonumber(slot) return slot and slot > 0 and slot < 9 @@ -97,7 +91,10 @@ local function harvesting(base_pos, mem) -- Do not cache the result of get_node_drops; it is a probabilistic function! local drops = minetest.get_node_drops(node.name) for _,itemstring in ipairs(drops) do - bot_inv_put_item(base_pos, 0, ItemStack(itemstring)) + local leftover = bot_inv_put_item(base_pos, 0, ItemStack(itemstring)) + if leftover and leftover:get_count() > 0 then + signs_bot.lib.drop_items(mem.robot_pos, leftover) + end end end end @@ -107,7 +104,7 @@ signs_bot.register_botcommand("harvest", { mod = "farming", params = "", num_param = 0, - description = I("Harvest farming products\nin front of the robot\non a 3x3 field."), + description = S("Harvest farming products\nin front of the robot\non a 3x3 field."), cmnd = function(base_pos, mem) if not mem.steps then mem.pos_tbl = signs_bot.lib.gen_position_table(mem.robot_pos, mem.robot_param2, 3, 3, 0) @@ -145,7 +142,7 @@ signs_bot.register_botcommand("plant_sapling", { mod = "farming", params = "", num_param = 1, - description = I("Plant a sapling\nin front of the robot"), + description = S("Plant a sapling\nin front of the robot"), check = function(slot) slot = tonumber(slot) return slot and slot > 0 and slot < 9 @@ -168,7 +165,7 @@ turn_around]] signs_bot.register_sign({ name = "farming", - description = I('Sign "farming"'), + description = S('Sign "farming"'), commands = CMD, image = "signs_bot_sign_farming.png", }) @@ -184,14 +181,14 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "farming", { - name = I("Sign 'farming'"), + name = S("Sign 'farming'"), data = { item = "signs_bot:farming", text = table.concat({ - I("Used to harvest and seed a 3x3 field."), - I("Place the sign in front of the field."), - I("The seed to be placed has to be in the first inventory slot of the bot."), - I("When finished, the bot turns."), + S("Used to harvest and seed a 3x3 field."), + S("Place the sign in front of the field."), + S("The seed to be placed has to be in the first inventory slot of the bot."), + S("When finished, the bot turns."), }, "\n") }, }) diff --git a/signs_bot/cmd_flowers.lua b/signs_bot/cmd_flowers.lua index 61d3c31..01fa355 100644 --- a/signs_bot/cmd_flowers.lua +++ b/signs_bot/cmd_flowers.lua @@ -11,14 +11,8 @@ Bot flower cutting command ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -48,16 +42,28 @@ minetest.after(1, function() end end) +local function is_tree(node) + if minetest.get_item_group(node.name, "tree") == 1 then + return signs_bot.handle_drop_like_a_player(node) + end + if minetest.get_item_group(node.name, "leaves") == 1 then + return signs_bot.handle_drop_like_a_player(node) + end +end + local function harvesting(base_pos, mem) local pos = mem.pos_tbl and mem.pos_tbl[mem.steps] mem.steps = (mem.steps or 1) + 1 if pos and lib.not_protected(base_pos, pos) then local node = minetest.get_node_or_nil(pos) - local drop = Flowers[node.name] + local drop = Flowers[node.name] or is_tree(node) if drop then minetest.remove_node(pos) - bot_inv_put_item(base_pos, 0, ItemStack(drop)) + local leftover = bot_inv_put_item(base_pos, 0, ItemStack(drop)) + if leftover and leftover:get_count() > 0 then + signs_bot.lib.drop_items(mem.robot_pos, leftover) + end end end end @@ -66,7 +72,7 @@ signs_bot.register_botcommand("cutting", { mod = "farming", params = "", num_param = 0, - description = I("Cutting flowers\nin front of the robot\non a 3x3 field."), + description = S("Cutting flowers, leaves and tree blocks\nin front of the robot\non a 3x3 field."), cmnd = function(base_pos, mem) if not mem.steps then mem.pos_tbl = signs_bot.lib.gen_position_table(mem.robot_pos, mem.robot_param2, 3, 3, 0) @@ -91,7 +97,7 @@ turn_around]] signs_bot.register_sign({ name = "flowers", - description = I('Sign "flowers"'), + description = S('Sign "flowers"'), commands = CMD, image = "signs_bot_sign_flowers.png", }) @@ -107,13 +113,13 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "flowers", { - name = I("Sign 'flowers'"), + name = S("Sign 'flowers'"), data = { item = "signs_bot:flowers", text = table.concat({ - I("Used to cut flowers on a 3x3 field."), - I("Place the sign in front of the field."), - I("When finished, the bot turns."), + S("Used to cut flowers on a 3x3 field."), + S("Place the sign in front of the field."), + S("When finished, the bot turns."), }, "\n") }, }) diff --git a/signs_bot/cmd_item.lua b/signs_bot/cmd_item.lua index 6aa1d12..4ed7d70 100644 --- a/signs_bot/cmd_item.lua +++ b/signs_bot/cmd_item.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,10 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -90,7 +87,7 @@ signs_bot.register_botcommand("take_item", { mod = "item", params = " ", num_param = 2, - description = I("Take items from a chest like node\nand put it into the item inventory.\n".. + description = S("Take items from a chest like node\nand put it into the item inventory.\n".. " is the inventory slot (1..8) or 0 for any one"), check = function(num, slot) num = tonumber(num) or 1 @@ -115,7 +112,7 @@ signs_bot.register_botcommand("add_item", { mod = "item", params = " ", num_param = 2, - description = I("Add items to a chest like node\ntaken from the item inventory.\n".. + description = S("Add items to a chest like node\ntaken from the item inventory.\n".. " is the inventory slot (1..8) or 0 for any one"), check = function(num, slot) num = tonumber(num) or 1 @@ -140,7 +137,7 @@ signs_bot.register_botcommand("add_fuel", { mod = "item", params = " ", num_param = 2, - description = I("Add fuel to a furnace like node\ntaken from the item inventory.\n".. + description = S("Add fuel to a furnace like node\ntaken from the item inventory.\n".. " is the inventory slot (1..8) or 0 for any one"), check = function(num, slot) num = tonumber(num) or 1 @@ -165,7 +162,7 @@ signs_bot.register_botcommand("cond_take_item", { mod = "item", params = " ", num_param = 2, - description = I("deprecated, use bot inventory configuration instead"), + description = S("deprecated, use bot inventory configuration instead"), check = function(num, slot) return false end, @@ -178,7 +175,7 @@ signs_bot.register_botcommand("cond_add_item", { mod = "item", params = " ", num_param = 2, - description = I("deprecated, use bot inventory configuration instead"), + description = S("deprecated, use bot inventory configuration instead"), check = function(num, slot) return false end, @@ -191,7 +188,7 @@ signs_bot.register_botcommand("pickup_items", { mod = "item", params = "", num_param = 1, - description = I("Pick up all objects\n".. + description = S("Pick up all objects\n".. "in a 3x3 field.\n".. " is the inventory slot (1..8) or 0 for any one"), check = function(slot) @@ -219,7 +216,7 @@ signs_bot.register_botcommand("drop_items", { mod = "item", params = " ", num_param = 2, - description = I("Drop items in front of the bot.\n".. + description = S("Drop items in front of the bot.\n".. " is the inventory slot (1..8) or 0 for any one"), check = function(num, slot) num = tonumber(num) or 1 @@ -246,18 +243,10 @@ signs_bot.register_botcommand("punch_cart", { mod = "item", params = "", num_param = 0, - description = I("Punch a rail cart to start it"), + description = S("Punch a rail cart to start it"), cmnd = function(base_pos, mem) - local pos = lib.dest_pos(mem.robot_pos, mem.robot_param2, {0}) - for _, object in pairs(minetest.get_objects_inside_radius(pos, 2)) do - if object:get_entity_name() == "minecart:cart" then - object:punch(object, 1.0, { - full_punch_interval = 1.0, - damage_groups = {fleshy = 1}, - }, minetest.facedir_to_dir(mem.robot_param2)) - break -- start only one cart - end - end + local punch_dir = minetest.facedir_to_dir(mem.robot_param2) + minecart.punch_cart(mem.robot_pos, mem.robot_param2, 1, punch_dir) return signs_bot.DONE end, }) diff --git a/signs_bot/cmd_move.lua b/signs_bot/cmd_move.lua index 5d11438..76cae4d 100644 --- a/signs_bot/cmd_move.lua +++ b/signs_bot/cmd_move.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -12,14 +12,8 @@ ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib local get_node_lvm = tubelib2.get_node_lvm @@ -135,7 +129,7 @@ signs_bot.register_botcommand("backward", { mod = "move", params = "", num_param = 0, - description = I("Move the robot one step back"), + description = S("Move the robot one step back"), cmnd = function(base_pos, mem) local new_pos = backward_robot(mem) if new_pos then -- not blocked? @@ -160,7 +154,7 @@ signs_bot.register_botcommand("turn_left", { mod = "move", params = "", num_param = 0, - description = I("Turn the robot to the left"), + description = S("Turn the robot to the left"), cmnd = function(base_pos, mem) mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "L") return signs_bot.DONE @@ -171,7 +165,7 @@ signs_bot.register_botcommand("turn_right", { mod = "move", params = "", num_param = 0, - description = I("Turn the robot to the right"), + description = S("Turn the robot to the right"), cmnd = function(base_pos, mem) mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R") return signs_bot.DONE @@ -182,7 +176,7 @@ signs_bot.register_botcommand("turn_around", { mod = "move", params = "", num_param = 0, - description = I("Turn the robot around"), + description = S("Turn the robot around"), cmnd = function(base_pos, mem) mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R") mem.robot_param2 = turn_robot(mem.robot_pos, mem.robot_param2, "R") @@ -217,7 +211,7 @@ signs_bot.register_botcommand("move_up", { mod = "move", params = "", num_param = 0, - description = I("Move the robot upwards"), + description = S("Move the robot upwards"), cmnd = function(base_pos, mem) local new_pos = robot_up(mem.robot_pos, mem.robot_param2) if new_pos then -- not blocked? @@ -251,7 +245,7 @@ signs_bot.register_botcommand("move_down", { mod = "move", params = "", num_param = 0, - description = I("Move the robot down"), + description = S("Move the robot down"), cmnd = function(base_pos, mem) local new_pos = robot_down(mem.robot_pos, mem.robot_param2) if new_pos then -- not blocked? @@ -265,14 +259,14 @@ signs_bot.register_botcommand("pause", { mod = "move", params = "", num_param = 1, - description = I("Stop the robot for seconds\n(1..9999)"), + description = S("Stop the robot for seconds\n(1..9999)"), check = function(sec) sec = tonumber(sec) or 1 return sec and sec > 0 and sec < 10000 end, cmnd = function(base_pos, mem, sec) if not mem.steps then - mem.steps = tonumber(sec or 1) + mem.steps = tonumber(sec) or 1 end mem.steps = mem.steps - 1 if mem.steps == 0 then @@ -290,7 +284,7 @@ signs_bot.register_botcommand("stop", { mod = "move", params = "", num_param = 0, - description = I("Stop the robot."), + description = S("Stop the robot."), cmnd = function(base_pos, mem, slot) if mem.capa then mem.capa = mem.capa + 2 @@ -303,7 +297,7 @@ signs_bot.register_botcommand("turn_off", { mod = "move", params = "", num_param = 0, - description = I("Turn the robot off\n".. + description = S("Turn the robot off\n".. "and put it back in the box."), cmnd = function(base_pos, mem) signs_bot.stop_robot(base_pos, mem) diff --git a/signs_bot/cmd_pattern.lua b/signs_bot/cmd_pattern.lua index 960cd7c..867e61f 100644 --- a/signs_bot/cmd_pattern.lua +++ b/signs_bot/cmd_pattern.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -12,14 +12,8 @@ ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -117,7 +111,7 @@ signs_bot.register_botcommand("pattern", { mod = "copy", params = "", num_param = 0, - description = I("Store pattern to be cloned."), + description = S("Store pattern to be cloned."), cmnd = function(base_pos, mem) mem.pttrn_pos = lib.next_pos(mem.robot_pos, mem.robot_param2) mem.pttrn_param2 = mem.robot_param2 @@ -129,7 +123,7 @@ signs_bot.register_botcommand("copy", { mod = "copy", params = " ", num_param = 2, - description = I("Copy the nodes from\n".. + description = S("Copy the nodes from\n".. "the stored pattern position\n".. " is: 3x1, 3x2, 3x3,\n".. "5x1, 5x2, 5x3 (wide x deep)\n".. @@ -178,7 +172,7 @@ minetest.register_node("signs_bot:missing", { signs_bot.register_sign({ name = "pattern", - description = I('Sign "pattern"'), + description = S('Sign "pattern"'), commands = "pattern\nturn_around", image = "signs_bot_sign_pattern.png", }) @@ -207,7 +201,7 @@ turn_around]] signs_bot.register_sign({ name = "copy3x3x3", - description = I('Sign "copy 3x3x3"'), + description = S('Sign "copy 3x3x3"'), commands = CMND, image = "signs_bot_sign_copy3x3x3.png", }) @@ -223,14 +217,14 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "pattern", { - name = I("Sign 'pattern'"), + name = S("Sign 'pattern'"), data = { item = "signs_bot:pattern", text = table.concat({ - I("Used to make a copy of a 3x3x3 cube."), - I("Place the sign in front of the pattern to be copied."), - I("Use the copy sign to make the copy of this pattern on a different location."), - I("The bot must first reach the pattern sign, then the copy sign."), + S("Used to make a copy of a 3x3x3 cube."), + S("Place the sign in front of the pattern to be copied."), + S("Use the copy sign to make the copy of this pattern on a different location."), + S("The bot must first reach the pattern sign, then the copy sign."), }, "\n") }, }) @@ -238,13 +232,13 @@ end if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "copy3x3x3", { - name = I("Sign 'copy3x3x3'"), + name = S("Sign 'copy3x3x3'"), data = { item = "signs_bot:copy3x3x3", text = table.concat({ - I("Used to make a copy of a 3x3x3 cube."), - I("Place the sign in front of the location, where the copy should be made."), - I("Use the pattern sign to mark the pattern."), + S("Used to make a copy of a 3x3x3 cube."), + S("Place the sign in front of the location, where the copy should be made."), + S("Use the pattern sign to mark the pattern."), }, "\n") }, }) diff --git a/signs_bot/cmd_place.lua b/signs_bot/cmd_place.lua index 23bfe6f..920d49e 100644 --- a/signs_bot/cmd_place.lua +++ b/signs_bot/cmd_place.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -12,14 +12,8 @@ ]]-- --- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos -local M = minetest.get_meta - --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib local bot_inv_take_item = signs_bot.bot_inv_take_item @@ -32,13 +26,23 @@ end local tValidLevels = {[-1] = -1, [0] = 0, [1] = 1} -- for items with paramtype2 = "facedir" -local tRotations = { - [0] = {8,20,4}, - [1] = {16,20,12}, - [2] = {4,20,8}, - [3] = {12,20,16}, +local tRotations = {} + +local Rotations = { + {0,8,22,4}, + {1,17,21,13}, + {2,6,20,10}, + {3,15,23,19}, } +for _,v in ipairs(Rotations) do + local t = table.copy(v) + for i = 1,4 do + table.insert(t, 1, table.remove(t)) + tRotations[t[1]] = {t[2], t[3], t[4]} + end +end + -- -- Place/dig items -- @@ -46,7 +50,7 @@ local function place_item(base_pos, robot_pos, param2, slot, route, level) local pos1, p2 = lib.dest_pos(robot_pos, param2, route) pos1.y = pos1.y + level if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if lib.is_air_like(pos1) then local taken = signs_bot.bot_inv_take_item(base_pos, slot, 1) @@ -74,7 +78,7 @@ signs_bot.register_botcommand("place_front", { mod = "place", params = " ", 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".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -95,7 +99,7 @@ signs_bot.register_botcommand("place_left", { mod = "place", params = " ", num_param = 2, - description = I("Place a block on the left side\n".. + description = S("Place a block on the left side\n".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -116,7 +120,7 @@ signs_bot.register_botcommand("place_right", { mod = "place", params = " ", num_param = 2, - description = I("Place a block on the right side\n".. + description = S("Place a block on the right side\n".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -136,7 +140,7 @@ signs_bot.register_botcommand("place_right", { local function place_item_below(base_pos, robot_pos, param2, slot) local pos1 = {x=robot_pos.x,y=robot_pos.y-1,z=robot_pos.z} if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end local node = tubelib2.get_node_lvm(pos1) if node.name == "signs_bot:robot_foot" then @@ -155,7 +159,7 @@ signs_bot.register_botcommand("place_below", { mod = "place", params = "", 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".. " is the inventory slot (1..8)"), check = function(slot) @@ -171,7 +175,7 @@ signs_bot.register_botcommand("place_below", { local function place_item_above(base_pos, robot_pos, param2, slot) local pos1 = {x=robot_pos.x,y=robot_pos.y+1,z=robot_pos.z} if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if lib.is_air_like(pos1) then local taken = bot_inv_take_item(base_pos, slot, 1) @@ -189,7 +193,7 @@ signs_bot.register_botcommand("place_above", { mod = "place", params = "", num_param = 1, - description = I("Place a block above the robot.\n".. + description = S("Place a block above the robot.\n".. " is the inventory slot (1..8)"), check = function(slot) slot = tonumber(slot) or 0 @@ -207,13 +211,13 @@ local function dig_item(base_pos, robot_pos, param2, slot, route, level) local node = tubelib2.get_node_lvm(pos1) local dug_name = lib.is_simple_node(node) if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if dug_name then if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then minetest.remove_node(pos1) else - return signs_bot.ERROR, I("Error: No free inventory space") + return signs_bot.ERROR, S("Error: No free inventory space") end end return signs_bot.DONE @@ -223,7 +227,7 @@ signs_bot.register_botcommand("dig_front", { mod = "place", params = " ", 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".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -245,7 +249,7 @@ signs_bot.register_botcommand("dig_left", { mod = "place", params = " ", num_param = 2, - description = I("Dig the block on the left side\n".. + description = S("Dig the block on the left side\n".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -267,7 +271,7 @@ signs_bot.register_botcommand("dig_right", { mod = "place", params = " ", num_param = 2, - description = I("Dig the block on the right side\n".. + description = S("Dig the block on the right side\n".. " is the inventory slot (1..8)\n".. " is one of: -1 0 +1"), check = function(slot, lvl) @@ -290,13 +294,13 @@ local function dig_item_below(base_pos, robot_pos, param2, slot) local node = tubelib2.get_node_lvm(pos1) local dug_name = lib.is_simple_node(node) if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if dug_name then if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then minetest.set_node(pos1, {name="signs_bot:robot_foot"}) else - return signs_bot.ERROR, I("Error: No free inventory space") + return signs_bot.ERROR, S("Error: No free inventory space") end end return signs_bot.DONE @@ -306,7 +310,7 @@ signs_bot.register_botcommand("dig_below", { mod = "place", params = "", num_param = 1, - description = I("Dig the block under the robot.\n".. + description = S("Dig the block under the robot.\n".. " is the inventory slot (1..8)"), check = function(slot) slot = tonumber(slot) or 0 @@ -324,13 +328,13 @@ local function dig_item_above(base_pos, robot_pos, param2, slot) local node = tubelib2.get_node_lvm(pos1) local dug_name = lib.is_simple_node(node) if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if dug_name then if bot_inv_put_item(base_pos, slot, ItemStack(dug_name)) then minetest.remove_node(pos1) else - return signs_bot.ERROR, I("Error: No free inventory space") + return signs_bot.ERROR, S("Error: No free inventory space") end end return signs_bot.DONE @@ -340,7 +344,7 @@ signs_bot.register_botcommand("dig_above", { mod = "place", params = "", num_param = 1, - description = I("Dig the block above the robot.\n".. + description = S("Dig the block above the robot.\n".. " is the inventory slot (1..8)"), check = function(slot) slot = tonumber(slot) or 0 @@ -358,7 +362,7 @@ local function rotate_item(base_pos, robot_pos, param2, route, level, steps) pos1.y = pos1.y + level local node = tubelib2.get_node_lvm(pos1) if not lib.not_protected(base_pos, pos1) then - return signs_bot.ERROR, I("Error: Position protected") + return signs_bot.ERROR, S("Error: Position protected") end if lib.is_simple_node(node) then local p2 = tRotations[node.param2] and tRotations[node.param2][steps] @@ -372,7 +376,8 @@ end signs_bot.register_botcommand("rotate_item", { mod = "place", params = " ", - 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".. " is one of: -1 0 +1\n".. " is one of: 1 2 3"), check = function(lvl, steps) @@ -391,7 +396,7 @@ signs_bot.register_botcommand("rotate_item", { -- Simplified torch which can be placed w/o a fake player minetest.register_node("signs_bot:torch", { - description = "Bot torch", + description = S("Bot torch"), inventory_image = "default_torch_on_floor.png", wield_image = "default_torch_on_floor.png", drawtype = "nodebox", @@ -428,6 +433,7 @@ minetest.register_node("signs_bot:torch", { "group:bakedclay", "group:soil"}, paramtype = "light", paramtype2 = "facedir", + use_texture_alpha = signs_bot.CLIP, sunlight_propagates = true, walkable = false, liquids_pointable = false, diff --git a/signs_bot/cmd_sign.lua b/signs_bot/cmd_sign.lua index 4e9f9aa..8917dab 100644 --- a/signs_bot/cmd_sign.lua +++ b/signs_bot/cmd_sign.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,10 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. -local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") +-- Load support for I18n. +local S = signs_bot.S local lib = signs_bot.lib @@ -48,12 +45,12 @@ local function formspec1(meta) default.gui_bg_img.. default.gui_slots.. "style_type[textarea,table;font=mono]".. - "tabheader[0,0;tab;"..I("Commands,Help")..";1;;true]".. - "field[0.3,0.5;9,1;name;"..I("Sign name:")..";"..name.."]".. + "tabheader[0,0;tab;"..S("Commands,Help")..";1;;true]".. + "field[0.3,0.5;9,1;name;"..S("Sign name:")..";"..name.."]".. "textarea[0.3,1.2;9,7.2;cmnd;;"..cmnd.."]".. "label[0.3,7.5;"..err_msg.."]".. - "button_exit[5,7.5;2,1;cancel;"..I("Cancel").."]".. - "button[7,7.5;2,1;check;"..I("Check").."]" + "button_exit[5,7.5;2,1;cancel;"..S("Cancel").."]".. + "button[7,7.5;2,1;check;"..S("Check").."]" end local function formspec2(pos, text) @@ -62,10 +59,10 @@ local function formspec2(pos, text) default.gui_bg_img.. default.gui_slots.. "style_type[textarea,table;font=mono]".. - "tabheader[0,0;tab;"..I("Commands,Help")..";2;;true]".. + "tabheader[0,0;tab;"..S("Commands,Help")..";2;;true]".. "table[0.1,0;8.6,4;command;"..sCmnds..";"..pos.."]".. - "textarea[0.3,4.5;9,3.5;help;Help:;"..text.."]".. - "button[3,7.5;3,1;copy;"..I("Copy Cmnd").."]" + "textarea[0.3,4.5;9,3.5;help;"..S("Help")..":;"..text.."]".. + "button[3,7.5;3,1;copy;"..S("Copy Cmnd").."]" end local function add_arrow(text, line_num) @@ -94,7 +91,7 @@ local function append_line(pos, meta, line) local text = meta:get_string("signs_bot_cmnd").."\n"..line meta:set_string("signs_bot_cmnd", text) meta:set_int("err_code", 1) -- zero means OK - meta:set_string("err_msg", "please check the added line(s)") + meta:set_string("err_msg", S("please check the added line(s)")) end local function check_and_store(pos, meta, fields) @@ -106,7 +103,7 @@ local function check_and_store(pos, meta, fields) end minetest.register_node("signs_bot:sign_cmnd", { - description = I('Sign "command"'), + description = S('Sign "command"'), drawtype = "nodebox", inventory_image = "signs_bot_sign_cmnd.png", node_box = { @@ -134,8 +131,8 @@ minetest.register_node("signs_bot:sign_cmnd", { nmeta:set_string("err_msg", imeta:get_string("err_msg")) nmeta:set_int("err_code", imeta:get_int("err_code")) else - nmeta:set_string("sign_name", I('Sign "command"')) - nmeta:set_string("signs_bot_cmnd", I("-- enter or copy commands from help page")) + nmeta:set_string("sign_name", S('Sign "command"')) + nmeta:set_string("signs_bot_cmnd", S("-- enter or copy commands from help page")) nmeta:set_int("err_code", 0) end nmeta:set_string("infotext", nmeta:get_string("sign_name")) @@ -175,6 +172,7 @@ minetest.register_node("signs_bot:sign_cmnd", { after_dig_node = lib.after_dig_sign_node, paramtype = "light", + use_texture_alpha = signs_bot.CLIP, sunlight_propagates = true, is_ground_content = false, drop = "", @@ -215,18 +213,18 @@ local function place_sign(base_pos, robot_pos, param2, slot) lib.place_sign(pos1, sign, param2) return signs_bot.DONE else - return signs_bot.ERROR, I("Error: Signs inventory empty") + return signs_bot.ERROR, S("Error: Signs inventory empty") end end end - return signs_bot.ERROR, I("Error: Position protected or occupied") + return signs_bot.ERROR, S("Error: Position protected or occupied") end signs_bot.register_botcommand("place_sign", { mod = "sign", params = "", 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".. " is the inventory slot (1..6)"), check = function(slot) slot = tonumber(slot) or 1 @@ -247,18 +245,18 @@ local function place_sign_behind(base_pos, robot_pos, param2, slot) lib.place_sign(pos1, sign, param2) return signs_bot.DONE else - return signs_bot.ERROR, I("Error: Signs inventory empty") + return signs_bot.ERROR, S("Error: Signs inventory empty") end end end - return signs_bot.ERROR, I("Error: Position protected or occupied") + return signs_bot.ERROR, S("Error: Position protected or occupied") end signs_bot.register_botcommand("place_sign_behind", { mod = "sign", params = "", 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".. " is the inventory slot (1..6)"), check = function(slot) slot = tonumber(slot) or 1 @@ -277,7 +275,7 @@ local function dig_sign(base_pos, robot_pos, param2, slot) local err_code = meta:get_int("err_code") local name = meta:get_string("sign_name") if cmnd == "" then - return signs_bot.ERROR, I("Error: No sign available") + return signs_bot.ERROR, S("Error: No sign available") end if lib.not_protected(base_pos, pos1) then local node = tubelib2.get_node_lvm(pos1) @@ -289,18 +287,18 @@ local function dig_sign(base_pos, robot_pos, param2, slot) minetest.remove_node(pos1) if not put_inv_sign(base_pos, slot, sign) then signs_bot.lib.drop_items(robot_pos, sign) - return signs_bot.ERROR, I("Error: Signs inventory slot is occupied") + return signs_bot.ERROR, S("Error: Signs inventory slot is occupied") end return signs_bot.DONE end - return signs_bot.ERROR, I("Error: Position is protected") + return signs_bot.ERROR, S("Error: Position is protected") end signs_bot.register_botcommand("dig_sign", { mod = "sign", params = "", 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".. " is the inventory slot (1..6)"), check = function(slot) @@ -317,23 +315,26 @@ local function trash_sign(base_pos, robot_pos, param2, slot) local pos1 = lib.dest_pos(robot_pos, param2, {0}) local cmnd = M(pos1):get_string("signs_bot_cmnd") if cmnd == "" then - return signs_bot.ERROR, I("Error: No sign available") + return signs_bot.ERROR, S("Error: No sign available") end if lib.not_protected(base_pos, pos1) then local node = tubelib2.get_node_lvm(pos1) local sign = ItemStack("signs_bot:sign_cmnd") minetest.remove_node(pos1) - signs_bot.bot_inv_put_item(base_pos, slot, sign) + local leftover = signs_bot.bot_inv_put_item(base_pos, slot, sign) + if leftover and leftover:get_count() > 0 then + signs_bot.lib.drop_items(robot_pos, leftover) + end return signs_bot.DONE end - return signs_bot.ERROR, I("Error: Position is protected") + return signs_bot.ERROR, S("Error: Position is protected") end signs_bot.register_botcommand("trash_sign", { mod = "sign", params = "", 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".. " is the inventory slot (1..8)"), check = function(slot) @@ -358,14 +359,14 @@ minetest.register_craft({ if minetest.get_modpath("doc") then doc.add_entry("signs_bot", "sign_cmnd", { - name = I("Sign 'command'"), + name = S("Sign 'command'"), data = { item = "signs_bot:sign_cmnd", text = table.concat({ - I("The 'command' sign can be programmed by the player."), - I("Place the sign in front of you and use the node menu to program your sequence of bot commands."), - I("The menu has an edit field for your commands and a help page with all available commands."), - I("The help page has a copy button to simplify the programming."), + S("The 'command' sign can be programmed by the player."), + S("Place the sign in front of you and use the node menu to program your sequence of bot commands."), + S("The menu has an edit field for your commands and a help page with all available commands."), + S("The help page has a copy button to simplify the programming."), }, "\n") }, }) diff --git a/signs_bot/commands.lua b/signs_bot/commands.lua index e009a8d..6347273 100644 --- a/signs_bot/commands.lua +++ b/signs_bot/commands.lua @@ -3,7 +3,7 @@ Signs Bot ========= - Copyright (C) 2019 Joachim Stolberg + Copyright (C) 2019-2021 Joachim Stolberg GPL v3 See LICENSE.txt for more information @@ -13,13 +13,12 @@ ]]-- -- for lazy programmers -local S = function(pos) if pos then return minetest.pos_to_string(pos) end end -local P = minetest.string_to_pos local M = minetest.get_meta --- Load support for intllib. +-- Load support for I18n. +local S = signs_bot.S + local MP = minetest.get_modpath("signs_bot") -local I,_ = dofile(MP.."/intllib.lua") local ci = dofile(MP.."/interpreter.lua") local lib = signs_bot.lib @@ -73,7 +72,7 @@ end function signs_bot.get_commands() local tbl = {} for _,mod in ipairs(SortedMods) do - tbl[#tbl+1] = mod.." "..I("commands:") + tbl[#tbl+1] = mod.." "..S("commands:") for _,cmnd in ipairs(SortedKeys[mod]) do local item = tCommands[cmnd] tbl[#tbl+1] = " "..item.name.." "..item.params @@ -90,7 +89,7 @@ function signs_bot.get_help_text(cmnd) return item.description end end - return I("unknown command") + return S("unknown command") end function signs_bot.check_commands(pos, text) @@ -154,12 +153,16 @@ local function activate_sensor(pos, param2) end end -local function bot_error(base_pos, mem, err) +local function bot_error(base_pos, mem, err, cmd) minetest.sound_play('signs_bot_error', {pos = base_pos}) minetest.sound_play('signs_bot_error', {pos = mem.robot_pos}) - print(err) - signs_bot.infotext(base_pos, err) - mem.error = true + if cmd then + signs_bot.infotext(base_pos, err .. ":\n'" .. cmd .. "'") + mem.error = err .. ": '" .. cmd .. "'" + else + signs_bot.infotext(base_pos, err) + mem.error = err + end return false end @@ -176,9 +179,9 @@ local function power_consumption(mem, cmnd) end function signs_bot.run_next_command(base_pos, mem) - local res, err = ci.run_script(base_pos, mem) + local res, err, cmd = ci.run_script(base_pos, mem) if res == ci.ERROR then - return bot_error(base_pos, mem, err) + return bot_error(base_pos, mem, err, cmd) elseif res == ci.EXIT then signs_bot.stop_robot(base_pos, mem) return false @@ -198,31 +201,31 @@ end signs_bot.register_botcommand("repeat", { mod = "core", params = "", - description = I("start of a 'repeat..end' block"), + description = S("start of a 'repeat..end' block"), }) signs_bot.register_botcommand("end", { mod = "core", params = "", - description = I("end command of a 'repeat..end' block"), + description = S("end command of a 'repeat..end' block"), }) signs_bot.register_botcommand("call", { mod = "core", params = "