diff --git a/API.md b/API.md index 0d52e29..b214585 100644 --- a/API.md +++ b/API.md @@ -11,7 +11,7 @@ This method triggers entities update for the display node at pos. Actual entity ### register\_display\_entity **display\_lib.register\_display\_entity(entity_name)** -This is a helper to register entities used for display. +This is a helper to register entities used for display. `entity_name`: Name of the entity to register. ## Provided callback implementations @@ -26,7 +26,7 @@ This is a helper to register entities used for display. ### on_destruct **display\_lib.on_destruct(pos)** -`on_destruct` node callback implementation. Display nodes should have this callback (removes display entities on node destruction). +`on_destruct` node callback implementation. Display nodes should have this callback (removes display entities on node destruction). ### on_rotate **display\_lib.on\_rotate(pos, node, user, mode, new_param2)** @@ -34,22 +34,20 @@ This is a helper to register entities used for display. ### on_activate **display\_lib.on_activate(entity, staticdata)** -`On_activate` entity callback implementation for display entities. No need of this method if display entities have been registered using `register_display_entity` (callback is already set). +`On_activate` entity callback implementation for display entities. No need of this method if display entities have been registered using `register_display_entity` (callback is already set). ## Howto register a display node * Register display entities with `register_display_entity` * Register node with : - `on_place`, `on_construct`, `on_destruct` and `on_rotate` callbacks using display_api callbacks. -  - - `display_modpack_node` group. This will make this node have their entities updated as soon as the mapblock is loaded (Useful after /clearobjects). -  + - `display_api` group. This will make this node have their entities updated as soon as the mapblock is loaded (Useful after /clearobjects). - a `display_entities` field in node definition containing a entity name indexed table. See below for description of each display_entities fields. ### Display_entities fields `on_display_update` is a callback in charge of setting up entity texture. If not set, entity will have no texture and will be displayed as unknown item. -`depth`, `right` and `height` : Entity position regarding to node facedir/wallmounted main axis. +`depth`, `right` and `height`: Entity position regarding to node facedir/wallmounted main axis. Values for these fields can be any number between -1.5 and 1.5 (default value is 0). Position 0,0,0 is the center of the node. `depth` goes from front (-0.5) to rear (0.5), `height` goes from bottom (-0.5) to top (0.5) and `right` goes from left (-0.5) to right (0.5). @@ -61,12 +59,12 @@ In order to avoid flickering text, it's better to have text a little behind node display_api.register_display_entity("mymod:entity1") display_api.register_display_entity("mymod:entity2") - function my_display_update1(pos, objref) + function my_display_update1(pos, objref) objref:set_properties({ textures= {"mytexture1.png"}, visual_size = {x=1, y=1} }) end - function my_display_update2(pos, objref) + function my_display_update2(pos, objref) objref:set_properties({ textures= {"mytexture2.png"},                         visual_size = {x=1, y=1} }) end @@ -75,13 +73,13 @@ In order to avoid flickering text, it's better to have text a little behind node ... paramtype2 = "facedir", ... - groups = { display_modpack_node = 1, ... }, + groups = { display_api = 1, ... }, ... display_entities = { - ["mymod:entity1"] = { + ["mymod:entity1"] = { depth = 0.3, on_display_update = my_display_update1 }, - ["mymod:entity1"] = { + ["mymod:entity1"] = { depth = 0.2, height = 0.1, on_display_update = my_display_update2 }, }, diff --git a/deprecation.lua b/deprecation.lua new file mode 100644 index 0000000..29bbd50 --- /dev/null +++ b/deprecation.lua @@ -0,0 +1,72 @@ +--[[ + 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 . +--]] + +-- Deprecation + +function deprecated_group(deprecated_group, replacement_group) + for name, ndef in pairs(minetest.registered_nodes) do + if ndef.groups and ndef.groups[deprecated_group] then + minetest.log("warning", string.format('Node %s belongs to deprecated "%s" group which should be replaced with new "%s" group.', + name, deprecated_group, replacement_group)) + end + end +end + +function deprecated_global_table(deprecated_global_name, replacement_global_name) + assert(type(deprecated_global_name) == 'string', "deprecated_global_name should be a string.") + assert(type(replacement_global_name) == 'string', "replacement_global_name should be a string.") + assert(deprecated_global_name ~= '', "deprecated_global_name should not be empty.") + assert(replacement_global_name ~= '', "replacement_global_name should not be empty.") + assert(rawget(_G, deprecated_global_name) == nil, "replacement global already exists.") + if _G[replacement_global_name] == nil then + print('warn_deprecated_functions: Warning, replacement global "'..replacement_global_name..'" does not exists.') + return + end + local meta = { + deprecated = deprecated_global_name, + replacement = replacement_global_name, + __index = function(table, key) + local meta = getmetatable(table) + local dbg = debug.getinfo(2, "lS") + minetest.log("warning", string.format('Warning: Accessing deprecated "%s" table, "%s" should be used instead (%s:%d).', + meta.deprecated, meta.replacement, (dbg.short_src or 'unknown'), (dbg.currentline or 0))) + return _G[meta.replacement][key] + end, + __newindex = function(table, key, value) + local meta = getmetatable(table) + local dbg = debug.getinfo(2, "lS") + minetest.log("warning", string.format('Warning: Accessing deprecated "%s" table, "%s" should be used instead (%s:%d).', + meta.deprecated, meta.replacement, (dbg.short_src or 'unknown'), (dbg.currentline or 0))) + _G[meta.replacement][key]=value + end, + } + rawset(_G, deprecated_global_name, {}) + setmetatable(_G[deprecated_global_name], meta) +end + + +-- deprecated(1) -- December 2018 - Deprecation of groups display_modpack_node and display_lib_node +-- Group to be removed from display API register_lbm +minetest.after(0, function() + deprecated_group("display_modpack_node", "display_api") + deprecated_group("display_lib_node", "display_api") +end) + +-- deprecated(2) -- December 2018 - Deprecation of display_lib +deprecated_global_table('display_lib', 'display_api') diff --git a/display.lua b/display.lua new file mode 100644 index 0000000..a8cee66 --- /dev/null +++ b/display.lua @@ -0,0 +1,239 @@ +--[[ + 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 . +--]] + +-- 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 + +-- Maximum entity position relative to the node pos +local max_entity_pos = 1.5 + +-- Miscelaneous values depending on wallmounted param2 +local wallmounted_values = { + [2]={dx=-1, dz=0, rx=0, rz=-1, yaw=-math.pi/2}, + [3]={dx=1, dz=0, rx=0, rz=1, yaw=math.pi/2 }, + [4]={dx=0, dz=-1, rx=1, rz=0, yaw=0 }, + [5]={dx=0, dz=1, rx=-1, rz=0, yaw=math.pi } +} + +-- Miscelaneous values depending on facedir param2 +local facedir_values = { + [0]={dx=0, dz=-1, rx=1, rz=0, yaw=0 }, + [1]={dx=-1, dz=0, rx=0, rz=-1, yaw=-math.pi/2}, + [2]={dx=0, dz=1, rx=-1, rz=0, yaw=math.pi }, + [3]={dx=1, dz=0, rx=0, rz=1, yaw=math.pi/2 } +} + +-- dx/dy = depth vector, rx/ly = right vector, yaw = yaw of entity, +local function get_values(node) + local ndef = minetest.registered_nodes[node.name] + + if ndef then + local paramtype2 = ndef.paramtype2 + if paramtype2 == "wallmounted" or paramtype2 == "colorwallmounted" then + return wallmounted_values[node.param2 % 8] + elseif paramtype2 == "facedir" or paramtype2 == "colorfacedir" then + return facedir_values[node.param2 % 32] + end + 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, max_entity_pos)) do + local entity = objref:get_luaentity() + if entity and ndef.display_entities[entity.name] and + entity.nodepos and vector.equals(pos, entity.nodepos) then + if objrefs[entity.name] then + objref:remove() -- Remove duplicates + else + objrefs[entity.name] = objref + end + end + end + end + return objrefs +end + +local function clip_pos_prop(posprop) + if posprop then + return math.max(-max_entity_pos, math.min(max_entity_pos, 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, + minetest.serialize({ nodepos = pos })) + 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 + + +--- Entity update +function update_entity(entity) + if not entity then + return + end + + if not entity.nodepos then + entity.object:remove() -- Remove old/buggy entity + return + end + + local node = minetest.get_node(entity.nodepos) + local ndef = minetest.registered_nodes[node.name] + if ndef and ndef.display_entities and + ndef.display_entities[entity.name] and + ndef.display_entities[entity.name].on_display_update + then + -- Call on_display_update callback of a node for one of its display entities + ndef.display_entities[entity.name].on_display_update(entity.nodepos, + entity.object) + else + -- Display node has been removed, remove entity also + entity.object:remove() + end +end + +--- Force entity update +function display_api.update_entities(pos) + for _, objref in pairs(place_entities(pos)) do + update_entity(objref:get_luaentity()) + 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 = minetest.deserialize(staticdata) + if data and type(data) == "table" then + entity.nodepos = data.nodepos + end + entity.object:set_armor_groups({immortal=1}) + end + update_entity(entity) + end +end + +--- On_place callback for display_api items. +-- Does nothing more than preventing node from being placed on ceiling or ground +function display_api.on_place(itemstack, placer, pointed_thing, override_param2) + 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 = 0 + if ndef then + local paramtype2 = ndef.paramtype2 + if paramtype2 == "wallmounted" or paramtype2 == "colorwallmounted" then + param2 = minetest.dir_to_wallmounted(dir) + elseif paramtype2 == "facedir" or paramtype2 == "colorfacedir" then + param2 = minetest.dir_to_facedir(dir) + end + end + return minetest.item_place(itemstack, placer, pointed_thing, + param2 + (override_param2 or 0)) +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) + for _, objref in pairs(get_entities(pos)) do + objref:remove() + end +end + +-- On_rotate (screwdriver) callback for display_api items. Prevents invalid rotations and reorients entities. +function display_api.on_rotate(pos, node, user, _, new_param2) + node.param2 = new_param2 + if get_values(node) then + minetest.swap_node(pos, node) + 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_entities[entity_name] 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({ nodepos = self.nodepos }) + end, + }) + end +end + +minetest.register_lbm({ + label = "Update display_api entities", + name = "display_api:update_entities", + run_at_every_load = true, + nodenames = {"group:display_api", + "group:display_modpack_node", "group:display_lib_node"}, -- See deprecated(1) + action = function(pos, node) display_api.update_entities(pos) end, +}) diff --git a/init.lua b/init.lua index d4f7dcd..f1e54e8 100644 --- a/init.lua +++ b/init.lua @@ -1,240 +1,31 @@ --[[ - display_api mod for Minetest - Library to add dynamic display - capabilities to nodes - (c) Pierre-Yves Rollo + 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 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. + 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 . + You should have received a copy of the GNU General Public License + along with this program. If not, see . --]] +-- Global variables +------------------- + display_api = {} +display_api.name = minetest.get_current_modname() +display_api.path = minetest.get_modpath(display_api.name) --- 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 +-- Inclusions +------------- --- Maximum entity position relative to the node pos -local max_entity_pos = 1.5 - --- Miscelaneous values depending on wallmounted param2 -local wallmounted_values = { - [2]={dx=-1, dz=0, rx=0, rz=-1, yaw=-math.pi/2}, - [3]={dx=1, dz=0, rx=0, rz=1, yaw=math.pi/2 }, - [4]={dx=0, dz=-1, rx=1, rz=0, yaw=0 }, - [5]={dx=0, dz=1, rx=-1, rz=0, yaw=math.pi } -} - --- Miscelaneous values depending on facedir param2 -local facedir_values = { - [0]={dx=0, dz=-1, rx=1, rz=0, yaw=0 }, - [1]={dx=-1, dz=0, rx=0, rz=-1, yaw=-math.pi/2}, - [2]={dx=0, dz=1, rx=-1, rz=0, yaw=math.pi }, - [3]={dx=1, dz=0, rx=0, rz=1, yaw=math.pi/2 } -} - --- dx/dy = depth vector, rx/ly = right vector, yaw = yaw of entity, -local function get_values(node) - local ndef = minetest.registered_nodes[node.name] - - if ndef then - local paramtype2 = ndef.paramtype2 - if paramtype2 == "wallmounted" or paramtype2 == "colorwallmounted" then - return wallmounted_values[node.param2 % 8] - elseif paramtype2 == "facedir" or paramtype2 == "colorfacedir" then - return facedir_values[node.param2 % 32] - end - 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, max_entity_pos)) do - local entity = objref:get_luaentity() - if entity and ndef.display_entities[entity.name] and - entity.nodepos and vector.equals(pos, entity.nodepos) then - if objrefs[entity.name] then - objref:remove() -- Remove duplicates - else - objrefs[entity.name] = objref - end - end - end - end - return objrefs -end - -local function clip_pos_prop(posprop) - if posprop then - return math.max(-max_entity_pos, math.min(max_entity_pos, 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, - minetest.serialize({ nodepos = pos })) - 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 - - ---- Entity update -function update_entity(entity) - if not entity then - return - end - - if not entity.nodepos then - entity.object:remove() -- Remove old/buggy entity - return - end - - local node = minetest.get_node(entity.nodepos) - local ndef = minetest.registered_nodes[node.name] - if ndef and ndef.display_entities and - ndef.display_entities[entity.name] and - ndef.display_entities[entity.name].on_display_update - then - -- Call on_display_update callback of a node for one of its display entities - ndef.display_entities[entity.name].on_display_update(entity.nodepos, - entity.object) - end -end - ---- Force entity update -function display_api.update_entities(pos) - for _, objref in pairs(place_entities(pos)) do - update_entity(objref:get_luaentity()) - 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 = minetest.deserialize(staticdata) - if data and type(data) == "table" then - entity.nodepos = data.nodepos - end - entity.object:set_armor_groups({immortal=1}) - end - update_entity(entity) - end -end - ---- On_place callback for display_api items. --- Does nothing more than preventing node from being placed on ceiling or ground -function display_api.on_place(itemstack, placer, pointed_thing, override_param2) - 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 = 0 - if ndef then - local paramtype2 = ndef.paramtype2 - if paramtype2 == "wallmounted" or paramtype2 == "colorwallmounted" then - param2 = minetest.dir_to_wallmounted(dir) - elseif paramtype2 == "facedir" or paramtype2 == "colorfacedir" then - param2 = minetest.dir_to_facedir(dir) - end - end - return minetest.item_place(itemstack, placer, pointed_thing, - param2 + (override_param2 or 0)) -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) - for _, objref in pairs(get_entities(pos)) do - objref:remove() - end -end - --- On_rotate (screwdriver) callback for display_api items. Prevents invalid rotations and reorients entities. -function display_api.on_rotate(pos, node, user, _, new_param2) - node.param2 = new_param2 - if get_values(node) then - minetest.swap_node(pos, node) - 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({ nodepos = self.nodepos }) - 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 +dofile(display_api.path.."/display.lua") +dofile(display_api.path.."/deprecation.lua")