diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..1592dbb --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,13 @@ +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU Lesser General Public License as published by +the Free Software Foundation; either version 2.1 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 Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public License along +with this program; if not, write to the Free Software Foundation, Inc., +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..5b026e1 --- /dev/null +++ b/README.txt @@ -0,0 +1,29 @@ +Tower Crane Mod +=============== + + + +Usage +----- + +1) Place the crane base. + The crane arm will later be build in the same direction yoi are currently looking + +2) Right-click the crane base node and set the crane dimensions in height and width (between 8 and 24). + The crane will be build according to the settings. + +3) Right-click the crane switch node to place the hook in front of the crane mast + +4) Enter the hook by right-clicking the hook + +5) "Fly" within the construction area (height, width) by means of the (default) controls + - Move mouse: Look around + - W, A, S, D: Move + - Space: move up + - Shift: move down + +6) Leave the hook by right-clicking the hook or right-clicking the crane switch node + +7) To destroy the crane, destroy the base node. + + diff --git a/depends.txt b/depends.txt new file mode 100644 index 0000000..4ad96d5 --- /dev/null +++ b/depends.txt @@ -0,0 +1 @@ +default diff --git a/description.txt b/description.txt new file mode 100644 index 0000000..000880f --- /dev/null +++ b/description.txt @@ -0,0 +1 @@ +The Tower Crane Mod forms a constrution area below the crane arm which gives the crane owner "fly privs". diff --git a/init.lua b/init.lua new file mode 100644 index 0000000..bf94b10 --- /dev/null +++ b/init.lua @@ -0,0 +1,525 @@ +--[[ + + Tower Crane Mod + =============== + + v0.01 by JoSt + + Copyright (C) 2017 Joachim Stolberg + LGPLv2.1+ + See LICENSE.txt for more information + + History: + 2017-06-04 v0.01 first version +]]-- + + +towercrane = {} + +--################################################################################################## +--## Tower Crane Hook +--################################################################################################## +local hook = { + physical = true, + collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2}, + collide_with_objects = false, + visual = "cube", + visual_size = {x=0.4, y=0.4}, + textures = { + "towercrane_hook.png", + "towercrane_hook.png", + "towercrane_hook.png", + "towercrane_hook.png", + "towercrane_hook.png", + "towercrane_hook.png", + }, + groups = {cracky=1}, + -- local variabels + driver = nil, + speed_forward=0, + speed_right=0, + speed_up=0, +} +---------------------------------------------------------------------------------------------------- +-- Enter/leave the Hook +---------------------------------------------------------------------------------------------------- +function hook:on_rightclick(clicker) + if self.driver and clicker == self.driver then -- leave? + clicker:set_detach() + self.driver = nil + elseif not self.driver then -- enter? + self.driver = clicker + clicker:set_attach(self.object, "", {x=0,y=0,z=0}, {x=0,y=0,z=0}) + end +end + +---------------------------------------------------------------------------------------------------- +-- Hook control +---------------------------------------------------------------------------------------------------- +function hook:on_step(dtime) + -- remove hook from last visit + if self.pos1 == nil or self.pos2 == nil then + self.object:remove() + return + end + if self.driver then + local ctrl = self.driver:get_player_control() + local yaw = self.driver:get_look_horizontal() + local pos = self.driver:getpos() + local max_speed = 5 + local velocity = 0.5 + + if ctrl.up then -- forward + self.speed_forward = math.min(self.speed_forward + velocity, max_speed) + elseif ctrl.down then -- backward + self.speed_forward = math.max(self.speed_forward - velocity, -max_speed) + elseif self.speed_forward > 0 then + self.speed_forward = self.speed_forward - velocity + elseif self.speed_forward < 0 then + self.speed_forward = self.speed_forward + velocity + end + + if ctrl.right then -- right + self.speed_right = math.min(self.speed_right + velocity, max_speed) + elseif ctrl.left then -- left + self.speed_right = math.max(self.speed_right - velocity, -max_speed) + elseif self.speed_right > 0 then + self.speed_right = self.speed_right - velocity + elseif self.speed_right < 0 then + self.speed_right = self.speed_right + velocity + end + + if ctrl.jump then -- up + self.speed_up = math.min(self.speed_up + velocity, 5) + elseif ctrl.sneak then -- down + self.speed_up = math.max(self.speed_up - velocity, -5) + elseif self.speed_up > 0 then + self.speed_up = self.speed_up - velocity + elseif self.speed_up < 0 then + self.speed_up = self.speed_up + velocity + end + + -- calculate the direction vector + local vx = math.cos(yaw+math.pi/2) * self.speed_forward + math.cos(yaw) * self.speed_right + local vz = math.sin(yaw+math.pi/2) * self.speed_forward + math.sin(yaw) * self.speed_right + + -- check if outside of the construction area + if pos.x < self.pos1.x then vx= velocity end + if pos.x > self.pos2.x then vx= -velocity end + if pos.y < self.pos1.y then self.speed_up= velocity end + if pos.y > self.pos2.y then self.speed_up= -velocity end + if pos.z < self.pos1.z then vz= velocity end + if pos.z > self.pos2.z then vz= -velocity end + + self.object:setvelocity({x=vx, y=self.speed_up,z=vz}) + else + self.object:setvelocity({x=0, y=0,z=0}) + end +end + +---------------------------------------------------------------------------------------------------- +-- LuaEntitySAO (non-player moving things): see http://dev.minetest.net/LuaEntitySAO +---------------------------------------------------------------------------------------------------- +minetest.register_entity("towercrane:hook", hook) + + + +--################################################################################################## +--## Tower Crane +--################################################################################################## + +local function turnright(dir) + local facedir = minetest.dir_to_facedir(dir) + return minetest.facedir_to_dir((facedir + 1) % 4) +end + +local function turnleft(dir) + local facedir = minetest.dir_to_facedir(dir) + return minetest.facedir_to_dir((facedir + 3) % 4) +end + +---------------------------------------------------------------------------------------------------- +-- Constuct mast and arm +---------------------------------------------------------------------------------------------------- +local function construct_crane(pos, dir, height, width, owner) + pos.y = pos.y + 1 + minetest.env:add_node(pos, {name="towercrane:mast_ctrl_off", param2=minetest.dir_to_facedir(dir)}) + local meta = minetest.get_meta(pos) + meta:set_string("dir", minetest.pos_to_string(dir)) + meta:set_string("owner", owner) + meta:set_int("height", height) + meta:set_int("width", width) + + for i = 1,height+1 do + pos.y = pos.y + 1 + minetest.env:add_node(pos, {name="towercrane:mast"}) + end + + pos.y = pos.y - 2 + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:add_node(pos, {name="towercrane:arm2"}) + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:add_node(pos, {name="towercrane:arm"}) + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:add_node(pos, {name="towercrane:balance"}) + pos.x = pos.x + 3 * dir.x + pos.z = pos.z + 3 * dir.z + + for i = 1,width do + pos.x = pos.x + dir.x + pos.z = pos.z + dir.z + if i % 2 == 0 then + minetest.env:add_node(pos, {name="towercrane:arm2"}) + else + minetest.env:add_node(pos, {name="towercrane:arm"}) + end + + end +end + +---------------------------------------------------------------------------------------------------- +-- Remove the crane +---------------------------------------------------------------------------------------------------- +local function dig_crane(pos, dir, height, width) + pos.y = pos.y + 1 + minetest.env:remove_node(pos, {name="towercrane:mast_ctrl_off"}) + + for i = 1,height+1 do + pos.y = pos.y + 1 + minetest.env:remove_node(pos, {name="towercrane:mast"}) + end + + pos.y = pos.y - 2 + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:remove_node(pos, {name="towercrane:arm2"}) + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:remove_node(pos, {name="towercrane:arm"}) + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:remove_node(pos, {name="towercrane:balance"}) + pos.x = pos.x + 3 * dir.x + pos.z = pos.z + 3 * dir.z + + for i = 1,width do + pos.x = pos.x + dir.x + pos.z = pos.z + dir.z + if i % 2 == 0 then + minetest.env:remove_node(pos, {name="towercrane:arm2"}) + else + minetest.env:remove_node(pos, {name="towercrane:arm"}) + end + end +end + +---------------------------------------------------------------------------------------------------- +-- Place the hook in front of the base +---------------------------------------------------------------------------------------------------- +local function place_hook(pos, dir) + pos.y = pos.y - 1 + pos.x = pos.x + dir.x + pos.z = pos.z + dir.z + return minetest.add_entity(pos, "towercrane:hook") +end + +---------------------------------------------------------------------------------------------------- +-- Register Crane base +---------------------------------------------------------------------------------------------------- +minetest.register_node("towercrane:base", { + description = "Tower Crane Base", + inventory_image = "towercrane_invent.png", + tiles = { + "towercrane_base_top.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {cracky=3}, + formspec = set_formspec, + + -- set meta data (form for crane height and width, dir of the arm) + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + local owner = placer:get_player_name() + meta:set_string("owner", owner) + local formspec = "size[5,4]".. + "label[0,0;Construction area size]" .. + "field[1,1.5;3,1;size;height,width;]" .. + "button_exit[1,2;2,1;exit;Save]" + meta:set_string("formspec", formspec) + + local fdir = minetest.dir_to_facedir(placer:get_look_dir(), false) + local dir = minetest.facedir_to_dir(fdir) + meta:set_string("dir", minetest.pos_to_string(dir)) + end, + + -- evaluate user input (height, width), destroyed old crane and build a new one with + -- the given size + on_receive_fields = function(pos, formname, fields, player) + if fields.size == nil then + return + end + local meta = minetest.get_meta(pos) + + if not player or not player:is_player() then + return + end + local owner = meta:get_string("owner") + if player:get_player_name() ~= owner then + return + end + + local dir = minetest.string_to_pos(meta:get_string("dir")) + local height = meta:get_int("height") + local width = meta:get_int("width") + local org_pos = table.copy(pos) + if dir ~= nil and height ~= nil and width ~= nil then + dig_crane(pos, dir, height, width) + end + --check for correct size format + size = string.split(fields.size, ",") + if #size == 2 then + local height = tonumber(size[1]) + local width = tonumber(size[2]) + if height ~= nil and width ~= nil then + height = math.max(height, 8) + height = math.min(height, 24) + width = math.max(width, 8) + width = math.min(width, 24) + meta:set_int("height", height) + meta:set_int("width", width) + meta:set_string("infotext", "Crane size: " .. height .. "," .. width) + if dir ~= nil then + construct_crane(org_pos, dir, height, width, owner) + end + end + end + end, + + -- remove mast and arm if base gets destroyed + on_destruct = function(pos) + local meta = minetest.get_meta(pos) + local dir = minetest.string_to_pos(meta:get_string("dir")) + local height = meta:get_int("height") + local width = meta:get_int("width") + + if dir ~= nil and height ~= nil and width ~= nil then + dig_crane(pos, dir, height, width) + end + end, +}) + +---------------------------------------------------------------------------------------------------- +-- Register Crane balance +---------------------------------------------------------------------------------------------------- +minetest.register_node("towercrane:balance", { + description = "Tower Crane Balance", + tiles = { + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) + +---------------------------------------------------------------------------------------------------- +-- Register Crane mast +---------------------------------------------------------------------------------------------------- +minetest.register_node("towercrane:mast", { + description = "Tower Crane Mast", + drawtype = "glasslike_framed", + tiles = { + "towercrane_mast.png", + "towercrane_mast.png", + "towercrane_mast.png", + "towercrane_mast.png", + "towercrane_mast.png", + "towercrane_mast.png", + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) + +---------------------------------------------------------------------------------------------------- +-- Register Crane Switch (on) +---------------------------------------------------------------------------------------------------- +minetest.register_node("towercrane:mast_ctrl_on", { + description = "Tower Crane Mast Ctrl On", + drawtype = "node", + tiles = { + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl_on.png", + "towercrane_mast_ctrl_on.png", + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl.png", + }, + on_rightclick = function (pos, node, clicker) + local meta = minetest.get_meta(pos) + if not clicker or not clicker:is_player() then + return + end + if clicker:get_player_name() ~= meta:get_string("owner") then + return + end + node.name = "towercrane:mast_ctrl_off" + minetest.swap_node(pos, node) + + local id = minetest.hash_node_position(pos) + if towercrane.id then + towercrane.id:remove() + towercrane.id = nil + else + + end + end, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Switch crane on/off") + end, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta(pos) + local owner = placer:get_player_name() + meta:set_string("owner", owner) + end, + + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) + +---------------------------------------------------------------------------------------------------- +-- Register Crane Switch (off) +---------------------------------------------------------------------------------------------------- +minetest.register_node("towercrane:mast_ctrl_off", { + description = "Tower Crane Mast Ctrl Off", + drawtype = "node", + tiles = { + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl_off.png", + "towercrane_mast_ctrl_off.png", + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl.png", + }, + on_rightclick = function (pos, node, clicker) + -- switch switch on, calculate the construction area, and place the hook + local meta = minetest.get_meta(pos) + -- only the owner is allowed to switch + if not clicker or not clicker:is_player() then + return + end + if clicker:get_player_name() ~= meta:get_string("owner") then + return + end + -- swap to the other node + node.name = "towercrane:mast_ctrl_on" + minetest.swap_node(pos, node) + local dir = minetest.string_to_pos(meta:get_string("dir")) + if pos ~= nil and dir ~= nil then + -- store hook instance in 'towercrane' + local id = minetest.hash_node_position(pos) + towercrane.id = place_hook(table.copy(pos), dir) + + -- calculate the construction area dimension (pos, pos2) + local height = meta:get_int("height") + local width = meta:get_int("width") + + -- pos1 = close/right + dir = turnright(dir) + local pos1 = vector.add(pos, vector.multiply(dir, width/2)) + dir = turnleft(dir) + local pos1 = vector.add(pos1, vector.multiply(dir, 1)) + pos1.y = pos.y - 1 + + -- pos2 = far/left + local pos2 = vector.add(pos1, vector.multiply(dir, width-1)) + dir = turnleft(dir) + pos2 = vector.add(pos2, vector.multiply(dir, width)) + pos2.y = pos.y - 4 + height + + -- normalize x/z so that pos2 > pos1 + if pos2.x < pos1.x then + pos2.x, pos1.x = pos1.x, pos2.x + end + if pos2.z < pos1.z then + pos2.z, pos1.z = pos1.z, pos2.z + end + + -- store pos1/pos2 in the hook (LuaEntitySAO) + towercrane.id:get_luaentity().pos1 = pos1 + towercrane.id:get_luaentity().pos2 = pos2 + end + end, + + on_construct = function(pos) + -- add infotext + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Switch crane on/off") + end, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + -- store owner for dig protection + local meta = minetest.get_meta(pos) + local owner = placer:get_player_name() + meta:set_string("owner", owner) + end, + + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) + +---------------------------------------------------------------------------------------------------- +-- Register Crane arm 1 +---------------------------------------------------------------------------------------------------- +minetest.register_node("towercrane:arm", { + description = "Tower Crane Arm", + drawtype = "glasslike_framed", + tiles = { + "towercrane_arm.png", + "towercrane_arm.png", + "towercrane_arm.png", + "towercrane_arm.png", + "towercrane_arm.png", + "towercrane_arm.png", + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) + +---------------------------------------------------------------------------------------------------- +-- Register Crane arm 2 +---------------------------------------------------------------------------------------------------- +minetest.register_node("towercrane:arm2", { + description = "Tower Crane Arm2", + drawtype = "glasslike_framed", + tiles = { + "towercrane_arm2.png", + "towercrane_arm2.png", + "towercrane_arm2.png", + "towercrane_arm2.png", + "towercrane_arm2.png", + "towercrane_arm2.png", + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) diff --git a/init.lua.org b/init.lua.org new file mode 100644 index 0000000..2180ae1 --- /dev/null +++ b/init.lua.org @@ -0,0 +1,483 @@ +--[[ + + Tower Crane (Liebherr) Mod + by JoSt + + Copyright (C) 2017 Joachim Stolberg + LGPLv2.1+ + See LICENSE for more information + +]]-- + + +towercrane = {} + +local hook = { + physical = true, -- should be true, otherwise you dive into the underground + collisionbox = {-0.2, -0.2, -0.2, 0.2, 0.2, 0.2}, -- Groesse des Wuerfels bei Kollisionen + collide_with_objects = false, + visual = "cube", -- Object als Wuerfel + visual_size = {x=0.4, y=0.4}, -- Groesse des sichtbaren Wuerfels + textures = { + "towercrane_hook.png", + "towercrane_hook.png", + "towercrane_hook.png", + "towercrane_hook.png", + "towercrane_hook.png", + "towercrane_hook.png", + }, + groups = {cracky=1}, + -- local variabels + driver = nil, + speed_forward=0, + speed_right=0, + speed_up=0, +} + +-- ":" methode von "hook" (self ist versteckt) +function hook:on_rightclick(clicker) + if self.driver and clicker == self.driver then -- aussteigen? + clicker:set_detach() + self.driver = nil + elseif not self.driver then -- einsteigen? + self.driver = clicker + clicker:set_attach(self.object, "", {x=0,y=0,z=0}, {x=0,y=0,z=0}) + end +end + +function hook:on_step(dtime) + -- remove hook from last visit + if self.pos1 == nil or self.pos2 == nil then + self.object:remove() + return + end + if self.driver then + local ctrl = self.driver:get_player_control() + local yaw = self.driver:get_look_horizontal() + local pos = self.driver:getpos() + local max_speed = 5 + local velocity = 0.5 + + if ctrl.up then -- forward + self.speed_forward = math.min(self.speed_forward + velocity, max_speed) + elseif ctrl.down then -- backward + self.speed_forward = math.max(self.speed_forward - velocity, -max_speed) + elseif self.speed_forward > 0 then + self.speed_forward = self.speed_forward - velocity + elseif self.speed_forward < 0 then + self.speed_forward = self.speed_forward + velocity + end + + if ctrl.right then -- right + self.speed_right = math.min(self.speed_right + velocity, max_speed) + elseif ctrl.left then -- left + self.speed_right = math.max(self.speed_right - velocity, -max_speed) + elseif self.speed_right > 0 then + self.speed_right = self.speed_right - velocity + elseif self.speed_right < 0 then + self.speed_right = self.speed_right + velocity + end + + if ctrl.jump then -- up + self.speed_up = math.min(self.speed_up + velocity, 5) + elseif ctrl.sneak then -- down + self.speed_up = math.max(self.speed_up - velocity, -5) + elseif self.speed_up > 0 then + self.speed_up = self.speed_up - velocity + elseif self.speed_up < 0 then + self.speed_up = self.speed_up + velocity + end + + -- calculate the direction vector + local vx = math.cos(yaw+math.pi/2) * self.speed_forward + math.cos(yaw) * self.speed_right + local vz = math.sin(yaw+math.pi/2) * self.speed_forward + math.sin(yaw) * self.speed_right + + -- check if outside of the construction area + if pos.x < self.pos1.x then vx= velocity end + if pos.x > self.pos2.x then vx= -velocity end + if pos.y < self.pos1.y then self.speed_up= velocity end + if pos.y > self.pos2.y then self.speed_up= -velocity end + if pos.z < self.pos1.z then vz= velocity end + if pos.z > self.pos2.z then vz= -velocity end + + self.object:setvelocity({x=vx, y=self.speed_up,z=vz}) + else + self.object:setvelocity({x=0, y=0,z=0}) + end +end + +-- LuaEntitySAO (non-player moving things): see http://dev.minetest.net/LuaEntitySAO +minetest.register_entity("towercrane:hook", hook) + +---------------------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------------------- +---------------------------------------------------------------------------------------------------- +local function turnright(dir) + local facedir = minetest.dir_to_facedir(dir) + return minetest.facedir_to_dir((facedir + 1) % 4) +end + +local function turnleft(dir) + local facedir = minetest.dir_to_facedir(dir) + return minetest.facedir_to_dir((facedir + 3) % 4) +end + +local function construct_crane(pos, dir, height, width, owner) + pos.y = pos.y + 1 + minetest.env:add_node(pos, {name="towercrane:mast_ctrl_off", param2=minetest.dir_to_facedir(dir)}) + local meta = minetest.get_meta(pos) + meta:set_string("dir", minetest.pos_to_string(dir)) + meta:set_string("owner", owner) + meta:set_int("height", height) + meta:set_int("width", width) + + for i = 1,height+1 do + pos.y = pos.y + 1 + minetest.env:add_node(pos, {name="towercrane:mast"}) + end + + pos.y = pos.y - 2 + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:add_node(pos, {name="towercrane:arm2"}) + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:add_node(pos, {name="towercrane:arm"}) + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:add_node(pos, {name="towercrane:balance"}) + pos.x = pos.x + 3 * dir.x + pos.z = pos.z + 3 * dir.z + + for i = 1,width do + pos.x = pos.x + dir.x + pos.z = pos.z + dir.z + if i % 2 == 0 then + minetest.env:add_node(pos, {name="towercrane:arm2"}) + else + minetest.env:add_node(pos, {name="towercrane:arm"}) + end + + end +end + +local function dig_crane(pos, dir, height, width) + pos.y = pos.y + 1 + minetest.env:remove_node(pos, {name="towercrane:mast_ctrl_off"}) + + for i = 1,height+1 do + pos.y = pos.y + 1 + minetest.env:remove_node(pos, {name="towercrane:mast"}) + end + + pos.y = pos.y - 2 + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:remove_node(pos, {name="towercrane:arm2"}) + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:remove_node(pos, {name="towercrane:arm"}) + pos.x = pos.x - dir.x + pos.z = pos.z - dir.z + minetest.env:remove_node(pos, {name="towercrane:balance"}) + pos.x = pos.x + 3 * dir.x + pos.z = pos.z + 3 * dir.z + + for i = 1,width do + pos.x = pos.x + dir.x + pos.z = pos.z + dir.z + if i % 2 == 0 then + minetest.env:remove_node(pos, {name="towercrane:arm2"}) + else + minetest.env:remove_node(pos, {name="towercrane:arm"}) + end + + end +end + +local function place_hook(pos, dir) + pos.y = pos.y - 1 + pos.x = pos.x + dir.x + pos.z = pos.z + dir.z + return minetest.add_entity(pos, "towercrane:hook") +end + +minetest.register_node("towercrane:base", { + description = "Tower Crane Base", + inventory_image = "towercrane_invent.png", + tiles = { + "towercrane_base_top.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {cracky=1}, + formspec = set_formspec, + + + after_place_node = function(pos, placer) + local meta = minetest.get_meta(pos) + local owner = placer:get_player_name() + meta:set_string("owner", owner) + local formspec = "size[5,4]".. + "label[0,0;Construction area size]" .. + "field[1,1.5;3,1;size;height,width;]" .. + "button_exit[1,2;2,1;exit;Save]" + meta:set_string("formspec", formspec) + + local fdir = minetest.dir_to_facedir(placer:get_look_dir(), false) + local dir = minetest.facedir_to_dir(fdir) + meta:set_string("dir", minetest.pos_to_string(dir)) + end, + + on_receive_fields = function(pos, formname, fields, player) + if fields.size == nil then + return + end + local meta = minetest.get_meta(pos) + + if not player or not player:is_player() then + return + end + local owner = meta:get_string("owner") + if player:get_player_name() ~= owner then + return + end + + local dir = minetest.string_to_pos(meta:get_string("dir")) + local height = meta:get_int("height") + local width = meta:get_int("width") + local org_pos = table.copy(pos) + if dir ~= nil and height ~= nil and width ~= nil then + dig_crane(pos, dir, height, width) + end + --check for correct size format + size = string.split(fields.size, ",") + if #size == 2 then + local height = tonumber(size[1]) + local width = tonumber(size[2]) + if height ~= nil and width ~= nil then + height = math.max(height, 8) + height = math.min(height, 24) + width = math.max(width, 8) + width = math.min(width, 24) + meta:set_int("height", height) + meta:set_int("width", width) + meta:set_string("infotext", "Crane size: " .. height .. "," .. width) + if dir ~= nil then + construct_crane(org_pos, dir, height, width, owner) + end + end + end + end, + + on_destruct = function(pos) + local meta = minetest.get_meta(pos) + local dir = minetest.string_to_pos(meta:get_string("dir")) + local height = meta:get_int("height") + local width = meta:get_int("width") + + if dir ~= nil and height ~= nil and width ~= nil then + dig_crane(pos, dir, height, width) + end + end, +}) + +minetest.register_on_leaveplayer(function(ObjectRef, timed_out) + print("Schade") + end +) + + +minetest.register_node("towercrane:balance", { + description = "Tower Crane Balance", + tiles = { + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + "towercrane_base.png", + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) + +minetest.register_node("towercrane:mast", { + description = "Tower Crane Mast", + drawtype = "glasslike_framed", + tiles = { + "towercrane_mast.png", + "towercrane_mast.png", + "towercrane_mast.png", + "towercrane_mast.png", + "towercrane_mast.png", + "towercrane_mast.png", + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) + +minetest.register_node("towercrane:mast_ctrl_on", { + description = "Tower Crane Mast Ctrl On", + drawtype = "node", + tiles = { + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl_on.png", + "towercrane_mast_ctrl_on.png", + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl.png", + }, + on_rightclick = function (pos, node, clicker) + local meta = minetest.get_meta(pos) + if not clicker or not clicker:is_player() then + return + end + if clicker:get_player_name() ~= meta:get_string("owner") then + return + end + node.name = "towercrane:mast_ctrl_off" + minetest.swap_node(pos, node) + + local id = minetest.hash_node_position(pos) + if towercrane.id then + towercrane.id:remove() + towercrane.id = nil + else + + end + end, + + on_construct = function(pos) + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Switch crane on/off") + end, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + local meta = minetest.get_meta(pos) + local owner = placer:get_player_name() + meta:set_string("owner", owner) + end, + + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) + +minetest.register_node("towercrane:mast_ctrl_off", { + description = "Tower Crane Mast Ctrl Off", + drawtype = "node", + tiles = { + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl_off.png", + "towercrane_mast_ctrl_off.png", + "towercrane_mast_ctrl.png", + "towercrane_mast_ctrl.png", + }, + on_rightclick = function (pos, node, clicker) + -- switch switch on, calculate the construction area, and place the hook + local meta = minetest.get_meta(pos) + -- only the owner is allowed to switch + if not clicker or not clicker:is_player() then + return + end + if clicker:get_player_name() ~= meta:get_string("owner") then + return + end + -- swap to the other node + node.name = "towercrane:mast_ctrl_on" + minetest.swap_node(pos, node) + local dir = minetest.string_to_pos(meta:get_string("dir")) + if pos ~= nil and dir ~= nil then + -- store hook instance in 'towercrane' + local id = minetest.hash_node_position(pos) + towercrane.id = place_hook(table.copy(pos), dir) + + -- calculate the construction area dimension (pos, pos2) + local height = meta:get_int("height") + local width = meta:get_int("width") + + -- pos1 = close/right + dir = turnright(dir) + local pos1 = vector.add(pos, vector.multiply(dir, width/2)) + dir = turnleft(dir) + local pos1 = vector.add(pos1, vector.multiply(dir, 1)) + pos1.y = pos.y - 1 + + -- pos2 = far/left + local pos2 = vector.add(pos1, vector.multiply(dir, width-1)) + dir = turnleft(dir) + pos2 = vector.add(pos2, vector.multiply(dir, width)) + pos2.y = pos.y - 4 + height + + -- normalize x/z so that pos2 > pos1 + if pos2.x < pos1.x then + pos2.x, pos1.x = pos1.x, pos2.x + end + if pos2.z < pos1.z then + pos2.z, pos1.z = pos1.z, pos2.z + end + + -- store pos1/pos2 in the hook (LuaEntitySAO) + towercrane.id:get_luaentity().pos1 = pos1 + towercrane.id:get_luaentity().pos2 = pos2 + end + end, + + on_construct = function(pos) + -- add infotext + local meta = minetest.get_meta(pos) + meta:set_string("infotext", "Switch crane on/off") + end, + + after_place_node = function(pos, placer, itemstack, pointed_thing) + -- store owner for dig protection + local meta = minetest.get_meta(pos) + local owner = placer:get_player_name() + meta:set_string("owner", owner) + end, + + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) + +minetest.register_node("towercrane:arm", { + description = "Tower Crane Arm", + drawtype = "glasslike_framed", + tiles = { + "towercrane_arm.png", + "towercrane_arm.png", + "towercrane_arm.png", + "towercrane_arm.png", + "towercrane_arm.png", + "towercrane_arm.png", + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) + +minetest.register_node("towercrane:arm2", { + description = "Tower Crane Arm2", + drawtype = "glasslike_framed", + tiles = { + "towercrane_arm2.png", + "towercrane_arm2.png", + "towercrane_arm2.png", + "towercrane_arm2.png", + "towercrane_arm2.png", + "towercrane_arm2.png", + }, + paramtype2 = "facedir", + is_ground_content = false, + groups = {crumbly=0, not_in_creative_inventory=1}, +}) diff --git a/lua_api.dtxt2 b/lua_api.dtxt2 new file mode 100644 index 0000000..975fd64 --- /dev/null +++ b/lua_api.dtxt2 @@ -0,0 +1,4676 @@ +# Minetest Lua Modding API Reference 0.4.16 + +* More information at +* Developer Wiki: + +Introduction +------------ +Content and functionality can be added to Minetest 0.4 by using Lua +scripting in run-time loaded mods. + +A mod is a self-contained bunch of scripts, textures and other related +things that is loaded by and interfaces with Minetest. + +Mods are contained and ran solely on the server side. Definitions and media +files are automatically transferred to the client. + +If you see a deficiency in the API, feel free to attempt to add the +functionality in the engine and API. You can send such improvements as +source code patches to . + +Programming in Lua +------------------ +If you have any difficulty in understanding this, please read +[Programming in Lua](http://www.lua.org/pil/). + +Startup +------- +Mods are loaded during server startup from the mod load paths by running +the `init.lua` scripts in a shared environment. + +Paths +----- +* `RUN_IN_PLACE=1` (Windows release, local build) + * `$path_user`: + * Linux: `` + * Windows: `` + * `$path_share` + * Linux: `` + * Windows: `` +* `RUN_IN_PLACE=0`: (Linux release) + * `$path_share` + * Linux: `/usr/share/minetest` + * Windows: `/minetest-0.4.x` + * `$path_user`: + * Linux: `$HOME/.minetest` + * Windows: `C:/users//AppData/minetest` (maybe) + +Games +----- +Games are looked up from: + +* `$path_share/games/gameid/` +* `$path_user/games/gameid/` + +where `gameid` is unique to each game. + +The game directory contains the file `game.conf`, which contains these fields: + + name = + +e.g. + + name = Minetest + +The game directory can contain the file minetest.conf, which will be used +to set default settings when running the particular game. +It can also contain a settingtypes.txt in the same format as the one in builtin. +This settingtypes.txt will be parsed by the menu and the settings will be displayed +in the "Games" category in the settings tab. + +### Menu images + +Games can provide custom main menu images. They are put inside a `menu` directory +inside the game directory. + +The images are named `$identifier.png`, where `$identifier` is +one of `overlay,background,footer,header`. +If you want to specify multiple images for one identifier, add additional images named +like `$identifier.$n.png`, with an ascending number $n starting with 1, and a random +image will be chosen from the provided ones. + + +Mod load path +------------- +Generic: + +* `$path_share/games/gameid/mods/` +* `$path_share/mods/` +* `$path_user/games/gameid/mods/` +* `$path_user/mods/` (User-installed mods) +* `$worldpath/worldmods/` + +In a run-in-place version (e.g. the distributed windows version): + +* `minetest-0.4.x/games/gameid/mods/` +* `minetest-0.4.x/mods/` (User-installed mods) +* `minetest-0.4.x/worlds/worldname/worldmods/` + +On an installed version on Linux: + +* `/usr/share/minetest/games/gameid/mods/` +* `$HOME/.minetest/mods/` (User-installed mods) +* `$HOME/.minetest/worlds/worldname/worldmods` + +Mod load path for world-specific games +-------------------------------------- +It is possible to include a game in a world; in this case, no mods or +games are loaded or checked from anywhere else. + +This is useful for e.g. adventure worlds. + +This happens if the following directory exists: + + $world/game/ + +Mods should be then be placed in: + + $world/game/mods/ + +Modpack support +---------------- +Mods can be put in a subdirectory, if the parent directory, which otherwise +should be a mod, contains a file named `modpack.txt`. This file shall be +empty, except for lines starting with `#`, which are comments. + +Mod directory structure +------------------------ + + mods + |-- modname + | |-- depends.txt + | |-- screenshot.png + | |-- description.txt + | |-- settingtypes.txt + | |-- init.lua + | |-- models + | |-- textures + | | |-- modname_stuff.png + | | `-- modname_something_else.png + | |-- sounds + | |-- media + | `-- + `-- another + + +### modname +The location of this directory can be fetched by using +`minetest.get_modpath(modname)`. + +### `depends.txt` +List of mods that have to be loaded before loading this mod. + +A single line contains a single modname. + +Optional dependencies can be defined by appending a question mark +to a single modname. Their meaning is that if the specified mod +is missing, that does not prevent this mod from being loaded. + +### `screenshot.png` +A screenshot shown in the mod manager within the main menu. It should +have an aspect ratio of 3:2 and a minimum size of 300×200 pixels. + +### `description.txt` +A File containing description to be shown within mainmenu. + +### `settingtypes.txt` +A file in the same format as the one in builtin. It will be parsed by the +settings menu and the settings will be displayed in the "Mods" category. + +### `init.lua` +The main Lua script. Running this script should register everything it +wants to register. Subsequent execution depends on minetest calling the +registered callbacks. + +`minetest.settings` can be used to read custom or existing settings at load +time, if necessary. (See `Settings`) + +### `models` +Models for entities or meshnodes. + +### `textures`, `sounds`, `media` +Media files (textures, sounds, whatever) that will be transferred to the +client and will be available for use by the mod. + +Naming convention for registered textual names +---------------------------------------------- +Registered names should generally be in this format: + + `modname:` + +`` can have these characters: + + a-zA-Z0-9_ + +This is to prevent conflicting names from corrupting maps and is +enforced by the mod loader. + +### Example +In the mod `experimental`, there is the ideal item/node/entity name `tnt`. +So the name should be `experimental:tnt`. + +Enforcement can be overridden by prefixing the name with `:`. This can +be used for overriding the registrations of some other mod. + +Example: Any mod can redefine `experimental:tnt` by using the name + + :experimental:tnt + +when registering it. +(also that mod is required to have `experimental` as a dependency) + +The `:` prefix can also be used for maintaining backwards compatibility. + +### Aliases +Aliases can be added by using `minetest.register_alias(name, convert_to)` or +`minetest.register_alias_force(name, convert_to)`. + +This will make Minetest to convert things called name to things called +`convert_to`. + +The only difference between `minetest.register_alias` and +`minetest.register_alias_force` is that if an item called `name` exists, +`minetest.register_alias` will do nothing while +`minetest.register_alias_force` will unregister it. + +This can be used for maintaining backwards compatibility. + +This can be also used for setting quick access names for things, e.g. if +you have an item called `epiclylongmodname:stuff`, you could do + + minetest.register_alias("stuff", "epiclylongmodname:stuff") + +and be able to use `/giveme stuff`. + +Textures +-------- +Mods should generally prefix their textures with `modname_`, e.g. given +the mod name `foomod`, a texture could be called: + + foomod_foothing.png + +Textures are referred to by their complete name, or alternatively by +stripping out the file extension: + +* e.g. `foomod_foothing.png` +* e.g. `foomod_foothing` + +Texture modifiers +----------------- +There are various texture modifiers that can be used +to generate textures on-the-fly. + +### Texture overlaying +Textures can be overlaid by putting a `^` between them. + +Example: + + default_dirt.png^default_grass_side.png + +`default_grass_side.png` is overlayed over `default_dirt.png`. +The texture with the lower resolution will be automatically upscaled to +the higher resolution texture. + +### Texture grouping +Textures can be grouped together by enclosing them in `(` and `)`. + +Example: `cobble.png^(thing1.png^thing2.png)` + +A texture for `thing1.png^thing2.png` is created and the resulting +texture is overlaid on top of `cobble.png`. + +### Escaping +Modifiers that accept texture names (e.g. `[combine`) accept escaping to allow +passing complex texture names as arguments. Escaping is done with backslash and +is required for `^` and `:`. + +Example: `cobble.png^[lowpart:50:color.png\^[mask\:trans.png` + +The lower 50 percent of `color.png^[mask:trans.png` are overlaid +on top of `cobble.png`. + +### Advanced texture modifiers + +#### `[crack::

