forked from mtcontrib/display_modpack
dfaf64cd61
Now uses player view direction when trying to place nodes on the floor or ceiling, for both wallmounted and facedir nodes. Also supports nodes where paramtype2 is not wallmounted or facedir.
270 lines
8.5 KiB
Lua
270 lines
8.5 KiB
Lua
--[[
|
|
display_api mod for Minetest - Library to add dynamic display
|
|
capabilities to nodes
|
|
(c) Pierre-Yves Rollo
|
|
|
|
This program is free software: you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation, either version 3 of the License, or
|
|
(at your option) any later version.
|
|
|
|
This program is distributed in the hope that it will be useful,
|
|
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
GNU General Public License for more details.
|
|
|
|
You should have received a copy of the GNU General Public License
|
|
along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
--]]
|
|
|
|
display_api = {}
|
|
|
|
-- Prefered gap between node and entity
|
|
-- Entity positionment is up to mods but it is a good practice to use this
|
|
-- variable as spacing between entity and node
|
|
display_api.entity_spacing = 0.002
|
|
|
|
-- Miscelaneous values depending on wallmounted param2
|
|
local wallmounted_values = {
|
|
[0]={dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0}, -- Should never be used
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=1}, -- Should never be used
|
|
{dx=-1, dz=0, rx=0, rz=-1, yaw=-math.pi/2, rotate=5},
|
|
{dx=1, dz=0, rx=0, rz=1, yaw=math.pi/2, rotate=4},
|
|
{dx=0, dz=-1, rx=1, rz=0, yaw=0, rotate=2},
|
|
{dx=0, dz=1, rx=-1, rz=0, yaw=math.pi, rotate=3}
|
|
}
|
|
|
|
-- Miscelaneous values depending on facedir param2
|
|
local facedir_values = {
|
|
[0]={dx=0, dz=-1, rx=1, rz=0, yaw=0, rotate=1},
|
|
{dx=-1, dz=0, rx=0, rz=-1, yaw=-math.pi/2, rotate=2},
|
|
{dx=0, dz=1, rx=-1, rz=0, yaw=math.pi, rotate=3},
|
|
{dx=1, dz=0, rx=0, rz=1, yaw=math.pi/2, rotate=0},
|
|
-- Forbiden values :
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
{dx=0, dz=0, rx=0, rz=0, yaw=0, rotate=0},
|
|
}
|
|
|
|
-- dx/dy = depth vector, rx/ly = right vector, yaw = yaw of entity,
|
|
-- rotate = next facedir/wallmount on rotate
|
|
|
|
local function get_values(node)
|
|
local ndef = minetest.registered_nodes[node.name]
|
|
|
|
if ndef then
|
|
if ndef.paramtype2 == "wallmounted" then
|
|
return wallmounted_values[node.param2]
|
|
end
|
|
if ndef.paramtype2 == "facedir" then
|
|
return facedir_values[node.param2]
|
|
end
|
|
end
|
|
end
|
|
|
|
--- Checks if the object is related to the given position
|
|
local function check_entity_pos(pos, objref)
|
|
local real_pos = vector.round(objref:get_pos())
|
|
local pos_hash = objref:get_luaentity().pos
|
|
if pos_hash == nil then
|
|
return vector.equals(real_pos, vector.round(pos))
|
|
else
|
|
return vector.equals(minetest.get_position_from_hash(pos_hash), pos)
|
|
end
|
|
end
|
|
|
|
--- Gets the display entities attached with a node. Removes extra ones
|
|
local function get_entities(pos)
|
|
local objrefs = {}
|
|
local ndef = minetest.registered_nodes[minetest.get_node(pos).name]
|
|
if ndef and ndef.display_entities then
|
|
for _, objref in ipairs(minetest.get_objects_inside_radius(pos, 1.5)) do
|
|
local entity = objref:get_luaentity()
|
|
if entity and ndef.display_entities[entity.name] and check_entity_pos(pos, objref) then
|
|
if objrefs[entity.name] then
|
|
objref:remove()
|
|
else
|
|
objrefs[entity.name] = objref
|
|
end
|
|
end
|
|
end
|
|
end
|
|
return objrefs
|
|
end
|
|
|
|
local function clip_pos_prop(posprop)
|
|
if posprop then
|
|
return math.max(-1.5, math.min(1.5, posprop))
|
|
else
|
|
return 0
|
|
end
|
|
end
|
|
|
|
--- (Create and) place display entities according to the node orientation
|
|
local function place_entities(pos)
|
|
local node = minetest.get_node(pos)
|
|
local ndef = minetest.registered_nodes[node.name]
|
|
local values = get_values(node)
|
|
local objrefs = get_entities(pos)
|
|
|
|
if values and ndef and ndef.display_entities then
|
|
|
|
for entity_name, props in pairs(ndef.display_entities) do
|
|
local depth = clip_pos_prop(props.depth)
|
|
local right = clip_pos_prop(props.right)
|
|
local top = clip_pos_prop(props.top)
|
|
if not objrefs[entity_name] then
|
|
objrefs[entity_name] = minetest.add_entity(pos, entity_name)
|
|
end
|
|
|
|
objrefs[entity_name]:setpos({
|
|
x = pos.x - values.dx * depth + values.rx * right,
|
|
y = pos.y - top,
|
|
z = pos.z - values.dz * depth + values.rz * right})
|
|
|
|
objrefs[entity_name]:setyaw(values.yaw)
|
|
end
|
|
end
|
|
return objrefs
|
|
end
|
|
|
|
--- Call on_display_update callback of a node for one of its display entities
|
|
local function call_node_on_display_update(pos, objref)
|
|
local ndef = minetest.registered_nodes[minetest.get_node(pos).name]
|
|
local entity = objref:get_luaentity()
|
|
if ndef and ndef.display_entities and entity and ndef.display_entities[entity.name] then
|
|
ndef.display_entities[entity.name].on_display_update(pos, objref)
|
|
end
|
|
end
|
|
|
|
--- Force entity update
|
|
function display_api.update_entities(pos)
|
|
local objrefs = place_entities(pos)
|
|
for _, objref in pairs(objrefs) do
|
|
objref:get_luaentity().pos = minetest.hash_node_position(pos)
|
|
call_node_on_display_update(pos, objref)
|
|
end
|
|
end
|
|
|
|
--- On_activate callback for display_api entities. Calls on_display_update callbacks
|
|
--- of corresponding node for each entity.
|
|
function display_api.on_activate(entity, staticdata)
|
|
if entity then
|
|
if string.sub(staticdata, 1, string.len("return")) == "return" then
|
|
local data = core.deserialize(staticdata)
|
|
if data and type(data) == "table" then
|
|
entity.pos = data.pos
|
|
end
|
|
end
|
|
entity.object:set_armor_groups({immortal=1})
|
|
local pos
|
|
if entity.pos then
|
|
pos = minetest.get_position_from_hash(entity.pos)
|
|
else
|
|
pos = entity.object:getpos()
|
|
end
|
|
display_api.update_entities(pos)
|
|
end
|
|
end
|
|
|
|
--- On_place callback for display_api items. Does nothing more than preventing item
|
|
--- from being placed on ceiling or ground
|
|
function display_api.on_place(itemstack, placer, pointed_thing)
|
|
local ndef = itemstack:get_definition()
|
|
local above = pointed_thing.above
|
|
local under = pointed_thing.under
|
|
local dir = {x = under.x - above.x,
|
|
y = 0,
|
|
z = under.z - above.z}
|
|
|
|
-- If item is not placed on a wall, use the player's view direction instead
|
|
if dir.x == 0 and dir.z == 0 then
|
|
dir = placer:get_look_dir()
|
|
dir.y = 0
|
|
end
|
|
|
|
local param2
|
|
if ndef then
|
|
if ndef.paramtype2 == "wallmounted" then
|
|
param2 = minetest.dir_to_wallmounted(dir)
|
|
elseif ndef.paramtype2 == "facedir" then
|
|
param2 = minetest.dir_to_facedir(dir)
|
|
end
|
|
end
|
|
return minetest.item_place(itemstack, placer, pointed_thing, param2)
|
|
end
|
|
|
|
--- On_construct callback for display_api items. Creates entities and update them.
|
|
function display_api.on_construct(pos)
|
|
display_api.update_entities(pos)
|
|
end
|
|
|
|
--- On_destruct callback for display_api items. Removes entities.
|
|
function display_api.on_destruct(pos)
|
|
local objrefs = get_entities(pos)
|
|
|
|
for _, objref in pairs(objrefs) do
|
|
objref:remove()
|
|
end
|
|
end
|
|
|
|
-- On_rotate (screwdriver) callback for display_api items. Prevents axis rotation and reorients entities.
|
|
function display_api.on_rotate(pos, node, user, mode, new_param2)
|
|
if mode ~= 1 then return false end
|
|
|
|
local values = get_values(node)
|
|
|
|
if values then
|
|
minetest.swap_node(pos, {name = node.name, param1 = node.param1, param2 = values.rotate})
|
|
place_entities(pos)
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
end
|
|
|
|
--- Creates display entity with some fields and the on_activate callback
|
|
function display_api.register_display_entity(entity_name)
|
|
if not minetest.registered_entity then
|
|
minetest.register_entity(':'..entity_name, {
|
|
collisionbox = { 0, 0, 0, 0, 0, 0 },
|
|
visual = "upright_sprite",
|
|
textures = {},
|
|
on_activate = display_api.on_activate,
|
|
get_staticdata = function(self)
|
|
return minetest.serialize({
|
|
pos = self.pos,
|
|
})
|
|
end,
|
|
})
|
|
end
|
|
end
|
|
|
|
minetest.register_lbm({
|
|
label = "Update display_api entities",
|
|
name = "display_api:update_entities",
|
|
run_at_every_load = true,
|
|
nodenames = {"group:display_modpack_node", "group:display_lib_node"},
|
|
action = function(pos, node) display_api.update_entities(pos) end,
|
|
})
|
|
|
|
-- Compatibility
|
|
display_lib = display_api
|