402 lines
11 KiB
Lua
402 lines
11 KiB
Lua
--[[
|
|
|
|
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})
|
|
local name = minetest.get_name_from_content_id(data[idx])
|
|
if name and tItems[name] 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.set_land_marker(pos, radius, ttl)
|
|
local offs = radius + 0.5
|
|
local posses = {
|
|
{x = pos.x + offs, y = pos.y, z=pos.z},
|
|
{x = pos.x, y = pos.y, z=pos.z + offs},
|
|
{x = pos.x - offs, y = pos.y, z=pos.z},
|
|
{x = pos.x, y = pos.y, z=pos.z - offs},
|
|
}
|
|
for i, pos in ipairs(posses) do
|
|
local marker = minetest.add_entity(pos, "minecart:marker")
|
|
if marker ~= nil then
|
|
marker:set_properties({
|
|
visual_size = {x = 2 * offs, y = 2 * offs},
|
|
collisionbox = {-offs, -offs, 0, offs, offs, 0},
|
|
})
|
|
marker:set_yaw(math.pi / 2 * i)
|
|
minetest.after(ttl, marker.remove, marker)
|
|
end
|
|
end
|
|
end
|
|
|
|
minetest.register_entity("minecart:marker", {
|
|
initial_properties = {
|
|
visual = "upright_sprite",
|
|
textures = {"minecart_marker_cube.png"},
|
|
use_texture_alpha = true,
|
|
physical = false,
|
|
glow = 12,
|
|
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
|
|
-- If no rail is around, use an available cart as new search center
|
|
pos2 = minetest.find_node_near(pos, 1, minecart.lRailsExt)
|
|
-- ...and search again.
|
|
if pos2 then
|
|
pos2 = minetest.find_node_near(pos2, 1, minecart.lRails)
|
|
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
|
|
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, facedir)
|
|
local route = {}
|
|
|
|
self.is_running = true
|
|
self.arrival_time = 0
|
|
self.start_pos = minecart.get_buffer_pos(pos, self.owner) or minecart.get_next_buffer(pos, facedir)
|
|
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)
|
|
local pos2 = minecart.add_nodecart(pos, entity.node_name, facedir, entity.cargo, entity.owner, entity.userID)
|
|
if pos2 then
|
|
minecart.stop_monitoring(entity.owner, entity.userID, pos2)
|
|
entity.object:remove()
|
|
else
|
|
minecart.start_entitycart(entity, pos, facedir)
|
|
end
|
|
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
|
|
if player then
|
|
minecart.add_node_to_player_inventory(pos, player, self.node_name or "minecart:cart")
|
|
end
|
|
minecart.stop_monitoring(self.owner, self.userID, pos)
|
|
minecart.stop_recording(self, pos)
|
|
self.object:remove()
|
|
end
|