built on 14/05/2021 19:19:09
152
minecart/README.md
Normal file
@ -0,0 +1,152 @@
|
||||
Minecart
|
||||
========
|
||||
|
||||
**Minecart, the lean railway transportation automation system**
|
||||
|
||||
|
||||
Browse on: [GitHub](https://github.com/joe7575/minecart)
|
||||
|
||||
Download: [GitHub](https://github.com/joe7575/minecart/archive/master.zip)
|
||||
|
||||
![minecart](https://github.com/joe7575/minecart/blob/master/screenshot.png)
|
||||
|
||||
|
||||
Minecart is based on carts, which is
|
||||
based almost entirely on the mod boost_cart [1], which
|
||||
itself is based on (and fully compatible with) the carts mod [2].
|
||||
|
||||
The model was originally designed by stujones11 [3] (CC-0).
|
||||
|
||||
Cart textures are based on original work from PixelBOX by Gambit (permissive
|
||||
license).
|
||||
|
||||
|
||||
1. https://github.com/SmallJoker/boost_cart/
|
||||
2. https://github.com/PilzAdam/carts/
|
||||
3. https://github.com/stujones11/railcart/
|
||||
|
||||
|
||||
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
|
||||
- protection of minecarts and cargo
|
||||
- Minecarts run through unloaded areas (only the stations/hopper have to be loaded)
|
||||
- Extra Minecart privs for rail workers
|
||||
- Ingame documentation (German and English), based on the mod "doc"
|
||||
- API to register carts from other mods
|
||||
- chat command '/mycart <num>' to output cart state and location
|
||||
|
||||
|
||||
Technical Background
|
||||
--------------------
|
||||
|
||||
The Minecart can "run" through unloaded areas. This is done by means of recorded
|
||||
and stored routes. If the area is unloaded the cart will simply follow the
|
||||
predefined route until an area is loaded again. In this case the cart will be
|
||||
spawned and run as usual.
|
||||
|
||||
|
||||
Introduction
|
||||
------------
|
||||
|
||||
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.
|
||||
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 waiting time in both buffers. The Minecart
|
||||
will then start automatically after the configured time
|
||||
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark
|
||||
at least every 16 nodes/meters)
|
||||
9. Place a Minecart in front of the buffer and check whether it starts after the
|
||||
configured time
|
||||
10. Check the cart state via the chat command: /mycart <num>
|
||||
'<num>' is the cart number, 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 cart and items back
|
||||
|
||||
|
||||
Hopper
|
||||
------
|
||||
|
||||
![hopper](https://github.com/joe7575/minecart/blob/master/hopper.png)
|
||||
|
||||
The Hopper is used to load/unload Minecarts.
|
||||
The Hopper can pull and push items into/out off chests and can drop/pick up 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.
|
||||
|
||||
|
||||
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
|
||||
-------
|
||||
|
||||
2019-04-19 v0.01 first commit
|
||||
2019-04-21 v0.02 functional, with junctions support
|
||||
2019-04-23 v0.03 bug fixes and improvements
|
||||
2019-04-25 v0.04 Landmarks and Minecart protection added
|
||||
2019-05-04 v0.05 Route recording protection added
|
||||
2019-05-22 v0.06 Pick up items from a cart improved
|
||||
2019-06-23 v0.07 'doc' mod support and German translation added
|
||||
2020-01-04 v1.00 Hopper added, buffer improved
|
||||
2020-02-09 v1.01 cart loading bugfix
|
||||
2020-02-24 v1.02 Hopper improved
|
||||
2020-03-05 v1.03 Hopper again improved
|
||||
2020-03-28 v1.04 cart unloading bugfix
|
||||
2020-05-14 v1.05 API changed to be able to register carts
|
||||
2020-06-14 v1.06 API changed and chat command added
|
||||
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
|
||||
2021-04-10 V2.00 Complete revision to make carts robust against server load/lag,
|
||||
Speed limit signs and cart terminal added
|
52
minecart/api.lua
Normal file
@ -0,0 +1,52 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
--
|
||||
-- API functions
|
||||
--
|
||||
|
||||
-- 'pos' is the position of the puncher/sensor, the cart
|
||||
-- position will be determined by means of 'param2' and 'radius'
|
||||
function minecart.is_cart_available(pos, param2, radius)
|
||||
local pos2 = minecart.get_nodecart_nearby(pos, param2, radius)
|
||||
if pos2 then
|
||||
return true
|
||||
end
|
||||
-- The entity check is needed for a cart with driver
|
||||
local entity = minecart.get_entitycart_nearby(pos, param2, radius)
|
||||
if entity then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.is_nodecart_available(pos, param2, radius)
|
||||
local pos2 = minecart.get_nodecart_nearby(pos, param2, radius)
|
||||
if pos2 then
|
||||
return true
|
||||
end
|
||||
end
|
||||
|
||||
-- 'pos' is the position of the puncher/sensor, the cart
|
||||
-- position will be determined by means of 'param2' and 'radius'
|
||||
function minecart.punch_cart(pos, param2, radius, punch_dir)
|
||||
local pos2, node = minecart.get_nodecart_nearby(pos, param2, radius)
|
||||
if pos2 then
|
||||
minecart.start_nodecart(pos2, node.name, nil, punch_dir)
|
||||
return true
|
||||
end
|
||||
-- The entity check is needed for a cart with driver
|
||||
local entity = minecart.get_entitycart_nearby(pos, param2, radius)
|
||||
if entity and entity.driver then
|
||||
minecart.push_entitycart(entity, punch_dir)
|
||||
return true
|
||||
end
|
||||
end
|
360
minecart/baselib.lua
Normal file
@ -0,0 +1,360 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2020 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
-- for lazy programmers
|
||||
local M = minetest.get_meta
|
||||
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
|
||||
local S2P = minetest.string_to_pos
|
||||
local P2H = minetest.hash_node_position
|
||||
local H2P = minetest.get_position_from_hash
|
||||
|
||||
local param2_to_dir = {[0]=
|
||||
{x=0, y=0, z=1},
|
||||
{x=1, y=0, z=0},
|
||||
{x=0, y=0, z=-1},
|
||||
{x=-1, y=0, z=0},
|
||||
{x=0, y=-1, z=0},
|
||||
{x=0, y=1, z=0}
|
||||
}
|
||||
|
||||
-- Registered carts
|
||||
minecart.tNodeNames = {} -- [<cart_node_name>] = <cart_entity_name>
|
||||
minecart.tEntityNames = {} -- [<cart_entity_name>] = true
|
||||
minecart.lCartNodeNames = {} -- {<cart_node_name>, <cart_node_name>, ...}
|
||||
minecart.tCartTypes = {}
|
||||
|
||||
function minecart.param2_to_dir(param2)
|
||||
return param2_to_dir[param2 % 6]
|
||||
end
|
||||
|
||||
function minecart.get_node_lvm(pos)
|
||||
local node = minetest.get_node_or_nil(pos)
|
||||
if node then
|
||||
return node
|
||||
end
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local MinEdge, MaxEdge = vm:read_from_map(pos, pos)
|
||||
local data = vm:get_data()
|
||||
local param2_data = vm:get_param2_data()
|
||||
local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
|
||||
local idx = area:indexp(pos)
|
||||
if data[idx] and param2_data[idx] then
|
||||
return {
|
||||
name = minetest.get_name_from_content_id(data[idx]),
|
||||
param2 = param2_data[idx]
|
||||
}
|
||||
end
|
||||
return {name="ignore", param2=0}
|
||||
end
|
||||
|
||||
function minecart.find_node_near_lvm(pos, radius, items)
|
||||
local npos = minetest.find_node_near(pos, radius, items)
|
||||
if npos then
|
||||
return npos
|
||||
end
|
||||
local tItems = {}
|
||||
for _,v in ipairs(items) do
|
||||
tItems[v] = true
|
||||
end
|
||||
local pos1 = {x = pos.x - radius, y = pos.y - radius, z = pos.z - radius}
|
||||
local pos2 = {x = pos.x + radius, y = pos.y + radius, z = pos.z + radius}
|
||||
local vm = minetest.get_voxel_manip()
|
||||
local MinEdge, MaxEdge = vm:read_from_map(pos1, pos2)
|
||||
local data = vm:get_data()
|
||||
local area = VoxelArea:new({MinEdge = MinEdge, MaxEdge = MaxEdge})
|
||||
for x = pos1.x, pos2.x do
|
||||
for y = pos1.y, pos2.y do
|
||||
for z = pos1.z, pos2.z do
|
||||
local idx = area:indexp({x = x, y = y, z = z})
|
||||
if minetest.get_name_from_content_id(data[idx]) then
|
||||
return {x = x, y = y, z = z}
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Marker entities for debugging purposes
|
||||
function minecart.set_marker(pos, text, size, ttl)
|
||||
local marker = minetest.add_entity(pos, "minecart:marker_cube")
|
||||
if marker ~= nil then
|
||||
marker:set_nametag_attributes({color = "#FFFFFF", text = text})
|
||||
size = size or 1
|
||||
marker:set_properties({visual_size = {x = size, y = size}})
|
||||
if ttl then
|
||||
minetest.after(ttl, marker.remove, marker)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_entity(":minecart:marker_cube", {
|
||||
initial_properties = {
|
||||
visual = "cube",
|
||||
textures = {
|
||||
"minecart_marker_cube.png",
|
||||
"minecart_marker_cube.png",
|
||||
"minecart_marker_cube.png",
|
||||
"minecart_marker_cube.png",
|
||||
"minecart_marker_cube.png",
|
||||
"minecart_marker_cube.png",
|
||||
},
|
||||
physical = false,
|
||||
visual_size = {x = 1, y = 1},
|
||||
collisionbox = {-0.25,-0.25,-0.25, 0.25,0.25,0.25},
|
||||
glow = 8,
|
||||
static_save = false,
|
||||
},
|
||||
on_punch = function(self)
|
||||
self.object:remove()
|
||||
end,
|
||||
})
|
||||
|
||||
function minecart.is_air_like(name)
|
||||
local ndef = minetest.registered_nodes[name]
|
||||
if ndef and ndef.buildable_to then
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
function minecart.range(val, min, max)
|
||||
val = tonumber(val)
|
||||
if val < min then return min end
|
||||
if val > max then return max end
|
||||
return val
|
||||
end
|
||||
|
||||
function minecart.get_next_node(pos, param2)
|
||||
local pos2 = param2 and vector.add(pos, param2_to_dir[param2]) or pos
|
||||
local node = minetest.get_node(pos2)
|
||||
return pos2, node
|
||||
end
|
||||
|
||||
function minecart.get_object_id(object)
|
||||
for id, entity in pairs(minetest.luaentities) do
|
||||
if entity.object == object then
|
||||
return id
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.is_owner(player, owner)
|
||||
if not player or not player:is_player() or not owner or owner == "" then
|
||||
return true
|
||||
end
|
||||
|
||||
local name = player:get_player_name()
|
||||
if minetest.check_player_privs(name, "minecart") then
|
||||
return true
|
||||
end
|
||||
return name == owner
|
||||
end
|
||||
|
||||
function minecart.get_buffer_pos(pos, player_name)
|
||||
local pos1 = minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"})
|
||||
if pos1 then
|
||||
local meta = minetest.get_meta(pos1)
|
||||
if player_name == nil or player_name == meta:get_string("owner") then
|
||||
return pos1
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.get_buffer_name(pos)
|
||||
local pos1 = minecart.find_node_near_lvm(pos, 1, {"minecart:buffer"})
|
||||
if pos1 then
|
||||
local name = M(pos1):get_string("name")
|
||||
if name ~= "" then
|
||||
return name
|
||||
end
|
||||
return P2S(pos1)
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.manage_attachment(player, entity, get_on)
|
||||
if not player then
|
||||
return
|
||||
end
|
||||
local player_name = player:get_player_name()
|
||||
if player_api.player_attached[player_name] == get_on then
|
||||
return
|
||||
end
|
||||
player_api.player_attached[player_name] = get_on
|
||||
|
||||
local obj = entity.object
|
||||
if get_on then
|
||||
player:set_attach(obj, "", {x=0, y=-4.5, z=-4}, {x=0, y=0, z=0})
|
||||
player:set_eye_offset({x=0, y=-6, z=0},{x=0, y=-6, z=0})
|
||||
player:set_properties({visual_size = {x = 2.5, y = 2.5}})
|
||||
player_api.set_animation(player, "sit")
|
||||
entity.driver = player:get_player_name()
|
||||
else
|
||||
player:set_detach()
|
||||
player:set_eye_offset({x=0, y=0, z=0},{x=0, y=0, z=0})
|
||||
player:set_properties({visual_size = {x = 1, y = 1}})
|
||||
player_api.set_animation(player, "stand")
|
||||
entity.driver = nil
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.register_cart_names(node_name, entity_name, cart_type)
|
||||
minecart.tNodeNames[node_name] = entity_name
|
||||
minecart.tEntityNames[entity_name] = true
|
||||
minecart.lCartNodeNames[#minecart.lCartNodeNames+1] = node_name
|
||||
minecart.add_raillike_nodes(node_name)
|
||||
minecart.tCartTypes[node_name] = cart_type
|
||||
end
|
||||
|
||||
function minecart.add_nodecart(pos, node_name, param2, cargo, owner, userID)
|
||||
if pos and node_name and param2 and cargo and owner and userID then
|
||||
local pos2
|
||||
if not minecart.is_rail(pos) then
|
||||
pos2 = minetest.find_node_near(pos, 1, minecart.lRails)
|
||||
if not pos2 or not minecart.is_rail(pos2) then
|
||||
pos2 = minetest.find_node_near(pos, 2, minecart.lRails)
|
||||
if not pos2 or not minecart.is_rail(pos2) then
|
||||
pos2 = minetest.find_node_near(pos, 2, {"air"})
|
||||
end
|
||||
end
|
||||
else
|
||||
pos2 = vector.new(pos)
|
||||
end
|
||||
if pos2 then
|
||||
local node = minetest.get_node(pos2)
|
||||
local ndef = minetest.registered_nodes[node_name]
|
||||
local rail = node.name
|
||||
minetest.swap_node(pos2, {name = node_name, param2 = param2})
|
||||
local meta = M(pos2)
|
||||
meta:set_string("removed_rail", rail)
|
||||
meta:set_string("owner", owner)
|
||||
meta:set_int("userID", userID)
|
||||
meta:set_string("infotext", owner .. ": " .. userID)
|
||||
|
||||
if cargo and ndef.set_cargo then
|
||||
ndef.set_cargo(pos2, cargo)
|
||||
end
|
||||
if ndef.after_place_node then
|
||||
ndef.after_place_node(pos2)
|
||||
end
|
||||
return pos2
|
||||
else
|
||||
minetest.add_item(pos, ItemStack({name = node_name}))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.add_entitycart(pos, node_name, entity_name, vel, cargo, owner, userID)
|
||||
local obj = minetest.add_entity(pos, entity_name)
|
||||
local objID = minecart.get_object_id(obj)
|
||||
|
||||
if objID then
|
||||
local entity = obj:get_luaentity()
|
||||
entity.start_pos = pos
|
||||
entity.owner = owner
|
||||
entity.node_name = node_name
|
||||
entity.userID = userID
|
||||
entity.objID = objID
|
||||
entity.cargo = cargo
|
||||
obj:set_nametag_attributes({color = "#ffff00", text = owner..": "..userID})
|
||||
obj:set_velocity(vel)
|
||||
return obj
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.start_entitycart(self, pos)
|
||||
local route = {}
|
||||
|
||||
self.is_running = true
|
||||
self.arrival_time = 0
|
||||
self.start_pos = minecart.get_buffer_pos(pos, self.owner)
|
||||
if self.start_pos then
|
||||
-- Read buffer route for the junction info
|
||||
route = minecart.get_route(self.start_pos) or {}
|
||||
self.junctions = route and route.junctions
|
||||
end
|
||||
-- If set the start waypoint will be deleted
|
||||
self.no_normal_start = self.start_pos == nil
|
||||
if self.driver == nil then
|
||||
minecart.start_monitoring(self.owner, self.userID, pos, self.objID,
|
||||
route.checkpoints, route.junctions, self.cargo or {})
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.remove_nodecart(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local ndef = minetest.registered_nodes[node.name]
|
||||
local meta = M(pos)
|
||||
local rail = meta:get_string("removed_rail")
|
||||
if rail == "" then rail = "air" end
|
||||
local userID = meta:get_int("userID")
|
||||
local owner = meta:get_string("owner")
|
||||
meta:set_string("infotext", "")
|
||||
meta:set_string("formspec", "")
|
||||
local cargo = ndef.get_cargo and ndef.get_cargo(pos) or {}
|
||||
minetest.swap_node(pos, {name = rail})
|
||||
return cargo, owner, userID
|
||||
end
|
||||
|
||||
function minecart.node_to_entity(pos, node_name, entity_name)
|
||||
-- Remove node
|
||||
local cargo, owner, userID = minecart.remove_nodecart(pos)
|
||||
local obj = minecart.add_entitycart(pos, node_name, entity_name,
|
||||
{x = 0, y = 0, z = 0}, cargo, owner, userID)
|
||||
if obj then
|
||||
return obj
|
||||
else
|
||||
print("Entity has no ID")
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.entity_to_node(pos, entity)
|
||||
-- Stop sound
|
||||
if entity.sound_handle then
|
||||
minetest.sound_stop(entity.sound_handle)
|
||||
entity.sound_handle = nil
|
||||
end
|
||||
|
||||
local rot = entity.object:get_rotation()
|
||||
local dir = minetest.yaw_to_dir(rot.y)
|
||||
local facedir = minetest.dir_to_facedir(dir)
|
||||
minecart.stop_recording(entity, pos)
|
||||
entity.object:remove()
|
||||
local pos2 = minecart.add_nodecart(pos, entity.node_name, facedir, entity.cargo, entity.owner, entity.userID)
|
||||
minecart.stop_monitoring(entity.owner, entity.userID, pos2)
|
||||
end
|
||||
|
||||
function minecart.add_node_to_player_inventory(pos, player, node_name)
|
||||
local inv = player:get_inventory()
|
||||
if not (creative and creative.is_enabled_for
|
||||
and creative.is_enabled_for(player:get_player_name()))
|
||||
or not inv:contains_item("main", node_name) then
|
||||
local leftover = inv:add_item("main", node_name)
|
||||
-- If no room in inventory, drop the cart
|
||||
if not leftover:is_empty() then
|
||||
minetest.add_item(pos, leftover)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Player removes the node
|
||||
function minecart.remove_entity(self, pos, player)
|
||||
-- Stop sound
|
||||
if self.sound_handle then
|
||||
minetest.sound_stop(self.sound_handle)
|
||||
self.sound_handle = nil
|
||||
end
|
||||
minecart.add_node_to_player_inventory(pos, player, self.node_name or "minecart:cart")
|
||||
minecart.stop_monitoring(self.owner, self.userID, pos)
|
||||
minecart.stop_recording(self, pos)
|
||||
minecart.monitoring_remove_cart(self.owner, self.userID)
|
||||
self.object:remove()
|
||||
end
|
162
minecart/buffer.lua
Normal file
@ -0,0 +1,162 @@
|
||||
--[[
|
||||
|
||||
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 S2P = minetest.string_to_pos
|
||||
local S = minecart.S
|
||||
|
||||
local CYCLE_TIME = 2
|
||||
|
||||
local StopTime = {}
|
||||
|
||||
local function formspec(pos)
|
||||
local name = M(pos):get_string("name")
|
||||
local time = M(pos):get_int("time")
|
||||
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]"..
|
||||
"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(pos)
|
||||
if route and route.dest_pos then
|
||||
return M(route.dest_pos):get_string("name")
|
||||
end
|
||||
return "none"
|
||||
end
|
||||
|
||||
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))
|
||||
minetest.get_node_timer(pos):start(CYCLE_TIME)
|
||||
|
||||
-- Optional Teleport function
|
||||
if not minecart.teleport_enabled then return end
|
||||
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 teleport = function()
|
||||
-- Make sure the player object still exists
|
||||
local player = minetest.get_player_by_name(playername)
|
||||
if player then player:set_pos(route.dest_pos) end
|
||||
end
|
||||
minetest.after(0.25, teleport)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_node("minecart:buffer", {
|
||||
description = S("Minecart Railway Buffer"),
|
||||
tiles = {
|
||||
'default_junglewood.png',
|
||||
'default_junglewood.png',
|
||||
'default_junglewood.png',
|
||||
'default_junglewood.png',
|
||||
'default_junglewood.png',
|
||||
'default_junglewood.png^minecart_buffer.png',
|
||||
},
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-8/16, -8/16, -8/16, 8/16, -4/16, 8/16},
|
||||
{-8/16, -4/16, -8/16, 8/16, 0/16, 4/16},
|
||||
{-8/16, 0/16, -8/16, 8/16, 4/16, 0/16},
|
||||
{-8/16, 4/16, -8/16, 8/16, 8/16, -4/16},
|
||||
},
|
||||
},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
|
||||
},
|
||||
after_place_node = function(pos, placer)
|
||||
M(pos):set_string("owner", placer:get_player_name())
|
||||
minecart.del_route(pos)
|
||||
M(pos):set_string("formspec", formspec(pos))
|
||||
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.is_cart_available(pos, param2, 0.5) then
|
||||
if StopTime[hash] then
|
||||
if StopTime[hash] < minetest.get_gametime() then
|
||||
StopTime[hash] = nil
|
||||
local dir = minetest.facedir_to_dir(param2)
|
||||
minecart.punch_cart(pos, param2, 0.5, dir)
|
||||
end
|
||||
else
|
||||
StopTime[hash] = minetest.get_gametime() + time
|
||||
end
|
||||
else
|
||||
StopTime[hash] = nil
|
||||
end
|
||||
end
|
||||
return true
|
||||
end,
|
||||
after_dig_node = function(pos)
|
||||
minecart.del_route(pos)
|
||||
local hash = minetest.hash_node_position(pos)
|
||||
StopTime[hash] = nil
|
||||
end,
|
||||
on_receive_fields = function(pos, formname, fields, player)
|
||||
if M(pos):get_string("owner") ~= player:get_player_name() then
|
||||
return
|
||||
end
|
||||
if (fields.key_enter == "true" or fields.exit == "Save") and fields.name ~= "" then
|
||||
M(pos):set_string("name", fields.name)
|
||||
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,
|
||||
paramtype = "light",
|
||||
sunlight_propagates = true,
|
||||
on_rotate = screwdriver.disallow,
|
||||
paramtype2 = "facedir",
|
||||
groups = {cracky=2, crumbly=2, choppy=2},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "minecart:buffer",
|
||||
recipe = {
|
||||
{"dye:red", "", "dye:white"},
|
||||
{"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,
|
||||
})
|
4
minecart/depends.txt
Normal file
@ -0,0 +1,4 @@
|
||||
default
|
||||
carts
|
||||
screwdriver
|
||||
doc?
|
131
minecart/doc.lua
Normal file
@ -0,0 +1,131 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
|
||||
minecart.doc = {}
|
||||
|
||||
if not minetest.get_modpath("doc") then
|
||||
return
|
||||
end
|
||||
|
||||
local S = minecart.S
|
||||
|
||||
local summary_doc = table.concat({
|
||||
S("Summary"),
|
||||
"------------",
|
||||
"",
|
||||
S("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."),
|
||||
S("2. Place a Railway Buffer at both endpoints (buffers are always needed, they store the route and timing information)."),
|
||||
S("3. Give both Railway Buffers unique station names, like Oxford and Cambridge."),
|
||||
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 waiting time in both buffers. The Minecart will then start automatically after the configured time."),
|
||||
S("8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters)."),
|
||||
S("9. Place a Minecart in front of the buffer and check whether it starts after the configured time."),
|
||||
S("10. Check the cart state via the chat command: /mycart <num>\n '<num>' is the cart number"),
|
||||
S("11. Drop items into the Minecart and punch the cart to start it."),
|
||||
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 cart and items back")
|
||||
|
||||
local buffer_doc = S("Used as buffer on both rail ends. Needed to be able to record the cart routes")
|
||||
|
||||
local landmark_doc = S("Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)")
|
||||
|
||||
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
|
||||
local image = "image["..(doc.FORMSPEC.ENTRY_WIDTH - 3)..",0;3,2;"..data.image.."]"
|
||||
local formstring = doc.widgets.text(data.text, doc.FORMSPEC.ENTRY_START_X, doc.FORMSPEC.ENTRY_START_Y+1.6, doc.FORMSPEC.ENTRY_WIDTH, doc.FORMSPEC.ENTRY_HEIGHT - 1.6)
|
||||
return image..formstring
|
||||
elseif data.item then
|
||||
local box = "box["..(doc.FORMSPEC.ENTRY_WIDTH - 1.6)..",0;1,1.1;#BBBBBB]"
|
||||
local image = "item_image["..(doc.FORMSPEC.ENTRY_WIDTH - 1.5)..",0.1;1,1;"..data.item.."]"
|
||||
local formstring = doc.widgets.text(data.text, doc.FORMSPEC.ENTRY_START_X, doc.FORMSPEC.ENTRY_START_Y+0.8, doc.FORMSPEC.ENTRY_WIDTH, doc.FORMSPEC.ENTRY_HEIGHT - 0.8)
|
||||
return box..image..formstring
|
||||
else
|
||||
return doc.entry_builders.text(data.text)
|
||||
end
|
||||
end
|
||||
|
||||
doc.add_category("minecart",
|
||||
{
|
||||
name = S("Minecart"),
|
||||
description = S("Minecart, the lean railway transportation automation system"),
|
||||
sorting = "custom",
|
||||
sorting_data = {"summary", "cart"},
|
||||
build_formspec = formspec,
|
||||
})
|
||||
|
||||
doc.add_entry("minecart", "summary", {
|
||||
name = S("Summary"),
|
||||
data = {text=summary_doc, image="minecart_doc_image.png"},
|
||||
})
|
||||
|
||||
doc.add_entry("minecart", "cart", {
|
||||
name = S("Minecart Cart"),
|
||||
data = {text=cart_doc, item="minecart:cart"},
|
||||
})
|
||||
|
||||
doc.add_entry("minecart", "buffer", {
|
||||
name = S("Minecart Railway Buffer"),
|
||||
data = {text=buffer_doc, item="minecart:buffer"},
|
||||
})
|
||||
|
||||
doc.add_entry("minecart", "landmark", {
|
||||
name = S("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"),
|
||||
data = {text=hopper_doc, item="minecart:hopper"},
|
||||
})
|
||||
end
|
318
minecart/entitylib.lua
Normal file
@ -0,0 +1,318 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
|
||||
local P2H = minetest.hash_node_position
|
||||
local H2P = minetest.get_position_from_hash
|
||||
local MAX_SPEED = minecart.MAX_SPEED
|
||||
local dot2dir = minecart.dot2dir
|
||||
local get_waypoint = minecart.get_waypoint
|
||||
local recording_waypoints = minecart.recording_waypoints
|
||||
local recording_junctions = minecart.recording_junctions
|
||||
local set_junctions = minecart.set_junctions
|
||||
local player_ctrl = minecart.player_ctrl
|
||||
local tEntityNames = minecart.tEntityNames
|
||||
|
||||
local function stop_cart(self, cart_pos)
|
||||
self.is_running = false
|
||||
self.arrival_time = 0
|
||||
|
||||
if self.driver then
|
||||
local player = minetest.get_player_by_name(self.driver)
|
||||
if player then
|
||||
minecart.stop_recording(self, cart_pos)
|
||||
minecart.manage_attachment(player, self, false)
|
||||
end
|
||||
end
|
||||
if not minecart.get_buffer_pos(cart_pos, self.owner) then
|
||||
-- Probably somewhere in the pampas
|
||||
minecart.delete_cart_waypoint(cart_pos)
|
||||
end
|
||||
minecart.entity_to_node(cart_pos, self)
|
||||
end
|
||||
|
||||
local function get_ctrl(self, pos)
|
||||
-- Use player ctrl or junction data from recorded routes
|
||||
return (self.driver and self.ctrl) or (self.junctions and self.junctions[P2H(pos)]) or {}
|
||||
end
|
||||
|
||||
local function new_speed(self, new_dir)
|
||||
self.cart_speed = self.cart_speed or 0
|
||||
local rail_speed = (self.waypoint.speed or 0) / 10
|
||||
|
||||
if rail_speed <= 0 then
|
||||
rail_speed = math.max(self.cart_speed + rail_speed, 0)
|
||||
elseif rail_speed <= self.cart_speed then
|
||||
rail_speed = math.max((self.cart_speed + rail_speed) / 2, 0)
|
||||
end
|
||||
|
||||
-- Speed corrections
|
||||
if new_dir.y == 1 then
|
||||
if rail_speed < 1 then rail_speed = 0 end
|
||||
else
|
||||
if rail_speed < 0.4 then rail_speed = 0 end
|
||||
end
|
||||
|
||||
self.cart_speed = rail_speed -- store for next cycle
|
||||
return rail_speed
|
||||
end
|
||||
|
||||
local function running(self)
|
||||
local rot = self.object:get_rotation()
|
||||
local dir = minetest.yaw_to_dir(rot.y)
|
||||
dir.y = math.floor((rot.x / (math.pi/4)) + 0.5)
|
||||
dir = vector.round(dir)
|
||||
local facedir = minetest.dir_to_facedir(dir)
|
||||
local cart_pos, wayp_pos, is_junction
|
||||
|
||||
if self.reenter then -- through monitoring
|
||||
cart_pos = H2P(self.reenter[1])
|
||||
wayp_pos = cart_pos
|
||||
is_junction = false
|
||||
self.waypoint = {pos = H2P(self.reenter[2]), power = 0, dot = self.reenter[4]}
|
||||
self.cart_speed = self.reenter[3]
|
||||
self.speed_limit = MAX_SPEED
|
||||
self.reenter = nil
|
||||
elseif not self.waypoint then
|
||||
-- get waypoint
|
||||
cart_pos = vector.round(self.object:get_pos())
|
||||
wayp_pos = cart_pos
|
||||
is_junction = false
|
||||
self.waypoint = get_waypoint(cart_pos, facedir, get_ctrl(self, cart_pos), true)
|
||||
if self.no_normal_start then
|
||||
-- Probably somewhere in the pampas
|
||||
minecart.delete_waypoint(cart_pos)
|
||||
self.no_normal_start = nil
|
||||
end
|
||||
self.cart_speed = 2 -- push speed
|
||||
self.speed_limit = MAX_SPEED
|
||||
else
|
||||
-- next waypoint
|
||||
cart_pos = vector.new(self.waypoint.cart_pos or self.waypoint.pos)
|
||||
wayp_pos = self.waypoint.pos
|
||||
local vel = self.object:get_velocity()
|
||||
self.waypoint, is_junction = get_waypoint(wayp_pos, facedir, get_ctrl(self, wayp_pos), self.cart_speed < 0.1)
|
||||
end
|
||||
|
||||
if not self.waypoint then
|
||||
stop_cart(self, wayp_pos)
|
||||
return
|
||||
end
|
||||
|
||||
if is_junction then
|
||||
if self.is_recording then
|
||||
set_junctions(self, wayp_pos)
|
||||
end
|
||||
self.ctrl = nil
|
||||
end
|
||||
|
||||
--print("dist", P2S(cart_pos), P2S(self.waypoint.pos), P2S(self.waypoint.cart_pos), self.waypoint.dot)
|
||||
local dist = vector.distance(cart_pos, self.waypoint.cart_pos or self.waypoint.pos)
|
||||
local new_dir = dot2dir(self.waypoint.dot)
|
||||
local new_speed = new_speed(self, new_dir)
|
||||
local straight_ahead = vector.equals(new_dir, dir)
|
||||
-- If straight_ahead, then it's probably a speed limit sign
|
||||
if straight_ahead then
|
||||
self.speed_limit = minecart.get_speedlimit(wayp_pos, facedir) or self.speed_limit
|
||||
end
|
||||
new_speed = math.min(new_speed, self.speed_limit)
|
||||
|
||||
local new_cart_pos, extra_cycle = minecart.get_current_cart_pos_correction(
|
||||
wayp_pos, facedir, dir.y, self.waypoint.dot) -- TODO: Why has self.waypoint no dot?
|
||||
if extra_cycle and not vector.equals(cart_pos, new_cart_pos) then
|
||||
self.waypoint = {pos = wayp_pos, cart_pos = new_cart_pos}
|
||||
new_dir = vector.direction(cart_pos, new_cart_pos)
|
||||
dist = vector.distance(cart_pos, new_cart_pos)
|
||||
--print("extra_cycle", P2S(cart_pos), P2S(wayp_pos), P2S(new_cart_pos), new_speed)
|
||||
end
|
||||
|
||||
-- Slope corrections
|
||||
--print("Slope corrections", P2S(new_dir), P2S(cart_pos))
|
||||
if new_dir.y ~= 0 then
|
||||
cart_pos.y = cart_pos.y + 0.2
|
||||
end
|
||||
|
||||
-- Calc velocity, rotation and arrival_time
|
||||
local yaw = minetest.dir_to_yaw(new_dir)
|
||||
local pitch = new_dir.y * math.pi/4
|
||||
--print("new_speed", new_speed / (new_dir.y ~= 0 and 1.41 or 1))
|
||||
local vel = vector.multiply(new_dir, new_speed / ((new_dir.y ~= 0) and 1.41 or 1))
|
||||
self.arrival_time = self.timebase + (dist / new_speed)
|
||||
-- needed for recording
|
||||
self.curr_speed = new_speed
|
||||
self.num_sections = (self.num_sections or 0) + 1
|
||||
|
||||
-- Got stuck somewhere
|
||||
if new_speed < 0.1 or dist < 0 then
|
||||
print("Got stuck somewhere", new_speed, dist)
|
||||
stop_cart(self, wayp_pos)
|
||||
return
|
||||
end
|
||||
|
||||
self.object:set_pos(cart_pos)
|
||||
self.object:set_rotation({x = pitch, y = yaw, z = 0})
|
||||
self.object:set_velocity(vel)
|
||||
return
|
||||
end
|
||||
|
||||
local function play_sound(self)
|
||||
if self.sound_handle then
|
||||
local handle = self.sound_handle
|
||||
self.sound_handle = nil
|
||||
minetest.after(0.2, minetest.sound_stop, handle)
|
||||
end
|
||||
if self.object then
|
||||
self.sound_handle = minetest.sound_play(
|
||||
"carts_cart_moving", {
|
||||
object = self.object,
|
||||
gain = self.curr_speed / MAX_SPEED,
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
local function on_step(self, dtime)
|
||||
self.timebase = (self.timebase or 0) + dtime
|
||||
|
||||
if self.is_running then
|
||||
if self.arrival_time <= self.timebase then
|
||||
running(self)
|
||||
end
|
||||
|
||||
if (self.sound_ttl or 0) <= self.timebase then
|
||||
play_sound(self)
|
||||
self.sound_ttl = self.timebase + 1.0
|
||||
end
|
||||
else
|
||||
if self.sound_handle then
|
||||
minetest.sound_stop(self.sound_handle)
|
||||
self.sound_handle = nil
|
||||
end
|
||||
end
|
||||
|
||||
if self.driver then
|
||||
if self.is_recording then
|
||||
if self.rec_time <= self.timebase then
|
||||
recording_waypoints(self)
|
||||
self.rec_time = self.rec_time + 2.0
|
||||
end
|
||||
recording_junctions(self)
|
||||
else
|
||||
player_ctrl(self)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function on_entitycard_activate(self, staticdata, dtime_s)
|
||||
self.object:set_armor_groups({immortal=1})
|
||||
end
|
||||
|
||||
-- Start the entity cart (or dig by shift+leftclick)
|
||||
local function on_entitycard_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)
|
||||
if minecart.is_owner(puncher, self.owner) then
|
||||
if puncher:get_player_control().sneak then
|
||||
if not self.only_dig_if_empty or not next(self.cargo) then
|
||||
-- drop items
|
||||
local pos = vector.round(self.object:get_pos())
|
||||
for _,item in ipairs(self.cargo or {}) do
|
||||
minetest.add_item(pos, ItemStack(item))
|
||||
end
|
||||
-- Dig cart
|
||||
if self.driver then
|
||||
-- remove cart as driver
|
||||
minecart.stop_recording(self, pos)
|
||||
minecart.monitoring_remove_cart(self.owner, self.userID)
|
||||
minecart.remove_entity(self, pos, puncher)
|
||||
minecart.manage_attachment(puncher, self, false)
|
||||
else
|
||||
-- remove cart from outside
|
||||
minecart.monitoring_remove_cart(self.owner, self.userID)
|
||||
minecart.remove_entity(self, pos, puncher)
|
||||
end
|
||||
end
|
||||
elseif not self.is_running then
|
||||
-- start the cart
|
||||
local pos = vector.round(self.object:get_pos())
|
||||
if puncher then
|
||||
local yaw = puncher:get_look_horizontal()
|
||||
self.object:set_rotation({x = 0, y = yaw, z = 0})
|
||||
end
|
||||
minecart.start_entitycart(self, pos)
|
||||
minecart.start_recording(self, pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Player get on / off
|
||||
local function on_entitycard_rightclick(self, clicker)
|
||||
if clicker and clicker:is_player() and self.driver_allowed then
|
||||
-- Get on / off
|
||||
if self.driver then
|
||||
-- get off
|
||||
local pos = vector.round(self.object:get_pos())
|
||||
minecart.manage_attachment(clicker, self, false)
|
||||
minecart.entity_to_node(pos, self)
|
||||
else
|
||||
-- get on
|
||||
local pos = vector.round(self.object:get_pos())
|
||||
minecart.stop_recording(self, pos)
|
||||
minecart.manage_attachment(clicker, self, true)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function on_entitycard_detach_child(self, child)
|
||||
if child and child:get_player_name() == self.driver then
|
||||
self.driver = nil
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.get_entitycart_nearby(pos, param2, radius)
|
||||
local pos2 = param2 and vector.add(pos, minecart.param2_to_dir(param2)) or pos
|
||||
for _, object in pairs(minetest.get_objects_inside_radius(pos2, radius or 0.5)) do
|
||||
local entity = object:get_luaentity()
|
||||
if entity and entity.name and tEntityNames[entity.name] then
|
||||
local vel = object:get_velocity()
|
||||
if vector.equals(vel, {x=0, y=0, z=0}) then -- still standing?
|
||||
return entity
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.push_entitycart(self, punch_dir)
|
||||
--print("push_entitycart")
|
||||
local vel = self.object:get_velocity()
|
||||
punch_dir.y = 0
|
||||
local yaw = minetest.dir_to_yaw(punch_dir)
|
||||
self.object:set_rotation({x = 0, y = yaw, z = 0})
|
||||
self.is_running = true
|
||||
self.arrival_time = 0
|
||||
end
|
||||
|
||||
function minecart.register_cart_entity(entity_name, node_name, cart_type, entity_def)
|
||||
entity_def.entity_name = entity_name
|
||||
entity_def.node_name = node_name
|
||||
entity_def.on_activate = on_entitycard_activate
|
||||
entity_def.on_punch = on_entitycard_punch
|
||||
entity_def.on_step = on_step
|
||||
entity_def.on_rightclick = on_entitycard_rightclick
|
||||
entity_def.on_detach_child = on_entitycard_detach_child
|
||||
|
||||
entity_def.owner = nil
|
||||
entity_def.driver = nil
|
||||
entity_def.cargo = {}
|
||||
|
||||
minetest.register_entity(entity_name, entity_def)
|
||||
-- register node for punching
|
||||
minecart.register_cart_names(node_name, entity_name, cart_type)
|
||||
end
|
||||
|
172
minecart/hopper.lua
Normal file
@ -0,0 +1,172 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
local NUM_ITEMS = 4
|
||||
|
||||
-- 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 function scan_for_objects(pos, inv)
|
||||
for _, object in pairs(minetest.get_objects_inside_radius(pos, 1)) do
|
||||
local lua_entity = object:get_luaentity()
|
||||
if not object:is_player() and lua_entity and lua_entity.name == "__builtin:item" then
|
||||
if lua_entity.itemstring ~= "" then
|
||||
local stack = ItemStack(lua_entity.itemstring)
|
||||
if inv:room_for_item("main", stack) then
|
||||
inv:add_item("main", stack)
|
||||
object:remove()
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function pull_push_item(pos, param2)
|
||||
local items = minecart.take_items(pos, param2, NUM_ITEMS)
|
||||
if items then
|
||||
local leftover = minecart.put_items(pos, param2, items)
|
||||
if leftover then
|
||||
-- place item back
|
||||
minecart.untake_items(pos, param2, leftover)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
else
|
||||
items = minecart.take_items({x=pos.x, y=pos.y+1, z=pos.z}, nil, NUM_ITEMS)
|
||||
if items then
|
||||
local leftover = minecart.put_items(pos, param2, items)
|
||||
if leftover then
|
||||
-- place item back
|
||||
minecart.untake_items({x=pos.x, y=pos.y+1, z=pos.z}, nil, leftover)
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function push_item(pos, inv, param2)
|
||||
local taken = minecart.inv_take_items(inv, "main", NUM_ITEMS)
|
||||
if taken then
|
||||
local leftover = minecart.put_items(pos, param2, taken)
|
||||
if leftover then
|
||||
inv:add_item("main", leftover)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local formspec = "size[8,6.5]"..
|
||||
"list[context;main;3,0;2,2;]"..
|
||||
"list[current_player;main;0,2.7;8,4;]"..
|
||||
"listring[context;main]"..
|
||||
"listring[current_player;main]"
|
||||
|
||||
minetest.register_node("minecart:hopper", {
|
||||
description = S("Minecart Hopper"),
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
"default_cobble.png^minecart_appl_hopper_top.png",
|
||||
"default_cobble.png^minecart_appl_hopper.png",
|
||||
"default_cobble.png^minecart_appl_hopper_right.png",
|
||||
"default_cobble.png^minecart_appl_hopper.png",
|
||||
"default_cobble.png^minecart_appl_hopper.png",
|
||||
"default_cobble.png^minecart_appl_hopper.png",
|
||||
},
|
||||
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-8/16, 2/16, -8/16, 8/16, 8/16, -6/16},
|
||||
{-8/16, 2/16, 6/16, 8/16, 8/16, 8/16},
|
||||
{-8/16, 2/16, -8/16, -6/16, 8/16, 8/16},
|
||||
{ 6/16, 2/16, -8/16, 8/16, 8/16, 8/16},
|
||||
{-6/16, 0/16, -6/16, 6/16, 3/16, 6/16},
|
||||
{-5/16, -4/16, -5/16, 5/16, 0/16, 5/16},
|
||||
{ 0/16, -4/16, -3/16, 11/16, 2/16, 3/16},
|
||||
},
|
||||
},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-8/16, 2/16, -8/16, 8/16, 8/16, 8/16},
|
||||
{-5/16, -4/16, -5/16, 5/16, 0/16, 5/16},
|
||||
{ 0/16, -4/16, -3/16, 11/16, 2/16, 3/16},
|
||||
},
|
||||
},
|
||||
|
||||
on_construct = function(pos)
|
||||
local inv = M(pos):get_inventory()
|
||||
inv:set_size('main', 4)
|
||||
end,
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = M(pos)
|
||||
meta:set_string("owner", placer:get_player_name())
|
||||
meta:set_string("formspec", formspec)
|
||||
minetest.get_node_timer(pos):start(2)
|
||||
end,
|
||||
|
||||
on_timer = function(pos, elapsed)
|
||||
local inv = M(pos):get_inventory()
|
||||
local param2 = minetest.get_node(pos).param2
|
||||
param2 = (param2 + 1) % 4 -- output is on the right
|
||||
if not pull_push_item(pos, param2) then
|
||||
scan_for_objects({x=pos.x, y=pos.y+1, z=pos.z}, inv)
|
||||
push_item(pos, inv, param2)
|
||||
end
|
||||
return true
|
||||
end,
|
||||
|
||||
allow_metadata_inventory_put = function(pos, listname, index, stack, player)
|
||||
if minetest.is_protected(pos, player:get_player_name()) then
|
||||
return 0
|
||||
end
|
||||
minetest.get_node_timer(pos):start(2)
|
||||
return stack:get_count()
|
||||
end,
|
||||
|
||||
allow_metadata_inventory_take = function(pos, listname, index, stack, player)
|
||||
if minetest.is_protected(pos, player:get_player_name()) then
|
||||
return 0
|
||||
end
|
||||
return stack:get_count()
|
||||
end,
|
||||
|
||||
after_dig_node = function(pos, oldnode, oldmetadata, digger)
|
||||
for _,stack in ipairs(oldmetadata.inventory.main) do
|
||||
minetest.add_item(pos, stack)
|
||||
end
|
||||
end,
|
||||
|
||||
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(),
|
||||
})
|
||||
|
||||
|
||||
minetest.register_craft({
|
||||
output = "minecart:hopper",
|
||||
recipe = {
|
||||
{"default:stone", "", "default:stone"},
|
||||
{"default:stone", "default:gold_ingot", "default:stone"},
|
||||
{"", "default:stone", ""},
|
||||
},
|
||||
})
|
BIN
minecart/hopper.png
Normal file
After Width: | Height: | Size: 121 KiB |
141
minecart/hopperlib.lua
Normal file
@ -0,0 +1,141 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2020 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
-- for lazy programmers
|
||||
local M = minetest.get_meta
|
||||
|
||||
local RegisteredInventories = {}
|
||||
|
||||
-- Take the given number of items from the inv.
|
||||
-- Returns nil if ItemList is empty.
|
||||
function minecart.inv_take_items(inv, listname, num)
|
||||
if inv:is_empty(listname) then
|
||||
return nil
|
||||
end
|
||||
local size = inv:get_size(listname)
|
||||
for idx = 1, size do
|
||||
local items = inv:get_stack(listname, idx)
|
||||
if items:get_count() > 0 then
|
||||
local taken = items:take_item(num)
|
||||
inv:set_stack(listname, idx, items)
|
||||
return taken
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
function minecart.take_items(pos, param2, num)
|
||||
local npos, node
|
||||
if param2 then
|
||||
npos, node = minecart.get_next_node(pos, (param2 + 2) % 4)
|
||||
else
|
||||
npos, node = pos, minetest.get_node(pos)
|
||||
end
|
||||
local def = RegisteredInventories[node.name]
|
||||
local owner = M(pos):get_string("owner")
|
||||
local inv = minetest.get_inventory({type="node", pos=npos})
|
||||
|
||||
if def and inv and def.take_listname and (not def.allow_take or def.allow_take(npos, nil, owner)) then
|
||||
return minecart.inv_take_items(inv, def.take_listname, num)
|
||||
elseif def and def.take_item then
|
||||
return def.take_item(npos, num, owner)
|
||||
else
|
||||
local ndef = minetest.registered_nodes[node.name]
|
||||
if ndef and ndef.minecart_hopper_takeitem then
|
||||
return ndef.minecart_hopper_takeitem(npos, num)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.put_items(pos, param2, stack)
|
||||
local npos, node = minecart.get_next_node(pos, param2)
|
||||
local def = RegisteredInventories[node.name]
|
||||
local owner = M(pos):get_string("owner")
|
||||
local inv = minetest.get_inventory({type="node", pos=npos})
|
||||
|
||||
if def and inv and def.put_listname and (not def.allow_put or def.allow_put(npos, stack, owner)) then
|
||||
local leftover = inv:add_item(def.put_listname, stack)
|
||||
if leftover:get_count() > 0 then
|
||||
return leftover
|
||||
end
|
||||
elseif def and def.put_item then
|
||||
return def.put_item(npos, stack, owner)
|
||||
elseif minecart.is_air_like(node.name) or minecart.is_nodecart_available(npos) then
|
||||
minetest.add_item(npos, stack)
|
||||
else
|
||||
local ndef = minetest.registered_nodes[node.name]
|
||||
if ndef and ndef.minecart_hopper_additem then
|
||||
local leftover = ndef.minecart_hopper_additem(npos, stack)
|
||||
if leftover:get_count() > 0 then
|
||||
return leftover
|
||||
end
|
||||
else
|
||||
return stack
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.untake_items(pos, param2, stack)
|
||||
local npos, node
|
||||
if param2 then
|
||||
npos, node = minecart.get_next_node(pos, (param2 + 2) % 4)
|
||||
else
|
||||
npos, node = pos, minetest.get_node(pos)
|
||||
end
|
||||
local def = RegisteredInventories[node.name]
|
||||
local inv = minetest.get_inventory({type="node", pos=npos})
|
||||
|
||||
if def and inv and def.put_listname then
|
||||
return inv:add_item(def.put_listname, stack)
|
||||
elseif def and def.untake_item then
|
||||
return def.untake_item(npos, stack)
|
||||
else
|
||||
local ndef = minetest.registered_nodes[node.name]
|
||||
if ndef and ndef.minecart_hopper_untakeitem then
|
||||
return ndef.minecart_hopper_untakeitem(npos, stack)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
-- Register inventory node for hopper access
|
||||
-- (for example, see below)
|
||||
function minecart.register_inventory(node_names, def)
|
||||
for _, name in ipairs(node_names) do
|
||||
RegisteredInventories[name] = {
|
||||
allow_put = def.put and def.put.allow_inventory_put,
|
||||
put_listname = def.put and def.put.listname,
|
||||
allow_take = def.take and def.take.allow_inventory_take,
|
||||
take_listname = def.take and def.take.listname,
|
||||
put_item = def.put and def.put.put_item,
|
||||
take_item = def.take and def.take.take_item,
|
||||
untake_item = def.take and def.take.untake_item,
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
-- Allow the hopper the access to itself
|
||||
minecart.register_inventory({"minecart:hopper"}, {
|
||||
put = {
|
||||
allow_inventory_put = function(pos, stack, player_name)
|
||||
local owner = M(pos):get_string("owner")
|
||||
return owner == player_name
|
||||
end,
|
||||
listname = "main",
|
||||
},
|
||||
take = {
|
||||
allow_inventory_take = function(pos, stack, player_name)
|
||||
local owner = M(pos):get_string("owner")
|
||||
return owner == player_name
|
||||
end,
|
||||
listname = "main",
|
||||
},
|
||||
})
|
469
minecart/i18n.py
Executable file
@ -0,0 +1,469 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Script to generate the template file and update the translation files.
|
||||
# Copy the script into the mod or modpack root folder and run it there.
|
||||
#
|
||||
# Copyright (C) 2019 Joachim Stolberg, 2020 FaceDeer, 2020 Louis Royer
|
||||
# LGPLv2.1+
|
||||
#
|
||||
# See https://github.com/minetest-tools/update_translations for
|
||||
# potential future updates to this script.
|
||||
|
||||
from __future__ import print_function
|
||||
import os, fnmatch, re, shutil, errno
|
||||
from sys import argv as _argv
|
||||
from sys import stderr as _stderr
|
||||
|
||||
# Running params
|
||||
params = {"recursive": False,
|
||||
"help": False,
|
||||
"mods": False,
|
||||
"verbose": False,
|
||||
"folders": [],
|
||||
"no-old-file": False,
|
||||
"break-long-lines": False,
|
||||
"sort": False,
|
||||
"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']
|
||||
}
|
||||
|
||||
# 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 = []
|
||||
with open(lua_file, encoding='utf-8') as text_file:
|
||||
text = text_file.read()
|
||||
#TODO remove comments here
|
||||
|
||||
text = re.sub(pattern_concat, "", text)
|
||||
|
||||
strings = []
|
||||
for s in pattern_lua_s.findall(text):
|
||||
strings.append(s[1])
|
||||
for s in pattern_lua_bracketed_s.findall(text):
|
||||
strings.append(s)
|
||||
for s in pattern_lua_fs.findall(text):
|
||||
strings.append(s[1])
|
||||
for s in pattern_lua_bracketed_fs.findall(text):
|
||||
strings.append(s)
|
||||
|
||||
for s in strings:
|
||||
s = re.sub(r'"\.\.\s+"', "", s)
|
||||
s = re.sub("@[^@=0-9]", "@@", s)
|
||||
s = s.replace('\\"', '"')
|
||||
s = s.replace("\\'", "'")
|
||||
s = s.replace("\n", "@n")
|
||||
s = s.replace("\\n", "@n")
|
||||
s = s.replace("=", "@=")
|
||||
lOut.append(s)
|
||||
return lOut
|
||||
|
||||
# Gets strings from an existing translation file
|
||||
# returns both a dictionary of translations
|
||||
# and the full original source text so that the new text
|
||||
# can be compared to it for changes.
|
||||
# Returns also header comments in the third return value.
|
||||
def import_tr_file(tr_file):
|
||||
dOut = {}
|
||||
text = None
|
||||
header_comment = None
|
||||
if os.path.exists(tr_file):
|
||||
with open(tr_file, "r", encoding='utf-8') as existing_file :
|
||||
# save the full text to allow for comparison
|
||||
# of the old version with the new output
|
||||
text = existing_file.read()
|
||||
existing_file.seek(0)
|
||||
# a running record of the current comment block
|
||||
# we're inside, to allow preceeding multi-line comments
|
||||
# to be retained for a translation line
|
||||
latest_comment_block = None
|
||||
for line in existing_file.readlines():
|
||||
line = line.rstrip('\n')
|
||||
if line.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
|
||||
|
||||
# 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)
|
||||
if params["verbose"]:
|
||||
print(f"{fname}: {str(len(found))} translatable strings")
|
||||
|
||||
for s in found:
|
||||
sources = dOut.get(s, set())
|
||||
sources.add(f"### {os.path.basename(fname)} ###")
|
||||
dOut[s] = sources
|
||||
|
||||
if len(dOut) == 0:
|
||||
return None
|
||||
templ_file = os.path.join(folder, "locale/template.txt")
|
||||
write_template(templ_file, dOut, mod_name)
|
||||
return dOut
|
||||
|
||||
# Updates an existing .tr file, copying the old one to a ".old" file
|
||||
# if any changes have happened
|
||||
# dNew is the data used to generate the template, it has all the
|
||||
# currently-existing localized strings
|
||||
def update_tr_file(dNew, mod_name, tr_file):
|
||||
if params["verbose"]:
|
||||
print(f"updating {tr_file}")
|
||||
|
||||
tr_import = import_tr_file(tr_file)
|
||||
dOld = tr_import[0]
|
||||
textOld = tr_import[1]
|
||||
|
||||
textNew = strings_to_text(dNew, dOld, mod_name, tr_import[2])
|
||||
|
||||
if textOld and textOld != textNew:
|
||||
print(f"{tr_file} has changed.")
|
||||
if not params["no-old-file"]:
|
||||
shutil.copyfile(tr_file, f"{tr_file}.old")
|
||||
|
||||
with open(tr_file, "w", encoding='utf-8') as new_tr_file:
|
||||
new_tr_file.write(textNew)
|
||||
|
||||
# Updates translation files for the mod in the given folder
|
||||
def update_mod(folder):
|
||||
modname = get_modname(folder)
|
||||
if modname is not None:
|
||||
process_po_files(folder, modname)
|
||||
print(f"Updating translations for {modname}")
|
||||
data = generate_template(folder, modname)
|
||||
if data == None:
|
||||
print(f"No translatable strings found in {modname}")
|
||||
else:
|
||||
for tr_file in get_existing_tr_files(folder):
|
||||
update_tr_file(data, modname, os.path.join(folder, "locale/", tr_file))
|
||||
else:
|
||||
print(f"\033[31mUnable to find modname in folder {folder}.\033[0m", file=_stderr)
|
||||
exit(1)
|
||||
|
||||
# Determines if the folder being pointed to is a mod or a mod pack
|
||||
# and then runs update_mod accordingly
|
||||
def update_folder(folder):
|
||||
is_modpack = os.path.exists(os.path.join(folder, "modpack.txt")) or os.path.exists(os.path.join(folder, "modpack.conf"))
|
||||
if is_modpack:
|
||||
subfolders = [f.path for f in os.scandir(folder) if f.is_dir() 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()
|
49
minecart/init.lua
Normal file
@ -0,0 +1,49 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
minecart = {}
|
||||
|
||||
-- Version for compatibility checks, see readme.md/history
|
||||
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 .. "/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")
|
||||
end
|
||||
|
||||
dofile(MP .. "/doc.lua")
|
||||
minetest.log("info", "[MOD] Minecart loaded")
|
54
minecart/license.txt
Normal file
@ -0,0 +1,54 @@
|
||||
|
||||
License of source code
|
||||
----------------------
|
||||
|
||||
The MIT License (MIT)
|
||||
Copyright (C) 2012-2016 PilzAdam
|
||||
Copyright (C) 2014-2016 SmallJoker
|
||||
Copyright (C) 2012-2016 Various Minetest developers and contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a
|
||||
copy of this software and associated documentation files (the "Software"),
|
||||
to deal in the Software without restriction, including without limitation the
|
||||
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
sell copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in
|
||||
all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||
THE SOFTWARE.
|
||||
|
||||
For more details:
|
||||
https://opensource.org/licenses/MIT
|
||||
|
||||
|
||||
Licenses of media
|
||||
-----------------
|
||||
|
||||
CC-0, see: https://creativecommons.org/share-your-work/public-domain/cc0/, except
|
||||
if other license is mentioned.
|
||||
|
||||
|
||||
Authors
|
||||
---------
|
||||
Originally from PixelBOX (Gambit):
|
||||
carts_cart_side.png
|
||||
carts_cart_top.png
|
||||
carts_cart_front.png*
|
||||
carts_cart.png*
|
||||
|
||||
sofar + stujones11:
|
||||
carts_cart.b3d and carts_cart.blend
|
||||
|
||||
hexafraction, modified by sofar
|
||||
carts_rail_*.png
|
||||
|
||||
http://www.freesound.org/people/YleArkisto/sounds/253159/ - YleArkisto - CC-BY-3.0
|
||||
carts_cart_moving.*.ogg
|
58
minecart/locale/minecart.de.tr
Normal file
@ -0,0 +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.
|
||||
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 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.
|
||||
10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=Prüfe den Status des Wagen mit dem Chat Kommando: /mycart <num>@n <num> ist die Wagennummer
|
||||
11. Drop items into the Minecart and punch the cart to start it.=11: Lege Gegenstände in ein Wagen (Taste Q) und starte dann den Wagen durch Anklicken.
|
||||
12. Dig the cart with 'sneak+click' (as usual). The items will be drop down.=10. Klicke mit gedrückter Shift-Taste auf den Wagen, um diesen zu entfernen. Die Gegenstände fallen dann zu Boden.
|
||||
Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back=Primär für den Transport von Gegenständen genutzt. Du kannst Gegenstände in ein Cart legen (Taste Q) und dann den Wagen durch Anklicken starten. Klicke mit gedrückter Shift-Taste auf den Wagen, um Cart und Gegenstände zurückzuerhalten
|
||||
Used as buffer on both rail ends. Needed to be able to record the cart routes=Preckblöcke müssen an beiden Schienenenden platziert sein, so dass Aufzeichnungen der Strecke gemacht werden können.
|
||||
Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=Schütze deine Schienen mit Hilfe der Meilensteine (ein Meilenstein mindestens alle 16 Blöcke der Strecke entlang)
|
||||
Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.=Um Wagen zu be- und entladen. Der Hopper kann Gegenstände aus Kisten Holen und legen, sowie diese in Wagen fallen lassen bzw. aus Wagen entnehmen. Um einen Wagen zu entladen, muss der Hopper unter die Schiene platziert werden. Um einen Wagen zu beladen, muss der Hopper direkt neben die Schiene platziert werden.
|
||||
Minecart=Minecart
|
||||
Minecart, the lean railway transportation automation system=Minecart, das schlanke Schienentransport Automatisierungssystem
|
||||
Minecart Cart=Wagen
|
||||
Minecart Speed Signs=Geschwindigkeitsbegrenzungszeichen
|
||||
If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.=Wenn mehrere Wagen auf einer Route fahren, kann es vorkommen,@ndass eine Prellbock Position bereits belegt ist und ein Wagen daher früher anhält.@nDer Cart Anschieber dient in diesem Fall dazu, die Wagen wieder in Richtung Prellbock anzuschieben.@nDieser Block muss unter der Schiene mit 2 m Abstand vor dem Prellbock platziert werden.
|
||||
Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.=Begrenze die Geschwindigkeit der Wagen mit Geschwindigkeitsbegrenzungszeichen@n@nDie Geschwindigkeit der Carts wird wie bisher auch über "power rails" beeinflusst. "Brake rails" sind ohne Bedeutung, das Cart bremst hier nicht. Die maximale Geschwindigkeit beträgt 8 m/s. Dies setzt eine Verhältnis von "power rails" zu "normal rails" von 1 zu 4 auf einem ebenen Streckenabschnitt voraus. Ein Streckenabschnitt ist dabei ein Reihe von Schienenblöcken ohne Richtungsänderung. Nach jeder Kurve/Knick wird die Geschwindigkeit für den nächsten Streckenabschnitt neu bestimmt, wobei hier der Schwung des Carts mit berücksichtigt wird. So kann ein Cart auch über kurze Streckenabschnitt ohne "power rails" rollen.@n@nUm das Cart zusätzlich an bestimmten Stellen abzubremsen (an Weichen oder vor einen Puffer), können Geschwindigkeitsbegrenzungszeichen an der Strecke platziert werden. Durch diese Zeichen kann die Geschwindigkeit auf 4, 2, oder 1 m/s reduziert werden. Durch das Aufhebungszeichen kann die Geschwindigkeitsbegrenzung wieder aufgehoben werden.@n@nDie Geschwindigkeitsbegrenzungszeichen müssen so neben die Strecke platziert werden, dass sie vom Cart ablesbar sind. Dies erlaubt damit unterschiedliche Geschwindigkeiten pro Fahrtrichtung.
|
||||
Minecart Hopper=Minecart Hopper
|
||||
Minecart (Sneak+Click to pick up)=Minecart (Shift+Klick zum Entfernen des Carts)
|
||||
Output cart state and position, or a list of carts, if no cart number is given.=Gibt Status und Position des Wagens, oder eine Liste aller Wagen aus, wenn keine Wagennummer angegeben ist.
|
||||
List of carts=Liste aller Wagen
|
||||
Enter cart number=Gebe Cart Nummer ein
|
||||
Save=Speichern
|
||||
[minecart] Area is protected!=[minecart] Bereich ist geschützt!
|
||||
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] 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.
|
53
minecart/locale/template.txt
Normal file
@ -0,0 +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.=
|
||||
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 waiting time in both buffers. The Minecart will then start automatically after the configured time.=
|
||||
8. Optional: Protect your rail network with the Protection Landmarks (one Landmark at least every 16 nodes/meters).=
|
||||
9. Place a Minecart in front of the buffer and check whether it starts after the configured time.=
|
||||
10. Check the cart state via the chat command: /mycart <num>@n '<num>' is the cart number=
|
||||
11. Drop items into the Minecart and punch the cart to start it.=
|
||||
12. Dig the cart with 'sneak+click' (as usual). The items will be drop down.=
|
||||
Primary used to transport items. You can drop items into the Minecart and punch the cart to get started. Sneak+click the cart to get cart and items back=
|
||||
Used as buffer on both rail ends. Needed to be able to record the cart routes=
|
||||
Protect your rails with the Landmarks (one Landmark at least every 16 blocks near the rail)=
|
||||
Used to load/unload Minecart. The Hopper can push/pull items to/from chests and drop/pickup items to/from Minecarts. To unload a Minecart place the hopper below the rail. To load the Minecart, place the hopper right next to the Minecart.=
|
||||
Minecart=
|
||||
Minecart, the lean railway transportation automation system=
|
||||
Minecart Cart=
|
||||
Minecart Speed Signs=
|
||||
If several carts are running on one route,@nit can happen that a buffer position is already occupied and one cart therefore stops earlier.@nIn this case, the cart pusher is used to push the cart towards the buffer again.@nThis block must be placed under the rail at a distance of 2 m in front of the buffer.=
|
||||
Limit the cart speed with speed limit signs.@n@nAs before, the speed of the carts is also influenced by power rails.@nBrake rails are irrelevant, the cart does not brake here.@nThe maximum speed is 8 m/s. This assumes a ratio of power rails@nto normal rails of 1 to 4 on a flat section of rail. A rail section is a@nseries of rail nodes without a change of direction. After every curve / kink,@nthe speed for the next section of the route is newly determined,@ntaking into account the swing of the cart. This means that a cart can@nroll over short rail sections without power rails.@n@nIn order to additionally brake the cart at certain points@n(at switches or in front of a buffer), speed limit signs can be placed@non the track. With these signs the speed can be reduced to 4, 2, or 1 m / s.@nThe "No speed limit" sign can be used to remove the speed limit.@n@nThe speed limit signs must be placed next to the track so that they can@nbe read from the cart. This allows different speeds in each direction of travel.=
|
||||
Minecart Hopper=
|
||||
Minecart (Sneak+Click to pick up)=
|
||||
Output cart state and position, or a list of carts, if no cart number is given.=
|
||||
List of carts=
|
||||
Enter cart number=
|
||||
Save=
|
||||
[minecart] Area is protected!=
|
||||
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] Speed @= %u m/s, Time @= %u s, Route length @= %u m=
|
||||
Speed "1"=
|
||||
Speed "2"=
|
||||
Speed "4"=
|
||||
No speed limit=
|
||||
Cart List=
|
||||
Cart Terminal=
|
107
minecart/minecart.lua
Normal file
@ -0,0 +1,107 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
local S = minecart.S
|
||||
local M = minetest.get_meta
|
||||
|
||||
minetest.register_node("minecart:cart", {
|
||||
description = S("Minecart (Sneak+Click to pick up)"),
|
||||
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,
|
||||
|
||||
on_rightclick = function(pos, node, clicker)
|
||||
if clicker and clicker:is_player() then
|
||||
if M(pos):get_int("userID") ~= 0 then
|
||||
-- enter the cart
|
||||
local object = minecart.node_to_entity(pos, "minecart:cart", "minecart:cart_entity")
|
||||
minecart.manage_attachment(clicker, object:get_luaentity(), true)
|
||||
else
|
||||
minecart.show_formspec(pos, clicker)
|
||||
end
|
||||
end
|
||||
end,
|
||||
|
||||
set_cargo = function(pos, data)
|
||||
for _,item in ipairs(data or {}) do
|
||||
minetest.add_item(pos, ItemStack(item))
|
||||
end
|
||||
end,
|
||||
|
||||
get_cargo = function(pos)
|
||||
local data = {}
|
||||
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 1)) do
|
||||
local entity = obj:get_luaentity()
|
||||
if not obj:is_player() and entity and entity.name == "__builtin:item" then
|
||||
obj:remove()
|
||||
data[#data + 1] = entity.itemstring
|
||||
end
|
||||
end
|
||||
return data
|
||||
end,
|
||||
})
|
||||
|
||||
minecart.register_cart_entity("minecart:cart_entity", "minecart:cart", "default", {
|
||||
initial_properties = {
|
||||
physical = false,
|
||||
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
|
||||
visual = "wielditem",
|
||||
textures = {"minecart:cart"},
|
||||
visual_size = {x=0.66, y=0.66, z=0.66},
|
||||
static_save = false,
|
||||
},
|
||||
driver_allowed = true,
|
||||
})
|
||||
|
||||
|
||||
minetest.register_craft({
|
||||
output = "minecart:cart",
|
||||
recipe = {
|
||||
{"default:steel_ingot", "default:cobble", "default:steel_ingot"},
|
||||
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
|
||||
},
|
||||
})
|
||||
|
4
minecart/mod.conf
Normal file
@ -0,0 +1,4 @@
|
||||
name=minecart
|
||||
depends = default,carts
|
||||
optional_depends = doc
|
||||
description = Minecart, the lean railway transportation automation system
|
131
minecart/mods_support.lua
Normal file
@ -0,0 +1,131 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
Wrapper functions to get hopper support for other mods
|
||||
|
||||
]]--
|
||||
|
||||
-- for lazy programmers
|
||||
local M = minetest.get_meta
|
||||
|
||||
local CacheForFuelNodeNames = {}
|
||||
|
||||
local function is_fuel(stack)
|
||||
local name = stack:get_name()
|
||||
if CacheForFuelNodeNames[name] then
|
||||
return true
|
||||
end
|
||||
if minetest.get_craft_result({method="fuel", width=1, items={stack}}).time ~= 0 then
|
||||
CacheForFuelNodeNames[name] = true
|
||||
end
|
||||
return CacheForFuelNodeNames[name]
|
||||
end
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- default
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
minecart.register_inventory({"default:chest", "default:chest_open"}, {
|
||||
put = {
|
||||
listname = "main",
|
||||
},
|
||||
take = {
|
||||
listname = "main",
|
||||
},
|
||||
})
|
||||
|
||||
minecart.register_inventory({"default:chest_locked", "default:chest_locked_open"}, {
|
||||
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",
|
||||
},
|
||||
})
|
||||
|
||||
minecart.register_inventory({"default:furnace", "default:furnace_active"}, {
|
||||
put = {
|
||||
-- distinguish between fuel and other items
|
||||
put_item = function(pos, stack, player_name)
|
||||
local inv = minetest.get_inventory({type="node", pos=pos})
|
||||
local listname = is_fuel(stack) and "fuel" or "src"
|
||||
local leftover = inv:add_item(listname, stack)
|
||||
minetest.get_node_timer(pos):start(1.0)
|
||||
if leftover:get_count() > 0 then
|
||||
return leftover
|
||||
end
|
||||
end,
|
||||
},
|
||||
take = {
|
||||
-- fuel can't be taken
|
||||
listname = "dst",
|
||||
},
|
||||
})
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- digtron
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
minecart.register_inventory({"digtron:inventory"}, {
|
||||
put = {
|
||||
listname = "main",
|
||||
},
|
||||
take = {
|
||||
listname = "main",
|
||||
},
|
||||
})
|
||||
|
||||
minecart.register_inventory({"digtron:fuelstore"}, {
|
||||
put = {
|
||||
listname = "fuel",
|
||||
},
|
||||
take = {
|
||||
listname = "fuel",
|
||||
},
|
||||
})
|
||||
|
||||
minecart.register_inventory({"digtron:combined_storage"}, {
|
||||
put = {
|
||||
-- distinguish between fuel and other items
|
||||
put_item = function(pos, stack, player_name)
|
||||
local inv = minetest.get_inventory({type="node", pos=pos})
|
||||
local listname = is_fuel(stack) and "fuel" or "main"
|
||||
local leftover = inv:add_item(listname, stack)
|
||||
if leftover:get_count() > 0 then
|
||||
return leftover
|
||||
end
|
||||
end,
|
||||
},
|
||||
take = {
|
||||
-- fuel can't be taken
|
||||
listname = "main",
|
||||
},
|
||||
})
|
||||
|
||||
------------------------------------------------------------------------------
|
||||
-- protector
|
||||
------------------------------------------------------------------------------
|
||||
|
||||
minecart.register_inventory({"protector:chest"}, {
|
||||
put = {
|
||||
listname = "main",
|
||||
},
|
||||
take = {
|
||||
listname = "main",
|
||||
},
|
||||
})
|
356
minecart/monitoring.lua
Normal file
@ -0,0 +1,356 @@
|
||||
--[[
|
||||
|
||||
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 S2P = minetest.string_to_pos
|
||||
local P2H = minetest.hash_node_position
|
||||
local H2P = minetest.get_position_from_hash
|
||||
local S = minecart.S
|
||||
|
||||
local tCartsOnRail = minecart.CartsOnRail
|
||||
local Queue = {}
|
||||
local first = 0
|
||||
local last = -1
|
||||
|
||||
local function push(cycle, item)
|
||||
last = last + 1
|
||||
item.cycle = cycle
|
||||
Queue[last] = item
|
||||
end
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
end
|
||||
|
||||
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
|
||||
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
|
||||
return "unknown", 0, "unknown"
|
||||
end
|
||||
|
||||
local function get_cart_info(owner, userID, query_pos)
|
||||
local state, loc, name = get_cart_state_and_loc(owner, userID, query_pos)
|
||||
local cart_type = minecart.tCartTypes[name] or "unknown"
|
||||
if type(loc) == "number" then
|
||||
return "Cart #" .. userID .. " (" .. cart_type .. ") " .. state .. " " .. loc .. " m away "
|
||||
else
|
||||
return "Cart #" .. userID .. " (" .. cart_type .. ") " .. state .. " at ".. loc .. " "
|
||||
end
|
||||
end
|
||||
|
||||
local function monitoring(cycle)
|
||||
local cart = pop(cycle)
|
||||
|
||||
while cart do
|
||||
-- All running cars
|
||||
if cart.objID and cart.objID ~= 0 then
|
||||
cart.idx = cart.idx + 1
|
||||
local entity = minetest.luaentities[cart.objID]
|
||||
if entity then -- cart entity running
|
||||
local pos = entity.object:get_pos()
|
||||
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
|
||||
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
|
||||
--
|
||||
|
||||
-- Needed by storage to re-construct the queue after server start
|
||||
minecart.push = push
|
||||
|
||||
minetest.register_chatcommand("mycart", {
|
||||
params = "<cart-num>",
|
||||
description = S("Output cart state and position, or a list of carts, if no cart number is given."),
|
||||
func = function(owner, param)
|
||||
local userID = tonumber(param)
|
||||
local query_pos = minetest.get_player_by_name(owner):get_pos()
|
||||
|
||||
if userID then
|
||||
return true, get_cart_info(owner, userID, query_pos)
|
||||
elseif tCartsOnRail[owner] then
|
||||
-- Output a list with all numbers
|
||||
local tbl = {}
|
||||
for userID, cart in pairs(tCartsOnRail[owner]) do
|
||||
tbl[#tbl + 1] = userID
|
||||
end
|
||||
return true, S("List of carts") .. ": "..table.concat(tbl, ", ").." "
|
||||
end
|
||||
end
|
||||
})
|
||||
|
||||
function minecart.cmnd_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)
|
||||
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
|
||||
|
||||
table.sort(userIDs, function(a,b) return a < b end)
|
||||
|
||||
for _, userID in ipairs(userIDs) do
|
||||
carts[#carts + 1] = get_cart_info(name, userID, pos)
|
||||
end
|
||||
|
||||
return table.concat(carts, "\n")
|
||||
end
|
||||
|
||||
minetest.register_on_mods_loaded(function()
|
||||
if minetest.global_exists("techage") then
|
||||
techage.icta_register_condition("cart_state", {
|
||||
title = "read cart state",
|
||||
formspec = {
|
||||
{
|
||||
type = "digits",
|
||||
name = "number",
|
||||
label = "cart number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Read state from one of your carts",
|
||||
},
|
||||
},
|
||||
button = function(data, environ) -- default button label
|
||||
local number = tonumber(data.number) or 0
|
||||
return 'cart_state('..number..')'
|
||||
end,
|
||||
code = function(data, environ)
|
||||
local condition = function(env, idx)
|
||||
local number = tonumber(data.number) or 0
|
||||
return minecart.cmnd_cart_state(environ.owner, number)
|
||||
end
|
||||
local result = function(val)
|
||||
return val ~= 0
|
||||
end
|
||||
return condition, result
|
||||
end,
|
||||
})
|
||||
techage.icta_register_condition("cart_location", {
|
||||
title = "read cart location",
|
||||
formspec = {
|
||||
{
|
||||
type = "digits",
|
||||
name = "number",
|
||||
label = "cart number",
|
||||
default = "",
|
||||
},
|
||||
{
|
||||
type = "label",
|
||||
name = "lbl",
|
||||
label = "Read location from one of your carts",
|
||||
},
|
||||
},
|
||||
button = function(data, environ) -- default button label
|
||||
local number = tonumber(data.number) or 0
|
||||
return 'cart_loc('..number..')'
|
||||
end,
|
||||
code = function(data, environ)
|
||||
local condition = function(env, idx)
|
||||
local number = tonumber(data.number) or 0
|
||||
return minecart.cmnd_cart_location(environ.owner, number, env.pos)
|
||||
end
|
||||
local result = function(val)
|
||||
return val ~= 0
|
||||
end
|
||||
return condition, result
|
||||
end,
|
||||
})
|
||||
techage.lua_ctlr.register_function("cart_state", {
|
||||
cmnd = function(self, num)
|
||||
num = tonumber(num) or 0
|
||||
return minecart.cmnd_cart_state(self.meta.owner, num)
|
||||
end,
|
||||
help = " $cart_state(num)\n"..
|
||||
" Read state from one of your carts.\n"..
|
||||
' "num" is the cart number\n'..
|
||||
' example: sts = $cart_state(2)'
|
||||
})
|
||||
techage.lua_ctlr.register_function("cart_location", {
|
||||
cmnd = function(self, num)
|
||||
num = tonumber(num) or 0
|
||||
return minecart.cmnd_cart_location(self.meta.owner, num, self.meta.pos)
|
||||
end,
|
||||
help = " $cart_location(num)\n"..
|
||||
" Read location from one of your carts.\n"..
|
||||
' "num" is the cart number\n'..
|
||||
' example: sts = $cart_location(2)'
|
||||
})
|
||||
end
|
||||
end)
|
139
minecart/nodelib.lua
Normal file
@ -0,0 +1,139 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
-- for lazy programmers
|
||||
local M = minetest.get_meta
|
||||
local S = minecart.S
|
||||
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
|
||||
local S2P = minetest.string_to_pos
|
||||
|
||||
function minecart.get_nodecart_nearby(pos, param2, radius)
|
||||
local pos2 = param2 and vector.add(pos, minecart.param2_to_dir(param2)) or pos
|
||||
local pos3 = minetest.find_node_near(pos2, radius or 0.5, minecart.lCartNodeNames, true)
|
||||
if pos3 then
|
||||
return pos3, minetest.get_node(pos3)
|
||||
end
|
||||
end
|
||||
|
||||
-- Convert node to entity and start cart
|
||||
function minecart.start_nodecart(pos, node_name, puncher, punch_dir)
|
||||
local owner = M(pos):get_string("owner")
|
||||
local userID = M(pos):get_int("userID")
|
||||
-- check if valid cart
|
||||
if not minecart.monitoring_valid_cart(owner, userID, pos, node_name) then
|
||||
--print("invalid cart", owner, userID, P2S(pos), node_name)
|
||||
M(pos):set_string("infotext",
|
||||
minetest.get_color_escape_sequence("#FFFF00") .. owner .. ": 0")
|
||||
return
|
||||
end
|
||||
-- Only the owner or a noplayer can start the cart, but owner has to be online
|
||||
if minecart.is_owner(puncher, owner) and minetest.get_player_by_name(owner) and
|
||||
userID ~= 0 then
|
||||
local entity_name = minecart.tNodeNames[node_name]
|
||||
local obj = minecart.node_to_entity(pos, node_name, entity_name)
|
||||
if obj then
|
||||
local entity = obj:get_luaentity()
|
||||
if puncher then
|
||||
local yaw = puncher:get_look_horizontal()
|
||||
entity.object:set_rotation({x = 0, y = yaw, z = 0})
|
||||
elseif punch_dir then
|
||||
local yaw = minetest.dir_to_yaw(punch_dir)
|
||||
entity.object:set_rotation({x = 0, y = yaw, z = 0})
|
||||
end
|
||||
minecart.start_entitycart(entity, pos)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.show_formspec(pos, clicker)
|
||||
local owner = M(pos):get_string("owner")
|
||||
if minecart.is_owner(clicker, owner) then
|
||||
clicker:get_meta():set_string("cart_pos", P2S(pos))
|
||||
minetest.show_formspec(owner, "minecart:userID_node",
|
||||
"size[4,3]" ..
|
||||
"label[0,0;" .. S("Enter cart number") .. ":]" ..
|
||||
"field[1,1;3,1;userID;;]" ..
|
||||
"button_exit[1,2;2,1;exit;" .. S("Save") .. "]")
|
||||
end
|
||||
end
|
||||
|
||||
-- Player places the node
|
||||
function minecart.on_nodecart_place(itemstack, placer, pointed_thing)
|
||||
local node_name = itemstack:get_name()
|
||||
local param2 = minetest.dir_to_facedir(placer:get_look_dir())
|
||||
local owner = placer:get_player_name()
|
||||
|
||||
-- Add node
|
||||
if minecart.is_rail(pointed_thing.under) then
|
||||
minecart.add_nodecart(pointed_thing.under, node_name, param2, {}, owner, 0)
|
||||
placer:get_meta():set_string("cart_pos", P2S(pointed_thing.under))
|
||||
minecart.show_formspec(pointed_thing.under, placer)
|
||||
elseif minecart.is_rail(pointed_thing.above) then
|
||||
minecart.add_nodecart(pointed_thing.above, node_name, param2, {}, owner, 0)
|
||||
placer:get_meta():set_string("cart_pos", P2S(pointed_thing.above))
|
||||
minecart.show_formspec(pointed_thing.above, placer)
|
||||
else
|
||||
return itemstack
|
||||
end
|
||||
|
||||
minetest.sound_play({name = "default_place_node_metal", gain = 0.5},
|
||||
{pos = pointed_thing.above})
|
||||
|
||||
if not (creative and creative.is_enabled_for
|
||||
and creative.is_enabled_for(placer:get_player_name())) then
|
||||
itemstack:take_item()
|
||||
end
|
||||
|
||||
return itemstack
|
||||
end
|
||||
|
||||
-- Start the node cart (or dig by shift+leftclick)
|
||||
function minecart.on_nodecart_punch(pos, node, puncher, pointed_thing)
|
||||
--print("on_nodecart_punch")
|
||||
local owner = M(pos):get_string("owner")
|
||||
local userID = M(pos):get_int("userID")
|
||||
if minecart.is_owner(puncher, owner) then
|
||||
if puncher:get_player_control().sneak then
|
||||
local ndef = minetest.registered_nodes[node.name]
|
||||
if not ndef.has_cargo or not ndef.has_cargo(pos) then
|
||||
minecart.remove_nodecart(pos)
|
||||
minecart.add_node_to_player_inventory(pos, puncher, node.name)
|
||||
minecart.monitoring_remove_cart(owner, userID)
|
||||
end
|
||||
else
|
||||
minecart.start_nodecart(pos, node.name, puncher)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_on_player_receive_fields(function(player, formname, fields)
|
||||
if formname == "minecart:userID_node" then
|
||||
if fields.exit or fields.key_enter == "true" then
|
||||
local cart_pos = S2P(player:get_meta():get_string("cart_pos"))
|
||||
local owner = M(cart_pos):get_string("owner")
|
||||
if minecart.is_owner(player, owner) then
|
||||
local userID = tonumber(fields.userID) or 0
|
||||
if minecart.userID_available(owner, userID) then
|
||||
M(cart_pos):set_int("userID", userID)
|
||||
M(cart_pos):set_string("infotext",
|
||||
minetest.get_color_escape_sequence("#FFFF00") ..
|
||||
player:get_player_name() .. ": " .. userID)
|
||||
local node = minetest.get_node(cart_pos)
|
||||
local entity_name = minecart.tNodeNames[node.name]
|
||||
minecart.monitoring_add_cart(owner, userID, cart_pos, node.name, entity_name)
|
||||
end
|
||||
end
|
||||
end
|
||||
return true
|
||||
end
|
||||
return false
|
||||
end)
|
205
minecart/protection.lua
Normal file
@ -0,0 +1,205 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
local S = minecart.S
|
||||
local RANGE = 8
|
||||
|
||||
local IsNodeUnderObservation = {}
|
||||
|
||||
-- Register all nodes, which should be protected by the "minecart:landmark"
|
||||
function minecart.register_protected_node(name)
|
||||
IsNodeUnderObservation[name] = true
|
||||
end
|
||||
|
||||
local function landmark_found(pos, name, range)
|
||||
local pos1 = {x=pos.x-range, y=pos.y-range, z=pos.z-range}
|
||||
local pos2 = {x=pos.x+range, y=pos.y+range, z=pos.z+range}
|
||||
for _,npos in ipairs(minetest.find_nodes_in_area(pos1, pos2, {"minecart:landmark"})) do
|
||||
if minetest.get_meta(npos):get_string("owner") ~= name then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return false
|
||||
end
|
||||
|
||||
local function is_protected(pos, name, range)
|
||||
if minetest.check_player_privs(name, "minecart")
|
||||
or not landmark_found(pos, name, range) then
|
||||
return false
|
||||
end
|
||||
return true
|
||||
end
|
||||
|
||||
local old_is_protected = minetest.is_protected
|
||||
|
||||
function minetest.is_protected(pos, name)
|
||||
if pos and name then
|
||||
local node = minetest.get_node(pos)
|
||||
if IsNodeUnderObservation[node.name] and is_protected(pos, name, RANGE) then
|
||||
return true
|
||||
end
|
||||
end
|
||||
return old_is_protected(pos, name)
|
||||
end
|
||||
|
||||
minetest.register_node("minecart:landmark", {
|
||||
description = S("Minecart Landmark"),
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-3/16, -8/16, -3/16, 3/16, 4/16, 3/16},
|
||||
{-2/16, 4/16, -3/16, 2/16, 5/16, 3/16},
|
||||
},
|
||||
},
|
||||
tiles = {
|
||||
'default_mossycobble.png',
|
||||
'default_mossycobble.png',
|
||||
'default_mossycobble.png',
|
||||
'default_mossycobble.png',
|
||||
'default_mossycobble.png^minecart_protect.png',
|
||||
'default_mossycobble.png^minecart_protect.png',
|
||||
},
|
||||
after_place_node = function(pos, placer, itemstack, pointed_thing)
|
||||
local meta = minetest.get_meta(pos)
|
||||
meta:set_string("owner", placer:get_player_name())
|
||||
if is_protected(pos, placer:get_player_name(), RANGE+3) then
|
||||
minetest.remove_node(pos)
|
||||
return true
|
||||
end
|
||||
end,
|
||||
|
||||
can_dig = function(pos, digger)
|
||||
local meta = minetest.get_meta(pos)
|
||||
if meta:get_string("owner") == digger:get_player_name() then
|
||||
return true
|
||||
end
|
||||
if minetest.check_player_privs(digger:get_player_name(), "minecart") then
|
||||
return true
|
||||
end
|
||||
minetest.chat_send_player(digger:get_player_name(),
|
||||
S("[minecart] Area is protected!").." (owner: "..meta:get_string("owner")..")")
|
||||
return false
|
||||
end,
|
||||
|
||||
paramtype2 = "facedir",
|
||||
sunlight_propagates = true,
|
||||
groups = {cracky = 3, stone = 1},
|
||||
is_ground_content = false,
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "minecart:landmark 6",
|
||||
recipe = {
|
||||
{"", "default:mossycobble", ""},
|
||||
{"", "default:mossycobble", ""},
|
||||
{"", "default:mossycobble", ""},
|
||||
},
|
||||
})
|
||||
|
||||
minetest.register_node("minecart:ballast", {
|
||||
description = "Minecart Ballast",
|
||||
tiles = {"minecart_ballast.png"},
|
||||
groups = {crumbly = 1, cracky = 3},
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_node("minecart:ballast_slope", {
|
||||
description = "Minecart Ballast Slope",
|
||||
tiles = {"minecart_ballast.png"},
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-8/16, -8/16, -8/16, 8/16, -4/16, 8/16},
|
||||
{-8/16, -4/16, -4/16, 8/16, 0/16, 8/16},
|
||||
{-8/16, 0/16, 0/16, 8/16, 4/16, 8/16},
|
||||
{-8/16, 4/16, 4/16, 8/16, 8/16, 8/16},
|
||||
},
|
||||
},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
|
||||
},
|
||||
paramtype2 = "facedir",
|
||||
groups = {crumbly = 1, cracky = 3},
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_node("minecart:ballast_ramp", {
|
||||
description = "Minecart Ballast Ramp",
|
||||
tiles = {"minecart_ballast.png"},
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
|
||||
{-8/16, -4/16, -4/16, 8/16, 12/16, 8/16},
|
||||
{-8/16, 0/16, 0/16, 8/16, 16/16, 8/16},
|
||||
{-8/16, 4/16, 4/16, 8/16, 20/16, 8/16},
|
||||
},
|
||||
},
|
||||
selection_box = {
|
||||
type = "fixed",
|
||||
fixed = {-8/16, -8/16, -8/16, 8/16, 8/16, 8/16},
|
||||
},
|
||||
paramtype2 = "facedir",
|
||||
groups = {crumbly = 1, cracky = 3},
|
||||
sounds = default.node_sound_stone_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "minecart:ballast 6",
|
||||
recipe = {
|
||||
{"", "", ""},
|
||||
{"default:cobble", "default:stone", "default:cobble"},
|
||||
{"default:cobble", "default:stone", "default:cobble"},
|
||||
},
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "minecart:ballast_slope 6",
|
||||
recipe = {
|
||||
{"", "", "default:cobble"},
|
||||
{"", "default:stone", "default:cobble"},
|
||||
{"default:cobble", "default:stone", "default:cobble"},
|
||||
},
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "minecart:ballast_ramp 2",
|
||||
recipe = {
|
||||
{"", "", ""},
|
||||
{"minecart:ballast_slope", "", ""},
|
||||
{"minecart:ballast", "", ""},
|
||||
},
|
||||
})
|
||||
|
||||
minetest.register_privilege("minecart", {
|
||||
description = S("Allow to dig/place rails in Minecart Landmark areas"),
|
||||
give_to_singleplayer = false,
|
||||
give_to_admin = true,
|
||||
})
|
||||
|
||||
minecart.register_protected_node("carts:rail")
|
||||
minecart.register_protected_node("carts:powerrail")
|
||||
minecart.register_protected_node("carts:brakerail")
|
||||
minecart.register_protected_node("minecart:buffer")
|
||||
minecart.register_protected_node("minecart:ballast")
|
||||
minecart.register_protected_node("minecart:ballast_slope")
|
||||
minecart.register_protected_node("minecart:ballast_ramp")
|
||||
minecart.register_protected_node("minecart:speed1")
|
||||
minecart.register_protected_node("minecart:speed2")
|
||||
minecart.register_protected_node("minecart:speed4")
|
||||
minecart.register_protected_node("minecart:speed8")
|
||||
|
67
minecart/pusher.lua
Normal file
@ -0,0 +1,67 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
-- for lazy programmers
|
||||
local M = minetest.get_meta
|
||||
local S = minecart.S
|
||||
local CYCLE_TIME = 4
|
||||
|
||||
local function node_timer(pos)
|
||||
local node = minetest.get_node(pos)
|
||||
local dir = minetest.facedir_to_dir(node.param2)
|
||||
minecart.punch_cart({x = pos.x, y = pos.y + 1, z = pos.z}, nil, 1, dir)
|
||||
return true
|
||||
end
|
||||
|
||||
|
||||
local function after_dig_node(pos, oldnode, oldmetadata, digger)
|
||||
techage.remove_node(pos, oldnode, oldmetadata)
|
||||
end
|
||||
|
||||
minetest.register_node("minecart:cart_pusher", {
|
||||
description = S("Cart Pusher"),
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{-8/16,-8/16,-8/16, 8/16, 8/16, 8/16},
|
||||
{-1/16, 8/16,-4/16, 1/16, 10/16, 4/16},
|
||||
},
|
||||
},
|
||||
tiles = {
|
||||
-- up, down, right, left, back, front
|
||||
"default_steel_block.png^minecart_pusher_top.png",
|
||||
"default_steel_block.png",
|
||||
"default_steel_block.png^minecart_pusher.png",
|
||||
"default_steel_block.png^minecart_pusher.png",
|
||||
"default_steel_block.png^minecart_pusher.png",
|
||||
"default_steel_block.png^minecart_pusher.png",
|
||||
},
|
||||
after_place_node = function(pos)
|
||||
minetest.get_node_timer(pos):start(CYCLE_TIME)
|
||||
end,
|
||||
|
||||
on_timer = node_timer,
|
||||
paramtype2 = "facedir",
|
||||
groups = {choppy=2, cracky=2, crumbly=2},
|
||||
is_ground_content = true,
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "minecart:cart_pusher",
|
||||
recipe = {
|
||||
{"dye:black", "default:steel_ingot", "dye:yellow"},
|
||||
{"default:steel_ingot", "default:mese_crystal", "default:steel_ingot"},
|
||||
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
|
||||
},
|
||||
})
|
548
minecart/rails.lua
Normal file
@ -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,
|
||||
--})
|
||||
|
185
minecart/recording.lua
Normal file
@ -0,0 +1,185 @@
|
||||
--[[
|
||||
|
||||
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 S2P = minetest.string_to_pos
|
||||
local P2H = minetest.hash_node_position
|
||||
local H2P = minetest.get_position_from_hash
|
||||
local S = minecart.S
|
||||
|
||||
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)
|
||||
--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
|
||||
end
|
||||
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.recording_waypoints(self)
|
||||
local pos = vector.round(self.object:get_pos())
|
||||
-- hier müsste überprüfung dest_pos rein
|
||||
self.sum_speed = self.sum_speed + self.curr_speed
|
||||
local wp_pos = check_waypoint(self, pos)
|
||||
self.checkpoints[#self.checkpoints+1] = {
|
||||
-- cart_pos, next_waypoint_pos, speed, dot
|
||||
P2H(pos),
|
||||
P2H(wp_pos),
|
||||
math.floor(self.curr_speed + 0.5),
|
||||
self.waypoint.dot
|
||||
}
|
||||
end
|
||||
|
||||
function minecart.recording_junctions(self)
|
||||
local player = minetest.get_player_by_name(self.driver)
|
||||
if player then
|
||||
local ctrl = player:get_player_control()
|
||||
if ctrl.left then
|
||||
self.ctrl = {left = true}
|
||||
elseif ctrl.right then
|
||||
self.ctrl = {right = true}
|
||||
elseif ctrl.up or ctrl.down then
|
||||
self.ctrl = nil
|
||||
end
|
||||
end
|
||||
if self.hud_time <= self.timebase then
|
||||
dashboard_update(self)
|
||||
self.hud_time = self.timebase + 0.5
|
||||
self.runtime = self.runtime + 0.5
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.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
|
BIN
minecart/screenshot.png
Normal file
After Width: | Height: | Size: 154 KiB |
3
minecart/settingtypes.txt
Normal file
@ -0,0 +1,3 @@
|
||||
# If enabled, allows the complete automation of Minecarts by means of Hopper and station stop times.
|
||||
minecart_hopper_enabled (Hopper enabled) bool true
|
||||
minecart_teleport_enabled (Teleport enabled) bool false
|
107
minecart/signs.lua
Normal file
@ -0,0 +1,107 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg 4
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
local S = minecart.S
|
||||
|
||||
|
||||
local function register_sign(def)
|
||||
minetest.register_node("minecart:"..def.name, {
|
||||
description = def.description,
|
||||
inventory_image = def.image,
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -1/16, -8/16, -1/16, 1/16,-2/16, 1/16},
|
||||
{ -5/16, -2/16, -1/32, 5/16, 8/16, 1/32},
|
||||
},
|
||||
},
|
||||
paramtype2 = "facedir",
|
||||
tiles = {
|
||||
"default_steel_block.png",
|
||||
"default_steel_block.png",
|
||||
"default_steel_block.png",
|
||||
"default_steel_block.png",
|
||||
"default_steel_block.png",
|
||||
"default_steel_block.png^"..def.image,
|
||||
},
|
||||
|
||||
after_place_node = minecart.delete_signs_waypoint,
|
||||
preserve_metadata = minecart.delete_signs_waypoint,
|
||||
|
||||
on_rotate = screwdriver.disallow,
|
||||
paramtype = "light",
|
||||
use_texture_alpha = minecart.CLIP,
|
||||
sunlight_propagates = true,
|
||||
is_ground_content = false,
|
||||
groups = {choppy = 2, oddly_breakable_by_hand = 2, flammable = 2, minecart_sign = 1},
|
||||
sounds = default.node_sound_wood_defaults(),
|
||||
})
|
||||
end
|
||||
|
||||
register_sign({
|
||||
name = "speed1",
|
||||
description = S('Speed "1"'),
|
||||
image = "minecart_sign1.png",
|
||||
})
|
||||
|
||||
register_sign({
|
||||
name = "speed2",
|
||||
description = S('Speed "2"'),
|
||||
image = "minecart_sign2.png",
|
||||
})
|
||||
|
||||
register_sign({
|
||||
name = "speed4",
|
||||
description = S('Speed "4"'),
|
||||
image = "minecart_sign4.png",
|
||||
})
|
||||
|
||||
register_sign({
|
||||
name = "speed8",
|
||||
description = S('No speed limit'),
|
||||
image = "minecart_sign8.png",
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "minecart:speed8 8",
|
||||
recipe = {
|
||||
{"default:tin_ingot", "dye:red", "default:tin_ingot"},
|
||||
{"", "default:steel_ingot", ""},
|
||||
{"", "default:steel_ingot", ""}
|
||||
}
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "minecart:speed4",
|
||||
recipe = {"minecart:speed8"}
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "minecart:speed2",
|
||||
recipe = {"minecart:speed4"}
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "minecart:speed1",
|
||||
recipe = {"minecart:speed2"}
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
type = "shapeless",
|
||||
output = "minecart:speed8",
|
||||
recipe = {"minecart:speed1"}
|
||||
})
|
||||
|
120
minecart/storage.lua
Normal file
@ -0,0 +1,120 @@
|
||||
--[[
|
||||
|
||||
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 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()
|
||||
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()
|
||||
storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail))
|
||||
end
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
-- Store routes (in buffers)
|
||||
-------------------------------------------------------------------------------
|
||||
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(pos)
|
||||
if pos then
|
||||
local s = M(pos):get_string("route")
|
||||
if s ~= "" then
|
||||
local route = minetest.deserialize(s)
|
||||
if route.waypoints then
|
||||
M(pos):set_string("route", "")
|
||||
M(pos):set_int("time", 0)
|
||||
return
|
||||
end
|
||||
return minetest.deserialize(s)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
function minecart.del_route(pos)
|
||||
M(pos):set_string("route", "")
|
||||
end
|
90
minecart/terminal.lua
Normal file
@ -0,0 +1,90 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
-- for lazy programmers
|
||||
local M = minetest.get_meta
|
||||
local S = minecart.S
|
||||
|
||||
local function is_player_nearby(pos)
|
||||
for _, object in pairs(minetest.get_objects_inside_radius(pos, 6)) do
|
||||
if object:is_player() then
|
||||
return true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function formspec(pos, text)
|
||||
text = minetest.formspec_escape(text)
|
||||
text = text:gsub("\n", ",")
|
||||
|
||||
return "size[11,9]"..
|
||||
default.gui_bg..
|
||||
default.gui_bg_img..
|
||||
default.gui_slots..
|
||||
"box[0,-0.1;10.8,0.5;#c6e8ff]"..
|
||||
"label[4.5,-0.1;"..minetest.colorize( "#000000", S("Cart List")).."]"..
|
||||
"style_type[table,field;font=mono]"..
|
||||
"table[0,0.5;10.8,8.6;output;"..text..";200]"
|
||||
end
|
||||
|
||||
minetest.register_node("minecart:terminal", {
|
||||
description = S("Cart Terminal"),
|
||||
inventory_image = "minecart_terminal_front.png",
|
||||
tiles = {
|
||||
"minecart_terminal_top.png",
|
||||
"minecart_terminal_top.png",
|
||||
"minecart_terminal_side.png",
|
||||
"minecart_terminal_side.png",
|
||||
"minecart_terminal_back.png",
|
||||
"minecart_terminal_front.png",
|
||||
},
|
||||
drawtype = "nodebox",
|
||||
node_box = {
|
||||
type = "fixed",
|
||||
fixed = {
|
||||
{ -8/16, -8/16, 0/16, 8/16, 8/16, 8/16},
|
||||
},
|
||||
},
|
||||
|
||||
after_place_node = function(pos, placer)
|
||||
local meta = M(pos)
|
||||
meta:set_string("owner", placer:get_player_name())
|
||||
meta:set_string("formspec", formspec(pos, ""))
|
||||
minetest.get_node_timer(pos):start(2)
|
||||
end,
|
||||
|
||||
on_timer = function(pos, elapsed)
|
||||
if is_player_nearby(pos) then
|
||||
local text = minecart.get_cart_list(pos, M(pos):get_string("owner"))
|
||||
M(pos):set_string("formspec", formspec(pos, text))
|
||||
end
|
||||
return true
|
||||
end,
|
||||
|
||||
paramtype2 = "facedir",
|
||||
paramtype = "light",
|
||||
use_texture_alpha = minecart.CLIP,
|
||||
on_rotate = screwdriver.disallow,
|
||||
sunlight_propagates = true,
|
||||
is_ground_content = false,
|
||||
groups = {cracky = 2, level = 2},
|
||||
sounds = default.node_sound_metal_defaults(),
|
||||
})
|
||||
|
||||
minetest.register_craft({
|
||||
output = "minecart:terminal",
|
||||
recipe = {
|
||||
{"", "default:obsidian_glass", "default:steel_ingot"},
|
||||
{"", "default:obsidian_glass", "default:copper_ingot"},
|
||||
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
|
||||
},
|
||||
})
|
BIN
minecart/textures/minecart_appl_cart_top.png
Normal file
After Width: | Height: | Size: 108 B |
BIN
minecart/textures/minecart_appl_hopper.png
Normal file
After Width: | Height: | Size: 138 B |
BIN
minecart/textures/minecart_appl_hopper_right.png
Normal file
After Width: | Height: | Size: 148 B |
BIN
minecart/textures/minecart_appl_hopper_top.png
Normal file
After Width: | Height: | Size: 182 B |
BIN
minecart/textures/minecart_ballast.png
Normal file
After Width: | Height: | Size: 572 B |
BIN
minecart/textures/minecart_buffer.png
Normal file
After Width: | Height: | Size: 222 B |
BIN
minecart/textures/minecart_cart.png
Normal file
After Width: | Height: | Size: 264 B |
BIN
minecart/textures/minecart_doc_image.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
minecart/textures/minecart_logo.png
Normal file
After Width: | Height: | Size: 201 B |
BIN
minecart/textures/minecart_marker_cube.png
Normal file
After Width: | Height: | Size: 195 B |
BIN
minecart/textures/minecart_protect.png
Normal file
After Width: | Height: | Size: 128 B |
BIN
minecart/textures/minecart_pusher.png
Normal file
After Width: | Height: | Size: 111 B |
BIN
minecart/textures/minecart_pusher_top.png
Normal file
After Width: | Height: | Size: 126 B |
BIN
minecart/textures/minecart_sign1.png
Normal file
After Width: | Height: | Size: 141 B |
BIN
minecart/textures/minecart_sign2.png
Normal file
After Width: | Height: | Size: 154 B |
BIN
minecart/textures/minecart_sign4.png
Normal file
After Width: | Height: | Size: 151 B |
BIN
minecart/textures/minecart_sign8.png
Normal file
After Width: | Height: | Size: 174 B |
BIN
minecart/textures/minecart_terminal_back.png
Normal file
After Width: | Height: | Size: 123 B |
BIN
minecart/textures/minecart_terminal_front.png
Normal file
After Width: | Height: | Size: 263 B |
BIN
minecart/textures/minecart_terminal_side.png
Normal file
After Width: | Height: | Size: 166 B |
BIN
minecart/textures/minecart_terminal_top.png
Normal file
After Width: | Height: | Size: 126 B |
BIN
minecart/textures/minecart_tool.png
Normal file
After Width: | Height: | Size: 202 B |
BIN
minecart/textures/minecart_waypoint.png
Normal file
After Width: | Height: | Size: 111 B |
2
minecart/textures/shrink.sh
Executable file
@ -0,0 +1,2 @@
|
||||
#!/bin/bash
|
||||
pngquant --skip-if-larger --quality=80 --strip *.png --ext .png --force
|
101
minecart/tool.lua
Normal file
@ -0,0 +1,101 @@
|
||||
--[[
|
||||
|
||||
Minecart
|
||||
========
|
||||
|
||||
Copyright (C) 2019-2021 Joachim Stolberg
|
||||
|
||||
MIT
|
||||
See license.txt for more information
|
||||
|
||||
]]--
|
||||
|
||||
-- for lazy programmers
|
||||
local M = minetest.get_meta
|
||||
local S = minecart.S
|
||||
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
|
||||
local S2P = minetest.string_to_pos
|
||||
local P2H = minetest.hash_node_position
|
||||
|
||||
local sDir = {[0] = "north", "east", "south", "west"}
|
||||
|
||||
local function DOTS(dots)
|
||||
if dots then
|
||||
return table.concat(dots, ", ")
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
local old_pos
|
||||
|
||||
local function test_get_route(pos, node, player)
|
||||
local yaw = player:get_look_horizontal()
|
||||
local dir = minetest.yaw_to_dir(yaw)
|
||||
local facedir = minetest.dir_to_facedir(dir)
|
||||
local route = minecart.get_waypoint(pos, facedir, {})
|
||||
if route then
|
||||
-- print(dump(route))
|
||||
minecart.set_marker(route.pos, "pos", 0.3, 10)
|
||||
if route.cart_pos then
|
||||
minecart.set_marker(route.cart_pos, "cart", 0.3, 10)
|
||||
end
|
||||
|
||||
-- determine some kind of current y
|
||||
old_pos = old_pos or pos
|
||||
local curr_y = pos.y > old_pos.y and 1 or pos.y < old_pos.y and -1 or 0
|
||||
|
||||
local cart_pos, extra_cycle = minecart.get_current_cart_pos_correction(pos, facedir, curr_y, route.dot)
|
||||
minecart.set_marker(cart_pos, "curr", 0.3, 10)
|
||||
old_pos = pos
|
||||
print(string.format("Route: dist = %u, dot = %u, speed = %d, extra cycle = %s",
|
||||
vector.distance(pos, route.pos), route.dot, route.speed or 0, extra_cycle))
|
||||
end
|
||||
end
|
||||
|
||||
local function test_get_connections(pos, node, player, ctrl)
|
||||
local wp = minecart.get_waypoints(pos)
|
||||
for i = 0,3 do
|
||||
if wp[i] then
|
||||
local dir = minecart.Dot2Dir[ wp[i].dot]
|
||||
print(sDir[i], vector.distance(pos, wp[i].pos), dir.y)
|
||||
end
|
||||
end
|
||||
print(dump(M(pos):to_table()))
|
||||
end
|
||||
|
||||
local function click_left(itemstack, placer, pointed_thing)
|
||||
if pointed_thing.type == "node" then
|
||||
local pos = pointed_thing.under
|
||||
local node = minetest.get_node(pos)
|
||||
if node.name == "carts:rail" or node.name == "carts:powerrail" then
|
||||
test_get_route(pos, node, placer)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
local function click_right(itemstack, placer, pointed_thing)
|
||||
if pointed_thing.type == "node" then
|
||||
local pos = pointed_thing.under
|
||||
local node = minetest.get_node(pos)
|
||||
if node.name == "carts:rail" or node.name == "carts:powerrail" then
|
||||
test_get_connections(pos, node, placer)
|
||||
elseif node.name == "minecart:buffer" then
|
||||
local route = minecart.get_route(P2S(pos))
|
||||
print(dump(route))
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
minetest.register_node("minecart:tool", {
|
||||
description = "Tool",
|
||||
inventory_image = "minecart_tool.png",
|
||||
wield_image = "minecart_tool.png",
|
||||
liquids_pointable = true,
|
||||
use_texture_alpha = true,
|
||||
groups = {cracky=1, book=1},
|
||||
on_use = click_left,
|
||||
on_place = click_right,
|
||||
node_placement_prediction = "",
|
||||
stack_max = 1,
|
||||
})
|