First draft for v2

master
Joachim Stolberg 2021-04-03 20:57:48 +02:00
parent 098066d476
commit 901a4bfaf2
18 changed files with 1230 additions and 1412 deletions

209
baselib.lua Normal file
View File

@ -0,0 +1,209 @@
--[[
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 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.tEntityNames = {} -- [<cart_node_name>] = <cart_entity_name>
minecart.lCartNodeNames = {}
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.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_route_key(pos, player_name)
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
if pos1 then
local meta = minetest.get_meta(pos1)
if player_name == nil or player_name == meta:get_string("owner") then
return P2S(pos1)
end
end
end
function minecart.get_station_name(pos)
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
if pos1 then
local name = M(pos1):get_string("name")
if name ~= "" then
return name
end
return P2S(pos1)
end
end
function minecart.register_cart_names(node_name, entity_name)
minecart.tEntityNames[node_name] = entity_name
minecart.lCartNodeNames[#minecart.lCartNodeNames+1] = node_name
end
function minecart.node_to_entity(pos, node_name, entity_name)
-- Remove node
local meta = M(pos)
local ndef = minetest.registered_nodes[node_name]
local rail = meta:get_string("removed_rail")
if rail == "" then rail = "carts:rail" end
local userID = meta:get_int("userID")
local owner = meta:get_string("owner")
local cargo = ndef.get_cargo and ndef.get_cargo(pos) or {}
minetest.swap_node(pos, {name = rail})
-- Add entity
local obj = minetest.add_entity(pos, entity_name)
local objID = minecart.get_object_id(obj)
if objID then
local entity = obj:get_luaentity()
entity.owner = owner
entity.userID = userID
entity.objID = objID
entity.cargo = cargo
obj:set_nametag_attributes({color = "#ffff00", text = owner..": "..userID})
minecart.update_cart_status(owner, userID, false, objID, pos, node_name, entity_name, cargo)
return objID, obj
else
print("Entity has no ID")
end
end
function minecart.entity_to_node(pos, entity, node_name, param2)
-- Stop sound
if entity.sound_handle then
minetest.sound_stop(entity.sound_handle)
entity.sound_handle = nil
end
-- Remove entity
local owner = entity.owner or ""
local userID = entity.userID or 0
local cargo = entity.cargo or {}
local entity_name = entity.name
entity.object:remove()
-- Add node
local ndef = minetest.registered_nodes[node_name]
local node = minetest.get_node(pos)
local rail = node.name
minetest.add_node(pos, {name = node_name, param2 = param2})
local meta = M(pos)
meta:set_string("removed_rail", rail)
meta:set_string("owner", owner)
meta:set_string("userID", userID)
meta:set_string("infotext", minetest.get_color_escape_sequence("#FFFF00")..owner..": "..userID)
if ndef.after_place_node then
ndef.after_place_node(pos)
end
if cargo and ndef.set_cargo then
ndef.set_cargo(pos, cargo)
end
minecart.update_cart_status(owner, userID, true, nil, pos, node_name, entity_name, cargo)
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.update_cart_status(self.owner, self.userID)
self.object:remove()
end

View File

@ -3,7 +3,7 @@
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
@ -31,7 +31,7 @@ local function formspec(pos)
end
local function remote_station_name(pos)
local route = minecart.get_route(P2S(pos))
local route = minecart.get_route(pos)
if route and route.dest_pos then
local pos2 = S2P(route.dest_pos)
return M(pos2):get_string("name")
@ -47,7 +47,7 @@ local function on_punch(pos, node, puncher)
-- Optional Teleport function
if not minecart.teleport_enabled then return end
local route = minecart.get_route(P2S(pos))
local route = minecart.get_route(pos)
if route and route.dest_pos and puncher and puncher:is_player() then
-- only teleport if the user is not pressing shift
@ -91,7 +91,7 @@ minetest.register_node("minecart:buffer", {
},
after_place_node = function(pos, placer)
M(pos):set_string("owner", placer:get_player_name())
minecart.del_route(minetest.pos_to_string(pos))
minecart.del_route(pos)
M(pos):set_string("formspec", formspec(pos))
minetest.get_node_timer(pos):start(CYCLE_TIME)
end,
@ -100,13 +100,12 @@ minetest.register_node("minecart:buffer", {
if time > 0 then
local hash = minetest.hash_node_position(pos)
local param2 = (minetest.get_node(pos).param2 + 2) % 4
if minecart.check_cart_for_pushing(pos, param2) then
local pos2, node = minecart.get_nodecart_nearby(pos, param2)
if pos2 then
if StopTime[hash] then
if StopTime[hash] < minetest.get_gametime() then
StopTime[hash] = nil
local node = minetest.get_node(pos)
local dir = minetest.facedir_to_dir(node.param2)
minecart.punch_cart(pos, param2, 0, dir)
minecart.start_nodecart(pos2, node.name, nil)
end
else
StopTime[hash] = minetest.get_gametime() + time
@ -118,7 +117,7 @@ minetest.register_node("minecart:buffer", {
return true
end,
after_dig_node = function(pos)
minecart.del_route(minetest.pos_to_string(pos))
minecart.del_route(pos)
local hash = minetest.hash_node_position(pos)
StopTime[hash] = nil
end,

View File

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

View File

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

View File

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

View File

@ -1,80 +0,0 @@
--[[
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
MIT
See license.txt for more information
Cart library base functions (level 3)
]]--
-- for lazy programmers
local M = minetest.get_meta
local S = minecart.S
local P2S = function(pos) if pos then return minetest.pos_to_string(pos) end end
local S2P = minetest.string_to_pos
local api = {}
local tRails = {
["carts:rail"] = true,
["carts:powerrail"] = true,
["carts:brakerail"] = true,
}
local lRails = {"carts:rail", "carts:powerrail", "carts:brakerail"}
function api.find_rail_node(rail_pos)
if not rail_pos then
return
end
local node = minecart.get_node_lvm(rail_pos)
if tRails[node.name] then
return rail_pos, node
end
local pos1 = {x=rail_pos.x-1, y=rail_pos.y-1, z=rail_pos.z-1}
local pos2 = {x=rail_pos.x+1, y=rail_pos.y+1, z=rail_pos.z+1}
for _,pos3 in ipairs(minetest.find_nodes_in_area(pos1, pos2, lRails)) do
return pos3, minecart.get_node_lvm(pos3)
end
pos1 = {x=rail_pos.x-3, y=rail_pos.y-3, z=rail_pos.z-3}
pos2 = {x=rail_pos.x+3, y=rail_pos.y+3, z=rail_pos.z+3}
for _,pos3 in ipairs(minetest.find_nodes_in_area(pos1, pos2, lRails)) do
return pos3, minecart.get_node_lvm(pos3)
end
end
function api.get_object_id(object)
for id, entity in pairs(minetest.luaentities) do
if entity.object == object then
return id
end
end
end
function api.get_route_key(pos, player_name)
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
if pos1 then
local meta = minetest.get_meta(pos1)
if player_name == nil or player_name == meta:get_string("owner") then
return P2S(pos1)
end
end
end
function api.get_station_name(pos)
local pos1 = minetest.find_node_near(pos, 1, {"minecart:buffer"})
if pos1 then
local name = M(pos1):get_string("name")
if name ~= "" then
return name
end
return P2S(pos1)
end
end
return api

230
entitylib.lua Normal file
View File

@ -0,0 +1,230 @@
--[[
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 MAX_SPEED = minecart.MAX_SPEED
local Dot2Dir = minecart.Dot2Dir
local Dir2Dot = minecart.Dir2Dot
local get_waypoint = minecart.get_waypoint
local recording = minecart.recording
local monitoring = minecart.monitoring
local function running(self)
local rot = self.object:get_rotation()
local dir = minetest.yaw_to_dir(rot.y)
local facedir = minetest.dir_to_facedir(dir)
local new_speed
local cart_pos
if not self.waypoint then
-- get waypoint
local pos = self.object:get_pos()
self.waypoint = minecart.get_waypoint(pos, facedir, {})
if not self.waypoint then return end
new_speed = math.max((self.waypoint.power / 100), 0)
cart_pos = pos
else
-- position correction
--self.object:set_pos(self.waypoint.cart_pos)
cart_pos = vector.new(self.waypoint.cart_pos)
-- next waypoint
self.waypoint = minecart.get_waypoint(self.waypoint.pos, facedir, self.ctrl or {})
if not self.waypoint then return end
self.ctrl = nil -- has to be determined for the next waypoint
local vel = self.object:get_velocity()
local speed = math.sqrt((vel.x+vel.z)^2 + vel.y^2)
if self.waypoint.power <= 0 then
new_speed = math.max(speed + (self.waypoint.power / 100), 0)
else
new_speed = math.min((self.waypoint.power / 100), MAX_SPEED)
end
end
-- Speed corrections
local new_dir = Dot2Dir[self.waypoint.dot]
if new_dir.y == 1 then
if new_speed < 1 then new_speed = 0 end
elseif new_dir.y == -1 then
if new_speed < 3 then new_speed = 3 end
else
if new_speed < 0.4 then new_speed = 0 end
end
-- Calc velocity, rotation, pos and arrival_time
local yaw = minetest.dir_to_yaw(new_dir)
local pitch = new_dir.y * math.pi/4
local dist = math.max(vector.distance(cart_pos, self.waypoint.cart_pos), 1)
self.arrival_time = self.timebase + (dist / new_speed)
-- Slope corrections
if new_dir.y ~= 0 then
cart_pos.y = cart_pos.y + 0.2
new_speed = new_speed / 1.41
end
local vel = vector.multiply(new_dir, new_speed)
self.object:set_pos(cart_pos)
self.object:set_rotation({x = pitch, y = yaw, z = 0})
self.object:set_velocity(vel)
--local s = string.format("power = %.1f, dist = %.1f, pos = %s, cart_pos = %s", self.waypoint.power, dist, P2S(self.waypoint.pos), P2S(self.waypoint.cart_pos))
--print("running", s)
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
self.sound_handle = minetest.sound_play(
"carts_cart_moving", {
object = self.object,
loop = true,
})
end
local function on_step(self, dtime)
-- if self.is_recording then
-- recording(self)
-- elseif self.is_monitoring then
-- monitoring(self)
-- end
if self.is_running then
self.timebase = (self.timebase or 0) + dtime
print("on_step", self.timebase)
if self.timebase >= (self.arrival_time or 0) then
running(self)
end
self.sound_ttl = (self.sound_ttl or 0) + dtime
if self.sound_ttl >= 1 then
play_sound(self)
self.sound_ttl = 0
end
else
if self.sound_handle then
minetest.sound_stop(self.sound_handle)
self.sound_handle = nil
end
end
end
local function on_activate(self, staticdata, dtime_s)
self.object:set_armor_groups({immortal=1})
end
-- Entity callback: Node is already converted to an entity.
local function on_punch(self, puncher, time_from_last_punch, tool_capabilities, dir)
-- local puncher_name = puncher and puncher:is_player() and puncher:get_player_name()
-- local puncher_is_owner = minecart.is_owner(puncher, self.owner)
-- local puncher_is_driver = self.driver and self.driver == puncher_name
-- local sneak_punch = puncher_name and puncher:get_player_control().sneak
-- local no_cargo = next(self.cargo or {}) == nil
-- local pos = self.object:get_pos()
-- -- driver wants to leave/remove the empty cart by sneak-punch
-- if sneak_punch and puncher_is_driver and no_cargo then
-- if puncher_is_owner then
-- minecart.hud_remove(self)
-- local pos = self.object:get_pos()
-- minecart.remove_entity(self, pos, puncher)
-- end
-- carts:manage_attachment(puncher, nil)
-- return
-- end
-- -- Punched by non-authorized player
-- if puncher_name and not puncher_is_owner then
-- minetest.chat_send_player(puncher_name, S("[minecart] Cart is protected by ")..(self.owner or ""))
-- return
-- end
-- -- Sneak-punched by owner
-- if sneak_punch then
-- -- Unload the cargo
-- if minetest.add_cargo_to_player_inv(self, pos, puncher) then
-- return
-- end
-- -- detach driver
-- if self.driver then
-- carts:manage_attachment(puncher, nil)
-- end
-- -- Pick up cart
-- minetest.remove_entity(self, pos, puncher)
-- return
-- end
-- minetest.load_cargo(self, pos)
-- -- Cart with driver punched to start recording
-- if puncher_is_driver then
-- minecart.start_recording(self, pos, puncher)
-- self.is_recording = true
-- else
-- self.is_recording = false
-- end
-- minetest.push_cart_entity(self, pos, nil, puncher)
local pos = self.object:get_pos()
minecart.remove_entity(self, pos, puncher)
end
-- Player get on / off
local function on_rightclick(self, clicker)
if not clicker or not clicker:is_player() then
return
end
local player_name = clicker:get_player_name()
if self.driver and player_name == self.driver then
minecart.hud_remove(self)
self.driver = nil
self.recording = false
carts:manage_attachment(clicker, nil)
elseif not self.driver then
self.driver = player_name
carts:manage_attachment(clicker, self.object)
-- player_api does not update the animation
-- when the player is attached, reset to default animation
player_api.set_animation(clicker, "stand")
end
end
local function on_detach_child(self, child)
if child and child:get_player_name() == self.driver then
self.driver = nil
end
end
function minecart.register_cart_entity(entity_name, node_name, entity_def)
entity_def.entity_name = entity_name
entity_def.node_name = node_name
entity_def.on_activate = on_activate
entity_def.on_punch = on_punch
entity_def.on_step = on_step
entity_def.on_rightclick = on_rightclick
entity_def.on_detach_child = on_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)
end

View File

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

141
hopperlib.lua Normal file
View File

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

View File

@ -3,7 +3,7 @@
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
@ -13,25 +13,30 @@
minecart = {}
-- Version for compatibility checks, see readme.md/history
minecart.version = 1.10
minecart.version = 2.00
minecart.hopper_enabled = minetest.settings:get_bool("minecart_hopper_enabled") ~= false
minecart.teleport_enabled = minetest.settings:get_bool("minecart_teleport_enabled") == true
minecart.S = minetest.get_translator("minecart")
local MP = minetest.get_modpath("minecart")
dofile(MP.."/storage.lua")
dofile(MP.."/lib.lua")
dofile(MP.."/monitoring.lua")
dofile(MP.."/recording.lua")
dofile(MP.."/minecart.lua")
dofile(MP.."/buffer.lua")
dofile(MP.."/protection.lua")
dofile(MP.."/.test_cart.lua")
dofile(MP .. "/storage.lua")
dofile(MP .. "/baselib.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 .. "/minecart.lua")
dofile(MP .. "/buffer.lua")
dofile(MP .. "/protection.lua")
dofile(MP .. "/tool.lua")
if minecart.hopper_enabled then
dofile(MP.."/hopper.lua")
dofile(MP.."/mods_support.lua")
dofile(MP .. "/hopper.lua")
dofile(MP .. "/mods_support.lua")
end
dofile(MP.."/doc.lua")
dofile(MP .. "/doc.lua")
minetest.log("info", "[MOD] Minecart loaded")

336
lib.lua
View File

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

View File

@ -3,7 +3,7 @@
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
@ -11,71 +11,82 @@
]]--
local S = minecart.S
local MP = minetest.get_modpath("minecart")
local lib = dofile(MP.."/cart_lib1.lua")
lib:init(false)
print(dump(minecart.on_nodecart_place))
local cart_entity = {
initial_properties = {
physical = false, -- otherwise going uphill breaks
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
visual = "mesh",
mesh = "carts_cart.b3d",
visual_size = {x=1, y=1},
textures = {"carts_cart.png^minecart_cart.png"},
static_save = false,
},
------------------------------------ changed
owner = nil,
------------------------------------ changed
driver = nil,
punched = false, -- used to re-send velocity and position
velocity = {x=0, y=0, z=0}, -- only used on punch
old_dir = {x=1, y=0, z=0}, -- random value to start the cart on punch
old_pos = nil,
old_switch = 0,
railtype = nil,
cargo = {},
on_rightclick = lib.on_rightclick,
on_activate = lib.on_activate,
on_detach_child = lib.on_detach_child,
on_punch = lib.on_punch,
on_step = lib.on_step,
}
minetest.register_entity("minecart:cart", cart_entity)
minecart.register_cart_names("minecart:cart", "minecart:cart")
minetest.register_craftitem("minecart:cart", {
minetest.register_node("minecart:cart_node", {
description = S("Minecart (Sneak+Click to pick up)"),
inventory_image = minetest.inventorycube("carts_cart_top.png", "carts_cart_side.png^minecart_logo.png", "carts_cart_side.png^minecart_logo.png"),
wield_image = "carts_cart_side.png",
on_place = function(itemstack, placer, pointed_thing)
-- use cart as tool
local under = pointed_thing.under
local node = minetest.get_node(under)
local udef = minetest.registered_nodes[node.name]
if udef and udef.on_rightclick and
not (placer and placer:is_player() and
placer:get_player_control().sneak) then
return udef.on_rightclick(under, node, placer, itemstack,
pointed_thing) or itemstack
tiles = {
-- up, down, right, left, back, front
"carts_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 = "",
on_place = minecart.on_nodecart_place,
--on_punch = minecart.on_nodecart_punch,
on_dig = minecart.on_nodecart_dig,
set_cargo = function(pos, data)
for _,item in ipairs(data or {}) do
minetest.add_item(pos, ItemStack(item))
end
if not pointed_thing.type == "node" then
return
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 lib.add_cart(itemstack, placer, pointed_thing, "minecart:cart")
return data
end,
})
minecart.register_cart_entity("minecart:cart", "minecart:cart_node", {
initial_properties = {
physical = false,
collisionbox = {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5},
visual = "wielditem",
textures = {"minecart:cart_node"},
visual_size = {x=0.66, y=0.66, z=0.66},
static_save = true,
},
})
minetest.register_craft({
output = "minecart:cart",
output = "minecart:cart_node",
recipe = {
{"default:steel_ingot", "default:cobble", "default:steel_ingot"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},

View File

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

View File

@ -23,9 +23,8 @@ 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 MP = minetest.get_modpath("minecart")
local lib = dofile(MP.."/cart_lib3.lua")
if 0 then
local CartsOnRail = minecart.CartsOnRail -- from storage.lua
local get_route = minecart.get_route -- from storage.lua
local NodesAtStation = {}
@ -36,19 +35,22 @@ local NodesAtStation = {}
local function get_pos_vel(item)
if item.start_time and item.start_key then -- cart on recorded route
local run_time = minetest.get_gametime() - item.start_time
local waypoints = get_route(item.start_key).waypoints
if not waypoints or not next(waypoints) then
return item.last_pos, {x = 0, y = 0, z = 0}, false
end
if run_time >= #waypoints then
local waypoint = waypoints[#waypoints]
return S2P(waypoint[1]), S2P(waypoint[2]), true -- time to appear
else
local waypoint = waypoints[run_time]
return S2P(waypoint[1]), S2P(waypoint[2]), false
local route = get_route(item.start_key)
if route then
local waypoints = route.waypoints
if not waypoints or not next(waypoints) then
return item.last_pos, {x = 0, y = 0, z = 0}, true
end
if run_time >= #waypoints then
local waypoint = waypoints[#waypoints]
return S2P(waypoint[1]), S2P(waypoint[2]), true -- time to appear
else
local waypoint = waypoints[run_time]
return S2P(waypoint[1]), S2P(waypoint[2]), false
end
end
end
return item.last_pos, {x = 0, y = 0, z = 0}, false
return item.last_pos, {x = 0, y = 0, z = 0}, true
end
local function is_player_nearby(pos)
@ -81,7 +83,7 @@ function minecart.add_to_monitoring(myID, pos, entity_name, owner, userID)
end
end
function minecart.monitoring_start_cart(pos, myID)
function minecart.monitoring_start_cart(self, pos, myID)
print("monitoring_start_cart")
if myID then
local item = CartsOnRail[myID]
@ -89,13 +91,16 @@ function minecart.monitoring_start_cart(pos, myID)
local start_key = lib.get_route_key(pos)
if start_key then
local route = minecart.get_route(start_key)
item.start_key = start_key
item.junctions = route.junctions
item.dest_pos = S2P(route.dest_pos)
item.stopped = false
item.start_time = minetest.get_gametime()
minecart.store_carts()
return true
if route then
item.start_key = start_key
item.junctions = route.junctions
item.dest_pos = S2P(route.dest_pos)
item.cargo = self.cargo or {}
item.stopped = false
item.start_time = minetest.get_gametime()
minecart.store_carts()
return true
end
end
end
end
@ -132,14 +137,24 @@ end
-- For the emergency "back to start"
function minecart.get_start_pos_vel(myID)
local item = CartsOnRail[myID]
print(1)
if item then
local waypoints = get_route(item.start_key).waypoints
local waypoint = waypoints[1]
if waypoint then
local pos = S2P(waypoint[1])
local vel = S2P(waypoint[2])
vel = vector.multiply(vel, -1)
return pos, vel
print(2)
local route = get_route(item.start_key)
if route then
print(3)
local waypoints = route.waypoints
if waypoints and next(waypoints) then
print(4)
local waypoint = waypoints[1]
if waypoint then
print(5)
local pos = S2P(waypoint[1])
local vel = S2P(waypoint[2])
vel = vector.multiply(vel, -2)
return pos, vel, item.start_key
end
end
end
end
end
@ -171,6 +186,7 @@ end
--
-- Copy item data to entity cart
local function item_to_entity(pos, vel, item)
print("item_to_entity", item.owner, item.userID)
pos = lib.find_rail_node(pos)
if pos then
-- Add cart to map
@ -185,8 +201,6 @@ local function item_to_entity(pos, vel, item)
entity.cargo = item.cargo or {}
entity.myID = myID
obj:set_nametag_attributes({color = "#FFFF00", text = entity.owner..": "..entity.userID})
-- Update item data
item.cargo = nil
-- Start cart
obj:set_velocity(vel)
obj:set_rotation({x = 0, y = 0, z = 0})
@ -199,6 +213,7 @@ local function item_to_entity(pos, vel, item)
end
local function entity_to_item(entity, item)
print("entity_to_item", item.owner, item.userID)
item.cargo = entity.cargo
-- Remove entity from map
entity.object:remove()
@ -210,14 +225,12 @@ local function entity_to_item(entity, item)
end
local function debug(item, pos, entity)
if item.owner == "wuffi" or item.owner == "singleplayer" then
print("Cart:",
item.userID,
P2S(vector.round(pos)),
item.stopped and "stopped" or "running",
entity and "entity" or "virtualized",
item.entity_name)
end
print("Cart:",
item.userID,
P2S(vector.round(pos)),
item.stopped and "stopped" or "running",
entity and "entity" or "virtualized",
item.entity_name)
end
local function monitoring()
@ -225,6 +238,8 @@ local function monitoring()
local to_be_removed = {}
for key, item in pairs(CartsOnRail) do
local present = minetest.get_player_by_name(item.owner or "") ~= nil
local appear = item.time_to_appear or item.stopped
local entity = minetest.luaentities[key]
if entity then -- cart entity running
local pos = entity.object:get_pos()
@ -232,19 +247,19 @@ local function monitoring()
if pos and vel then
--debug(item, pos, entity)
item.last_pos = pos
if not is_player_nearby(pos) and not item.time_to_appear and not item.stopped then
if not is_player_nearby(pos) and not appear then
entity_to_item(entity, item)
end
else
minetest.log("error", "[minecart] Entity issues")
end
--entity.stopped = false -- force to stop monitoring
else -- no cart running
elseif present then -- no cart running
local pos, vel, time_to_appear = get_pos_vel(item)
if pos and vel then
--debug(item, pos, entity)
item.last_pos = pos
if time_to_appear or is_player_nearby(pos) then
if is_player_nearby(pos) or appear then
local myID = item_to_entity(pos, vel, item)
if myID then
item.time_to_appear = time_to_appear
@ -257,6 +272,7 @@ local function monitoring()
to_be_removed[#to_be_removed + 1] = key
end
end
item.time_to_appear = nil
end
-- table maintenance
@ -440,3 +456,15 @@ minetest.register_on_mods_loaded(function()
end
end)
end -- if 0
function minecart.monitoring(self)
-- if not self.ctrl then
-- self.ctrl = minecart.get_next_ctrl(self.route.pos)
-- end
end
function minecart.update_cart_status(owner, userID, stopped, objID, pos, node_name, entity_name, cargo)
print("update_cart_status", owner, userID, stopped)
end

131
nodelib.lua Normal file
View File

@ -0,0 +1,131 @@
--[[
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)
local owner = M(pos):get_string("owner")
if minecart.is_owner(puncher, owner) then
local entity_name = minecart.tEntityNames[node_name]
local objID, obj = minecart.node_to_entity(pos, node_name, entity_name)
if objID then
local entity = obj:get_luaentity()
entity.is_running = true
end
end
end
-- Player places the node
function minecart.on_nodecart_place(itemstack, placer, pointed_thing)
local add_cart = function(pos, node_name, param2, owner)
local ndef = minetest.registered_nodes[node_name]
local node = minetest.get_node(pos)
local rail = node.name
minetest.swap_node(pos, {name = node_name, param2 = param2})
local meta = M(pos)
meta:set_string("removed_rail", rail)
meta:set_string("owner", owner)
meta:set_string("infotext",
minetest.get_color_escape_sequence("#FFFF00") .. owner .. ": 0")
--meta:set_string("cart_pos", P2S(pos))
if ndef.after_place_node then
ndef.after_place_node(pos)
end
end
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
add_cart(pointed_thing.under, node_name, param2, owner)
placer:get_meta():set_string("cart_pos", P2S(pointed_thing.under))
elseif minecart.is_rail(pointed_thing.above) then
add_cart(pointed_thing.above, node_name, param2, owner)
placer:get_meta():set_string("cart_pos", P2S(pointed_thing.above))
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
minetest.show_formspec(owner, "minecart:userID_node",
"size[4,3]" ..
"label[0,0;Enter cart number:]" ..
"field[1,1;3,1;userID;;]" ..
"button_exit[1,2;2,1;exit;Save]")
return itemstack
end
function minecart.on_nodecart_punch(pos, node, puncher, pointed_thing)
--minecart.start_nodecart(pos, node.name, puncher)
end
function minecart.on_nodecart_dig(pos, node, digger)
local meta = M(pos)
local userID = meta:get_int("userID")
local owner = meta:get_string("owner")
local ndef = minetest.registered_nodes[node.name]
if not ndef.can_dig or ndef.can_dig(pos, digger) then
minecart.add_node_to_player_inventory(pos, digger, node.name)
node.name = M(pos):get_string("removed_rail")
print("on_nodecart_dig", userID, owner, node.name)
if node.name == "" then
node.name = "carts:rail"
end
minetest.swap_node(pos, node)
minecart.update_cart_status(owner, userID)
end
end
minetest.register_on_player_receive_fields(function(player, formname, fields)
if formname == "minecart:userID_node" then
if fields.exit == "Save" or fields.key_enter == "true" then
local cart_pos = S2P(player:get_meta():get_string("cart_pos"))
local owner = M(cart_pos):get_string("owner")
local pname = player:get_player_name()
if owner == pname then
local userID = tonumber(fields.userID) or 0
M(cart_pos):set_int("userID", userID)
M(cart_pos):set_string("infotext",
minetest.get_color_escape_sequence("#FFFF00") ..
player:get_player_name() .. ": " .. userID)
minecart.update_cart_status(owner, userID, true)
end
end
return true
end
return false
end)

296
rails.lua Normal file
View File

@ -0,0 +1,296 @@
--[[
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 SLOPE_ACCELERATION = 1
local MAX_SPEED = 8
local SLOWDOWN = 0.4
--waypoint = {
-- dot = travel direction,
-- pos = destination pos,
-- power = 10 times the waypoint speed (as int),
-- cart_pos = destination cart pos
--}
--
-- waypoints = {facedir = waypoint,...}
local tWaypoints = {} -- {pos_hash = waypoints, ...}
local tRails = {
["carts:rail"] = true,
["carts:powerrail"] = true,
["carts:brakerail"] = true,
}
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
}
-- Create helper tables
for dot = 1,12 do
local facedir = math.floor((dot - 1) / 3)
local dir = minetest.facedir_to_dir(facedir)
dir.y = ((dot - 1) % 3) - 1
Dot2Dir[dot] = vector.new(dir)
Dir2Dot[P2H(dir)] = dot
-- dot = facedir * 3 + dir.y + 2
end
local function check_front_up_down(pos, facedir)
local npos
npos = vector.add(pos, Facedir2Dir[facedir])
if tRails[get_node_lvm(npos).name] then
-- We also have to check the next node to find the next upgoing rail.
npos = vector.add(npos, Facedir2Dir[facedir])
npos.y = npos.y + 1
if tRails[get_node_lvm(npos).name] then
--print("check_front_up_down: 2up")
return facedir * 3 + 3 -- up
end
--print("check_front_up_down: front")
return facedir * 3 + 2 -- front
end
npos.y = npos.y - 1
if tRails[get_node_lvm(npos).name] then
--print("check_front_up_down: down")
return facedir * 3 + 1 -- down
end
npos.y = npos.y + 2
if tRails[get_node_lvm(npos).name] then
--print("check_front_up_down: up")
return facedir * 3 + 3 -- up
end
end
-- Search for rails in 3 directions (based on given facedir)
local function find_rails_nearby(pos, facedir)
-- Do not check the direction we are coming from
facedir = flip[facedir]
local tbl = {}
for fd = 0, 3 do
if fd ~= facedir then
tbl[#tbl + 1] = check_front_up_down(pos, fd)
end
end
return tbl
end
local function get_rail_power(pos, dot)
local y = ((dot - 1) % 3) - 1
local node
if y == 1 then
node = get_node_lvm({x = pos.x, y = pos.y - 1, z = pos.z})
else
node = get_node_lvm(pos)
end
return (carts.railparams[node.name] or {}).acceleration or 0
end
local function find_next_waypoint(pos, dot)
--print("find_next_waypoint", P2S(pos), dot)
local npos = vector.new(pos)
local facedir = math.floor((dot - 1) / 3)
local y = ((dot - 1) % 3) - 1
local power = 0
local cnt = 0
while cnt < 1000 do
npos = vector.add(npos, Dot2Dir[dot])
power = power + get_rail_power(npos, dot)
local dots = find_rails_nearby(npos, facedir)
if #dots == 0 then -- end of rail
return npos, power, npos
elseif #dots > 1 then -- junction
return npos, power, npos
elseif dots[1] ~= dot then -- curve
-- If the direction changes to upwards ,
-- the cart position must be half a block further and
-- the destination pos must be one block further to hit the next rail.
if y == 1 then
local dir = Dot2Dir[dot]
local cart_pos = vector.add(npos, {x = dir.x / 2, y = 0, z = dir.z / 2})
npos = vector.add(npos, Facedir2Dir[facedir])
return npos, power, cart_pos
end
-- If the direction changes between straight, up and down,
-- the cart position must be half a block further.
local chng = dot - dots[1]
if chng >= -2 and chng <= 1 then
local dir = Dot2Dir[dot]
local cart_pos = vector.add(npos, {x = dir.x / 2, y = 0, z = dir.z / 2})
return npos, power, cart_pos
end
return npos, power, npos
end
cnt = cnt + 1
end
return pos, 0, pos
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)
end
return tbl
end
-- Recalc the value based on waypoint length and slope
local function recalc_power(dot, power, pos1, pos2)
local y = ((dot - 1) % 3) - 1
local dist = vector.distance(pos1, pos2)
local offs
if y == 1 then
--print("recalc_power", power, dist * -SLOPE_ACCELERATION)
offs = dist * -SLOPE_ACCELERATION
elseif y == -1 then
--print("recalc_power", power, dist * SLOPE_ACCELERATION)
offs = dist * SLOPE_ACCELERATION
else
offs = dist * -SLOWDOWN
end
power = power + offs
if power > MAX_SPEED then
return MAX_SPEED
elseif power < -MAX_SPEED then
return -MAX_SPEED
else
return power
end
end
local function is_waypoint(dots)
if #dots ~= 2 then return true end
local facedir1 = math.floor((dots[1] - 1) / 3)
local facedir2 = math.floor((dots[2] - 1) / 3)
local y1 = ((dots[1] - 1) % 3) - 1
local y2 = ((dots[2] - 1) % 3) - 1
if facedir1 ~= flip[facedir2] then return true end
if y1 ~= y2 * -1 then return true end
return false
end
local function determine_waypoints(pos)
--print("determine_waypoints")
local t = minetest.get_us_time()
local waypoints = {}
local dots = {}
for _,dot in ipairs(find_all_rails_nearby(pos, 0)) do
local npos, power, cart_pos = find_next_waypoint(pos, dot)
local facedir = math.floor((dot - 1) / 3)
power = math.floor(recalc_power(dot, power, pos, npos) * 100)
waypoints[facedir] = {dot = dot, pos = npos, power = power, cart_pos = cart_pos}
dots[#dots + 1] = dot
end
if is_waypoint(dots) then
M(pos):set_string("waypoints", minetest.serialize(waypoints))
end
t = minetest.get_us_time() - t
print("time = ", t)
return waypoints
end
local function get_metadata(pos)
local s = M(pos):get_string("waypoints")
if s ~= "" then
return minetest.deserialize(s)
end
end
local function get_waypoint(pos, facedir, ctrl)
local hash = P2H(pos)
tWaypoints[hash] = tWaypoints[hash] or get_metadata(pos) or determine_waypoints(pos)
local t = tWaypoints[hash]
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] end
if ctrl.left and t[left] then return t[left] end
if t[facedir] then return t[facedir] end
if t[right] then return t[right] end
if t[left] then return t[left] end
if t[back] then return t[back] end
end
local function after_dig_node(pos, oldnode, oldmetadata, digger)
print("after_dig_node")
for _, waypoint in pairs(determine_waypoints(pos)) do
if waypoint.pos then
print("after_dig_node", P2S(waypoint.pos))
local hash = P2H(waypoint.pos)
tWaypoints[hash] = nil
M(waypoint.pos):set_string("waypoints", "")
end
end
end
for name,_ in pairs(tRails) do
minetest.override_item(name, {after_dig_node = after_dig_node})
end
minecart.MAX_SPEED = MAX_SPEED
minecart.Dot2Dir = Dot2Dir
minecart.Dir2Dot = Dir2Dot
minecart.find_next_waypoint = find_next_waypoint
minecart.get_waypoint = get_waypoint
function minecart.is_rail(pos)
return tRails[get_node_lvm(pos).name] ~= nil
end
minetest.register_lbm({
label = "Delete waypoints",
name = "minecart:rails",
nodenames = {"carts:rail", "carts:powerrail", "carts:brakerail"},
run_at_every_load = true,
action = function(pos, node)
M(pos):set_string("waypoints", "")
end,
})

View File

@ -129,3 +129,28 @@ function minecart.hud_remove(self)
end
end
end
function minecart.recording(self)
local ctrl, player
player = minetest.get_player_by_name(self.driver)
if player then
ctrl = player:get_player_control()
if ctrl.left then
self.left_req = true
self.right_req = false
elseif ctrl.right then
self.right_req = true
self.left_req = false
end
ctrl = {left = self.left_req, right = self.right_req}
end
minecart.store_next_waypoint(self, rail_pos, new_vel)
-- New direction
elseif dir.x ~= new_dir.x or dir.z ~= new_dir.z then
if self.recording and self.left_req or self.right_req then
minecart.set_junction(self, rail_pos, new_dir, keys)
end
self.left_req = false
self.right_req = false
end
end

View File

@ -3,7 +3,7 @@
Minecart
========
Copyright (C) 2019-2020 Joachim Stolberg
Copyright (C) 2019-2021 Joachim Stolberg
MIT
See license.txt for more information
@ -14,6 +14,7 @@
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 S = minecart.S
local storage = minetest.get_mod_storage()
@ -23,116 +24,49 @@ local storage = minetest.get_mod_storage()
-------------------------------------------------------------------------------
minecart.CartsOnRail = {}
minetest.register_on_mods_loaded(function()
for key,val in pairs(minetest.deserialize(storage:get_string("CartsOnRail")) or {}) do
if key > 0 then -- valid key
-- use invalid keys to force the cart spawning
minecart.CartsOnRail[-key] = val
else
minecart.CartsOnRail[key] = val
end
end
end)
--minetest.register_on_mods_loaded(function()
-- local version = storage:get_int("version")
-- if version < 2 then
-- minecart.CartsOnRail = convert_to_v2()
-- storage:set_int("version", 2)
-- else
-- minecart.CartsOnRail = minetest.deserialize(storage:get_string("CartsOnRail")) or {}
-- end
--end)
minetest.register_on_shutdown(function()
storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail))
end)
--minetest.register_on_shutdown(function()
-- storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail))
--end)
function minecart.store_carts()
storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail))
end
--function minecart.store_carts()
-- storage:set_string("CartsOnRail", minetest.serialize(minecart.CartsOnRail))
--end
-------------------------------------------------------------------------------
-- Store routes
-- Store routes (in buffers)
-------------------------------------------------------------------------------
-- All positions as "pos_to_string" string
--Routes = {
-- start_pos = {
-- waypoints = {{spos, svel}, {spos, svel}, ...},
-- dest_pos = spos,
-- junctions = {
-- spos = num,
-- spos = num,
-- },
-- },
-- start_pos = {...},
--}
local Routes = {}
local NEW_ROUTE = {waypoints = {}, junctions = {}}
-- Remove waypoints which are too near to start and dest pos
local function shape_route(start_key, route)
if not route.shaped then
local changed = false
if not route.waypoints or not next(route.waypoints) then
return false
end
local dist1 = vector.distance(S2P(start_key), S2P(route.waypoints[1][1]))
local dist2 = vector.distance(S2P(route.dest_pos), S2P(route.waypoints[#route.waypoints][1]))
if dist1 < 3 then
table.remove(route.waypoints)
route.shaped = true
changed = true
end
if dist2 < 3 then
table.remove(route.waypoints, 1)
route.shaped = true
changed = true
end
return changed
end
end
function minecart.store_route(key, route)
if key and route then
Routes[key] = route
local meta = M(S2P(key))
if meta then
meta:set_string("route", minetest.serialize(route))
return true
end
function minecart.store_route(pos, route)
if pos and route then
M(pos):set_string("route", minetest.serialize(route))
return true
end
return false
end
function minecart.get_route(key)
if not Routes[key] then
local s = M(S2P(key)):get_string("route")
function minecart.get_route(pos)
if spos then
local s = M(pos):get_string("route")
if s ~= "" then
Routes[key] = minetest.deserialize(s) or NEW_ROUTE
else
Routes[key] = NEW_ROUTE
end
end
-- covert data for V3 (players nearby instead of loaded areas)
if shape_route(key, Routes[key]) then
minecart.store_route(key, Routes[key])
end
return Routes[key]
end
function minecart.del_route(key)
Routes[key] = nil -- remove from memory
M(S2P(key)):set_string("route", "") -- and as metadata
end
-------------------------------------------------------------------------------
-- Convert data to v2 (routes as buffer metadata)
-------------------------------------------------------------------------------
minetest.after(5, function()
local tbl = storage:to_table()
for key,s in pairs(tbl.fields) do
if key ~= "CartsOnRail" then
local route = minetest.deserialize(s)
if route.waypoints and route.junctions then
if minecart.store_route(key, route) then
storage:set_string(key, "")
end
else
storage:set_string(key, "")
if route.waypoints then
M(pos):set_string("route", "")
return
end
return minetest.deserialize(s)
end
end
end)
end
function minecart.del_route(pos)
M(pos):set_string("route", "")
end