` +* `` = animation frame count +* `

` = current animation frame + +Draw a step of the crack animation on the texture. + +Example: + + default_cobble.png^[crack:10:1 + +#### `[combine:x:,=:,=:...` +* `` = width +* `` = height +* `` = x position +* `` = y position +* `` = texture to combine + +Creates a texture of size `` times `` and blits the listed files to their +specified coordinates. + +Example: + + [combine:16x32:0,0=default_cobble.png:0,16=default_wood.png + +#### `[resize:x` +Resizes the texture to the given dimensions. + +Example: + + default_sandstone.png^[resize:16x16 + +#### `[opacity:` +Makes the base image transparent according to the given ratio. + +`r` must be between 0 and 255. +0 means totally transparent. 255 means totally opaque. + +Example: + + default_sandstone.png^[opacity:127 + +#### `[invert:` +Inverts the given channels of the base image. +Mode may contain the characters "r", "g", "b", "a". +Only the channels that are mentioned in the mode string will be inverted. + +Example: + + default_apple.png^[invert:rgb + +#### `[brighten` +Brightens the texture. + +Example: + + tnt_tnt_side.png^[brighten + +#### `[noalpha` +Makes the texture completely opaque. + +Example: + + default_leaves.png^[noalpha + +#### `[makealpha:,,` +Convert one color to transparency. + +Example: + + default_cobble.png^[makealpha:128,128,128 + +#### `[transform` +* `` = transformation(s) to apply + +Rotates and/or flips the image. + +`` can be a number (between 0 and 7) or a transform name. +Rotations are counter-clockwise. + + 0 I identity + 1 R90 rotate by 90 degrees + 2 R180 rotate by 180 degrees + 3 R270 rotate by 270 degrees + 4 FX flip X + 5 FXR90 flip X then rotate by 90 degrees + 6 FY flip Y + 7 FYR90 flip Y then rotate by 90 degrees + +Example: + + default_stone.png^[transformFXR90 + +#### `[inventorycube{{{` +Escaping does not apply here and `^` is replaced by `&` in texture names instead. + +Create an inventory cube texture using the side textures. + +Example: + + [inventorycube{grass.png{dirt.png&grass_side.png{dirt.png&grass_side.png + +Creates an inventorycube with `grass.png`, `dirt.png^grass_side.png` and +`dirt.png^grass_side.png` textures + +#### `[lowpart::` +Blit the lower ``% part of `` on the texture. + +Example: + + base.png^[lowpart:25:overlay.png + +#### `[verticalframe::` +* `` = animation frame count +* `` = current animation frame + +Crops the texture to a frame of a vertical animation. + +Example: + + default_torch_animated.png^[verticalframe:16:8 + +#### `[mask:` +Apply a mask to the base image. + +The mask is applied using binary AND. + +#### `[sheet:x:,` +Retrieves a tile at position x,y from the base image +which it assumes to be a tilesheet with dimensions w,h. + + +#### `[colorize::` +Colorize the textures with the given color. +`` is specified as a `ColorString`. +`` is an int ranging from 0 to 255 or the word "`alpha`". If +it is an int, then it specifies how far to interpolate between the +colors where 0 is only the texture color and 255 is only ``. If +omitted, the alpha of `` will be used as the ratio. If it is +the word "`alpha`", then each texture pixel will contain the RGB of +`` and the alpha of `` multiplied by the alpha of the +texture pixel. + +#### `[multiply:` +Multiplies texture colors with the given color. +`` is specified as a `ColorString`. +Result is more like what you'd expect if you put a color on top of another +color. Meaning white surfaces get a lot of your new color while black parts don't +change very much. + +Hardware coloring +----------------- +The goal of hardware coloring is to simplify the creation of +colorful nodes. If your textures use the same pattern, and they only +differ in their color (like colored wool blocks), you can use hardware +coloring instead of creating and managing many texture files. +All of these methods use color multiplication (so a white-black texture +with red coloring will result in red-black color). + +### Static coloring +This method is useful if you wish to create nodes/items with +the same texture, in different colors, each in a new node/item definition. + +#### Global color +When you register an item or node, set its `color` field (which accepts a +`ColorSpec`) to the desired color. + +An `ItemStack`s static color can be overwritten by the `color` metadata +field. If you set that field to a `ColorString`, that color will be used. + +#### Tile color +Each tile may have an individual static color, which overwrites every +other coloring methods. To disable the coloring of a face, +set its color to white (because multiplying with white does nothing). +You can set the `color` property of the tiles in the node's definition +if the tile is in table format. + +### Palettes +For nodes and items which can have many colors, a palette is more +suitable. A palette is a texture, which can contain up to 256 pixels. +Each pixel is one possible color for the node/item. +You can register one node/item, which can have up to 256 colors. + +#### Palette indexing +When using palettes, you always provide a pixel index for the given +node or `ItemStack`. The palette is read from left to right and from +top to bottom. If the palette has less than 256 pixels, then it is +stretched to contain exactly 256 pixels (after arranging the pixels +to one line). The indexing starts from 0. + +Examples: +* 16x16 palette, index = 0: the top left corner +* 16x16 palette, index = 4: the fifth pixel in the first row +* 16x16 palette, index = 16: the pixel below the top left corner +* 16x16 palette, index = 255: the bottom right corner +* 2 (width)x4 (height) palette, index=31: the top left corner. + The palette has 8 pixels, so each pixel is stretched to 32 pixels, + to ensure the total 256 pixels. +* 2x4 palette, index=32: the top right corner +* 2x4 palette, index=63: the top right corner +* 2x4 palette, index=64: the pixel below the top left corner + +#### Using palettes with items +When registering an item, set the item definition's `palette` field to +a texture. You can also use texture modifiers. + +The `ItemStack`'s color depends on the `palette_index` field of the +stack's metadata. `palette_index` is an integer, which specifies the +index of the pixel to use. + +#### Linking palettes with nodes +When registering a node, set the item definition's `palette` field to +a texture. You can also use texture modifiers. +The node's color depends on its `param2`, so you also must set an +appropriate `drawtype`: +* `drawtype = "color"` for nodes which use their full `param2` for + palette indexing. These nodes can have 256 different colors. + The palette should contain 256 pixels. +* `drawtype = "colorwallmounted"` for nodes which use the first + five bits (most significant) of `param2` for palette indexing. + The remaining three bits are describing rotation, as in `wallmounted` + draw type. Division by 8 yields the palette index (without stretching the + palette). These nodes can have 32 different colors, and the palette + should contain 32 pixels. + Examples: + * `param2 = 17` is 2 * 8 + 1, so the rotation is 1 and the third (= 2 + 1) + pixel will be picked from the palette. + * `param2 = 35` is 4 * 8 + 3, so the rotation is 3 and the fifth (= 4 + 1) + pixel will be picked from the palette. +* `drawtype = "colorfacedir"` for nodes which use the first + three bits of `param2` for palette indexing. The remaining + five bits are describing rotation, as in `facedir` draw type. + Division by 32 yields the palette index (without stretching the + palette). These nodes can have 8 different colors, and the + palette should contain 8 pixels. + Examples: + * `param2 = 17` is 0 * 32 + 17, so the rotation is 17 and the + first (= 0 + 1) pixel will be picked from the palette. + * `param2 = 35` is 1 * 32 + 3, so the rotation is 3 and the + second (= 1 + 1) pixel will be picked from the palette. + +To colorize a node on the map, set its `param2` value (according +to the node's draw type). + +### Conversion between nodes in the inventory and the on the map +Static coloring is the same for both cases, there is no need +for conversion. + +If the `ItemStack`'s metadata contains the `color` field, it will be +lost on placement, because nodes on the map can only use palettes. + +If the `ItemStack`'s metadata contains the `palette_index` field, you +currently must manually convert between it and the node's `param2` with +custom `on_place` and `on_dig` callbacks. + +### Colored items in craft recipes +Craft recipes only support item strings, but fortunately item strings +can also contain metadata. Example craft recipe registration: + + local stack = ItemStack("wool:block") + dyed:get_meta():set_int("palette_index", 3) -- add index + minetest.register_craft({ + output = dyed:to_string(), -- convert to string + type = "shapeless", + recipe = { + "wool:block", + "dye:red", + }, + }) + +Metadata field filtering in the `recipe` field are not supported yet, +so the craft output is independent of the color of the ingredients. + +Soft texture overlay +-------------------- +Sometimes hardware coloring is not enough, because it affects the +whole tile. Soft texture overlays were added to Minetest to allow +the dynamic coloring of only specific parts of the node's texture. +For example a grass block may have colored grass, while keeping the +dirt brown. + +These overlays are 'soft', because unlike texture modifiers, the layers +are not merged in the memory, but they are simply drawn on top of each +other. This allows different hardware coloring, but also means that +tiles with overlays are drawn slower. Using too much overlays might +cause FPS loss. + +To define an overlay, simply set the `overlay_tiles` field of the node +definition. These tiles are defined in the same way as plain tiles: +they can have a texture name, color etc. +To skip one face, set that overlay tile to an empty string. + +Example (colored grass block): + + minetest.register_node("default:dirt_with_grass", { + description = "Dirt with Grass", + -- Regular tiles, as usual + -- The dirt tile disables palette coloring + tiles = {{name = "default_grass.png"}, + {name = "default_dirt.png", color = "white"}}, + -- Overlay tiles: define them in the same style + -- The top and bottom tile does not have overlay + overlay_tiles = {"", "", + {name = "default_grass_side.png", tileable_vertical = false}}, + -- Global color, used in inventory + color = "green", + -- Palette in the world + paramtype2 = "color", + palette = "default_foilage.png", + }) + +Sounds +------ +Only Ogg Vorbis files are supported. + +For positional playing of sounds, only single-channel (mono) files are +supported. Otherwise OpenAL will play them non-positionally. + +Mods should generally prefix their sounds with `modname_`, e.g. given +the mod name "`foomod`", a sound could be called: + + foomod_foosound.ogg + +Sounds are referred to by their name with a dot, a single digit and the +file extension stripped out. When a sound is played, the actual sound file +is chosen randomly from the matching sounds. + +When playing the sound `foomod_foosound`, the sound is chosen randomly +from the available ones of the following files: + +* `foomod_foosound.ogg` +* `foomod_foosound.0.ogg` +* `foomod_foosound.1.ogg` +* (...) +* `foomod_foosound.9.ogg` + +Examples of sound parameter tables: + + -- Play locationless on all clients + { + gain = 1.0, -- default + fade = 0.0, -- default, change to a value > 0 to fade the sound in + } + -- Play locationless to one player + { + to_player = name, + gain = 1.0, -- default + fade = 0.0, -- default, change to a value > 0 to fade the sound in + } + -- Play locationless to one player, looped + { + to_player = name, + gain = 1.0, -- default + loop = true, + } + -- Play in a location + { + pos = {x = 1, y = 2, z = 3}, + gain = 1.0, -- default + max_hear_distance = 32, -- default, uses an euclidean metric + } + -- Play connected to an object, looped + { + object = , + gain = 1.0, -- default + max_hear_distance = 32, -- default, uses an euclidean metric + loop = true, + } + +Looped sounds must either be connected to an object or played locationless to +one player using `to_player = name,` + +### `SimpleSoundSpec` +* e.g. `""` +* e.g. `"default_place_node"` +* e.g. `{}` +* e.g. `{name = "default_place_node"}` +* e.g. `{name = "default_place_node", gain = 1.0}` + +## Registered definitions of stuff +------------------------------- +Anything added using certain `minetest.register_*` functions get added to +the global `minetest.registered_*` tables. + +* `minetest.register_entity(name, prototype table)` + * added to `minetest.registered_entities[name]` + +* `minetest.register_node(name, node definition)` + * added to `minetest.registered_items[name]` + * added to `minetest.registered_nodes[name]` + +* `minetest.register_tool(name, item definition)` + * added to `minetest.registered_items[name]` + +* `minetest.register_craftitem(name, item definition)` + * added to `minetest.registered_items[name]` + +* `minetest.unregister_item(name)` + * Unregisters the item name from engine, and deletes the entry with key + * `name` from `minetest.registered_items` and from the associated item + * table according to its nature: `minetest.registered_nodes[]` etc + +* `minetest.register_biome(biome definition)` + * returns an integer uniquely identifying the registered biome + * added to `minetest.registered_biome` with the key of `biome.name` + * if `biome.name` is nil, the key is the returned ID + +* `minetest.register_ore(ore definition)` + * returns an integer uniquely identifying the registered ore + * added to `minetest.registered_ores` with the key of `ore.name` + * if `ore.name` is nil, the key is the returned ID + +* `minetest.register_decoration(decoration definition)` + * returns an integer uniquely identifying the registered decoration + * added to `minetest.registered_decorations` with the key of `decoration.name` + * if `decoration.name` is nil, the key is the returned ID + +* `minetest.register_schematic(schematic definition)` + * returns an integer uniquely identifying the registered schematic + * added to `minetest.registered_schematic` with the key of `schematic.name` + * if `schematic.name` is nil, the key is the returned ID + * if the schematic is loaded from a file, schematic.name is set to the filename + * if the function is called when loading the mod, and schematic.name is a relative + path, then the current mod path will be prepended to the schematic filename + +* `minetest.clear_registered_biomes()` + * clears all biomes currently registered + +* `minetest.clear_registered_ores()` + * clears all ores currently registered + +* `minetest.clear_registered_decorations()` + * clears all decorations currently registered + +* `minetest.clear_registered_schematics()` + * clears all schematics currently registered + +Note that in some cases you will stumble upon things that are not contained +in these tables (e.g. when a mod has been removed). Always check for +existence before trying to access the fields. + +Example: If you want to check the drawtype of a node, you could do: + + local function get_nodedef_field(nodename, fieldname) + if not minetest.registered_nodes[nodename] then + return nil + end + return minetest.registered_nodes[nodename][fieldname] + end + local drawtype = get_nodedef_field(nodename, "drawtype") + +Example: `minetest.get_item_group(name, group)` has been implemented as: + + function minetest.get_item_group(name, group) + if not minetest.registered_items[name] or not + minetest.registered_items[name].groups[group] then + return 0 + end + return minetest.registered_items[name].groups[group] + end + +## Nodes +----- +Nodes are the bulk data of the world: cubes and other things that take the +space of a cube. Huge amounts of them are handled efficiently, but they +are quite static. + +The definition of a node is stored and can be accessed by name in + + minetest.registered_nodes[node.name] + +See "Registered definitions of stuff". + +Nodes are passed by value between Lua and the engine. +They are represented by a table: + + {name="name", param1=num, param2=num} + +`param1` and `param2` are 8-bit integers ranging from 0 to 255. The engine uses +them for certain automated functions. If you don't use these functions, you can +use them to store arbitrary values. + +The functions of `param1` and `param2` are determined by certain fields in the +node definition: + +`param1` is reserved for the engine when `paramtype != "none"`: + + paramtype = "light" + ^ The value stores light with and without sun in its upper and lower 4 bits + respectively. Allows light to propagate from or through the node with + light value falling by 1 per node. This is essential for a light source + node to spread its light. + +`param2` is reserved for the engine when any of these are used: + + liquidtype == "flowing" + ^ The level and some flags of the liquid is stored in param2 + drawtype == "flowingliquid" + ^ The drawn liquid level is read from param2 + drawtype == "torchlike" + drawtype == "signlike" + paramtype2 == "wallmounted" + ^ The rotation of the node is stored in param2. You can make this value + by using minetest.dir_to_wallmounted(). + paramtype2 == "facedir" + ^ The rotation of the node is stored in param2. Furnaces and chests are + rotated this way. Can be made by using minetest.dir_to_facedir(). + Values range 0 - 23 + facedir / 4 = axis direction: + 0 = y+ 1 = z+ 2 = z- 3 = x+ 4 = x- 5 = y- + facedir modulo 4 = rotation around that axis + paramtype2 == "leveled" + paramtype2 == "degrotate" + ^ The rotation of this node is stored in param2. Plants are rotated this way. + Values range 0 - 179. The value stored in param2 is multiplied by two to + get the actual rotation of the node. + paramtype2 == "meshoptions" + ^ Only valid for "plantlike". The value of param2 becomes a bitfield which can + be used to change how the client draws plantlike nodes. Bits 0, 1 and 2 form + a mesh selector. Currently the following meshes are choosable: + 0 = a "x" shaped plant (ordinary plant) + 1 = a "+" shaped plant (just rotated 45 degrees) + 2 = a "*" shaped plant with 3 faces instead of 2 + 3 = a "#" shaped plant with 4 faces instead of 2 + 4 = a "#" shaped plant with 4 faces that lean outwards + 5-7 are unused and reserved for future meshes. + Bits 3 through 7 are optional flags that can be combined and give these + effects: + bit 3 (0x08) - Makes the plant slightly vary placement horizontally + bit 4 (0x10) - Makes the plant mesh 1.4x larger + bit 5 (0x20) - Moves each face randomly a small bit down (1/8 max) + bits 6-7 are reserved for future use. + paramtype2 == "color" + ^ `param2` tells which color is picked from the palette. + The palette should have 256 pixels. + paramtype2 == "colorfacedir" + ^ Same as `facedir`, but with colors. + The first three bits of `param2` tells which color + is picked from the palette. + The palette should have 8 pixels. + paramtype2 == "colorwallmounted" + ^ Same as `wallmounted`, but with colors. + The first five bits of `param2` tells which color + is picked from the palette. + The palette should have 32 pixels. + paramtype2 == "glasslikeliquidlevel" + ^ Only valid for "glasslike_framed" or "glasslike_framed_optional" drawtypes. + param2 defines 64 levels of internal liquid. + Liquid texture is defined using `special_tiles = {"modname_tilename.png"},` + +Nodes can also contain extra data. See "Node Metadata". + +Node drawtypes +--------------- +There are a bunch of different looking node types. + +Look for examples in `games/minimal` or `games/minetest_game`. + +* `normal` +* `airlike` +* `liquid` +* `flowingliquid` +* `glasslike` +* `glasslike_framed` +* `glasslike_framed_optional` +* `allfaces` +* `allfaces_optional` +* `torchlike` +* `signlike` +* `plantlike` +* `firelike` +* `fencelike` +* `raillike` +* `nodebox` -- See below. (**Experimental!**) +* `mesh` -- use models for nodes + +`*_optional` drawtypes need less rendering time if deactivated (always client side). + +Node boxes +----------- +Node selection boxes are defined using "node boxes" + +The `nodebox` node drawtype allows defining visual of nodes consisting of +arbitrary number of boxes. It allows defining stuff like stairs. Only the +`fixed` and `leveled` box type is supported for these. + +Please note that this is still experimental, and may be incompatibly +changed in the future. + +A nodebox is defined as any of: + + { + -- A normal cube; the default in most things + type = "regular" + } + { + -- A fixed box (facedir param2 is used, if applicable) + type = "fixed", + fixed = box OR {box1, box2, ...} + } + { + -- A box like the selection box for torches + -- (wallmounted param2 is used, if applicable) + type = "wallmounted", + wall_top = box, + wall_bottom = box, + wall_side = box + } + { + -- A node that has optional boxes depending on neighbouring nodes' + -- presence and type. See also `connects_to`. + type = "connected", + fixed = box OR {box1, box2, ...} + connect_top = box OR {box1, box2, ...} + connect_bottom = box OR {box1, box2, ...} + connect_front = box OR {box1, box2, ...} + connect_left = box OR {box1, box2, ...} + connect_back = box OR {box1, box2, ...} + connect_right = box OR {box1, box2, ...} + } + +A `box` is defined as: + + {x1, y1, z1, x2, y2, z2} + +A box of a regular node would look like: + + {-0.5, -0.5, -0.5, 0.5, 0.5, 0.5}, + +`type = "leveled"` is same as `type = "fixed"`, but `y2` will be automatically +set to level from `param2`. + + +Meshes +------ +If drawtype `mesh` is used, tiles should hold model materials textures. +Only static meshes are implemented. +For supported model formats see Irrlicht engine documentation. + + +Noise Parameters +---------------- +Noise Parameters, or commonly called "`NoiseParams`", define the properties of +perlin noise. + +### `offset` +Offset that the noise is translated by (i.e. added) after calculation. + +### `scale` +Factor that the noise is scaled by (i.e. multiplied) after calculation. + +### `spread` +Vector containing values by which each coordinate is divided by before calculation. +Higher spread values result in larger noise features. + +A value of `{x=250, y=250, z=250}` is common. + +### `seed` +Random seed for the noise. Add the world seed to a seed offset for world-unique noise. +In the case of `minetest.get_perlin()`, this value has the world seed automatically added. + +### `octaves` +Number of times the noise gradient is accumulated into the noise. + +Increase this number to increase the amount of detail in the resulting noise. + +A value of `6` is common. + +### `persistence` +Factor by which the effect of the noise gradient function changes with each successive octave. + +Values less than `1` make the details of successive octaves' noise diminish, while values +greater than `1` make successive octaves stronger. + +A value of `0.6` is common. + +### `lacunarity` +Factor by which the noise feature sizes change with each successive octave. + +A value of `2.0` is common. + +### `flags` +Leave this field unset for no special handling. + +Currently supported are `defaults`, `eased` and `absvalue`. + +#### `defaults` +Specify this if you would like to keep auto-selection of eased/not-eased while specifying +some other flags. + +#### `eased` +Maps noise gradient values onto a quintic S-curve before performing interpolation. +This results in smooth, rolling noise. Disable this (`noeased`) for sharp-looking noise. +If no flags are specified (or defaults is), 2D noise is eased and 3D noise is not eased. + +#### `absvalue` +Accumulates the absolute value of each noise gradient result. + +Noise parameters format example for 2D or 3D perlin noise or perlin noise maps: + + np_terrain = { + offset = 0, + scale = 1, + spread = {x=500, y=500, z=500}, + seed = 571347, + octaves = 5, + persist = 0.63, + lacunarity = 2.0, + flags = "defaults, absvalue" + } + ^ A single noise parameter table can be used to get 2D or 3D noise, + when getting 2D noise spread.z is ignored. + + +Ore types +--------- +These tell in what manner the ore is generated. + +All default ores are of the uniformly-distributed scatter type. + +### `scatter` +Randomly chooses a location and generates a cluster of ore. + +If `noise_params` is specified, the ore will be placed if the 3D perlin noise at +that point is greater than the `noise_threshold`, giving the ability to create +a non-equal distribution of ore. + +### `sheet` +Creates a sheet of ore in a blob shape according to the 2D perlin noise +described by `noise_params` and `noise_threshold`. This is essentially an +improved version of the so-called "stratus" ore seen in some unofficial mods. + +This sheet consists of vertical columns of uniform randomly distributed height, +varying between the inclusive range `column_height_min` and `column_height_max`. +If `column_height_min` is not specified, this parameter defaults to 1. +If `column_height_max` is not specified, this parameter defaults to `clust_size` +for reverse compatibility. New code should prefer `column_height_max`. + +The `column_midpoint_factor` parameter controls the position of the column at which +ore eminates from. If 1, columns grow upward. If 0, columns grow downward. If 0.5, +columns grow equally starting from each direction. `column_midpoint_factor` is a +decimal number ranging in value from 0 to 1. If this parameter is not specified, +the default is 0.5. + +The ore parameters `clust_scarcity` and `clust_num_ores` are ignored for this ore type. + +### `puff` +Creates a sheet of ore in a cloud-like puff shape. + +As with the `sheet` ore type, the size and shape of puffs are described by +`noise_params` and `noise_threshold` and are placed at random vertical positions +within the currently generated chunk. + +The vertical top and bottom displacement of each puff are determined by the noise +parameters `np_puff_top` and `np_puff_bottom`, respectively. + + +### `blob` +Creates a deformed sphere of ore according to 3d perlin noise described by +`noise_params`. The maximum size of the blob is `clust_size`, and +`clust_scarcity` has the same meaning as with the `scatter` type. + +### `vein` +Creates veins of ore varying in density by according to the intersection of two +instances of 3d perlin noise with diffferent seeds, both described by +`noise_params`. `random_factor` varies the influence random chance has on +placement of an ore inside the vein, which is `1` by default. Note that +modifying this parameter may require adjusting `noise_threshold`. +The parameters `clust_scarcity`, `clust_num_ores`, and `clust_size` are ignored +by this ore type. This ore type is difficult to control since it is sensitive +to small changes. The following is a decent set of parameters to work from: + + noise_params = { + offset = 0, + scale = 3, + spread = {x=200, y=200, z=200}, + seed = 5390, + octaves = 4, + persist = 0.5, + flags = "eased", + }, + noise_threshold = 1.6 + +**WARNING**: Use this ore type *very* sparingly since it is ~200x more +computationally expensive than any other ore. + +Ore attributes +-------------- +See section "Flag Specifier Format". + +Currently supported flags: +`absheight`, `puff_cliffs`, `puff_additive_composition`. + +### `absheight` +Also produce this same ore between the height range of `-y_max` and `-y_min`. + +Useful for having ore in sky realms without having to duplicate ore entries. + +### `puff_cliffs` +If set, puff ore generation will not taper down large differences in displacement +when approaching the edge of a puff. This flag has no effect for ore types other +than `puff`. + +### `puff_additive_composition` +By default, when noise described by `np_puff_top` or `np_puff_bottom` results in a +negative displacement, the sub-column at that point is not generated. With this +attribute set, puff ore generation will instead generate the absolute difference in +noise displacement values. This flag has no effect for ore types other than `puff`. + +Decoration types +---------------- +The varying types of decorations that can be placed. + +### `simple` +Creates a 1 times `H` times 1 column of a specified node (or a random node from +a list, if a decoration list is specified). Can specify a certain node it must +spawn next to, such as water or lava, for example. Can also generate a +decoration of random height between a specified lower and upper bound. +This type of decoration is intended for placement of grass, flowers, cacti, +papyri, waterlilies and so on. + +### `schematic` +Copies a box of `MapNodes` from a specified schematic file (or raw description). +Can specify a probability of a node randomly appearing when placed. +This decoration type is intended to be used for multi-node sized discrete +structures, such as trees, cave spikes, rocks, and so on. + + +Schematic specifier +-------------------- +A schematic specifier identifies a schematic by either a filename to a +Minetest Schematic file (`.mts`) or through raw data supplied through Lua, +in the form of a table. This table specifies the following fields: + +* The `size` field is a 3D vector containing the dimensions of the provided schematic. (required) +* The `yslice_prob` field is a table of {ypos, prob} which sets the `ypos`th vertical slice + of the schematic to have a `prob / 256 * 100` chance of occuring. (default: 255) +* The `data` field is a flat table of MapNode tables making up the schematic, + in the order of `[z [y [x]]]`. (required) + Each MapNode table contains: + * `name`: the name of the map node to place (required) + * `prob` (alias `param1`): the probability of this node being placed (default: 255) + * `param2`: the raw param2 value of the node being placed onto the map (default: 0) + * `force_place`: boolean representing if the node should forcibly overwrite any + previous contents (default: false) + +About probability values: + +* A probability value of `0` or `1` means that node will never appear (0% chance). +* A probability value of `254` or `255` means the node will always appear (100% chance). +* If the probability value `p` is greater than `1`, then there is a + `(p / 256 * 100)` percent chance that node will appear when the schematic is + placed on the map. + + +Schematic attributes +-------------------- +See section "Flag Specifier Format". + +Currently supported flags: `place_center_x`, `place_center_y`, `place_center_z`, + `force_placement`. + +* `place_center_x`: Placement of this decoration is centered along the X axis. +* `place_center_y`: Placement of this decoration is centered along the Y axis. +* `place_center_z`: Placement of this decoration is centered along the Z axis. +* `force_placement`: Schematic nodes other than "ignore" will replace existing nodes. + + +HUD element types +----------------- +The position field is used for all element types. + +To account for differing resolutions, the position coordinates are the percentage +of the screen, ranging in value from `0` to `1`. + +The name field is not yet used, but should contain a description of what the +HUD element represents. The direction field is the direction in which something +is drawn. + +`0` draws from left to right, `1` draws from right to left, `2` draws from +top to bottom, and `3` draws from bottom to top. + +The `alignment` field specifies how the item will be aligned. It ranges from `-1` to `1`, +with `0` being the center, `-1` is moved to the left/up, and `1` is to the right/down. +Fractional values can be used. + +The `offset` field specifies a pixel offset from the position. Contrary to position, +the offset is not scaled to screen size. This allows for some precisely-positioned +items in the HUD. + +**Note**: `offset` _will_ adapt to screen DPI as well as user defined scaling factor! + +Below are the specific uses for fields in each type; fields not listed for that type are ignored. + +**Note**: Future revisions to the HUD API may be incompatible; the HUD API is still +in the experimental stages. + +### `image` +Displays an image on the HUD. + +* `scale`: The scale of the image, with 1 being the original texture size. + Only the X coordinate scale is used (positive values). + Negative values represent that percentage of the screen it + should take; e.g. `x=-100` means 100% (width). +* `text`: The name of the texture that is displayed. +* `alignment`: The alignment of the image. +* `offset`: offset in pixels from position. + +### `text` +Displays text on the HUD. + +* `scale`: Defines the bounding rectangle of the text. + A value such as `{x=100, y=100}` should work. +* `text`: The text to be displayed in the HUD element. +* `number`: An integer containing the RGB value of the color used to draw the text. + Specify `0xFFFFFF` for white text, `0xFF0000` for red, and so on. +* `alignment`: The alignment of the text. +* `offset`: offset in pixels from position. + +### `statbar` +Displays a horizontal bar made up of half-images. + +* `text`: The name of the texture that is used. +* `number`: The number of half-textures that are displayed. + If odd, will end with a vertically center-split texture. +* `direction` +* `offset`: offset in pixels from position. +* `size`: If used, will force full-image size to this value (override texture pack image size) + +### `inventory` +* `text`: The name of the inventory list to be displayed. +* `number`: Number of items in the inventory to be displayed. +* `item`: Position of item that is selected. +* `direction` +* `offset`: offset in pixels from position. + +### `waypoint` +Displays distance to selected world position. + +* `name`: The name of the waypoint. +* `text`: Distance suffix. Can be blank. +* `number:` An integer containing the RGB value of the color used to draw the text. +* `world_pos`: World position of the waypoint. + +Representations of simple things +-------------------------------- + +### Position/vector + + {x=num, y=num, z=num} + +For helper functions see "Vector helpers". + +### `pointed_thing` +* `{type="nothing"}` +* `{type="node", under=pos, above=pos}` +* `{type="object", ref=ObjectRef}` + +Flag Specifier Format +--------------------- +Flags using the standardized flag specifier format can be specified in either of +two ways, by string or table. + +The string format is a comma-delimited set of flag names; whitespace and +unrecognized flag fields are ignored. Specifying a flag in the string sets the +flag, and specifying a flag prefixed by the string `"no"` explicitly +clears the flag from whatever the default may be. + +In addition to the standard string flag format, the schematic flags field can +also be a table of flag names to boolean values representing whether or not the +flag is set. Additionally, if a field with the flag name prefixed with `"no"` +is present, mapped to a boolean of any value, the specified flag is unset. + +E.g. A flag field of value + + {place_center_x = true, place_center_y=false, place_center_z=true} + +is equivalent to + + {place_center_x = true, noplace_center_y=true, place_center_z=true} + +which is equivalent to + + "place_center_x, noplace_center_y, place_center_z" + +or even + + "place_center_x, place_center_z" + +since, by default, no schematic attributes are set. + +Items +----- + +### Item types +There are three kinds of items: nodes, tools and craftitems. + +* Node (`register_node`): A node from the world. +* Tool (`register_tool`): A tool/weapon that can dig and damage + things according to `tool_capabilities`. +* Craftitem (`register_craftitem`): A miscellaneous item. + +### Amount and wear +All item stacks have an amount between 0 to 65535. It is 1 by +default. Tool item stacks can not have an amount greater than 1. + +Tools use a wear (=damage) value ranging from 0 to 65535. The +value 0 is the default and used is for unworn tools. The values +1 to 65535 are used for worn tools, where a higher value stands for +a higher wear. Non-tools always have a wear value of 0. + +### Item formats +Items and item stacks can exist in three formats: Serializes, table format +and `ItemStack`. + +#### Serialized +This is called "stackstring" or "itemstring". It is a simple string with +1-3 components: the full item identifier, an optional amount and an optional +wear value. Syntax: + + [[ ]] + +Examples: + +* `'default:apple'`: 1 apple +* `'default:dirt 5'`: 5 dirt +* `'default:pick_stone'`: a new stone pickaxe +* `'default:pick_wood 1 21323'`: a wooden pickaxe, ca. 1/3 worn out + +#### Table format +Examples: + +5 dirt nodes: + + {name="default:dirt", count=5, wear=0, metadata=""} + +A wooden pick about 1/3 worn out: + + {name="default:pick_wood", count=1, wear=21323, metadata=""} + +An apple: + + {name="default:apple", count=1, wear=0, metadata=""} + +#### `ItemStack` +A native C++ format with many helper methods. Useful for converting +between formats. See the Class reference section for details. + +When an item must be passed to a function, it can usually be in any of +these formats. + + +## Groups +------ +In a number of places, there is a group table. Groups define the +properties of a thing (item, node, armor of entity, capabilities of +tool) in such a way that the engine and other mods can can interact with +the thing without actually knowing what the thing is. + +### Usage +Groups are stored in a table, having the group names with keys and the +group ratings as values. For example: + + groups = {crumbly=3, soil=1} + -- ^ Default dirt + + groups = {crumbly=2, soil=1, level=2, outerspace=1} + -- ^ A more special dirt-kind of thing + +Groups always have a rating associated with them. If there is no +useful meaning for a rating for an enabled group, it shall be `1`. + +When not defined, the rating of a group defaults to `0`. Thus when you +read groups, you must interpret `nil` and `0` as the same value, `0`. + +You can read the rating of a group for an item or a node by using + + minetest.get_item_group(itemname, groupname) + +### Groups of items +Groups of items can define what kind of an item it is (e.g. wool). + +### Groups of nodes +In addition to the general item things, groups are used to define whether +a node is destroyable and how long it takes to destroy by a tool. + +### Groups of entities +For entities, groups are, as of now, used only for calculating damage. +The rating is the percentage of damage caused by tools with this damage group. +See "Entity damage mechanism". + + object.get_armor_groups() --> a group-rating table (e.g. {fleshy=100}) + object.set_armor_groups({fleshy=30, cracky=80}) + +### Groups of tools +Groups in tools define which groups of nodes and entities they are +effective towards. + +### Groups in crafting recipes +An example: Make meat soup from any meat, any water and any bowl: + + { + output = 'food:meat_soup_raw', + recipe = { + {'group:meat'}, + {'group:water'}, + {'group:bowl'}, + }, + -- preserve = {'group:bowl'}, -- Not implemented yet (TODO) + } + +Another example: Make red wool from white wool and red dye: + + { + type = 'shapeless', + output = 'wool:red', + recipe = {'wool:white', 'group:dye,basecolor_red'}, + } + +### Special groups +* `immortal`: Disables the group damage system for an entity +* `punch_operable`: For entities; disables the regular damage mechanism for + players punching it by hand or a non-tool item, so that it can do something + else than take damage. +* `level`: Can be used to give an additional sense of progression in the game. + * A larger level will cause e.g. a weapon of a lower level make much less + damage, and get worn out much faster, or not be able to get drops + from destroyed nodes. + * `0` is something that is directly accessible at the start of gameplay + * There is no upper limit +* `dig_immediate`: (player can always pick up node without reducing tool wear) + * `2`: the node always gets the digging time 0.5 seconds (rail, sign) + * `3`: the node always gets the digging time 0 seconds (torch) +* `disable_jump`: Player (and possibly other things) cannot jump from node +* `fall_damage_add_percent`: damage speed = `speed * (1 + value/100)` +* `bouncy`: value is bounce speed in percent +* `falling_node`: if there is no walkable block under the node it will fall +* `attached_node`: if the node under it is not a walkable block the node will be + dropped as an item. If the node is wallmounted the wallmounted direction is + checked. +* `soil`: saplings will grow on nodes in this group +* `connect_to_raillike`: makes nodes of raillike drawtype with same group value + connect to each other + +### Known damage and digging time defining groups +* `crumbly`: dirt, sand +* `cracky`: tough but crackable stuff like stone. +* `snappy`: something that can be cut using fine tools; e.g. leaves, small + plants, wire, sheets of metal +* `choppy`: something that can be cut using force; e.g. trees, wooden planks +* `fleshy`: Living things like animals and the player. This could imply + some blood effects when hitting. +* `explody`: Especially prone to explosions +* `oddly_breakable_by_hand`: + Can be added to nodes that shouldn't logically be breakable by the + hand but are. Somewhat similar to `dig_immediate`, but times are more + like `{[1]=3.50,[2]=2.00,[3]=0.70}` and this does not override the + speed of a tool if the tool can dig at a faster speed than this + suggests for the hand. + +### Examples of custom groups +Item groups are often used for defining, well, _groups of items_. + +* `meat`: any meat-kind of a thing (rating might define the size or healing + ability or be irrelevant -- it is not defined as of yet) +* `eatable`: anything that can be eaten. Rating might define HP gain in half + hearts. +* `flammable`: can be set on fire. Rating might define the intensity of the + fire, affecting e.g. the speed of the spreading of an open fire. +* `wool`: any wool (any origin, any color) +* `metal`: any metal +* `weapon`: any weapon +* `heavy`: anything considerably heavy + +### Digging time calculation specifics +Groups such as `crumbly`, `cracky` and `snappy` are used for this +purpose. Rating is `1`, `2` or `3`. A higher rating for such a group implies +faster digging time. + +The `level` group is used to limit the toughness of nodes a tool can dig +and to scale the digging times / damage to a greater extent. + +**Please do understand this**, otherwise you cannot use the system to it's +full potential. + +Tools define their properties by a list of parameters for groups. They +cannot dig other groups; thus it is important to use a standard bunch of +groups to enable interaction with tools. + +#### Tools definition +Tools define: + +* Full punch interval +* Maximum drop level +* For an arbitrary list of groups: + * Uses (until the tool breaks) + * Maximum level (usually `0`, `1`, `2` or `3`) + * Digging times + * Damage groups + +#### Full punch interval +When used as a weapon, the tool will do full damage if this time is spent +between punches. If e.g. half the time is spent, the tool will do half +damage. + +#### Maximum drop level +Suggests the maximum level of node, when dug with the tool, that will drop +it's useful item. (e.g. iron ore to drop a lump of iron). + +This is not automated; it is the responsibility of the node definition +to implement this. + +#### Uses +Determines how many uses the tool has when it is used for digging a node, +of this group, of the maximum level. For lower leveled nodes, the use count +is multiplied by `3^leveldiff`. + +* `uses=10, leveldiff=0`: actual uses: 10 +* `uses=10, leveldiff=1`: actual uses: 30 +* `uses=10, leveldiff=2`: actual uses: 90 + +#### Maximum level +Tells what is the maximum level of a node of this group that the tool will +be able to dig. + +#### Digging times +List of digging times for different ratings of the group, for nodes of the +maximum level. + +For example, as a Lua table, `times={2=2.00, 3=0.70}`. This would +result in the tool to be able to dig nodes that have a rating of `2` or `3` +for this group, and unable to dig the rating `1`, which is the toughest. +Unless there is a matching group that enables digging otherwise. + +If the result digging time is 0, a delay of 0.15 seconds is added between +digging nodes; If the player releases LMB after digging, this delay is set to 0, +i.e. players can more quickly click the nodes away instead of holding LMB. + +#### Damage groups +List of damage for groups of entities. See "Entity damage mechanism". + +#### Example definition of the capabilities of a tool + + tool_capabilities = { + full_punch_interval=1.5, + max_drop_level=1, + groupcaps={ + crumbly={maxlevel=2, uses=20, times={[1]=1.60, [2]=1.20, [3]=0.80}} + } + damage_groups = {fleshy=2}, + } + +This makes the tool be able to dig nodes that fulfil both of these: + +* Have the `crumbly` group +* Have a `level` group less or equal to `2` + +Table of resulting digging times: + + crumbly 0 1 2 3 4 <- level + -> 0 - - - - - + 1 0.80 1.60 1.60 - - + 2 0.60 1.20 1.20 - - + 3 0.40 0.80 0.80 - - + + level diff: 2 1 0 -1 -2 + +Table of resulting tool uses: + + -> 0 - - - - - + 1 180 60 20 - - + 2 180 60 20 - - + 3 180 60 20 - - + +**Notes**: + +* At `crumbly==0`, the node is not diggable. +* At `crumbly==3`, the level difference digging time divider kicks in and makes + easy nodes to be quickly breakable. +* At `level > 2`, the node is not diggable, because it's `level > maxlevel` + +Entity damage mechanism +----------------------- +Damage calculation: + + damage = 0 + foreach group in cap.damage_groups: + damage += cap.damage_groups[group] * limit(actual_interval / + cap.full_punch_interval, 0.0, 1.0) + * (object.armor_groups[group] / 100.0) + -- Where object.armor_groups[group] is 0 for inexistent values + return damage + +Client predicts damage based on damage groups. Because of this, it is able to +give an immediate response when an entity is damaged or dies; the response is +pre-defined somehow (e.g. by defining a sprite animation) (not implemented; +TODO). +Currently a smoke puff will appear when an entity dies. + +The group `immortal` completely disables normal damage. + +Entities can define a special armor group, which is `punch_operable`. This +group disables the regular damage mechanism for players punching it by hand or +a non-tool item, so that it can do something else than take damage. + +On the Lua side, every punch calls: + + entity:on_punch(puncher, time_from_last_punch, tool_capabilities, direction, damage) + +This should never be called directly, because damage is usually not handled by +the entity itself. + +* `puncher` is the object performing the punch. Can be `nil`. Should never be + accessed unless absolutely required, to encourage interoperability. +* `time_from_last_punch` is time from last punch (by `puncher`) or `nil`. +* `tool_capabilities` can be `nil`. +* `direction` is a unit vector, pointing from the source of the punch to + the punched object. +* `damage` damage that will be done to entity +Return value of this function will determin if damage is done by this function +(retval true) or shall be done by engine (retval false) + +To punch an entity/object in Lua, call: + + object:punch(puncher, time_from_last_punch, tool_capabilities, direction) + +* Return value is tool wear. +* Parameters are equal to the above callback. +* If `direction` equals `nil` and `puncher` does not equal `nil`, + `direction` will be automatically filled in based on the location of `puncher`. + +## Node Metadata +------------- +The instance of a node in the world normally only contains the three values +mentioned in "Nodes". However, it is possible to insert extra data into a +node. It is called "node metadata"; See `NodeMetaRef`. + +Node metadata contains two things: + +* A key-value store +* An inventory + +Some of the values in the key-value store are handled specially: + +* `formspec`: Defines a right-click inventory menu. See "Formspec". +* `infotext`: Text shown on the screen when the node is pointed at + +Example stuff: + + local meta = minetest.get_meta(pos) + meta:set_string("formspec", + "size[8,9]".. + "list[context;main;0,0;8,4;]".. + "list[current_player;main;0,5;8,4;]") + meta:set_string("infotext", "Chest"); + local inv = meta:get_inventory() + inv:set_size("main", 8*4) + print(dump(meta:to_table())) + meta:from_table({ + inventory = { + main = {[1] = "default:dirt", [2] = "", [3] = "", [4] = "", + [5] = "", [6] = "", [7] = "", [8] = "", [9] = "", + [10] = "", [11] = "", [12] = "", [13] = "", + [14] = "default:cobble", [15] = "", [16] = "", [17] = "", + [18] = "", [19] = "", [20] = "default:cobble", [21] = "", + [22] = "", [23] = "", [24] = "", [25] = "", [26] = "", + [27] = "", [28] = "", [29] = "", [30] = "", [31] = "", + [32] = ""} + }, + fields = { + formspec = "size[8,9]list[context;main;0,0;8,4;]list[current_player;main;0,5;8,4;]", + infotext = "Chest" + } + }) + +Item Metadata +------------- +Item stacks can store metadata too. See `ItemStackMetaRef`. + +Item metadata only contains a key-value store. + +Some of the values in the key-value store are handled specially: + +* `description`: Set the item stack's description. Defaults to `idef.description` +* `color`: A `ColorString`, which sets the stack's color. +* `palette_index`: If the item has a palette, this is used to get the + current color from the palette. + +Example stuff: + + local meta = stack:get_meta() + meta:set_string("key", "value") + print(dump(meta:to_table())) + +Formspec +-------- +Formspec defines a menu. Currently not much else than inventories are +supported. It is a string, with a somewhat strange format. + +Spaces and newlines can be inserted between the blocks, as is used in the +examples. + +### Examples + +#### Chest + + size[8,9] + list[context;main;0,0;8,4;] + list[current_player;main;0,5;8,4;] + +#### Furnace + + size[8,9] + list[context;fuel;2,3;1,1;] + list[context;src;2,1;1,1;] + list[context;dst;5,1;2,2;] + list[current_player;main;0,5;8,4;] + +#### Minecraft-like player inventory + + size[8,7.5] + image[1,0.6;1,2;player.png] + list[current_player;main;0,3.5;8,4;] + list[current_player;craft;3,0;3,3;] + list[current_player;craftpreview;7,1;1,1;] + +### Elements + +#### `size[,,]` +* Define the size of the menu in inventory slots +* `fixed_size`: `true`/`false` (optional) +* deprecated: `invsize[,;]` + +#### `position[,]` +* Define the position of the formspec +* A value between 0.0 and 1.0 represents a position inside the screen +* The default value is the center of the screen (0.5, 0.5) + +#### `anchor[,]` +* Define the anchor of the formspec +* A value between 0.0 and 1.0 represents an anchor inside the formspec +* The default value is the center of the formspec (0.5, 0.5) + +#### `container[,]` +* Start of a container block, moves all physical elements in the container by (X, Y) +* Must have matching `container_end` +* Containers can be nested, in which case the offsets are added + (child containers are relative to parent containers) + +#### `container_end[]` +* End of a container, following elements are no longer relative to this container + +#### `list[;;,;,;]` +* Show an inventory list + +#### `list[;;,;,;]` +* Show an inventory list + +#### `listring[;]` +* Allows to create a ring of inventory lists +* Shift-clicking on items in one element of the ring + will send them to the next inventory list inside the ring +* The first occurrence of an element inside the ring will + determine the inventory where items will be sent to + +#### `listring[]` +* Shorthand for doing `listring[;]` + for the last two inventory lists added by list[...] + +#### `listcolors[;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering + +#### `listcolors[;;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering +* Sets color of slots border + +#### `listcolors[;;;;]` +* Sets background color of slots as `ColorString` +* Sets background color of slots on mouse hovering +* Sets color of slots border +* Sets default background color of tooltips +* Sets default font color of tooltips + +#### `tooltip[;;;]` +* Adds tooltip for an element +* `` tooltip background color as `ColorString` (optional) +* `` tooltip font color as `ColorString` (optional) + +#### `image[,;,;]` +* Show an image +* Position and size units are inventory slots + +#### `item_image[,;,;]` +* Show an inventory image of registered item/node +* Position and size units are inventory slots + +#### `bgcolor[;]` +* Sets background color of formspec as `ColorString` +* If `true`, the background color is drawn fullscreen (does not effect the size of the formspec) + +#### `background[,;,;]` +* Use a background. Inventory rectangles are not drawn then. +* Position and size units are inventory slots +* Example for formspec 8x4 in 16x resolution: image shall be sized + 8 times 16px times 4 times 16px. + +#### `background[,;,;;]` +* Use a background. Inventory rectangles are not drawn then. +* Position and size units are inventory slots +* Example for formspec 8x4 in 16x resolution: + image shall be sized 8 times 16px times 4 times 16px +* If `true` the background is clipped to formspec size + (`x` and `y` are used as offset values, `w` and `h` are ignored) + +#### `pwdfield[,;,;;