minecart/baselib.lua

406 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 = minecart.CLIP,
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)
if pos then
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
end
function minecart.get_buffer_name(pos)
if pos then
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
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