Charakterbewegungen hinzugefügt, Deko hinzugefügt, Kochrezepte angepasst

This commit is contained in:
N-Nachtigal 2025-05-14 16:36:42 +02:00
parent 95945c0306
commit a0c893ca0b
1124 changed files with 64294 additions and 763 deletions

3385
mods/xdecor/src/chess.lua Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,200 @@
local chessbot = {}
local realchess = xdecor.chess
-- Delay in seconds for a bot moving a piece (excluding choosing a promotion)
local BOT_DELAY_MOVE = 1.0
-- Delay in seconds for a bot promoting a piece
local BOT_DELAY_PROMOTE = 1.0
local function best_move(moves)
local value, choices = 0, {}
for from, _ in pairs(moves) do
for to, val in pairs(_) do
if val > value then
value = val
choices = {{
from = from,
to = to
}}
elseif val == value then
choices[#choices + 1] = {
from = from,
to = to
}
end
end
end
if #choices == 0 then
return nil
end
local random = math.random(1, #choices)
local choice_from, choice_to = choices[random].from, choices[random].to
return tonumber(choice_from), choice_to
end
function chessbot.choose_move(board_t, meta_t)
local lastMove = meta_t["lastMove"]
local gameResult = meta_t["gameResult"]
local botColor = meta_t["botColor"]
local prevDoublePawnStepTo = meta_t["prevDoublePawnStepTo"]
local castlingRights = {
castlingWhiteR = meta_t["castlingWhiteR"],
castlingWhiteL = meta_t["castlingWhiteL"],
castlingBlackR = meta_t["castlingBlackR"],
castlingBlackL = meta_t["castlingBlackL"],
}
if botColor == "" then
return
end
local currentBotColor, opponentColor
if botColor == "black" then
currentBotColor = "black"
opponentColor = "white"
elseif botColor == "white" then
currentBotColor = "white"
opponentColor = "black"
elseif botColor == "both" then
opponentColor = lastMove
if lastMove == "black" or lastMove == "" then
currentBotColor = "white"
else
currentBotColor = "black"
end
end
if (lastMove == opponentColor or ((botColor == "white" or botColor == "both") and lastMove == "")) and gameResult == "" then
local moves = realchess.get_theoretical_moves_for(board_t, currentBotColor, prevDoublePawnStepTo, castlingRights)
local safe_moves, safe_moves_count = realchess.get_king_safe_moves(moves, board_t, currentBotColor)
if safe_moves_count == 0 then
-- No safe move: stalemate or checkmate
end
local choice_from, choice_to = best_move(safe_moves)
if choice_from == nil then
-- No best move: stalemate or checkmate
return
end
return choice_from, choice_to
else
minetest.log("error", "[xdecor] Chess: chessbot.choose_move was apparently called in an invalid game state!")
return
end
end
chessbot.perform_move = function(choice_from, choice_to, meta)
local lastMove = meta:get_string("lastMove")
local botColor = meta:get_string("botColor")
local currentBotColor, opponentColor
local botName
if botColor == "black" then
currentBotColor = "black"
opponentColor = "white"
elseif botColor == "white" then
currentBotColor = "white"
opponentColor = "black"
elseif botColor == "both" then
opponentColor = lastMove
if lastMove == "black" or lastMove == "" then
currentBotColor = "white"
else
currentBotColor = "black"
end
end
-- Bot resigns if no move chosen
if not choice_from or not choice_to then
realchess.resign(meta, currentBotColor)
return
end
if currentBotColor == "white" then
botName = meta:get_string("playerWhite")
else
botName = meta:get_string("playerBlack")
end
local gameResult = meta:get_string("gameResult")
if gameResult ~= "" then
return
end
local botColor = meta:get_string("botColor")
if botColor == "" then
minetest.log("error", "[xdecor] Chess: chessbot.perform_move: botColor in meta string was empty!")
return
end
local lastMove = meta:get_string("lastMove")
local lastMoveTime = meta:get_int("lastMoveTime")
if lastMoveTime > 0 or lastMove == "" then
-- Set the bot name if not set already
if currentBotColor == "black" and meta:get_string("playerBlack") == "" then
meta:set_string("playerBlack", botName)
elseif currentBotColor == "white" and meta:get_string("playerWhite") == "" then
meta:set_string("playerWhite", botName)
end
-- Make a move
local moveOK = realchess.move(meta, "board", choice_from, "board", choice_to, botName)
if not moveOK then
minetest.log("error", "[xdecor] Chess: Bot tried to make an invalid move from "..
realchess.index_to_notation(choice_from).." to "..realchess.index_to_notation(choice_to))
end
-- Bot resigns if it tried to make an invalid move
if not moveOK then
realchess.resign(meta, currentBotColor)
end
else
minetest.log("error", "[xdecor] Chess: chessbot.perform_move: No last move!")
end
end
function chessbot.choose_promote(board_t, pawnIndex)
-- Bot always promotes to queen
return "queen"
end
function chessbot.perform_promote(meta, promoteTo)
minetest.after(BOT_DELAY_PROMOTE, function()
local lastMove = meta:get_string("lastMove")
local color
if lastMove == "black" or lastMove == "" then
color = "white"
else
color = "black"
end
realchess.promote_pawn(meta, color, promoteTo)
end)
end
function chessbot.move(inv, meta)
local board_t = realchess.board_to_table(inv)
local meta_t = {
lastMove = meta:get_string("lastMove"),
gameResult = meta:get_string("gameResult"),
botColor = meta:get_string("botColor"),
prevDoublePawnStepTo = meta:get_int("prevDoublePawnStepTo"),
castlingWhiteL = meta:get_int("castlingWhiteL"),
castlingWhiteR = meta:get_int("castlingWhiteR"),
castlingBlackL = meta:get_int("castlingBlackL"),
castlingBlackR = meta:get_int("castlingBlackR"),
}
local choice_from, choice_to = chessbot.choose_move(board_t, meta_t)
minetest.after(BOT_DELAY_MOVE, function()
chessbot.perform_move(choice_from, choice_to, meta)
end)
end
function chessbot.promote(inv, meta, pawnIndex)
local board_t = realchess.board_to_table(inv)
local promoteTo = chessbot.choose_promote(board_t, pawnIndex)
if not promoteTo then
promoteTo = "queen"
end
chessbot.perform_promote(meta, promoteTo)
end
return chessbot

517
mods/xdecor/src/cooking.lua Normal file
View file

@ -0,0 +1,517 @@
local cauldron, sounds = {}, {}
local S = minetest.get_translator("xdecor")
-- Set to true to print soup ingredients and fire nodes to console
local DEBUG_RECOGNIZED_ITEMS = false
--~ cauldron hint
local hint_fire = S("Light a fire below to heat it up")
--~ cauldron hint
local hint_eat = S("Use a bowl to eat the soup")
--~ cauldron hint
local hint_recipe = S("Drop foods inside to make a soup")
local infotexts = {
["xdecor:cauldron_empty"] = S("Cauldron (empty)"),
["xdecor:cauldron_idle"] = S("Cauldron (cold water)").."\n"..hint_fire,
["xdecor:cauldron_idle_river_water"] = S("Cauldron (cold river water)").."\n"..hint_fire,
["xdecor:cauldron_idle_soup"] = S("Cauldron (cold soup)").."\n"..hint_eat,
["xdecor:cauldron_boiling"] = S("Cauldron (boiling water)").."\n"..hint_recipe,
["xdecor:cauldron_boiling_river_water"] = S("Cauldron (boiling river water)").."\n"..hint_recipe,
["xdecor:cauldron_soup"] = S("Cauldron (boiling soup)").."\n"..hint_eat,
}
local function set_infotext(meta, node)
if infotexts[node.name] then
meta:set_string("infotext", infotexts[node.name])
end
end
-- HACKY list of soup ingredients.
-- The cauldron will check if any of these strings are contained in the itemname
-- after the ":".
local ingredients_list = {
"apple", "mushroom", "honey", "pumpkin", "egg", "bread", "meat",
"chicken", "carrot", "potato", "melon", "rhubarb", "cucumber",
"corn", "beans", "berries", "grapes", "tomato", "wheat"
}
-- List of items that can never be soup ingredients. Overwrites anything else.
local non_ingredients = {
-- xdecor
"xdecor:bowl_soup",
-- Minetest Game: default
"default:apple_mark", "default:blueberry_bush_leaves_with_berries",
-- Minetest Game: farming
"farming:seed_wheat",
"farming:wheat_1", "farming:wheat_2", "farming:wheat_3", "farming:wheat_4",
"farming:wheat_5", "farming:wheat_6", "farming:wheat_7", "farming:wheat_8",
}
local non_ingredients_keyed = table.key_value_swap(non_ingredients)
cauldron.cbox = {
{0, 0, 0, 16, 16, 0},
{0, 0, 16, 16, 16, 0},
{0, 0, 0, 0, 16, 16},
{16, 0, 0, 0, 16, 16},
{0, 0, 0, 16, 8, 16}
}
-- Returns true is given item is a fire
local function is_fire(itemstring)
return minetest.get_item_group(itemstring, "fire") ~= 0
end
-- Returns true if the node at pos is above fire
local function is_heated(pos)
local below_node = {x = pos.x, y = pos.y - 1, z = pos.z}
local nn = minetest.get_node(below_node).name
-- Check fire group
if is_fire(nn) then
return true
else
return false
end
end
function cauldron.stop_sound(pos)
local spos = minetest.hash_node_position(pos)
if sounds[spos] then
minetest.sound_stop(sounds[spos])
sounds[spos] = nil
end
end
function cauldron.start_sound(pos)
local spos = minetest.hash_node_position(pos)
-- Stop sound if one already exists.
-- Only 1 sound per position at maximum allowed.
if sounds[spos] then
cauldron.stop_sound(pos)
end
sounds[spos] = minetest.sound_play("xdecor_boiling_water", {
pos = pos,
max_hear_distance = 5,
gain = 0.8,
loop = true
})
end
function cauldron.idle_construct(pos)
local timer = minetest.get_node_timer(pos)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
set_infotext(meta, node)
timer:start(10.0)
cauldron.stop_sound(pos)
end
function cauldron.boiling_construct(pos)
cauldron.start_sound(pos)
local meta = minetest.get_meta(pos)
local node = minetest.get_node(pos)
set_infotext(meta, node)
local timer = minetest.get_node_timer(pos)
timer:start(5.0)
end
function cauldron.filling(pos, node, clicker, itemstack)
local inv = clicker:get_inventory()
local wield_item = clicker:get_wielded_item():get_name()
do
if wield_item == "bucket:bucket_empty" and node.name:sub(-6) ~= "_empty" then
local bucket_item
if node.name:sub(-11) == "river_water" then
bucket_item = "bucket:bucket_river_water 1"
else
bucket_item = "bucket:bucket_water 1"
end
if itemstack:get_count() > 1 then
if inv:room_for_item("main", bucket_item) then
itemstack:take_item()
inv:add_item("main", bucket_item)
else
minetest.chat_send_player(clicker:get_player_name(),
S("No room in your inventory to add a bucket of water."))
return itemstack
end
else
itemstack:replace(bucket_item)
end
minetest.set_node(pos, {name = "xdecor:cauldron_empty", param2 = node.param2})
elseif minetest.get_item_group(wield_item, "water_bucket") == 1 and node.name:sub(-6) == "_empty" then
local newnode
if wield_item == "bucket:bucket_river_water" then
newnode = "xdecor:cauldron_idle_river_water"
else
newnode = "xdecor:cauldron_idle"
end
minetest.set_node(pos, {name = newnode, param2 = node.param2})
itemstack:replace("bucket:bucket_empty")
end
return itemstack
end
end
function cauldron.idle_timer(pos)
if not is_heated(pos) then
return true
end
local node = minetest.get_node(pos)
if node.name:sub(-4) == "soup" then
node.name = "xdecor:cauldron_soup"
elseif node.name:sub(-11) == "river_water" then
node.name = "xdecor:cauldron_boiling_river_water"
else
node.name = "xdecor:cauldron_boiling"
end
minetest.set_node(pos, node)
return true
end
-- Ugly hack to determine if an item has the function `minetest.item_eat` in its definition.
local function eatable(itemstring)
local item = itemstring:match("[%w_:]+")
local on_use_def = minetest.registered_items[item].on_use
if not on_use_def then return end
return string.format("%q", string.dump(on_use_def)):find("item_eat")
end
-- Checks if the given item can be used as ingredient for the soup
local function is_ingredient(itemstring)
if non_ingredients_keyed[itemstring] then
return false
end
local basename = itemstring:match(":([%w_]+)")
if not basename then
return false
end
for _, ingredient in ipairs(ingredients_list) do
if eatable(itemstring) or basename:find(ingredient) then
return true
end
end
return false
end
function cauldron.boiling_timer(pos)
-- Cool down cauldron if there is no fire
local node = minetest.get_node(pos)
if not is_heated(pos) then
local newnode
if node.name:sub(-4) == "soup" then
newnode = "xdecor:cauldron_idle_soup"
elseif node.name:sub(-11) == "river_water" then
newnode = "xdecor:cauldron_idle_river_water"
else
newnode = "xdecor:cauldron_idle"
end
minetest.set_node(pos, {name = newnode, param2 = node.param2})
return true
end
if node.name:sub(-4) == "soup" then
return true
end
-- Cooking:
-- Count the ingredients in the cauldron
local objs = minetest.get_objects_inside_radius(pos, 0.5)
if not next(objs) then
return true
end
local ingredients = {}
for _, obj in pairs(objs) do
if obj and not obj:is_player() and obj:get_luaentity().itemstring then
local itemstring = obj:get_luaentity().itemstring
local item = ItemStack(itemstring)
local itemname = item:get_name()
if is_ingredient(itemname) then
local basename = itemstring:match(":([%w_]+)")
table.insert(ingredients, basename)
end
end
end
-- Remove ingredients and turn liquid into soup
if #ingredients >= 2 then
for _, obj in pairs(objs) do
obj:remove()
end
minetest.set_node(pos, {name = "xdecor:cauldron_soup", param2 = node.param2})
end
return true
end
function cauldron.take_soup(pos, node, clicker, itemstack)
local inv = clicker:get_inventory()
local wield_item = clicker:get_wielded_item()
local item_name = wield_item:get_name()
if item_name == "xdecor:bowl" or item_name == "farming:bowl" then
if wield_item:get_count() > 1 then
if inv:room_for_item("main", "xdecor:bowl_soup 1") then
itemstack:take_item()
inv:add_item("main", "xdecor:bowl_soup 1")
else
minetest.chat_send_player(clicker:get_player_name(),
S("No room in your inventory to add a bowl of soup."))
return itemstack
end
else
itemstack:replace("xdecor:bowl_soup 1")
end
minetest.set_node(pos, {name = "xdecor:cauldron_empty", param2 = node.param2})
end
return itemstack
end
xdecor.register("cauldron_empty", {
description = S("Cauldron"),
_tt_help = S("For storing water and cooking soup"),
groups = {cracky=2, oddly_breakable_by_hand=1,cauldron=1},
is_ground_content = false,
on_rotate = screwdriver.rotate_simple,
tiles = {"xdecor_cauldron_top_empty.png", "xdecor_cauldron_bottom.png", "xdecor_cauldron_sides.png"},
sounds = default.node_sound_metal_defaults(),
collision_box = xdecor.pixelbox(16, cauldron.cbox),
on_rightclick = cauldron.filling,
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local node = minetest.get_node(pos)
set_infotext(meta, node)
cauldron.stop_sound(pos)
end,
})
xdecor.register("cauldron_idle", {
description = S("Cauldron with Water (cold)"),
groups = {cracky=2, oddly_breakable_by_hand=1, not_in_creative_inventory=1,cauldron=2},
is_ground_content = false,
on_rotate = screwdriver.rotate_simple,
tiles = {"xdecor_cauldron_top_idle.png", "xdecor_cauldron_bottom.png", "xdecor_cauldron_sides.png"},
sounds = default.node_sound_metal_defaults(),
drop = "xdecor:cauldron_empty",
collision_box = xdecor.pixelbox(16, cauldron.cbox),
on_rightclick = cauldron.filling,
on_construct = cauldron.idle_construct,
on_timer = cauldron.idle_timer,
})
xdecor.register("cauldron_idle_river_water", {
description = S("Cauldron with River Water (cold)"),
groups = {cracky=2, oddly_breakable_by_hand=1, not_in_creative_inventory=1,cauldron=2},
is_ground_content = false,
on_rotate = screwdriver.rotate_simple,
tiles = {"xdecor_cauldron_top_idle_river_water.png", "xdecor_cauldron_bottom.png", "xdecor_cauldron_sides.png"},
sounds = default.node_sound_metal_defaults(),
drop = "xdecor:cauldron_empty",
collision_box = xdecor.pixelbox(16, cauldron.cbox),
on_rightclick = cauldron.filling,
on_construct = cauldron.idle_construct,
on_timer = cauldron.idle_timer,
})
xdecor.register("cauldron_idle_soup", {
description = S("Cauldron with Soup (cold)"),
groups = {cracky = 2, oddly_breakable_by_hand = 1, not_in_creative_inventory = 1,cauldron=2},
is_ground_content = false,
on_rotate = screwdriver.rotate_simple,
drop = "xdecor:cauldron_empty",
tiles = {"xdecor_cauldron_top_idle_soup.png", "xdecor_cauldron_bottom.png", "xdecor_cauldron_sides.png"},
sounds = default.node_sound_metal_defaults(),
collision_box = xdecor.pixelbox(16, cauldron.cbox),
on_construct = function(pos)
local meta = minetest.get_meta(pos)
local node = minetest.get_node(pos)
set_infotext(meta, node)
local timer = minetest.get_node_timer(pos)
timer:start(10.0)
cauldron.stop_sound(pos)
end,
on_timer = cauldron.idle_timer,
on_rightclick = cauldron.take_soup,
})
xdecor.register("cauldron_boiling", {
description = S("Cauldron with Water (boiling)"),
groups = {cracky=2, oddly_breakable_by_hand=1, not_in_creative_inventory=1,cauldron=3},
is_ground_content = false,
on_rotate = screwdriver.rotate_simple,
drop = "xdecor:cauldron_empty",
damage_per_second = 2,
tiles = {
{
name = "xdecor_cauldron_top_anim_boiling_water.png",
animation = {type = "vertical_frames", length = 3.0}
},
"xdecor_cauldron_bottom.png",
"xdecor_cauldron_sides.png"
},
sounds = default.node_sound_metal_defaults(),
collision_box = xdecor.pixelbox(16, cauldron.cbox),
on_rightclick = cauldron.filling,
on_construct = cauldron.boiling_construct,
on_timer = cauldron.boiling_timer,
on_destruct = function(pos)
cauldron.stop_sound(pos)
end,
})
xdecor.register("cauldron_boiling_river_water", {
description = S("Cauldron with River Water (boiling)"),
groups = {cracky=2, oddly_breakable_by_hand=1, not_in_creative_inventory=1,cauldron=3},
is_ground_content = false,
on_rotate = screwdriver.rotate_simple,
drop = "xdecor:cauldron_empty",
damage_per_second = 2,
tiles = {
{
name = "xdecor_cauldron_top_anim_boiling_river_water.png",
animation = {type = "vertical_frames", length = 3.0}
},
"xdecor_cauldron_bottom.png",
"xdecor_cauldron_sides.png"
},
sounds = default.node_sound_metal_defaults(),
collision_box = xdecor.pixelbox(16, cauldron.cbox),
on_rightclick = cauldron.filling,
on_construct = cauldron.boiling_construct,
on_timer = cauldron.boiling_timer,
on_destruct = function(pos)
cauldron.stop_sound(pos)
end,
})
xdecor.register("cauldron_soup", {
description = S("Cauldron with Soup (boiling)"),
groups = {cracky = 2, oddly_breakable_by_hand = 1, not_in_creative_inventory = 1,cauldron=3},
is_ground_content = false,
on_rotate = screwdriver.rotate_simple,
drop = "xdecor:cauldron_empty",
damage_per_second = 2,
tiles = {
{
name = "xdecor_cauldron_top_anim_soup.png",
animation = {type = "vertical_frames", length = 3.0}
},
"xdecor_cauldron_bottom.png",
"xdecor_cauldron_sides.png"
},
sounds = default.node_sound_metal_defaults(),
collision_box = xdecor.pixelbox(16, cauldron.cbox),
on_construct = function(pos)
cauldron.start_sound(pos)
local meta = minetest.get_meta(pos)
local node = minetest.get_node(pos)
set_infotext(meta, node)
local timer = minetest.get_node_timer(pos)
timer:start(5.0)
end,
on_timer = cauldron.boiling_timer,
on_rightclick = cauldron.take_soup,
on_destruct = function(pos)
cauldron.stop_sound(pos)
end,
})
-- Craft items
minetest.register_craftitem("xdecor:bowl", {
description = S("Bowl"),
inventory_image = "xdecor_bowl.png",
wield_image = "xdecor_bowl.png",
groups = {food_bowl = 1, flammable = 2},
})
minetest.register_craftitem("xdecor:bowl_soup", {
description = S("Bowl of soup"),
inventory_image = "xdecor_bowl_soup.png",
wield_image = "xdecor_bowl_soup.png",
groups = {},
stack_max = 1,
on_use = minetest.item_eat(30, "xdecor:bowl")
})
-- Recipes
minetest.register_craft({
output = "xdecor:bowl 3",
recipe = {
{"group:wood", "", "group:wood"},
{"", "group:wood", ""}
}
})
minetest.register_craft({
output = "xdecor:cauldron_empty",
recipe = {
{"default:iron_lump", "", "default:iron_lump"},
{"default:iron_lump", "", "default:iron_lump"},
{"default:iron_lump", "default:iron_lump", "default:iron_lump"}
}
})
minetest.register_lbm({
label = "Restart boiling cauldron sounds",
name = "xdecor:restart_boiling_cauldron_sounds",
nodenames = {"xdecor:cauldron_boiling", "xdecor:cauldron_boiling_river_water", "xdecor:cauldron_soup"},
run_at_every_load = true,
action = function(pos, node)
cauldron.start_sound(pos)
end,
})
minetest.register_lbm({
label = "Update cauldron infotexts",
name = "xdecor:update_cauldron_infotexts",
nodenames = {"group:cauldron"},
run_at_every_load = false,
action = function(pos, node)
local meta = minetest.get_meta(pos)
set_infotext(meta, node)
end,
})
if DEBUG_RECOGNIZED_ITEMS then
-- Print all soup ingredients and fire nodes
-- in console
minetest.register_on_mods_loaded(function()
local ingredients = {}
local fires = {}
for k,v in pairs(minetest.registered_items) do
if is_ingredient(k) then
table.insert(ingredients, k)
end
if is_fire(k) then
table.insert(fires, k)
end
end
table.sort(ingredients)
table.sort(fires)
local str_i = table.concat(ingredients, ", ")
local str_f = table.concat(fires, ", ")
print("[xdecor] List of ingredients for soup: "..str_i)
print("[xdecor] List of nodes that can heat cauldron: "..str_f)
end)
end

View file

@ -0,0 +1,115 @@
-- Register enchanted tools.
local S = minetest.get_translator("xdecor")
-- Number of uses for the (normal) steel hoe from Minetest Game (as of 01/12/20224)
-- This is technically redundant because we cannot access that number
-- directly, but it's unlikely to change in future because Minetest Game is
-- unlikely to change.
local STEEL_HOE_USES = 500
-- Modifier of the steel hoe uses for the enchanted steel hoe
local STEEL_HOE_USES_MODIFIER = 2.2
-- Modifier of the bug net uses for the enchanted bug net
local BUG_NET_USES_MODIFIER = 4
-- Multiplies by much faster the fast hammer repairs
local HAMMER_FAST_MODIFIER = 1.3
-- Reduces the wear taken by the hammer for a single repair step
-- (absolute value)
local HAMMER_DURABLE_MODIFIER = 100
-- Register enchantments for default tools from Minetest Game
local materials = {"steel", "bronze", "mese", "diamond"}
local tooltypes = {
{ "axe", { "durable", "fast" }, "choppy" },
{ "pick", { "durable", "fast" }, "cracky" },
{ "shovel", { "durable", "fast" }, "crumbly" },
{ "sword", { "sharp" }, nil },
}
for t=1, #tooltypes do
for m=1, #materials do
local tooltype = tooltypes[t][1]
local enchants = tooltypes[t][2]
local dig_group = tooltypes[t][3]
local material = materials[m]
xdecor.register_enchantable_tool("default:"..tooltype.."_"..material, {
enchants = enchants,
dig_group = dig_group,
})
end
end
-- Register enchantment for bug net
xdecor.register_enchantable_tool("fireflies:bug_net", {
enchants = { "durable" },
dig_group = "catchable",
bonuses = {
uses = BUG_NET_USES_MODIFIER,
}
})
-- Register enchanted steel hoe (more durability)
if farming.register_hoe then
local percent = math.round((STEEL_HOE_USES_MODIFIER - 1) * 100)
local hitem = ItemStack("farming:hoe_steel")
local hdesc = hitem:get_short_description() or "farming:hoe_steel"
local ehdesc, ehsdesc = xdecor.enchant_description(hdesc, "durable", percent)
farming.register_hoe(":farming:enchanted_hoe_steel_durable", {
description = ehdesc,
short_description = ehsdesc,
inventory_image = xdecor.enchant_texture("farming_tool_steelhoe.png"),
max_uses = STEEL_HOE_USES * STEEL_HOE_USES_MODIFIER,
groups = {hoe = 1, not_in_creative_inventory = 1}
})
xdecor.register_custom_enchantable_tool("farming:hoe_steel", {
durable = "farming:enchanted_hoe_steel_durable",
})
end
-- Register enchanted hammer (more durbility and efficiency)
local hammerdef = minetest.registered_items["xdecor:hammer"]
if hammerdef then
local hitem = ItemStack("xdecor:hammer")
local hdesc = hitem:get_short_description() or "xdecor:hammer"
local repair = hammerdef._xdecor_hammer_repair
local repair_cost = hammerdef._xdecor_hammer_repair_cost
-- Durable hammer (reduces wear taken by each repair step)
local d_repair_cost_modified = repair_cost - HAMMER_DURABLE_MODIFIER
local d_percent = math.round(100 - d_repair_cost_modified/repair_cost * 100)
local d_ehdesc, d_ehsdesc = xdecor.enchant_description(hdesc, "durable", d_percent)
xdecor.register_hammer("xdecor:enchanted_hammer_durable", {
description = d_ehdesc,
short_description = d_ehsdesc,
image = xdecor.enchant_texture("xdecor_hammer.png"),
repair_cost = d_repair_cost_modified,
groups = {repair_hammer = 1, not_in_creative_inventory = 1}
})
-- Fast hammer (increases both repair amount and repair cost per
-- repair step by an equal amount)
local f_repair_modified = math.round(repair * HAMMER_FAST_MODIFIER)
local repair_diff = f_repair_modified - repair
local f_repair_cost_modified = repair_cost + repair_diff
local f_percent = math.round(HAMMER_FAST_MODIFIER * 100 - 100)
local f_ehdesc, f_ehsdesc = xdecor.enchant_description(hdesc, "fast", f_percent)
xdecor.register_hammer("xdecor:enchanted_hammer_fast", {
description = f_ehdesc,
short_description = f_ehsdesc,
image = xdecor.enchant_texture("xdecor_hammer.png"),
repair = f_repair_modified,
repair_cost = f_repair_cost_modified,
groups = {repair_hammer = 1, not_in_creative_inventory = 1}
})
xdecor.register_custom_enchantable_tool("xdecor:hammer", {
durable = "xdecor:enchanted_hammer_durable",
fast = "xdecor:enchanted_hammer_fast",
})
end

View file

@ -0,0 +1,506 @@
local enchanting = {}
screwdriver = screwdriver or {}
local S = minetest.get_translator("xdecor")
local NS = function(s) return s end
local FS = function(...) return minetest.formspec_escape(S(...)) end
local ceil, abs, random = math.ceil, math.abs, math.random
local reg_tools = minetest.registered_tools
local reg_enchantable_tools = {}
local available_tool_enchants = {}
-- Cost in Mese crystal(s) for enchanting.
local MESE_COST = 1
-- Default strenth of the enchantments
local DEFAULT_ENCHANTING_USES = 1.2 -- Durability
local DEFAULT_ENCHANTING_TIMES = 0.1 -- Efficiency
local DEFAULT_ENCHANTING_DAMAGES = 1 -- Sharpness
local function to_percent(orig_value, final_value)
return abs(ceil(((final_value - orig_value) / orig_value) * 100))
end
function enchanting:get_tooltip_raw(enchant, percent)
local specs = {
durable = "#00baff",
fast = "#74ff49",
sharp = "#ffff00",
}
local enchant_loc = {
--~ Enchantment
fast = S("Efficiency"),
--~ Enchantment
durable = S("Durability"),
--~ Enchantment
sharp = S("Sharpness"),
}
if minetest.colorize then
--~ Tooltip in format "<enchantment name> (+<bonus>%)", e.g. "Efficiency (+5%)"
return minetest.colorize(specs[enchant], S("@1 (+@2%)", enchant_loc[enchant], percent))
else
return S("@1 (+@2%)", enchant_loc[enchant], percent)
end
end
function enchanting:get_tooltip(enchant, orig_caps, fleshy, bonus_defs)
local bonus = {durable = 0, efficiency = 0, damages = 0}
if orig_caps then
bonus.durable = to_percent(orig_caps.uses, orig_caps.uses * bonus_defs.uses)
local sum_caps_times = 0
for i=1, #orig_caps.times do
sum_caps_times = sum_caps_times + orig_caps.times[i]
end
local average_caps_time = sum_caps_times / #orig_caps.times
bonus.efficiency = to_percent(average_caps_time, average_caps_time -
bonus_defs.times)
end
if fleshy then
bonus.damages = to_percent(fleshy, fleshy + bonus_defs.damages)
end
local specs = {
durable = bonus.durable,
fast = bonus.efficiency,
sharp = bonus.damages,
}
local percent = specs[enchant]
return enchanting:get_tooltip_raw(enchant, percent)
end
local enchant_buttons = {
fast = "image_button[3.6,0.67;4.75,0.85;bg_btn.png;fast;"..FS("Efficiency").."]",
durable = "image_button[3.6,1.65;4.75,1.05;bg_btn.png;durable;"..FS("Durability").."]",
sharp = "image_button[3.6,2.8;4.75,0.85;bg_btn.png;sharp;"..FS("Sharpness").."]",
}
function enchanting.formspec(pos, enchants)
local meta = minetest.get_meta(pos)
local formspec = [[
size[9,8.6;]
no_prepend[]
bgcolor[#080808BB;true]
listcolors[#00000069;#5A5A5A;#141318;#30434C;#FFF]
background9[0,0;9,9;ench_ui.png;6]
list[context;tool;0.9,2.9;1,1;]
list[context;mese;2,2.9;1,1;]
list[current_player;main;0.55,4.5;8,4;]
listring[current_player;main]
listring[context;tool]
listring[current_player;main]
listring[context;mese]
image[2,2.9;1,1;mese_layout.png]
]]
--~ Sharpness enchantment
.."tooltip[sharp;"..FS("Your weapon inflicts more damage").."]"
--~ Durability enchantment
.."tooltip[durable;"..FS("Your tool lasts longer").."]"
--~ Efficiency enchantment
.."tooltip[fast;"..FS("Your tool digs faster").."]"
..default.gui_slots .. default.get_hotbar_bg(0.55, 4.5)
if enchants then
for e=1, #enchants do
formspec = formspec .. enchant_buttons[enchants[e]]
end
end
meta:set_string("formspec", formspec)
end
function enchanting.on_put(pos, listname, _, stack)
if listname == "tool" then
local stackname = stack:get_name()
local enchants = available_tool_enchants[stackname]
if enchants then
enchanting.formspec(pos, enchants)
end
end
end
function enchanting.fields(pos, _, fields, sender)
if not next(fields) or fields.quit then return end
local inv = minetest.get_meta(pos):get_inventory()
local tool = inv:get_stack("tool", 1)
local mese = inv:get_stack("mese", 1)
local orig_wear = tool:get_wear()
local mod, name = tool:get_name():match("(.*):(.*)")
local enchanted_tool = (mod or "") .. ":enchanted_" .. (name or "") .. "_" .. next(fields)
if mese:get_count() >= MESE_COST and reg_tools[enchanted_tool] then
minetest.sound_play("xdecor_enchanting", {
to_player = sender:get_player_name(),
gain = 0.8
})
tool:replace(enchanted_tool)
tool:add_wear(orig_wear)
mese:take_item(MESE_COST)
inv:set_stack("mese", 1, mese)
inv:set_stack("tool", 1, tool)
end
end
function enchanting.dig(pos)
local inv = minetest.get_meta(pos):get_inventory()
return inv:is_empty("tool") and inv:is_empty("mese")
end
function enchanting.blast(pos)
local drops = xdecor.get_inventory_drops(pos, {"tool", "mese"})
minetest.remove_node(pos)
return drops
end
local function allowed(tool)
if not tool then
return false
end
if reg_enchantable_tools[tool] then
return true
else
return false
end
end
function enchanting.put(_, listname, _, stack)
local stackname = stack:get_name()
if listname == "mese" and (stackname == "default:mese_crystal" or
stackname == "imese:industrial_mese_crystal") then
return stack:get_count()
elseif listname == "tool" and allowed(stackname) then
return 1
end
return 0
end
function enchanting.on_take(pos, listname)
if listname == "tool" then
enchanting.formspec(pos)
end
end
function enchanting.construct(pos)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", S("Enchantment Table"))
enchanting.formspec(pos)
local inv = meta:get_inventory()
inv:set_size("tool", 1)
inv:set_size("mese", 1)
minetest.add_entity({x = pos.x, y = pos.y + 0.85, z = pos.z}, "xdecor:book_open")
local timer = minetest.get_node_timer(pos)
timer:start(0.5)
end
function enchanting.destruct(pos)
for _, obj in pairs(minetest.get_objects_inside_radius(pos, 0.9)) do
if obj and obj:get_luaentity() and
obj:get_luaentity().name == "xdecor:book_open" then
obj:remove()
break
end
end
end
function enchanting.timer(pos)
local minp = {x = pos.x - 2, y = pos.y, z = pos.z - 2}
local maxp = {x = pos.x + 2, y = pos.y + 1, z = pos.z + 2}
local bookshelves = minetest.find_nodes_in_area(minp, maxp, "default:bookshelf")
if #bookshelves == 0 then
return true
end
local bookshelf_pos = bookshelves[random(1, #bookshelves)]
local x = pos.x - bookshelf_pos.x
local y = bookshelf_pos.y - pos.y
local z = pos.z - bookshelf_pos.z
if tostring(x .. z):find(2) then
minetest.add_particle({
pos = bookshelf_pos,
velocity = {x = x, y = 2 - y, z = z},
acceleration = {x = 0, y = -2.2, z = 0},
expirationtime = 1,
size = 1.5,
glow = 5,
texture = "xdecor_glyph" .. random(1,18) .. ".png"
})
end
return true
end
xdecor.register("enchantment_table", {
description = S("Enchantment Table"),
_tt_help = S("Enchant your tools with mese crystals"),
tiles = {
"xdecor_enchantment_top.png", "xdecor_enchantment_bottom.png",
"xdecor_enchantment_side.png", "xdecor_enchantment_side.png",
"xdecor_enchantment_side.png", "xdecor_enchantment_side.png"
},
groups = {cracky = 1, level = 1},
is_ground_content = false,
light_source = 6,
sounds = default.node_sound_stone_defaults(),
on_rotate = screwdriver.rotate_simple,
can_dig = enchanting.dig,
on_blast = enchanting.blast,
on_timer = enchanting.timer,
on_construct = enchanting.construct,
on_destruct = enchanting.destruct,
on_receive_fields = enchanting.fields,
on_metadata_inventory_put = enchanting.on_put,
on_metadata_inventory_take = enchanting.on_take,
allow_metadata_inventory_put = enchanting.put,
allow_metadata_inventory_move = function()
return 0
end,
})
minetest.register_entity("xdecor:book_open", {
initial_properties = {
visual = "sprite",
visual_size = {x=0.75, y=0.75},
collisionbox = {0,0,0,0,0,0},
pointable = false,
physical = false,
textures = {"xdecor_book_open.png"},
static_save = false,
},
})
minetest.register_lbm({
label = "recreate book entity",
name = "xdecor:create_book_entity",
nodenames = {"xdecor:enchantment_table"},
run_at_every_load = true,
action = function(pos, node)
local objs = minetest.get_objects_inside_radius(pos, 0.9)
for _, obj in ipairs(objs) do
local e = obj:get_luaentity()
if e and e.name == "xdecor:book_open" then
return
end
end
minetest.add_entity({x = pos.x, y = pos.y + 0.85, z = pos.z}, "xdecor:book_open")
end,
})
function enchanting:enchant_texture(img)
if img == nil or img == "" or type(img) ~= "string" then
return "no_texture.png"
else
return "("..img.. ")^[colorize:violet:50"
end
end
function enchanting:register_tool(original_tool_name, def)
local original_tool = reg_tools[original_tool_name]
if not original_tool then
minetest.log("error", "[xdecor] Called enchanting:register_tool for non-existing tool: "..original_tool_name)
return
end
local original_toolcaps = original_tool.tool_capabilities
if not original_toolcaps then
minetest.log("error", "[xdecor] Called enchanting:register_tool for tool without tool_capabilities: "..original_tool_name)
return
end
local original_damage_groups = original_toolcaps.damage_groups
local original_groupcaps = original_toolcaps.groupcaps
local original_basename = original_tool_name:match(".*:(.*)")
local toolitem = ItemStack(original_tool_name)
local original_desc = toolitem:get_short_description() or original_tool_name
local groups
if def.groups then
groups = table.copy(def.groups)
elseif original_tool.groups then
groups = table.copy(original_tool.groups)
else
groups = {}
end
groups.not_in_creative_inventory = 1
for _, enchant in ipairs(def.enchants) do
local groupcaps = table.copy(original_groupcaps)
local full_punch_interval = original_toolcaps.full_punch_interval
local max_drop_level = original_toolcaps.max_drop_level
local dig_group = def.dig_group
local fleshy
if not def.bonuses then
def.bonuses = {}
end
local bonus_defs = {
uses = def.bonuses.uses or DEFAULT_ENCHANTING_USES,
times = def.bonuses.times or DEFAULT_ENCHANTING_TIMES,
damages = def.bonuses.damages or DEFAULT_ENCHANTING_DAMAGES,
}
if enchant == "durable" then
groupcaps[dig_group].uses = ceil(original_groupcaps[dig_group].uses *
bonus_defs.uses)
elseif enchant == "fast" then
for i, time in pairs(original_groupcaps[dig_group].times) do
groupcaps[dig_group].times[i] = time - bonus_defs.times
end
elseif enchant == "sharp" then
fleshy = original_damage_groups.fleshy
fleshy = fleshy + bonus_defs.damages
else
minetest.log("error", "[xdecor] Called enchanting:register_tool with unsupported enchant: "..tostring(enchant))
return
end
local arg1 = original_desc
local arg2 = self:get_tooltip(enchant, original_groupcaps[dig_group], fleshy, bonus_defs)
local enchantedTool = original_tool.mod_origin .. ":enchanted_" .. original_basename .. "_" .. enchant
local invimg = original_tool.inventory_image
invimg = enchanting:enchant_texture(invimg)
local wieldimg = original_tool.wield_image
if wieldimg == nil or wieldimg == "" then
wieldimg = invimg
end
minetest.register_tool(":" .. enchantedTool, {
--~ Enchanted tool description, e.g. "Enchanted Diamond Sword". @1 is the original tool name, @2 is the enchantment text, e.g. "Durability (+20%)"
description = S("Enchanted @1\n@2", arg1, arg2),
--~ Enchanted tool description, e.g. "Enchanted Diamond Sword"
short_description = S("Enchanted @1", arg1),
inventory_image = invimg,
wield_image = wieldimg,
groups = groups,
tool_capabilities = {
groupcaps = groupcaps, damage_groups = {fleshy = fleshy},
full_punch_interval = full_punch_interval,
max_drop_level = max_drop_level
},
pointabilities = original_tool.pointabilities,
})
if minetest.get_modpath("toolranks") then
toolranks.add_tool(enchantedTool)
end
end
available_tool_enchants[original_tool_name] = table.copy(def.enchants)
reg_enchantable_tools[original_tool_name] = true
end
function enchanting:register_custom_tool(original_tool_name, enchanted_tools)
if not available_tool_enchants[original_tool_name] then
available_tool_enchants[original_tool_name] = {}
end
for enchant, v in pairs(enchanted_tools) do
table.insert(available_tool_enchants[original_tool_name], enchant)
end
reg_enchantable_tools[original_tool_name] = true
end
-- Recipes
minetest.register_craft({
output = "xdecor:enchantment_table",
recipe = {
{"", "default:book", ""},
{"default:diamond", "default:obsidian", "default:diamond"},
{"default:obsidian", "default:obsidian", "default:obsidian"}
}
})
--[[ API FUNCTIONS ]]
--[[
Register one or more enchantments for an already defined tool.
This will register a new tool for each enchantment. The new tools will
have the following changes over the original:
* New description and short_description
* Apply a purple glow on wield_image and inventory_image using
"(<original_texture_string>)^[colorize:purple"
* Change tool_capabilities and damage_groups, depending on
enchantments.
* Have groups set to { not_in_creative_inventory = 1 }
The new tools will follow this naming scheme:
<original_mod>:enchanted_<original_basename>_<enchantment>
e.g. example:sword_diamond with the enchantment "sharp" will
have "example:enchanted_sword_diamond_sharp" added.
You must make sure this name is available before calling this
function.
Arguments:
* toolname: Itemstring of original tool to enchant
* def: Definition table with the following fields:
* enchants: a list of strings, one for each enchantment to add.
there must be at least one enchantment.
Available enchantments:
* "durable": Durability (tool lasts longer)
* "fast": Efficiency (tool digs faster)
* "sharp": Sharpness (more damage using the damage group "fleshy")
* dig_group: Must be specified if Durability or Efficiency is used.
This defines the tool's digging group that enchantment will improve.
* bonuses: optional table to customize the enchantment "strengths":
* uses: multiplies number of uses (Durability) (default: 1.2)
* times: subtracts from digging time; higher = faster (Efficiency) (default: 0.1)
* damages: adds to damage (Sharpness) (default: 1)
* groups: optional table specifying all item groups. If specified,
this should at least contain `not_in_creative_inventory=1`.
If unspecified (recommended), the enchanted tools will inherit all
groups from the original tool, plus they receive `not_in_creative_inventory=1`
]]
xdecor.register_enchantable_tool = function(toolname, def)
enchanting:register_tool(toolname, def)
end
--[[ Registers a custom tool enchantment.
Here, you are fully free to design the tool yourself.
The enchanted tools should follow these guidelines:
1) Use xdecor.enchant_description to generate the description and short_description
2) Use xdecor.enchant_texture to generate the inventory_image and wield_image
3) Set groups to { not_in_creative_inventory = 1 }
Arguments:
* toolname: Itemstring of original tool to enchant
* enchanted_tools: Table of enchanted tools.
* The keys are enchantment names from "enchants" in xdecor.register_enchantable_tool
* The values are the itemstrings of the enchanted tools for those
enchantments
]]
xdecor.register_custom_enchantable_tool = function(toolname, enchanted_tools)
enchanting:register_custom_tool(toolname, enchanted_tools)
end
-- Takes a texture (string) and applies an "enchanting" modifier on it.
-- Useful when you want to register custom tool enchantments.
xdecor.enchant_texture = function(texture)
return enchanting:enchant_texture(texture)
end
--[[
Takes a description of a normal tool and modifies it for the enchanted tool variant.
Arguments:
* description: Original description to modify
* enchant: Enchantment type. One of the enchantment names from "enchants" in xdecor.register_enchantable_tool
* percent: Percentage to display
Returns: <description>, <short_description>
-- Useful when you want to register custom tool enchantments.
]]
xdecor.enchant_description = function(description, enchant, percent)
local append = enchanting:get_tooltip_raw(enchant, percent)
local desc = S("Enchanted @1\n@2", description, append)
local short_desc S("Enchanted @1", description)
return desc, short_desc
end

204
mods/xdecor/src/hive.lua Normal file
View file

@ -0,0 +1,204 @@
local hive = {}
local S = minetest.get_translator("xdecor")
local FS = function(...) return minetest.formspec_escape(S(...)) end
local HONEY_MAX = 16
local NEEDED_FLOWERS = 3
local TIMER_MIN = 64
local TIMER_MAX = 128
local text_busy = FS("The bees are busy making honey.")
local text_noflowers = FS("The bees are looking for flowers.")
local text_fewflowers = FS("The bees want to pollinate more flowers.")
local text_idle = FS("The bees are idle.")
local text_sleep = FS("The bees are resting.")
function hive.set_formspec(meta, status)
local statustext
if status == "busy" then
statustext = text_busy
elseif status == "noflowers" then
statustext = text_noflowers
elseif status == "fewflowers" then
statustext = text_fewflowers
elseif status == "idle" then
statustext = text_idle
elseif status == "sleep" then
statustext = text_sleep
end
local formspec = "size[8,6;]"
.."label[0.5,0;"..statustext.."]"
..[[ image[6,1;1,1;hive_bee.png]
image[5,1;1,1;hive_layout.png]
list[context;honey;5,1;1,1;]
list[current_player;main;0,2.35;8,4;]
listring[current_player;main]
listring[context;honey] ]] ..
xdecor.xbg .. default.get_hotbar_bg(0,2.35)
meta:set_string("formspec", formspec)
end
local function count_flowers(pos)
local radius = 4
local minp = vector.add(pos, -radius)
local maxp = vector.add(pos, radius)
local flowers = minetest.find_nodes_in_area_under_air(minp, maxp, "group:flower")
return #flowers
end
local function is_sleeptime()
local time = (minetest.get_timeofday() or 0) * 24000
if time < 5500 or time > 18500 then
return true
else
return false
end
end
function hive.construct(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local status = "idle"
local flowers = count_flowers(pos)
if is_sleeptime() then
status = "sleep"
elseif flowers >= NEEDED_FLOWERS then
status = "busy"
elseif flowers > 0 then
status = "fewflowers"
else
status = "noflowers"
end
hive.set_formspec(meta, status)
meta:set_string("infotext", S("Artificial Hive"))
inv:set_size("honey", 1)
local timer = minetest.get_node_timer(pos)
timer:start(math.random(TIMER_MIN, TIMER_MAX))
end
function hive.timer(pos)
local meta = minetest.get_meta(pos)
if is_sleeptime() then
hive.set_formspec(meta, "sleep")
return true
end
local inv = minetest.get_meta(pos):get_inventory()
local honeystack = inv:get_stack("honey", 1)
local honey = honeystack:get_count()
local flowers = count_flowers(pos)
if flowers >= NEEDED_FLOWERS and honey < HONEY_MAX then
if honey == HONEY_MAX - 1 then
hive.set_formspec(meta, "idle")
else
hive.set_formspec(meta, "busy")
end
inv:add_item("honey", "xdecor:honey")
elseif honey == HONEY_MAX then
hive.set_formspec(meta, "idle")
local timer = minetest.get_node_timer(pos)
timer:stop()
return true
end
if flowers == 0 then
hive.set_formspec(meta, "noflowers")
elseif flowers < NEEDED_FLOWERS then
hive.set_formspec(meta, "fewflowers")
end
return true
end
function hive.blast(pos)
local drops = xdecor.get_inventory_drops(pos, {"honey"})
minetest.remove_node(pos)
return drops
end
xdecor.register("hive", {
description = S("Artificial Hive"),
--~ Tooltip of artificial hive
_tt_help = S("Bees live here and produce honey"),
tiles = {"xdecor_hive_top.png", "xdecor_hive_top.png",
"xdecor_hive_side.png", "xdecor_hive_side.png",
"xdecor_hive_side.png", "xdecor_hive_front.png"},
groups = {choppy=3, oddly_breakable_by_hand=2, flammable=1},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
on_construct = hive.construct,
on_timer = hive.timer,
on_blast = hive.blast,
can_dig = function(pos)
local inv = minetest.get_meta(pos):get_inventory()
return inv:is_empty("honey")
end,
on_punch = function(_, _, puncher)
puncher:set_hp(puncher:get_hp() - 2)
end,
allow_metadata_inventory_put = function()
return 0
end,
on_metadata_inventory_take = function(pos, list, index, stack)
local inv = minetest.get_inventory({type="node", pos=pos})
local remainstack = inv:get_stack(list, index)
-- Trigger if taking anything from full honey slot
if remainstack:get_count() + stack:get_count() >= HONEY_MAX then
local timer = minetest.get_node_timer(pos)
timer:start(math.random(TIMER_MIN, TIMER_MAX))
if not is_sleeptime() and count_flowers(pos) >= NEEDED_FLOWERS then
local meta = minetest.get_meta(pos)
hive.set_formspec(meta, "busy")
end
end
end
})
-- Craft items
minetest.register_craftitem("xdecor:honey", {
description = S("Honey"),
inventory_image = "xdecor_honey.png",
wield_image = "xdecor_honey.png",
on_use = minetest.item_eat(2),
groups = {
food_honey = 1,
food_sugar = 1,
flammable = 2,
},
})
-- Recipes
minetest.register_craft({
output = "xdecor:hive",
recipe = {
{"group:stick", "group:stick", "group:stick"},
{"default:paper", "default:paper", "default:paper"},
{"group:stick", "group:stick", "group:stick"}
}
})
if minetest.get_modpath("unified_inventory") then
unified_inventory.register_craft_type("xdecor:hive", {
description = S("Made by bees"),
icon = "hive_bee.png",
width = 1,
height = 1,
uses_crafting_grid = false
})
unified_inventory.register_craft({
output = "xdecor:honey",
type = "xdecor:hive",
items = {"xdecor:hive"},
width = 1
})
end

View file

@ -0,0 +1,249 @@
-- Item frames.
-- Hint:
-- If your item appears behind or too far in front of the item frame, add
-- _xdecor_itemframe_offset = <number>
-- to your item definition to fix it.
local itemframe, tmp = {}, {}
local S = minetest.get_translator("xdecor")
screwdriver = screwdriver or {}
local function remove_item(pos, node)
local objs = minetest.get_objects_inside_radius(pos, 0.5)
if not objs then return end
for _, obj in pairs(objs) do
local ent = obj:get_luaentity()
if obj and ent and ent.name == "xdecor:f_item" then
obj:remove() break
end
end
end
local facedir = {
[0] = {x = 0, y = 0, z = 1},
{x = 1, y = 0, z = 0},
{x = 0, y = 0, z = -1},
{x = -1, y = 0, z = 0}
}
local function update_item(pos, node)
remove_item(pos, node)
local meta = minetest.get_meta(pos)
local itemstring = meta:get_string("item")
local posad = facedir[node.param2]
if not posad or itemstring == "" then return end
local itemdef = ItemStack(itemstring):get_definition()
local offset_plus = 0
if itemdef and itemdef._xdecor_itemframe_offset then
offset_plus = itemdef._xdecor_itemframe_offset
offset_plus = math.min(6, math.max(-6, offset_plus))
end
local offset = (6.5+offset_plus)/16
pos = vector.add(pos, vector.multiply(posad, offset))
tmp.nodename = node.name
tmp.texture = ItemStack(itemstring):get_name()
local entity = minetest.add_entity(pos, "xdecor:f_item")
local yaw = (math.pi * 2) - node.param2 * (math.pi / 2)
entity:set_yaw(yaw)
local timer = minetest.get_node_timer(pos)
timer:start(15.0)
end
local function drop_item(pos, node)
local meta = minetest.get_meta(pos)
local item = meta:get_string("item")
if item == "" then return end
minetest.add_item(pos, item)
meta:set_string("item", "")
remove_item(pos, node)
local timer = minetest.get_node_timer(pos)
timer:stop()
end
function itemframe.set_infotext(meta)
local itemstring = meta:get_string("item")
local owner = meta:get_string("owner")
if itemstring == "" then
if owner ~= "" then
--~ Item frame infotext. @1 = item frame name, @2 = owner name (player)
meta:set_string("infotext", S("@1 (owned by @2)",
S("Item Frame"), owner))
else
meta:set_string("infotext", S("Item Frame"))
end
else
local itemstack = ItemStack(itemstring)
local tooltip = itemstack:get_short_description()
if tooltip == "" then
tooltip = itemstack:get_name()
end
if itemstring == "" then
tooltip = S("Item Frame")
end
if owner ~= "" then
meta:set_string("infotext", S("@1 (owned by @2)", tooltip, owner))
else
meta:set_string("infotext", tooltip)
end
end
end
function itemframe.after_place(pos, placer, itemstack)
local meta = minetest.get_meta(pos)
local name = placer:get_player_name()
meta:set_string("owner", name)
itemframe.set_infotext(meta)
end
function itemframe.timer(pos)
local node = minetest.get_node(pos)
local meta = minetest.get_meta(pos)
local num = #minetest.get_objects_inside_radius(pos, 0.5)
if num == 0 and meta:get_string("item") ~= "" then
update_item(pos, node)
end
return true
end
function itemframe.rightclick(pos, node, clicker, itemstack)
local meta = minetest.get_meta(pos)
local player_name = clicker:get_player_name()
local owner = meta:get_string("owner")
local admin = minetest.check_player_privs(player_name, "protection_bypass")
if not admin and (player_name ~= owner or not itemstack) then
return itemstack
end
drop_item(pos, node)
local itemstring = itemstack:take_item():to_string()
meta:set_string("item", itemstring)
itemframe.set_infotext(meta)
update_item(pos, node)
return itemstack
end
function itemframe.punch(pos, node, puncher)
local meta = minetest.get_meta(pos)
local player_name = puncher:get_player_name()
local owner = meta:get_string("owner")
local admin = minetest.check_player_privs(player_name, "protection_bypass")
if admin and player_name == owner then
drop_item(pos, node)
end
end
function itemframe.dig(pos, player)
if not player then return end
local meta = minetest.get_meta(pos)
local player_name = player and player:get_player_name()
local owner = meta:get_string("owner")
local admin = minetest.check_player_privs(player_name, "protection_bypass")
return admin or player_name == owner
end
function itemframe.blast(pos)
return
end
xdecor.register("itemframe", {
description = S("Item Frame"),
--~ Item frame tooltip
_tt_help = S("For presenting a single item"),
groups = {choppy = 3, oddly_breakable_by_hand = 2, flammable = 3},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
on_rotate = screwdriver.disallow,
sunlight_propagates = true,
inventory_image = "xdecor_itemframe.png",
node_box = xdecor.nodebox.slab_z(0.9375),
tiles = {
"xdecor_wood.png", "xdecor_wood.png", "xdecor_wood.png",
"xdecor_wood.png", "xdecor_wood.png", "xdecor_itemframe.png"
},
after_place_node = itemframe.after_place,
on_timer = itemframe.timer,
on_rightclick = itemframe.rightclick,
on_punch = itemframe.punch,
can_dig = itemframe.dig,
on_blast = itemframe.blast,
after_destruct = remove_item,
_xdecor_itemframe_offset = -3.5,
})
minetest.register_entity("xdecor:f_item", {
initial_properties = {
visual = "wielditem",
visual_size = {x = 0.33, y = 0.33},
collisionbox = {0,0,0,0,0,0},
pointable = false,
physical = false,
textures = {"air"},
},
on_activate = function(self, staticdata)
local pos = self.object:get_pos()
if minetest.get_node(pos).name ~= "xdecor:itemframe" then
self.object:remove()
end
if tmp.nodename and tmp.texture then
self.nodename = tmp.nodename
tmp.nodename = nil
self.texture = tmp.texture
tmp.texture = nil
elseif staticdata and staticdata ~= "" then
local data = staticdata:split(";")
if data and data[1] and data[2] then
self.nodename = data[1]
self.texture = data[2]
end
end
if self.texture then
self.object:set_properties({
textures = {self.texture}
})
end
end,
get_staticdata = function(self)
if self.nodename and self.texture then
return self.nodename .. ";" .. self.texture
end
return ""
end
})
-- Recipes
minetest.register_craft({
output = "xdecor:itemframe",
recipe = {
{"group:stick", "group:stick", "group:stick"},
{"group:stick", "default:paper", "group:stick"},
{"group:stick", "group:stick", "group:stick"}
}
})
minetest.register_lbm({
label = "Update itemframe infotexts",
name = "xdecor:update_itemframe_infotexts",
nodenames = {"xdecor:itemframe"},
run_at_every_load = true,
action = function(pos, node)
local meta = minetest.get_meta(pos)
itemframe.set_infotext(meta)
end,
})

211
mods/xdecor/src/mailbox.lua Normal file
View file

@ -0,0 +1,211 @@
local mailbox = {}
screwdriver = screwdriver or {}
local S = minetest.get_translator("xdecor")
local FS = function(...) return minetest.formspec_escape(S(...)) end
-- Max. length of the list of givers in mailbox formspec
local GIVER_LIST_LENGTH = 7
local function get_img(img)
if not img then return end
local img_name = img:match("(.*)%.png")
if img_name then
return img_name .. ".png"
end
end
local function img_col(stack)
local def = minetest.registered_items[stack]
if not def then
return ""
end
if def.inventory_image ~= "" then
local img = get_img(def.inventory_image)
if img then
return img
end
end
if def.tiles then
local tile, img = def.tiles[1]
if type(tile) == "table" then
img = get_img(tile.name)
elseif type(tile) == "string" then
img = get_img(tile)
end
if img then
return img
end
end
return ""
end
function mailbox:formspec(pos, owner, is_owner)
local spos = pos.x .. "," .. pos.y .. "," .. pos.z
local meta = minetest.get_meta(pos)
local giver, img = "", ""
if is_owner then
for i = 1, GIVER_LIST_LENGTH do
local giving = meta:get_string("giver" .. i)
if giving ~= "" then
local stack = meta:get_string("stack" .. i)
local giver_name = giving:sub(1,12)
local stack_name = stack:match("[%w_:]+")
local stack_count = stack:match("%s(%d+)") or 1
-- List of donors. A line looks like this:
-- <donor name> <item icon> × <item count>
giver = giver .. "#FFFF00," .. giver_name .. "," .. i ..
--~ Used in the mailbox donor list. Will be displayed as item icon followed by this string. @1 = item count
",#FFFFFF," .. FS("× @1", stack_count) .. ","
img = img .. i .. "=" ..
img_col(stack_name) .. "^\\[resize:16x16,"
end
end
return "size[9.5,9]"
.."label[0,0;"..FS("Mailbox").."]"
.."label[6,0;"..FS("Last donators").."]"
..[[ box[6,0.72;3.3,3.9;#555555]
listring[current_player;main]
list[current_player;main;0.75,5.25;8,4;]
tableoptions[background=#00000000;highlight=#00000000;border=false] ]] ..
"tablecolumns[color;text;image," .. img .. "0;color;text]" ..
"table[6,0.75;3.3,4;givers;" .. giver .. "]" ..
"list[nodemeta:" .. spos .. ";mailbox;0,0.75;6,4;]" ..
"listring[nodemeta:" .. spos .. ";mailbox]" ..
xdecor.xbg .. default.get_hotbar_bg(0.75, 5.25)
end
return "size[8,5]" ..
"list[current_player;main;0,1.25;8,4;]" ..
"label[0,0;"..FS("Send your goods to\n@1",
(minetest.colorize and
minetest.colorize("#FFFF00", owner) or owner)) .. "]" ..
"list[nodemeta:" .. spos .. ";drop;3.5,0;1,1;]" ..
"listring[]" ..
xdecor.xbg .. default.get_hotbar_bg(0, 1.25)
end
function mailbox.dig(pos, player)
local meta = minetest.get_meta(pos)
local owner = meta:get_string("owner")
local player_name = player and player:get_player_name()
local inv = meta:get_inventory()
return inv:is_empty("mailbox") and player_name == owner
end
function mailbox.blast(pos)
return
end
function mailbox.after_place_node(pos, placer)
local meta = minetest.get_meta(pos)
local player_name = placer:get_player_name()
meta:set_string("owner", player_name)
meta:set_string("infotext", S("@1's Mailbox", player_name))
local inv = meta:get_inventory()
inv:set_size("mailbox", 6 * 4)
inv:set_size("drop", 1)
end
function mailbox.rightclick(pos, node, clicker, itemstack, pointed_thing)
local meta = minetest.get_meta(pos)
local player = clicker:get_player_name()
local owner = meta:get_string("owner")
minetest.show_formspec(player, "xdecor:mailbox",
mailbox:formspec(pos, owner, (player == owner)))
return itemstack
end
function mailbox.put(pos, listname, _, stack, player)
if listname == "drop" then
local inv = minetest.get_meta(pos):get_inventory()
if inv:room_for_item("mailbox", stack) then
return -1
else
minetest.chat_send_player(player:get_player_name(),
S("The mailbox is full."))
end
end
return 0
end
function mailbox.on_put(pos, listname, _, stack, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
if listname == "drop" and inv:room_for_item("mailbox", stack) then
inv:set_list("drop", {})
inv:add_item("mailbox", stack)
for i = GIVER_LIST_LENGTH, 2, -1 do
meta:set_string("giver" .. i, meta:get_string("giver" .. (i - 1)))
meta:set_string("stack" .. i, meta:get_string("stack" .. (i - 1)))
end
meta:set_string("giver1", player:get_player_name())
meta:set_string("stack1", stack:to_string())
end
end
function mailbox.allow_take(pos, listname, index, stack, player)
if listname == "drop" then
return 0
end
local meta = minetest.get_meta(pos)
if player:get_player_name() ~= meta:get_string("owner") then
return 0
end
return stack:get_count()
end
function mailbox.allow_move(pos)
return 0
end
xdecor.register("mailbox", {
description = S("Mailbox"),
--~ Mailbox tooltip
_tt_help = S("Lets other players give you things"),
tiles = {"xdecor_mailbox_top.png", "xdecor_mailbox_bottom.png",
"xdecor_mailbox_side.png", "xdecor_mailbox_side.png",
"xdecor_mailbox.png", "xdecor_mailbox.png"},
groups = {cracky = 3, oddly_breakable_by_hand = 1},
is_ground_content = false,
sounds = default.node_sound_metal_defaults(),
on_rotate = screwdriver.rotate_simple,
can_dig = mailbox.dig,
on_blast = mailbox.blast,
on_rightclick = mailbox.rightclick,
allow_metadata_inventory_take = mailbox.allow_take,
allow_metadata_inventory_move = mailbox.allow_move,
on_metadata_inventory_put = mailbox.on_put,
allow_metadata_inventory_put = mailbox.put,
after_place_node = mailbox.after_place_node
})
-- Recipes
minetest.register_craft({
output = "xdecor:mailbox",
recipe = {
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
{"dye:red", "default:paper", "dye:red"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"}
}
})

View file

@ -0,0 +1,325 @@
-- Thanks to sofar for helping with that code.
local plate = {}
screwdriver = screwdriver or {}
local S = minetest.get_translator("xdecor")
local ALPHA_OPAQUE = minetest.features.use_texture_alpha_string_modes and "opaque" or false
-- Number of seconds an actuator (pressure plate, lever) stays active.
-- After this time, it will return to the disabled state again.
local DISABLE_ACTUATOR_AFTER = 2.0
-- Effect area of pressure plates and levers. Doors within this area
-- can be affected.
local PRESSURE_PLATE_AREA_MIN = {x = -2, y = 0, z = -2}
local PRESSURE_PLATE_AREA_MAX = {x = 2, y = 0, z = 2}
local LEVER_AREA_MIN = {x = -2, y = -1, z = -2}
local LEVER_AREA_MAX = {x = 2, y = 1, z = 2}
-- Pressure plates check for players within this radius
local PRESSURE_PLATE_PLAYER_RADIUS = 0.8
-- Interval in seconds that pressure plates check for players
local PRESSURE_PLATE_CHECK_TIMER = 0.1
local function door_open(pos_door, player)
local door = doors.get(pos_door)
if not door then
return
end
door:open(player)
end
local function door_close(pos_door, player)
local door = doors.get(pos_door)
if not door then
return
end
door:close(player)
end
-- Returns true if the door node at pos is currently next to any
-- active actuator node (lever, pressure plate)
local function door_is_actuatored(pos_door)
local minp = vector.add(LEVER_AREA_MIN, pos_door)
local maxp = vector.add(LEVER_AREA_MAX, pos_door)
local levers = minetest.find_nodes_in_area(minp, maxp, "group:lever")
for l=1, #levers do
local lnode = minetest.get_node(levers[l])
if minetest.get_item_group(lnode.name, "xdecor_actuator") == 2 then
return true
end
end
minp = vector.add(PRESSURE_PLATE_AREA_MIN, pos_door)
maxp = vector.add(PRESSURE_PLATE_AREA_MAX, pos_door)
local pressure_plates = minetest.find_nodes_in_area(minp, maxp, "group:pressure_plate")
for p=1, #pressure_plates do
local pnode = minetest.get_node(pressure_plates[p])
if minetest.get_item_group(pnode.name, "xdecor_actuator") == 2 then
return true
end
end
return false
end
local function actuator_timeout(pos_actuator, actuator_area_min, actuator_area_max)
local actuator = minetest.get_node(pos_actuator)
-- Get name of last player that triggered the actuator
local meta = minetest.get_meta(pos_actuator)
local last_triggerer_str = meta:get_string("last_triggerer")
local last_triggerer_obj = minetest.get_player_by_name(last_triggerer_str)
-- Turn off actuator
if minetest.get_item_group(actuator.name, "xdecor_actuator") == 2 then
local def = minetest.registered_nodes[actuator.name]
if def._xdecor_actuator_off then
minetest.set_node(pos_actuator, { name = def._xdecor_actuator_off, param2 = actuator.param2 })
end
end
-- Close neighboring doors that are no longer next to any active actuator
local minp = vector.add(actuator_area_min, pos_actuator)
local maxp = vector.add(actuator_area_max, pos_actuator)
local doors = minetest.find_nodes_in_area(minp, maxp, "group:door")
for d=1, #doors do
if not door_is_actuatored(doors[d]) then
local dnode = minetest.get_node(doors[d])
local ddef = minetest.registered_nodes[dnode.name]
if (ddef.protected and last_triggerer_obj) or (not ddef.protected) then
door_close(doors[d], last_triggerer_obj)
end
end
end
end
local function actuator_activate(pos_actuator, actuator_area_min, actuator_area_max, player)
local player_name = player:get_player_name()
local actuator = minetest.get_node(pos_actuator)
local ga = minetest.get_item_group(actuator.name, "xdecor_actuator")
if ga == 2 then
-- No-op if actuator is already active
return
elseif ga == 1 then
local def = minetest.registered_nodes[actuator.name]
-- Turn actuator on
if def._xdecor_actuator_on then
minetest.set_node(pos_actuator, { name = def._xdecor_actuator_on, param2 = actuator.param2 })
-- Store name of last player that triggered the actuator
local meta = minetest.get_meta(pos_actuator)
meta:set_string("last_triggerer", player_name)
end
end
-- Turn on neighboring doors
local minp = vector.add(actuator_area_min, pos_actuator)
local maxp = vector.add(actuator_area_max, pos_actuator)
local doors = minetest.find_nodes_in_area(minp, maxp, "group:door")
for i = 1, #doors do
door_open(doors[i], player)
end
end
function plate.construct(pos)
local timer = minetest.get_node_timer(pos)
timer:start(PRESSURE_PLATE_CHECK_TIMER)
end
function plate.has_player_standing_on(pos)
local objs = minetest.get_objects_inside_radius(pos, PRESSURE_PLATE_PLAYER_RADIUS)
for _, player in pairs(objs) do
if player:is_player() then
return true, player
end
end
return false
end
function plate.timer(pos)
if not doors.get then
return true
end
local ok, player = plate.has_player_standing_on(pos)
if ok then
actuator_activate(pos, PRESSURE_PLATE_AREA_MIN, PRESSURE_PLATE_AREA_MAX, player)
return false
end
return true
end
function plate.construct_on(pos)
local timer = minetest.get_node_timer(pos)
timer:start(DISABLE_ACTUATOR_AFTER)
end
function plate.timer_on(pos)
if plate.has_player_standing_on(pos) then
-- If player is still standing on active pressure plate, restart timer
local timer = minetest.get_node_timer(pos)
timer:start(DISABLE_ACTUATOR_AFTER)
return
end
actuator_timeout(pos, PRESSURE_PLATE_AREA_MIN, PRESSURE_PLATE_AREA_MAX)
end
function plate.register(material, desc, def)
local groups
if def.groups then
groups = table.copy(def.groups)
else
groups = {}
end
groups.pressure_plate = 1
groups.xdecor_actuator = 1
xdecor.register("pressure_" .. material .. "_off", {
description = def.description or (desc .. " Pressure Plate"),
--~ Pressure plate tooltip
_tt_help = S("Opens doors when stepped on"),
tiles = {"xdecor_pressure_" .. material .. ".png"},
use_texture_alpha = ALPHA_OPAQUE,
drawtype = "nodebox",
node_box = xdecor.pixelbox(16, {{1, 0, 1, 14, 1, 14}}),
groups = groups,
is_ground_content = false,
sounds = def.sounds,
sunlight_propagates = true,
on_rotate = screwdriver.rotate_simple,
on_construct = plate.construct,
on_timer = plate.timer,
_xdecor_actuator_off = "xdecor:pressure_"..material.."_off",
_xdecor_actuator_on = "xdecor:pressure_"..material.."_on",
})
local groups_on = table.copy(groups)
groups_on.xdecor_actuator = 2
groups_on.pressure_plate = 2
xdecor.register("pressure_" .. material .. "_on", {
tiles = {"xdecor_pressure_" .. material .. ".png"},
use_texture_alpha = ALPHA_OPAQUE,
drawtype = "nodebox",
node_box = xdecor.pixelbox(16, {{1, 0, 1, 14, 0.4, 14}}),
groups = groups_on,
is_ground_content = false,
sounds = def.sounds,
drop = "xdecor:pressure_" .. material .. "_off",
sunlight_propagates = true,
on_rotate = screwdriver.rotate_simple,
on_construct = plate.construct_on,
on_timer = plate.timer_on,
_xdecor_actuator_off = "xdecor:pressure_"..material.."_off",
_xdecor_actuator_on = "xdecor:pressure_"..material.."_on",
})
end
plate.register("wood", "Wooden", {
sounds = default.node_sound_wood_defaults(),
groups = {choppy = 3, oddly_breakable_by_hand = 2, flammable = 2},
description = S("Wooden Pressure Plate"),
})
plate.register("stone", "Stone", {
sounds = default.node_sound_stone_defaults(),
groups = {cracky = 3, oddly_breakable_by_hand = 2},
description = S("Stone Pressure Plate"),
})
xdecor.register("lever_off", {
description = S("Lever"),
--~ Lever tooltip
_tt_help = S("Opens doors when pulled"),
tiles = {"xdecor_lever_off.png"},
use_texture_alpha = ALPHA_OPAQUE,
drawtype = "nodebox",
node_box = xdecor.pixelbox(16, {{2, 1, 15, 12, 14, 1}}),
groups = {cracky = 3, oddly_breakable_by_hand = 2, lever = 1, xdecor_actuator = 1},
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
on_rotate = screwdriver.rotate_simple,
on_rightclick = function(pos, node, clicker, itemstack)
if not doors.get then
return itemstack
end
actuator_activate(pos, LEVER_AREA_MIN, LEVER_AREA_MAX, clicker)
return itemstack
end,
_xdecor_itemframe_offset = -3.5,
_xdecor_actuator_off = "xdecor:lever_off",
_xdecor_actuator_on = "xdecor:lever_on",
})
xdecor.register("lever_on", {
tiles = {"xdecor_lever_on.png"},
use_texture_alpha = ALPHA_OPAQUE,
drawtype = "nodebox",
node_box = xdecor.pixelbox(16, {{2, 1, 15, 12, 14, 1}}),
groups = {cracky = 3, oddly_breakable_by_hand = 2, lever = 2, xdecor_actuator = 2, not_in_creative_inventory = 1},
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
on_rotate = screwdriver.rotate_simple,
on_rightclick = function(pos, node, clicker, itemstack)
-- Prevent placing nodes on activated lever with the place key
-- for consistent behavior with the lever in "off" state.
-- The player may still place nodes using [Sneak].
return itemstack
end,
on_construct = function(pos)
local timer = minetest.get_node_timer(pos)
timer:start(DISABLE_ACTUATOR_AFTER)
end,
on_timer = function(pos)
local node = minetest.get_node(pos)
actuator_timeout(pos, LEVER_AREA_MIN, LEVER_AREA_MAX)
end,
drop = "xdecor:lever_off",
_xdecor_itemframe_offset = -3.5,
_xdecor_actuator_off = "xdecor:lever_off",
_xdecor_actuator_on = "xdecor:lever_on",
})
-- Make sure the node timers of active actuators are still
-- active when these nodes load again. If not, start them
-- again to trigger their timer action, which is expected
-- to turn off the actuator soon.
minetest.register_lbm({
label = "Restart actuator timers (X-Decor-libre)",
name = "xdecor:restart_actuator_timers",
nodenames = { "group:xdecor_actuator" },
run_at_every_load = true,
action = function(pos, node)
local g = minetest.get_item_group(node.name, "xdecor_actuator")
if g ~= 2 then
return
end
local timer = minetest.get_node_timer(pos)
if not timer:is_started() then
timer:start(DISABLE_ACTUATOR_AFTER)
end
end,
})
-- Recipes
minetest.register_craft({
output = "xdecor:pressure_stone_off",
type = "shapeless",
recipe = {"group:stone", "group:stone"}
})
minetest.register_craft({
output = "xdecor:pressure_wood_off",
type = "shapeless",
recipe = {"group:wood", "group:wood"}
})
minetest.register_craft({
output = "xdecor:lever_off",
recipe = {
{"group:stick"},
{"group:stone"}
}
})

962
mods/xdecor/src/nodes.lua Normal file
View file

@ -0,0 +1,962 @@
screwdriver = screwdriver or {}
local S = minetest.get_translator("xdecor")
local ALPHA_CLIP = minetest.features.use_texture_alpha_string_modes and "clip" or true
local ALPHA_OPAQUE = minetest.features.use_texture_alpha_string_modes and "opaque" or false
local function register_pane(name, desc, def)
xpanes.register_pane(name, {
description = desc,
tiles = {"xdecor_" .. name .. ".png"},
drawtype = "airlike",
paramtype = "light",
textures = def.textures or {"xdecor_" .. name .. ".png", "" ,"xdecor_" .. name .. ".png"},
inventory_image = "xdecor_" .. name .. ".png",
wield_image = "xdecor_" .. name .. ".png",
groups = def.groups,
sounds = def.sounds or default.node_sound_defaults(),
recipe = def.recipe
})
end
register_pane("bamboo_frame", S("Bamboo Frame"), {
groups = {choppy = 3, oddly_breakable_by_hand = 2, pane = 1, flammable = 2},
recipe = {
{"default:papyrus", "default:papyrus", "default:papyrus"},
{"default:papyrus", "farming:cotton", "default:papyrus"},
{"default:papyrus", "default:papyrus", "default:papyrus"}
},
sounds = default.node_sound_wood_defaults(),
})
register_pane("chainlink", S("Chainlink"), {
groups = {cracky = 3, oddly_breakable_by_hand = 2, pane = 1},
recipe = {
{"default:steel_ingot", "", "default:steel_ingot"},
{"", "default:steel_ingot", ""},
{"default:steel_ingot", "", "default:steel_ingot"}
}
})
register_pane("rusty_bar", S("Rusty Iron Bars"), {
sounds = default.node_sound_stone_defaults(),
textures = {"xdecor_rusty_bar.png", "", "xdecor_rusty_bar_top.png"},
groups = {cracky = 2, pane = 1},
recipe = {
{"", "default:dirt", ""},
{"default:iron_lump", "default:iron_lump", "default:iron_lump"},
{"default:iron_lump", "default:iron_lump", "default:iron_lump"},
},
sounds = default.node_sound_metal_defaults(),
})
register_pane("wood_frame", S("Wood Frame"), {
sounds = default.node_sound_wood_defaults(),
textures = {"xdecor_wood_frame.png", "", "xdecor_wood_frame_top.png"},
groups = {choppy = 2, pane = 1, flammable = 2},
recipe = {
{"group:wood", "group:stick", "group:wood"},
{"group:stick", "group:stick", "group:stick"},
{"group:wood", "group:stick", "group:wood"}
}
})
xdecor.register("baricade", {
description = S("Barricade"),
drawtype = "plantlike",
paramtype2 = "facedir",
inventory_image = "xdecor_baricade.png",
tiles = {"xdecor_baricade.png"},
groups = {choppy = 2, oddly_breakable_by_hand = 1, flammable = 2},
is_ground_content = false,
damage_per_second = 4,
selection_box = xdecor.nodebox.slab_y(0.3),
collision_box = xdecor.pixelbox(2, {{0, 0, 1, 2, 2, 0}})
})
xdecor.register("barrel", {
description = S("Barrel"),
tiles = {"xdecor_barrel_top.png", "xdecor_barrel_top.png", "xdecor_barrel_sides.png"},
on_place = minetest.rotate_node,
groups = {choppy = 2, oddly_breakable_by_hand = 1, flammable = 2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults()
})
local function blast_storage(pos)
local drops = xdecor.get_inventory_drops(pos, {"main"})
minetest.remove_node(pos)
return drops
end
local function register_storage(name, desc, def)
xdecor.register(name, {
description = desc,
_tt_help = def._tt_help,
inventory = {size = def.inv_size or 24},
infotext = desc,
tiles = def.tiles,
use_texture_alpha = ALPHA_OPAQUE,
node_box = def.node_box,
on_rotate = def.on_rotate,
on_place = def.on_place,
on_blast = blast_storage,
groups = def.groups or {choppy = 2, oddly_breakable_by_hand = 1, flammable = 2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults()
})
end
register_storage("cabinet", S("Wooden Cabinet"), {
_tt_help = S("24 inventory slots"),
on_rotate = screwdriver.rotate_simple,
tiles = {
"xdecor_cabinet_sides.png", "xdecor_cabinet_sides.png",
"xdecor_cabinet_sides.png", "xdecor_cabinet_sides.png",
"xdecor_cabinet_sides.png", "xdecor_cabinet_front.png"
}
})
register_storage("cabinet_half", S("Half Wooden Cabinet"), {
inv_size = 8,
_tt_help = S("8 inventory slots"),
node_box = xdecor.nodebox.slab_y(0.5, 0.5),
on_rotate = screwdriver.rotate_simple,
tiles = {
"xdecor_cabinet_sides.png", "xdecor_cabinet_sides.png",
"xdecor_half_cabinet_sides.png", "xdecor_half_cabinet_sides.png",
"xdecor_half_cabinet_sides.png", "xdecor_half_cabinet_front.png"
}
})
if minetest.get_modpath("moreblocks") then
minetest.register_alias("xdecor:empty_shelf", "moreblocks:empty_shelf")
else
-- Node renamed from "Empty Shelf" because it was misleading.
-- (you can still put things in it, making it non-empty)
register_storage("empty_shelf", S("Plain Shelf"), {
_tt_help = S("24 inventory slots"),
on_rotate = screwdriver.rotate_simple,
tiles = {
"default_wood.png", "default_wood.png", "default_wood.png",
"default_wood.png", "default_wood.png^xdecor_empty_shelf.png"
},
})
-- Update infotext of old empty_shelf nodes to "Plain Shelf"
minetest.register_lbm({
label = "Update plain shelf infotext",
name = "xdecor:empty_shelf_to_plain_shelf",
nodenames = {"xdecor:empty_shelf"},
run_at_every_load = false,
action = function(pos, node)
local meta = minetest.get_meta(pos)
meta:set_string("infotext", S("Plain Shelf"))
end,
})
end
register_storage("multishelf", S("Multi Shelf"), {
_tt_help = S("24 inventory slots"),
on_rotate = screwdriver.rotate_simple,
tiles = {
"default_wood.png", "default_wood.png", "default_wood.png",
"default_wood.png", "default_wood.png^xdecor_multishelf.png"
},
})
xdecor.register("candle", {
description = S("Candle"),
light_source = 12,
drawtype = "torchlike",
inventory_image = "xdecor_candle_inv.png",
wield_image = "xdecor_candle_wield.png",
paramtype2 = "wallmounted",
walkable = false,
groups = {dig_immediate = 3, attached_node = 1},
is_ground_content = false,
tiles = {
{
name = "xdecor_candle_floor.png",
animation = {type="vertical_frames", length = 1.5}
},
{
name = "xdecor_candle_hanging.png",
animation = {type="vertical_frames", length = 1.5}
},
{
name = "xdecor_candle_wall.png",
animation = {type="vertical_frames", length = 1.5}
}
},
selection_box = {
type = "wallmounted",
wall_top = {-0.25, -0.3, -0.25, 0.25, 0.5, 0.25},
wall_bottom = {-0.25, -0.5, -0.25, 0.25, 0.1, 0.25},
wall_side = {-0.5, -0.35, -0.15, -0.15, 0.4, 0.15}
}
})
xdecor.register("chair", {
description = S("Chair"),
tiles = {"xdecor_wood.png"},
sounds = default.node_sound_wood_defaults(),
groups = {choppy = 3, oddly_breakable_by_hand = 2, flammable = 2, sittable = 1},
is_ground_content = false,
on_rotate = screwdriver.rotate_simple,
node_box = xdecor.pixelbox(16, {
{3, 0, 11, 2, 16, 2},
{11, 0, 11, 2, 16, 2},
{5, 9, 11.5, 6, 6, 1},
{3, 0, 3, 2, 6, 2},
{11, 0, 3, 2, 6, 2},
{3, 6, 3, 10, 2, 8}
}),
can_dig = xdecor.sit_dig,
after_destruct = xdecor.sit_destruct,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
xdecor.sit(pos, node, clicker, pointed_thing)
return itemstack
end,
_xdecor_itemframe_offset = -1.5,
})
xdecor.register("cobweb", {
description = S("Cobweb"),
drawtype = "plantlike",
tiles = {"xdecor_cobweb.png"},
inventory_image = "xdecor_cobweb.png",
move_resistance = 8,
walkable = false,
selection_box = {type = "regular"},
groups = {snappy = 3, flammable = 3},
is_ground_content = false,
sounds = default.node_sound_leaves_defaults()
})
local curtain_colors = {
red = { S("Red Curtain"), "wool_red.png", "wool:red" },
}
local CURTAIN_OFFSET = 1/16
-- For preserve_metadata for curtains.
-- Erases metadata from the drops
-- because the item metadata should be empty
-- to allow proper item stacking.
local cleanup_curtain_meta = function(_,_,_,drops)
for d=1, #drops do
local meta = drops[d]:get_meta()
meta:set_string("palette_index", "")
end
end
for c, info in pairs(curtain_colors) do
local desc = info[1]
local base_texture = info[2]
local craft_item = info[3]
xdecor.register("curtain_" .. c, {
description = desc,
walkable = false,
tiles = {base_texture, "("..base_texture..")^[transformFY", base_texture},
use_texture_alpha = ALPHA_CLIP,
inventory_image = base_texture.."^xdecor_curtain_open_overlay.png^[makealpha:255,126,126",
wield_image = base_texture.."^xdecor_curtain_open_overlay.png^[makealpha:255,126,126",
drawtype = "nodebox",
paramtype2 = "wallmounted",
node_box = {
type = "wallmounted",
wall_side = { -0.5, -0.5, -0.5, -0.5+CURTAIN_OFFSET, 0.5, 0.5 },
wall_top = { -0.5, 0.5-CURTAIN_OFFSET, -0.5, 0.5, 0.5, 0.5 },
wall_bottom = { -0.5, -0.5, -0.5, 0.5, -0.5+CURTAIN_OFFSET, 0.5 },
},
groups = {dig_immediate = 3, flammable = 3},
is_ground_content = false,
on_rightclick = function(pos, node, _, itemstack)
minetest.set_node(pos, {name = "xdecor:curtain_open_" .. c, param2 = node.param2})
return itemstack
end,
preserve_metadata = cleanup_curtain_meta,
})
local open_tile = base_texture.."^xdecor_curtain_open_overlay.png^[makealpha:255,126,126"
xdecor.register("curtain_open_" .. c, {
tiles = {
open_tile,
"("..open_tile..")^[transformFY",
base_texture,
base_texture,
base_texture.."^xdecor_curtain_open_overlay_top.png^[makealpha:255,126,126",
base_texture.."^xdecor_curtain_open_overlay_bottom.png^[makealpha:255,126,126",
},
use_texture_alpha = ALPHA_CLIP,
drawtype = "nodebox",
paramtype2 = "wallmounted",
node_box = {
type = "wallmounted",
wall_side = { -0.5, -0.5, -0.5, -0.5+CURTAIN_OFFSET, 0.5, 0.5 },
wall_top = { -0.5, 0.5-CURTAIN_OFFSET, -0.5, 0.5, 0.5, 0.5 },
wall_bottom = { -0.5, -0.5, -0.5, 0.5, -0.5+CURTAIN_OFFSET, 0.5 },
},
walkable = false,
groups = {dig_immediate = 3, flammable = 3, not_in_creative_inventory = 1},
is_ground_content = false,
drop = "xdecor:curtain_" .. c,
on_rightclick = function(pos, node, _, itemstack)
minetest.set_node(pos, {name="xdecor:curtain_" .. c, param2 = node.param2})
return itemstack
end,
preserve_metadata = cleanup_curtain_meta,
})
minetest.register_craft({
output = "xdecor:curtain_" .. c .. " 4",
recipe = {
{"", craft_item, ""},
{"", craft_item, ""}
}
})
end
xdecor.register("cushion", {
description = S("Cushion"),
tiles = {"xdecor_cushion.png"},
groups = {snappy = 3, flammable = 3, fall_damage_add_percent = -50, sittable = 1},
is_ground_content = false,
on_place = minetest.rotate_node,
node_box = xdecor.nodebox.slab_y(0.5),
can_dig = xdecor.sit_dig,
after_destruct = xdecor.sit_destruct,
on_rightclick = function(pos, node, clicker, itemstack, pointed_thing)
xdecor.sit(pos, node, clicker, pointed_thing)
return itemstack
end
})
xdecor.register("cushion_block", {
description = S("Cushion Block"),
tiles = {"xdecor_cushion.png"},
groups = {snappy = 3, flammable = 3, fall_damage_add_percent = -75},
is_ground_content = false,
})
local function door_access(name)
return name:find("prison")
end
local xdecor_doors = {
japanese = {
recipe = {
{"group:wood", "default:paper"},
{"default:paper", "group:wood"},
{"group:wood", "default:paper"}
},
desc = S("Japanese Door"),
},
prison = {
recipe = {
{"xpanes:bar_flat", "xpanes:bar_flat",},
{"xpanes:bar_flat", "default:steel_ingot",},
{"xpanes:bar_flat", "xpanes:bar_flat"}
},
desc = S("Prison Door"),
sounds = default.node_sound_metal_defaults(),
sound_open = "xpanes_steel_bar_door_open",
sound_close = "xpanes_steel_bar_door_close",
gain_open = 0.18,
gain_close = 0.16,
},
rusty_prison = {
recipe = {
{"xpanes:rusty_bar_flat", "xpanes:rusty_bar_flat",},
{"xpanes:rusty_bar_flat", "xpanes:rusty_bar_flat",},
{"xpanes:rusty_bar_flat", "xpanes:rusty_bar_flat"}
},
desc = S("Rusty Prison Door"),
sounds = default.node_sound_metal_defaults(),
sound_open = "xpanes_steel_bar_door_open",
sound_close = "xpanes_steel_bar_door_close",
gain_open = 0.21,
gain_close = 0.19,
},
screen = {
recipe = {
{"group:wood", "group:wood"},
{"xpanes:chainlink_flat", "xpanes:chainlink_flat"},
{"group:wood", "group:wood"}
},
desc = S("Screen Door"),
},
slide = {
recipe = {
{"default:paper", "default:paper"},
{"default:paper", "default:paper"},
{"group:wood", "group:wood"}
},
desc = S("Paper Door"),
},
woodglass = {
recipe = {
{"default:glass", "default:glass"},
{"group:wood", "group:wood"},
{"group:wood", "group:wood"}
},
desc = S("Woodglass Door"),
},
}
local mesecons_register
if minetest.global_exists("mesecon") then
mesecons_register = { effector = {
action_on = function(pos, node)
local door = doors.get(pos)
if door then
door:open()
end
end,
action_off = function(pos, node)
local door = doors.get(pos)
if door then
door:close()
end
end,
rules = mesecon.rules.pplate
}}
end
for name, def in pairs(xdecor_doors) do
if not doors.register then break end
doors.register(name .. "_door", {
tiles = {
{name = "xdecor_" .. name .. "_door.png", backface_culling = true}
},
description = def.desc,
inventory_image = "xdecor_" .. name .. "_door_inv.png",
sounds = def.sounds,
sound_open = def.sound_open,
sound_close = def.sound_close,
gain_open = def.gain_open,
gain_close = def.gain_close,
protected = door_access(name),
groups = {choppy = 2, cracky = 2, oddly_breakable_by_hand = 1, door = 1, node = 1},
recipe = def.recipe,
mesecons = mesecons_register,
})
end
xdecor.register("enderchest", {
description = S("Ender Chest"),
_tt_help = S("Interdimensional inventory"),
tiles = {
"xdecor_enderchest_top.png", "xdecor_enderchest_top.png",
"xdecor_enderchest_side.png", "xdecor_enderchest_side.png",
"xdecor_enderchest_side.png", "xdecor_enderchest_front.png"
},
groups = {cracky = 1, choppy = 1},
is_ground_content = false,
sounds = default.node_sound_stone_defaults(),
on_rotate = screwdriver.rotate_simple,
on_construct = function(pos)
local meta = minetest.get_meta(pos)
meta:set_string("formspec", [[ size[8,9]
list[current_player;enderchest;0,0;8,4;]
list[current_player;main;0,5;8,4;]
listring[current_player;enderchest]
listring[current_player;main] ]]
.. xdecor.xbg .. default.get_hotbar_bg(0,5))
meta:set_string("infotext", S("Ender Chest"))
end
})
minetest.register_on_joinplayer(function(player)
local inv = player:get_inventory()
inv:set_size("enderchest", 8*4)
end)
xdecor.register("ivy", {
description = S("Ivy"),
drawtype = "signlike",
walkable = false,
climbable = true,
groups = {snappy = 3, attached_node = 1, plant = 1, flammable = 3},
paramtype2 = "wallmounted",
selection_box = {type="wallmounted"},
tiles = {"xdecor_ivy.png"},
inventory_image = "xdecor_ivy.png",
wield_image = "xdecor_ivy.png",
sounds = default.node_sound_leaves_defaults()
})
xdecor.register("rooster", {
description = S("Weathercock"),
drawtype = "torchlike",
inventory_image = "xdecor_rooster.png",
walkable = false,
groups = {snappy = 3, attached_node = 1},
is_ground_content = false,
tiles = {"xdecor_rooster.png"},
sounds = default.node_sound_metal_defaults(),
})
-- Lantern which attaches to the floor.
-- Has a hanging variant
xdecor.register("lantern", {
description = S("Lantern"),
light_source = 13,
drawtype = "plantlike",
inventory_image = "xdecor_lantern_inv.png",
wield_image = "xdecor_lantern_inv.png",
walkable = false,
groups = {snappy = 3, attached_node = 3},
is_ground_content = false,
tiles = {
{
name = "xdecor_lantern.png",
animation = {type="vertical_frames", length = 1.5}
}
},
selection_box = xdecor.pixelbox(16, {{4, 0, 4, 8, 16, 8}}),
sounds = default.node_sound_metal_defaults(),
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.type ~= "node" then
return itemstack
end
-- Use pointed node's on_rightclick function first, if present
if placer and not placer:get_player_control().sneak then
local node = minetest.get_node(pointed_thing.under)
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
end
end
-- Check protection
if minetest.is_protected(pointed_thing.above, placer:get_player_name()) and
not minetest.check_player_privs(placer, "protection_bypass") then
minetest.record_protection_violation(pointed_thing.above, placer:get_player_name())
return itemstack
end
-- Decide whether the lantern attaches the the floor
-- (default) or the ceiling.
local leftover, place_pos, nodename
local up = vector.new(pointed_thing.above.x, pointed_thing.above.y+1, pointed_thing.above.z)
local upnode = minetest.get_node(up)
local updef = minetest.registered_nodes[upnode.name]
local down = vector.new(pointed_thing.above.x, pointed_thing.above.y-1, pointed_thing.above.z)
local downnode = minetest.get_node(down)
local downdef = minetest.registered_nodes[downnode.name]
local sound_play = false
if pointed_thing.under.y > pointed_thing.above.y then
nodename = "xdecor:lantern_hanging"
if downdef and not downdef.walkable then
sound_play = true
end
elseif downdef and not downdef.walkable and updef and updef.walkable then
nodename = "xdecor:lantern_hanging"
sound_play = true
else
nodename = "xdecor:lantern"
end
leftover, place_pos = minetest.item_place_node(ItemStack(nodename), placer, pointed_thing)
if place_pos == nil then
return
end
if leftover:get_count() == 0 and
not minetest.is_creative_enabled(placer:get_player_name()) then
itemstack:take_item()
end
if sound_play then
minetest.sound_play(default.node_sound_metal_defaults().place, {pos=place_pos}, true)
end
return itemstack
end,
})
-- Same as lantern, but attaches to ceiling
xdecor.register("lantern_hanging", {
description = S("Hanging Lantern"),
light_source = 13,
drawtype = "plantlike",
inventory_image = "xdecor_lantern_inv.png^xdecor_lantern_hanging_overlay_inv.png",
wield_image = "xdecor_lantern_inv.png",
walkable = false,
groups = {snappy = 3, attached_node = 4, not_in_creative_inventory = 1},
is_ground_content = false,
tiles = {
{
name = "xdecor_lantern.png",
animation = {type="vertical_frames", length = 1.5}
}
},
selection_box = xdecor.pixelbox(16, {{4, 0, 4, 8, 16, 8}}),
sounds = default.node_sound_metal_defaults(),
drop = "xdecor:lantern",
})
-- Update legacy lantern (back when they were wallmounted)
-- that are hanging to the proper node.
minetest.register_lbm({
label = "Update hanging lanterns",
name = "xdecor:update_hanging_lanterns",
nodenames = {"xdecor:lantern"},
run_at_every_load = false,
action = function(pos, node)
if node.param2 == 0 then -- wallmounted 0 value means attached to the ceiling
-- Only convert the node if it needs to hang
-- (walkable node above, non-walkable node below)
local up = vector.new(pos.x, pos.y+1, pos.z)
local upnode = minetest.get_node(up)
local updef = minetest.registered_nodes[upnode.name]
local down = vector.new(pos.x, pos.y-1, pos.z)
local downnode = minetest.get_node(down)
local downdef = minetest.registered_nodes[downnode.name]
if updef and updef.walkable and downdef and not downdef.walkable then
minetest.swap_node(pos, {name="xdecor:lantern_hanging"})
end
end
end,
})
local xdecor_lightbox = {
iron = S("Steel Lattice Light Box"),
wooden = S("Wooden Cross Light Box"),
wooden2 = S("Wooden Rhombus Light Box"),
}
for l, desc in pairs(xdecor_lightbox) do
xdecor.register(l .. "_lightbox", {
description = desc,
tiles = {"xdecor_" .. l .. "_lightbox.png"},
groups = {cracky = 3, choppy = 3, oddly_breakable_by_hand = 2},
is_ground_content = false,
light_source = 13,
sounds = default.node_sound_glass_defaults()
})
end
local xdecor_potted = {
dandelion_white = S("Potted White Dandelion"),
dandelion_yellow = S("Potted Yellow Dandelion"),
geranium = S("Potted Geranium"),
rose = S("Potted Rose"),
tulip = S("Potted Tulip"),
viola = S("Potted Viola"),
}
for f, desc in pairs(xdecor_potted) do
xdecor.register("potted_" .. f, {
description = desc,
walkable = false,
groups = {snappy = 3, flammable = 3, plant = 1, flower = 1},
is_ground_content = false,
tiles = {"xdecor_" .. f .. "_pot.png"},
inventory_image = "xdecor_" .. f .. "_pot.png",
drawtype = "plantlike",
sounds = default.node_sound_leaves_defaults({
place = default.node_sound_stone_defaults().place,
dug = default.node_sound_stone_defaults().dug,
}),
selection_box = xdecor.nodebox.slab_y(0.3)
})
minetest.register_craft({
output = "xdecor:potted_" .. f,
recipe = {
{"default:clay_brick", "flowers:" .. f, "default:clay_brick"},
{"", "default:clay_brick", ""}
}
})
end
local painting_box = {
type = "wallmounted",
wall_top = {-0.4375, 0.4375, -0.3125, 0.4375, 0.5, 0.3125},
wall_bottom = {-0.4375, -0.5, -0.3125, 0.4375, -0.4375, 0.3125},
wall_side = {-0.5, -0.3125, -0.4375, -0.4375, 0.3125, 0.4375}
}
xdecor.register("painting_1", {
description = S("Painting"),
tiles = {"xdecor_painting_1.png","xdecor_painting_1.png^[transformR180","xdecor_painting_1.png"},
use_texture_alpha = ALPHA_OPAQUE,
inventory_image = "xdecor_painting_empty.png",
wield_image = "xdecor_painting_empty.png",
paramtype2 = "wallmounted",
wallmounted_rotate_vertical = true,
sunlight_propagates = true,
groups = {choppy = 3, oddly_breakable_by_hand = 2, flammable = 2, attached_node = 1},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
node_box = painting_box,
node_placement_prediction = "",
on_place = function(itemstack, placer, pointed_thing)
if pointed_thing.type ~= "node" then
return itemstack
end
-- Use pointed node's on_rightclick function first, if present
if placer and not placer:get_player_control().sneak then
local node = minetest.get_node(pointed_thing.under)
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
end
end
-- Check protection
if minetest.is_protected(pointed_thing.above, placer:get_player_name()) and
not minetest.check_player_privs(placer, "protection_bypass") then
minetest.record_protection_violation(pointed_thing.above, placer:get_player_name())
return itemstack
end
local num = math.random(4)
local leftover, place_pos = minetest.item_place_node(
ItemStack("xdecor:painting_" .. num), placer, pointed_thing)
if not place_pos then
return itemstack
end
if leftover:get_count() == 0 and
not minetest.is_creative_enabled(placer:get_player_name()) then
itemstack:take_item()
end
-- Play 'place' sound manually
minetest.sound_play(default.node_sound_wood_defaults().place, {pos=place_pos}, true)
return itemstack
end,
sounds = default.node_sound_wood_defaults(),
})
for i = 2, 4 do
xdecor.register("painting_" .. i, {
tiles = {"xdecor_painting_"..i..".png","xdecor_painting_"..i..".png^[transformR180","xdecor_painting_"..i..".png"},
use_texture_alpha = ALPHA_OPAQUE,
paramtype2 = "wallmounted",
wallmounted_rotate_vertical = true,
drop = "xdecor:painting_1",
sunlight_propagates = true,
groups = {
choppy = 3,
oddly_breakable_by_hand = 2,
flammable = 2,
attached_node = 1,
not_in_creative_inventory = 1
},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
node_box = painting_box
})
end
xdecor.register("stonepath", {
description = S("Garden Stone Path"),
tiles = {"default_stone.png"},
groups = {snappy = 3},
is_ground_content = false,
on_rotate = screwdriver.rotate_simple,
sounds = default.node_sound_stone_defaults(),
sunlight_propagates = true,
node_box = xdecor.pixelbox(16, {
{8, 0, 8, 6, .5, 6}, {1, 0, 1, 6, .5, 6},
{1, 0, 10, 5, .5, 5}, {10, 0, 2, 4, .5, 4}
}),
selection_box = xdecor.nodebox.slab_y(0.05)
})
local function register_hard_node(name, desc, def)
def = def or {}
xdecor.register(name, {
description = desc,
tiles = def.tiles or {"xdecor_" .. name .. ".png"},
groups = def.groups or {cracky = 1},
is_ground_content = false,
sounds = def.sounds or default.node_sound_stone_defaults()
})
end
register_hard_node("cactusbrick", S("Cactus Brick"))
register_hard_node("coalstone_tile", S("Coal Stone Tile"))
register_hard_node("desertstone_tile", S("Polished Desert Stone Block"))
register_hard_node("hard_clay", S("Hardened Clay"))
register_hard_node("moonbrick", S("Moon Brick"))
register_hard_node("stone_rune", S("Runestone"))
-- renamed from stone_tile to fix naming collision with moreblocks
-- mod for the registrations under the 'stairs:' namespace
register_hard_node("stone_tile_x", S("Polished Stone Block"), {
tiles = {"xdecor_stone_tile.png"},
})
xdecor.register_legacy_aliases("stone_tile", "stone_tile_x")
register_hard_node("packed_ice", S("Packed Ice"), {
groups = {cracky = 1, cools_lava = 1, slippery = 3},
sounds = default.node_sound_glass_defaults()
})
-- renamed from wood_tile to fix naming collision with moreblocks
-- mod for the registrations under the 'stairs:' namespace
register_hard_node("wood_tile_x", S("Wooden Tile"), {
groups = {choppy = 1, wood = 1, flammable = 2},
sounds = default.node_sound_wood_defaults(),
tiles = {"xdecor_wood_tile.png"},
})
xdecor.register_legacy_aliases("wood_tile", "wood_tile_x")
xdecor.register("table", {
description = S("Table"),
tiles = {"xdecor_wood.png"},
groups = {choppy = 2, oddly_breakable_by_hand = 1, flammable = 2},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
node_box = xdecor.pixelbox(16, {
{0, 14, 0, 16, 2, 16}, {5.5, 0, 5.5, 5, 14, 6}
})
})
xdecor.register("tatami", {
description = S("Tatami"),
tiles = {"xdecor_tatami.png"},
wield_image = "xdecor_tatami.png",
groups = {snappy = 3, flammable = 3},
is_ground_content = false,
sunlight_propagates = true,
node_box = xdecor.nodebox.slab_y(0.0625)
})
xdecor.register("trampoline", {
description = S("Trampoline"),
tiles = {"xdecor_trampoline.png", "xdecor_trampoline_bottom.png", "xdecor_trampoline_sides.png"},
use_texture_alpha = ALPHA_CLIP,
groups = {cracky = 3, oddly_breakable_by_hand = 1, fall_damage_add_percent = -80, bouncy = 90},
is_ground_content = false,
node_box = {
type = "fixed",
fixed = {
{ -0.5, -1/16, -0.5, 0.5, 0, 0.5 }, -- bouncy top
{ -0.5, -0.5, -0.5, -3/16, 0, -3/16 }, -- leg 1
{ 3/16, -0.5, -0.5, 0.5, 0, -3/16 }, -- leg 2
{ -0.5, -0.5, 3/16, -3/16, 0, 0.5 }, -- leg 3
{ 3/16, -0.5, 3/16, 0.5, 0, 0.5 }, -- leg 4
{ -3/16, -5/16, -0.5, 3/16, -1/16, -7/16 }, -- connector 1
{ -0.5, -5/16, -3/16, -7/16, -1/16, 3/16 }, -- connector 2
{ -3/16, -5/16, 7/16, 3/16, -1/16, 0.5 }, -- connector 3
{ 7/16, -5/16, -3/16, 0.5, -1/16, 3/16 }, -- connector 4
},
},
selection_box = xdecor.nodebox.slab_y(0.5),
collision_box = xdecor.nodebox.slab_y(0.5),
sounds = default.node_sound_defaults({
footstep = {
name = "xdecor_bouncy",
gain = 0.8
},
dig = default.node_sound_wood_defaults().dig,
}),
})
xdecor.register("tv", {
description = S("Television"),
light_source = 11,
groups = {cracky = 3, oddly_breakable_by_hand = 2},
is_ground_content = false,
on_rotate = screwdriver.rotate_simple,
tiles = {
"xdecor_television_left.png^[transformR270",
"xdecor_television_left.png^[transformR90",
"xdecor_television_left.png^[transformFX",
"xdecor_television_left.png", "xdecor_television_back.png",
{
name = "xdecor_television_front_animated.png",
animation = {type = "vertical_frames", length = 80.0}
}
},
sounds = default.node_sound_metal_defaults(),
})
xdecor.register("woodframed_glass", {
description = S("Wood Framed Glass"),
drawtype = "glasslike_framed",
sunlight_propagates = true,
tiles = {"xdecor_woodframed_glass.png", "xdecor_woodframed_glass_detail.png"},
use_texture_alpha = ALPHA_CLIP,
groups = {cracky = 2, oddly_breakable_by_hand = 1},
is_ground_content = false,
sounds = default.node_sound_glass_defaults(),
_xdecor_custom_noncube_tiles = {
stair = {
"xdecor_woodframed_glass_split.png",
"xdecor_woodframed_glass.png",
"xdecor_woodframed_glass_stairside_flip.png",
"xdecor_woodframed_glass_stairside.png",
"xdecor_woodframed_glass.png",
"xdecor_woodframed_glass_split.png",
},
stair_inner = {
"xdecor_woodframed_glass_stairside.png^[transformR270",
"xdecor_woodframed_glass.png",
"xdecor_woodframed_glass_stairside_flip.png",
"xdecor_woodframed_glass.png",
"xdecor_woodframed_glass.png",
"xdecor_woodframed_glass_stairside.png",
},
stair_outer = {
"xdecor_woodframed_glass_stairside.png^[transformR90",
"xdecor_woodframed_glass.png",
"xdecor_woodframed_glass_outer_stairside.png",
"xdecor_woodframed_glass_stairside_flip.png",
"xdecor_woodframed_glass_stairside.png^[transformR90",
"xdecor_woodframed_glass_outer_stairside.png",
},
halfstair = {
"xdecor_woodframed_glass_cube.png",
"xdecor_woodframed_glass.png",
"xdecor_woodframed_glass_stairside_flip.png",
"xdecor_woodframed_glass_stairside.png",
"xdecor_woodframed_glass_split.png^[transformR90",
"xdecor_woodframed_glass_cube.png",
},
slab = {
"xdecor_woodframed_glass.png",
"xdecor_woodframed_glass.png",
"xdecor_woodframed_glass_split.png",
},
cube = { "xdecor_woodframed_glass_cube.png" },
thinstair = { "xdecor_woodframed_glass_split.png" },
micropanel = { "xdecor_woodframed_glass_split.png" },
panel = {
"xdecor_woodframed_glass_split.png",
"xdecor_woodframed_glass_split.png",
"xdecor_woodframed_glass_cube.png",
"xdecor_woodframed_glass_cube.png",
"xdecor_woodframed_glass_split.png",
},
},
})
local devices = {
{ "radio", S("Radio"), default.node_sound_metal_defaults() },
--~ as in "loudspeaker"
{ "speaker", S("Speaker"), default.node_sound_metal_defaults() },
}
for _, v in pairs(devices) do
xdecor.register(v[1], {
description = v[2],
on_rotate = screwdriver.rotate_simple,
tiles = {
"xdecor_" .. v[1] .. "_top.png",
"xdecor_" .. v[1] .. "_side.png",
"xdecor_" .. v[1] .. "_side.png",
"xdecor_" .. v[1] .. "_side.png",
"xdecor_" .. v[1] .. "_back.png",
"xdecor_" .. v[1] .. "_front.png",
},
groups = {cracky = 2, not_cuttable = 1},
is_ground_content = false,
sounds = v[3],
})
end

405
mods/xdecor/src/recipes.lua Normal file
View file

@ -0,0 +1,405 @@
minetest.register_craft({
output = "xdecor:baricade",
recipe = {
{"group:stick", "", "group:stick"},
{"", "default:steel_ingot", ""},
{"group:stick", "", "group:stick"}
}
})
minetest.register_craft({
output = "xdecor:barrel",
recipe = {
{"group:wood", "group:wood", "group:wood"},
{"default:iron_lump", "", "default:iron_lump"},
{"group:wood", "group:wood", "group:wood"}
}
})
minetest.register_craft({
output = "xdecor:candle",
recipe = {
{"default:torch"}
}
})
minetest.register_craft({
output = "xdecor:cabinet",
recipe = {
{"group:wood", "group:wood", "group:wood"},
{"doors:trapdoor", "", "doors:trapdoor"},
{"group:wood", "group:wood", "group:wood"}
}
})
minetest.register_craft({
output = "xdecor:cabinet_half 2",
recipe = {
{"xdecor:cabinet"}
}
})
minetest.register_craft({
output = "xdecor:cactusbrick",
recipe = {
{"default:brick", "default:cactus"}
}
})
minetest.register_craft({
output = "xdecor:chair",
recipe = {
{"group:stick", "", ""},
{"group:stick", "group:stick", "group:stick"},
{"group:stick", "", "group:stick"}
}
})
minetest.register_craft({
output = "xdecor:coalstone_tile 4",
recipe = {
{"default:coalblock", "default:stone"},
{"default:stone", "default:coalblock"}
}
})
minetest.register_craft({
output = "xdecor:cobweb",
recipe = {
{"farming:string", "", "farming:string"},
{"", "farming:string", ""},
{"farming:string", "", "farming:string"}
}
})
minetest.register_craft({
output = "xdecor:cushion 3",
recipe = {
{"wool:red", "wool:red", "wool:red"}
}
})
minetest.register_craft({
output = "xdecor:cushion_block",
recipe = {
{"xdecor:cushion"},
{"xdecor:cushion"}
}
})
minetest.register_craft({
output = "xdecor:desertstone_tile 4",
recipe = {
{"default:desert_stone_block", "default:desert_stone_block"},
{"default:desert_stone_block", "default:desert_stone_block"},
}
})
if not minetest.get_modpath("moreblocks") then
minetest.register_craft({
output = "xdecor:empty_shelf",
recipe = {
{"group:wood", "group:wood", "group:wood"},
{"", "", ""},
{"group:wood", "group:wood", "group:wood"}
}
})
end
minetest.register_craft({
output = "xdecor:enderchest",
recipe = {
{"", "default:obsidian", ""},
{"default:obsidian", "default:chest", "default:obsidian"},
{"", "default:obsidian", ""}
}
})
minetest.register_craft({
output = "xdecor:hard_clay",
recipe = {
{"default:clay", "default:clay"},
{"default:clay", "default:clay"}
}
})
minetest.register_craft({
output = "xdecor:iron_lightbox",
recipe = {
{"xpanes:bar_flat", "default:torch", "xpanes:bar_flat"},
{"xpanes:bar_flat", "default:glass", "xpanes:bar_flat"},
{"xpanes:bar_flat", "default:torch", "xpanes:bar_flat"}
}
})
minetest.register_craft({
output = "xdecor:ivy 4",
recipe = {
{"group:leaves"},
{"group:leaves"}
}
})
minetest.register_craft({
output = "xdecor:lantern",
recipe = {
{"default:iron_lump"},
{"default:torch"},
{"default:iron_lump"}
}
})
minetest.register_craft({
output = "xdecor:moonbrick",
recipe = {
{"default:brick", "default:stone"}
}
})
minetest.register_craft({
output = "xdecor:multishelf",
recipe = {
{"group:wood", "group:wood", "group:wood"},
{"group:vessel", "group:book", "group:vessel"},
{"group:wood", "group:wood", "group:wood"}
}
})
minetest.register_craft({
output = "xdecor:packed_ice",
recipe = {
{"", "default:ice", ""},
{"default:ice", "", "default:ice"},
{"", "default:ice", ""},
}
})
minetest.register_craft({
output = "xdecor:painting_1",
recipe = {
{"default:sign_wall_wood", "group:dye"}
}
})
minetest.register_craft({
output = "xdecor:radio",
type = "shapeless",
recipe = {"xdecor:speaker", "xdecor:speaker"}
})
minetest.register_craft({
output = "xdecor:rooster",
recipe = {
{"default:gold_ingot", "", "default:gold_ingot"},
{"", "default:gold_ingot", ""},
{"default:gold_ingot", "", "default:gold_ingot"}
}
})
minetest.register_craft({
output = "xdecor:speaker",
recipe = {
{"default:gold_ingot", "default:copper_ingot", "default:gold_ingot"},
{"default:copper_ingot", "", "default:copper_ingot"},
{"default:gold_ingot", "default:copper_ingot", "default:gold_ingot"}
}
})
minetest.register_craft({
output = "xdecor:stone_tile_x 4",
recipe = {
{"default:stone_block", "default:stone_block"},
{"default:stone_block", "default:stone_block"},
}
})
minetest.register_craft({
output = "xdecor:stone_rune 4",
recipe = {
{"default:stone_block", "default:stone_block", "default:stone_block"},
{"default:stone_block", "", "default:stone_block"},
{"default:stone_block", "default:stone_block", "default:stone_block"}
}
})
minetest.register_craft({
output = "xdecor:stonepath 16",
recipe = {
{"stairs:slab_cobble", "", "stairs:slab_cobble"},
{"", "stairs:slab_cobble", ""},
{"stairs:slab_cobble", "", "stairs:slab_cobble"}
}
})
minetest.register_craft({
output = "xdecor:table",
recipe = {
{"stairs:slab_wood", "stairs:slab_wood", "stairs:slab_wood"},
{"", "group:stick", ""},
{"", "group:stick", ""}
}
})
minetest.register_craft({
output = "xdecor:tatami",
recipe = {
{"farming:wheat", "farming:wheat", "farming:wheat"}
}
})
minetest.register_craft({
output = "xdecor:trampoline",
recipe = {
{"farming:string", "farming:string", "farming:string"},
{"default:steel_ingot", "default:steel_ingot", "default:steel_ingot"},
{"default:steel_ingot", "", "default:steel_ingot"}
}
})
minetest.register_craft({
output = "xdecor:tv",
recipe = {
{"default:steel_ingot", "default:copper_ingot", "default:steel_ingot"},
{"default:steel_ingot", "default:glass", "default:steel_ingot"},
{"default:steel_ingot", "default:copper_ingot", "default:steel_ingot"}
}
})
minetest.register_craft({
output = "xdecor:woodframed_glass",
recipe = {
{"group:stick", "group:stick", "group:stick"},
{"group:stick", "default:glass", "group:stick"},
{"group:stick", "group:stick", "group:stick"}
}
})
minetest.register_craft({
output = "xdecor:wood_tile_x 2",
recipe = {
{"", "group:wood", ""},
{"group:wood", "", "group:wood"},
{"", "group:wood", ""}
}
})
minetest.register_craft({
output = "xdecor:wooden_lightbox",
recipe = {
{"group:stick", "default:torch", "group:stick"},
{"group:stick", "default:glass", "group:stick"},
{"group:stick", "default:torch", "group:stick"}
}
})
minetest.register_craft({
output = "xdecor:wooden2_lightbox",
recipe = {
{"group:stick", "group:stick", "group:stick"},
{"default:torch", "default:glass", "default:torch"},
{"group:stick", "group:stick", "group:stick"}
},
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:empty_shelf",
burntime = 30,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:multishelf",
burntime = 30,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:cabinet",
burntime = 30,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:barrel",
burntime = 30,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:cabinet_half",
burntime = 15,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:workbench",
burntime = 15,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:table",
burntime = 12,
})
minetest.register_craft({
type = "fuel",
recipe = "doors:woodglass_door",
burntime = 13,
})
minetest.register_craft({
type = "fuel",
recipe = "doors:screen_door",
burntime = 10,
})
minetest.register_craft({
type = "fuel",
recipe = "doors:slide_door",
burntime = 8,
})
minetest.register_craft({
type = "fuel",
recipe = "xpanes:wood_frame_flat",
burntime = 5,
})
minetest.register_craft({
type = "fuel",
recipe = "xpanes:bamboo_frame_flat",
burntime = 3,
})
minetest.register_craft({
type = "fuel",
recipe = "doors:japanese_door",
burntime = 8,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:chair",
burntime = 6,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:baricade",
burntime = 6,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:wood_tile_x",
burntime = 10,
})
minetest.register_craft({
type = "fuel",
recipe = "realchess:chessboard",
burntime = 4,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:painting_1",
burntime = 3,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:tatami",
burntime = 1,
})
minetest.register_craft({
type = "fuel",
recipe = "xdecor:ivy",
burntime = 1,
})

132
mods/xdecor/src/rope.lua Normal file
View file

@ -0,0 +1,132 @@
local rope = {}
local S = minetest.get_translator("xdecor")
-- Maximum length a rope can extend to
local MAX_ROPES = 30
local ropesounds = default.node_sound_leaves_defaults()
-- Code by Mirko K. (modified by Temperest, Wulfsdad, kilbith and Wuzzy) (License: GPL).
function rope.place(itemstack, placer, pointed_thing)
local creative = minetest.is_creative_enabled(placer:get_player_name())
local protection_bypass = minetest.check_player_privs(placer, "protection_bypass")
local pname = placer:get_player_name()
if pointed_thing.type == "node" then
-- Use pointed node's on_rightclick function first, if present
if placer and not placer:get_player_control().sneak then
local node = minetest.get_node(pointed_thing.under)
if minetest.registered_nodes[node.name] and minetest.registered_nodes[node.name].on_rightclick then
return minetest.registered_nodes[node.name].on_rightclick(pointed_thing.under, node, placer, itemstack) or itemstack
end
end
local pos = pointed_thing.above
-- Check protection
if minetest.is_protected(pos, pname) and not protection_bypass then
minetest.record_protection_violation(pos, pname)
return itemstack
end
local oldnode = minetest.get_node(pos)
local stackname = itemstack:get_name()
-- Limit rope length to max. stack size or MAX_ROPES (whatever is smaller).
-- Prevents the rope to extend infinitely in Creative Mode.
local max_ropes = math.min(itemstack:get_stack_max(), MAX_ROPES)
-- Start placing ropes and extend it downwards until we hit an obstacle,
-- run out of ropes or hit the maximum rope length.
local start_pos = table.copy(pos)
local ropes_to_place = 0
local new_rope_nodes = {}
while oldnode.name == "air" and (creative or (ropes_to_place < itemstack:get_count())) and ropes_to_place < max_ropes do
-- Stop extending rope into protected area
if minetest.is_protected(pos, pname) and not protection_bypass then
break
end
table.insert(new_rope_nodes, table.copy(pos))
pos.y = pos.y - 1
oldnode = minetest.get_node(pos)
ropes_to_place = ropes_to_place + 1
end
local newnode = {name = stackname}
if ropes_to_place == 1 then
minetest.set_node(new_rope_nodes[1], newnode)
else
minetest.bulk_set_node(new_rope_nodes, newnode)
end
if not creative then
itemstack:take_item(ropes_to_place)
end
-- Play placement sound manually
if ropes_to_place > 0 then
minetest.sound_play(ropesounds.place, {pos=start_pos}, true)
end
end
return itemstack
end
function rope.remove(pos, oldnode, digger, rope_name)
local num = 0
local below = {x = pos.x, y = pos.y, z = pos.z}
local digger_inv = digger:get_inventory()
while minetest.get_node(below).name == rope_name do
minetest.remove_node(below)
below.y = below.y - 1
num = num + 1
end
if num == 0 then return end
-- Play dig sound manually
minetest.sound_play(ropesounds.dug, {pos=pos}, true)
-- Give/drop rope items
local creative = minetest.is_creative_enabled(digger:get_player_name())
if not creative or not digger_inv:contains_item("main", rope_name) then
if creative then
num = 1
end
local item = rope_name.." "..num
local leftover = digger_inv:add_item("main", rope_name.." "..num)
if not leftover:is_empty() then
minetest.add_item(pos, leftover)
end
end
return true
end
xdecor.register("rope", {
description = S("Rope"),
drawtype = "plantlike",
walkable = false,
climbable = true,
groups = {dig_immediate = 3, flammable = 3},
is_ground_content = false,
tiles = {"xdecor_rope.png"},
inventory_image = "xdecor_rope_inv.png",
wield_image = "xdecor_rope_inv.png",
selection_box = xdecor.pixelbox(8, {{3, 0, 3, 2, 8, 2}}),
node_placement_prediction = "",
on_place = rope.place,
after_dig_node = function(pos, oldnode, oldmetadata, digger)
pos = vector.new(pos.x, pos.y-1, pos.z)
rope.remove(pos, oldnode, digger, "xdecor:rope")
end,
sounds = ropesounds,
})
-- Recipes
minetest.register_craft({
output = "xdecor:rope",
recipe = {
{"farming:string"},
{"farming:string"},
{"farming:string"}
}
})

View file

@ -0,0 +1,602 @@
local workbench = {}
local registered_cuttable_nodes = {}
local special_cuts = {}
screwdriver = screwdriver or {}
local min, ceil = math.min, math.ceil
local S = minetest.get_translator("xdecor")
local FS = function(...) return minetest.formspec_escape(S(...)) end
local DEFAULT_HAMMER_REPAIR = 500
local DEFAULT_HAMMER_REPAIR_COST = 700
-- Nodeboxes definitions
workbench.defs = {
-- Name Yield Nodeboxes (X Y Z W H L) Description
{"nanoslab", 16, {{ 0, 0, 0, 8, 1, 8 }}, S("Nanoslab")},
{"micropanel", 16, {{ 0, 0, 0, 16, 1, 8 }}, S("Micropanel")},
{"microslab", 8, {{ 0, 0, 0, 16, 1, 16 }}, S("Microslab")},
{"thinstair", 8, {{ 0, 7, 0, 16, 1, 8 },
{ 0, 15, 8, 16, 1, 8 }}, S("Thin Stair")},
{"cube", 4, {{ 0, 0, 0, 8, 8, 8 }}, S("Cube")},
{"panel", 4, {{ 0, 0, 0, 16, 8, 8 }}, S("Panel")},
{"slab", 2, nil, S("Slab") },
{"doublepanel", 2, {{ 0, 0, 0, 16, 8, 8 },
{ 0, 8, 8, 16, 8, 8 }}, S("Double Panel")},
{"halfstair", 2, {{ 0, 0, 0, 8, 8, 16 },
{ 0, 8, 8, 8, 8, 8 }}, S("Half-Stair")},
{"stair_outer", 1, nil, nil},
{"stair", 1, nil, S("Stair")},
{"stair_inner", 1, nil, nil},
}
local custom_repairable = {}
function xdecor:register_repairable(item)
custom_repairable[item] = true
end
-- Tools allowed to be repaired
function workbench:repairable(stack)
-- Explicitly registered as repairable: Overrides everything else
if custom_repairable[stack] then
return true
end
-- no repair if non-tool
if not minetest.registered_tools[stack] then
return false
end
-- no repair if disable_repair group
if minetest.get_item_group(stack, "disable_repair") == 1 then
return false
end
return true
end
-- Returns true if item can be cut into basic stairs and slabs
function workbench:cuttable(itemname)
local split = string.split(itemname, ":")
if split and split[1] and split[2] then
if minetest.registered_nodes["stairs:stair_"..split[2]] ~= nil or
minetest.registered_nodes["stairs:slab_"..split[2]] ~= nil then
return true
end
end
if registered_cuttable_nodes[itemname] == true then
return true
end
return false
end
-- Returns true if item can be cut into xdecor extended shapes (thinslab, panel, cube, etc.)
function workbench:cuttable_extended(itemname)
return registered_cuttable_nodes[itemname] == true
end
-- method to allow other mods to check if an item is repairable
function xdecor:is_repairable(stack)
return workbench:repairable(stack)
end
function workbench:get_output(inv, input, name)
local output = {}
local extended = workbench:cuttable_extended(input:get_name())
for i = 1, #self.defs do
local nbox = self.defs[i]
local cuttype = nbox[1]
local count = nbox[2] * input:get_count()
local max_count = input:get_stack_max()
if count > max_count then
-- Limit count to maximum multiple to avoid waste
count = nbox[2] * math.floor(max_count / nbox[2])
end
local was_cut = false
if extended or nbox[3] == nil then
local item = name .. "_" .. cuttype
item = nbox[3] and item or "stairs:" .. cuttype .. "_" .. name:match(":(.*)")
if minetest.registered_items[item] then
output[i] = item .. " " .. count
was_cut = true
end
end
if not was_cut and special_cuts[input:get_name()] ~= nil then
local cut = special_cuts[input:get_name()][cuttype]
if cut then
output[i] = cut .. " " .. count
was_cut = true
end
end
end
inv:set_list("forms", output)
end
local main_fs = ""..
--~ Verb shown in workbench form where you can cut a node
"label[0.9,1.23;"..FS("Cut").."]"
--~ Verb shown in workbench form where you can repair an item
.."label[0.9,2.23;"..FS("Repair").."]"
..[[ box[-0.05,1;2.05,0.9;#555555]
box[-0.05,2;2.05,0.9;#555555] ]]
--~ Button in workbench form
.."button[0,0;2,1;craft;"..FS("Crafting").."]"
--~ Button in workbench form
.."button[2,0;2,1;storage;"..FS("Storage").."]"
..[[ image[3,1;1,1;gui_arrow.png]
image[0,1;1,1;worktable_saw.png]
image[0,2;1,1;worktable_anvil.png]
image[3,2;1,1;hammer_layout.png]
list[context;input;2,1;1,1;]
list[context;tool;2,2;1,1;]
list[context;hammer;3,2;1,1;]
list[context;forms;4,0;4,3;]
listring[current_player;main]
listring[context;tool]
listring[current_player;main]
listring[context;hammer]
listring[current_player;main]
listring[context;forms]
listring[current_player;main]
listring[context;input]
]]
local crafting_fs = "image[5,1;1,1;gui_furnace_arrow_bg.png^[transformR270]"
.."button[0,0;1.5,1;back;< "..FS("Back").."]"
..[[ list[current_player;craft;2,0;3,3;]
list[current_player;craftpreview;6,1;1,1;]
listring[current_player;main]
listring[current_player;craft]
]]
local storage_fs = "list[context;storage;0,1;8,2;]"
.."button[0,0;1.5,1;back;< "..FS("Back").."]"
..[[listring[context;storage]
listring[current_player;main]
]]
local formspecs = {
-- Main formspec
main_fs,
-- Crafting formspec
crafting_fs,
-- Storage formspec
storage_fs,
}
function workbench:set_formspec(meta, id)
meta:set_string("formspec",
"size[8,7;]list[current_player;main;0,3.25;8,4;]" ..
formspecs[id] .. xdecor.xbg .. default.get_hotbar_bg(0,3.25))
end
function workbench.construct(pos)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
inv:set_size("tool", 1)
inv:set_size("input", 1)
inv:set_size("hammer", 1)
inv:set_size("forms", 4*3)
inv:set_size("storage", 8*2)
meta:set_string("infotext", S("Work Bench"))
workbench:set_formspec(meta, 1)
end
function workbench.fields(pos, _, fields)
if fields.quit then return end
local meta = minetest.get_meta(pos)
local id = fields.back and 1 or fields.craft and 2 or fields.storage and 3
if not id then return end
workbench:set_formspec(meta, id)
end
function workbench.dig(pos)
local inv = minetest.get_meta(pos):get_inventory()
return inv:is_empty("input") and inv:is_empty("hammer") and
inv:is_empty("tool") and inv:is_empty("storage")
end
function workbench.blast(pos)
local drops = xdecor.get_inventory_drops(pos, {"input", "hammer", "tool", "storage"})
minetest.remove_node(pos)
return drops
end
function workbench.timer(pos)
local timer = minetest.get_node_timer(pos)
local inv = minetest.get_meta(pos):get_inventory()
local tool = inv:get_stack("tool", 1)
local hammer = inv:get_stack("hammer", 1)
if tool:is_empty() or hammer:is_empty() or tool:get_wear() == 0 then
timer:stop()
return
end
local hammerdef = hammer:get_definition()
-- Tool's wearing range: 0-65535; 0 = new condition
tool:add_wear(-hammerdef._xdecor_hammer_repair or DEFAULT_HAMMER_REPAIR)
hammer:add_wear(hammerdef._xdecor_hammer_repair_cost or DEFAULT_HAMMER_REPAIR_COST)
inv:set_stack("tool", 1, tool)
inv:set_stack("hammer", 1, hammer)
return true
end
function workbench.allow_put(pos, listname, index, stack, player)
local stackname = stack:get_name()
if (listname == "tool" and workbench:repairable(stackname)) or
(listname == "input" and workbench:cuttable(stackname)) or
(listname == "hammer" and minetest.get_item_group(stackname, "repair_hammer") == 1) or
listname == "storage" then
return stack:get_count()
end
return 0
end
function workbench.on_put(pos, listname, index, stack, player)
local inv = minetest.get_meta(pos):get_inventory()
if listname == "input" then
local input = inv:get_stack("input", 1)
workbench:get_output(inv, input, stack:get_name())
elseif listname == "tool" or listname == "hammer" then
local timer = minetest.get_node_timer(pos)
timer:start(3.0)
end
end
function workbench.allow_move(pos, from_list, from_index, to_list, to_index, count, player)
if (to_list == "storage" and from_list ~= "forms") then
return count
elseif (to_list == "hammer" and from_list == "tool") or (to_list == "tool" and from_list == "hammer") then
local inv = minetest.get_inventory({type="node", pos=pos})
local stack = inv:get_stack(from_list, from_index)
if minetest.get_item_group(stack:get_name(), "repair_hammer") == 1 then
return count
end
end
return 0
end
function workbench.on_move(pos, from_list, from_index, to_list, to_index, count, player)
local meta = minetest.get_meta(pos)
local inv = meta:get_inventory()
local from_stack = inv:get_stack(from_list, from_index)
local to_stack = inv:get_stack(to_list, to_index)
workbench.on_take(pos, from_list, from_index, from_stack, player)
workbench.on_put(pos, to_list, to_index, to_stack, player)
end
function workbench.allow_take(pos, listname, index, stack, player)
return stack:get_count()
end
function workbench.on_take(pos, listname, index, stack, player)
local inv = minetest.get_meta(pos):get_inventory()
local input = inv:get_stack("input", 1)
local inputname = input:get_name()
local stackname = stack:get_name()
if listname == "input" then
if stackname == inputname and workbench:cuttable(inputname) then
workbench:get_output(inv, input, stackname)
else
inv:set_list("forms", {})
end
elseif listname == "forms" then
local fromstack = inv:get_stack(listname, index)
if not fromstack:is_empty() and fromstack:get_name() ~= stackname then
local player_inv = player:get_inventory()
if player_inv:room_for_item("main", fromstack) then
player_inv:add_item("main", fromstack)
end
end
input:take_item(ceil(stack:get_count() / workbench.defs[index][2]))
inv:set_stack("input", 1, input)
workbench:get_output(inv, input, inputname)
end
end
xdecor.register("workbench", {
description = S("Work Bench"),
_tt_help = S("For cutting blocks, repairing tools with a hammer, crafting and storing items"),
groups = {cracky = 2, choppy = 2, oddly_breakable_by_hand = 1},
is_ground_content = false,
sounds = default.node_sound_wood_defaults(),
tiles = {
"xdecor_workbench_top.png","xdecor_workbench_bottom.png",
"xdecor_workbench_sides.png", "xdecor_workbench_sides.png",
"xdecor_workbench_front.png", "xdecor_workbench_front.png"
},
on_rotate = screwdriver.rotate_simple,
can_dig = workbench.dig,
on_blast = workbench.blast,
on_timer = workbench.timer,
on_construct = workbench.construct,
on_receive_fields = workbench.fields,
on_metadata_inventory_put = workbench.on_put,
on_metadata_inventory_take = workbench.on_take,
on_metadata_inventory_move = workbench.on_move,
allow_metadata_inventory_put = workbench.allow_put,
allow_metadata_inventory_take = workbench.allow_take,
allow_metadata_inventory_move = workbench.allow_move
})
local function register_cut_raw(node, workbench_def)
local mod_name, item_name = node:match("^(.-):(.*)")
local def = minetest.registered_nodes[node]
if item_name and workbench_def[3] then
local groups = {}
local tiles
groups.not_in_creative_inventory = 1
for k, v in pairs(def.groups) do
if k ~= "wood" and k ~= "stone" and k ~= "level" then
groups[k] = v
end
end
if def.tiles then
if #def.tiles > 1 and (def.drawtype:sub(1,5) ~= "glass") then
tiles = def.tiles
else
tiles = {def.tiles[1]}
end
else
tiles = {def.tile_images[1]}
end
-- Erase `tileable_vertical=false` from tiles because it
-- lead to buggy textures (e.g. with default:permafrost_with_moss)
for t=1, #tiles do
if type(tiles[t]) == "table" and tiles[t].tileable_vertical == false then
tiles[t].tileable_vertical = nil
end
end
local custom_tiles = xdecor.glasscuts[node]
if custom_tiles then
if not custom_tiles.nanoslab then
custom_tiles.nanoslab = custom_tiles.cube
end
if not custom_tiles.micropanel then
custom_tiles.micropanel = custom_tiles.micropanel
end
if not custom_tiles.doublepanel then
custom_tiles.doublepanel = custom_tiles.panel
end
end
if not minetest.registered_nodes["stairs:slab_" .. item_name] then
if custom_tiles and (custom_tiles.slab or custom_tiles.stair) then
if custom_tiles.stair then
stairs.register_stair(item_name, node,
groups, custom_tiles.stair, S("@1 Stair", def.description),
def.sounds)
stairs.register_stair_inner(item_name, node,
groups, custom_tiles.stair_inner, "", def.sounds, nil, S("Inner @1 Stair", def.description))
stairs.register_stair_outer(item_name, node,
groups, custom_tiles.stair_outer, "", def.sounds, nil, S("Outer @1 Stair", def.description))
end
if custom_tiles.slab then
stairs.register_slab(item_name, node,
groups, custom_tiles.slab, S("@1 Slab", def.description),
def.sounds)
end
else
stairs.register_stair_and_slab(item_name, node,
groups, tiles,
S("@1 Stair", def.description),
S("@1 Slab", def.description),
def.sounds, nil,
S("Inner @1 Stair", def.description),
S("Outer @1 Stair", def.description))
end
end
local cutname = workbench_def[1]
local tiles_special_cut
if custom_tiles and custom_tiles[cutname] then
tiles_special_cut = custom_tiles[cutname]
else
tiles_special_cut = tiles
end
local cutnodename = node .. "_" .. cutname
if minetest.registered_nodes[cutnodename] then
minetest.log("error", "[xdecor] register_cut_raw: Refusing to register node "..cutnodename.." becaut it was already registered!")
return false
end
minetest.register_node(":" .. cutnodename, {
--~ Format of the description of a cut node. @1: Base node description (e.g. "Stone"); @2: modifier (e.g. "Nanoslab")
description = S("@1 @2", def.description, workbench_def[4]),
paramtype = "light",
paramtype2 = "facedir",
drawtype = "nodebox",
sounds = def.sounds,
tiles = tiles_special_cut,
use_texture_alpha = def.use_texture_alpha,
groups = groups,
is_ground_content = def.is_ground_content,
node_box = xdecor.pixelbox(16, workbench_def[3]),
sunlight_propagates = true,
on_place = minetest.rotate_node
})
elseif item_name and mod_name then
minetest.register_alias_force(
("%s:%s_innerstair"):format(mod_name, item_name),
("stairs:stair_inner_%s"):format(item_name)
)
minetest.register_alias_force(
("%s:%s_outerstair"):format(mod_name, item_name),
("stairs:stair_outer_%s"):format(item_name)
)
end
return true
end
function workbench:register_cut(nodename, cutlist)
if registered_cuttable_nodes[nodename] then
minetest.log("error", "[xdecor] Workbench: Tried to register cut for node "..nodename..", but it was already registered!")
return false
end
local ok = true
for _, d in ipairs(workbench.defs) do
local ok = register_cut_raw(nodename, d)
if not ok then
ok = false
end
end
registered_cuttable_nodes[nodename] = true
return ok
end
function workbench:register_special_cut(nodename, cutlist)
if registered_cuttable_nodes[nodename] or special_cuts[nodename] then
minetest.log("error", "[xdecor] Workbench: Tried to register special cut for node "..nodename..", but it was already registered!")
return false
end
registered_cuttable_nodes[nodename] = true
special_cuts[nodename] = cutlist
end
-- Workbench craft
minetest.register_craft({
output = "xdecor:workbench",
recipe = {
{"group:wood", "group:wood"},
{"group:wood", "group:wood"}
}
})
-- Register default cuttable blocks
do
local cuttable_nodes = {}
-- Nodes allowed to be cut:
-- Only the regular, solid blocks without metas or explosivity
-- from the xdecor or default mods.
for nodename, def in pairs(minetest.registered_nodes) do
local nodenamesplit = string.split(nodename, ":")
local modname = nodenamesplit[1]
if (modname == "xdecor" or modname == "default") and xdecor.stairs_valid_def(def) then
cuttable_nodes[#cuttable_nodes + 1] = nodename
end
end
for i = 1, #cuttable_nodes do
local node = cuttable_nodes[i]
workbench:register_cut(node)
end
end
-- Special cuts for cushion block and cabinet
workbench:register_special_cut("xdecor:cushion_block", { slab = "xdecor:cushion" })
workbench:register_special_cut("xdecor:cabinet", { slab = "xdecor:cabinet_half" })
--[[ API FUNCTIONS ]]
--[[ Register a custom hammer (for repairing).
A hammer repair items at the work bench. The workbench repeatedly
checks if a hammer and a repairable tool are in the slots. The hammer
will repair the tool in regular intervals. This is called a "step".
In each step, the hammer reduces the wear of the repairable
tool but increases its own wear, each by a fixed amount.
This function allows you to register a custom hammer with custom
name, item image and wear stats.
Arguments:
* name: Internal itemname
* def: Definition table:
* description: Item `description`
* image: Inventory image and wield image
* groups: Item groups (MUST contain at least `repair_hammer = 1`)
* repair: How much item wear the hammer repairs per step
* repair_cost: How much item wear the hammer takes itself per step
Note: Mind the implication of repair_cost! If repair_cost is lower than
repair, this means practically infinite durability if you have two
hammers that repair each other. If repair_cost is higher than repair,
then hammers will break eventually.
]]
function xdecor.register_hammer(name, def)
minetest.register_tool(name, {
description = def.description,
_tt_help = S("Repairs tools at the work bench"),
inventory_image = def.image,
wield_image = def.image,
on_use = function() do
return end
end,
groups = def.groups,
_xdecor_hammer_repair = def.repair or DEFAULT_HAMMER_REPAIR,
_xdecor_hammer_repair_cost = def.repair_cost or DEFAULT_HAMMER_REPAIR_COST,
})
end
--[[ EXPERIMENTAL FUNCTION:
Registers various 'cut' node variants for the node with the given nodename,
which will be available in the workbench.
This must only be called once per node. Calling it again is an error.
The following nodes will be registered:
* <nodename>_nanoslab
* <nodename>_micropanel
* <nodename>_microslab
* <nodename>_thinstair
* <nodename>_cube
* <nodename>_panel
* <nodename>_doublepanel
* <nodename>_halfstair
You MUST make sure these names are not already taken before
calling this function. Failing to do so is an error.
Additionally, a slab, stair, inner stair and outer stair
will be registered by using the `stairs` mod if the slab
node does not exist yet. Refer to the `stairs` mod documentation
for details.
Returns true if all nodes were registered successfully,
returns false (and writes to error log) if any error occurred.
]]
xdecor.register_cut = function(nodename)
return workbench:register_cut(nodename)
end
--[[ END OF API FUNCTIONS ]]
-- Register xdecor's built-in hammer
xdecor.register_hammer("xdecor:hammer", {
description = S("Hammer"),
image = "xdecor_hammer.png",
groups = { repair_hammer = 1 },
repair = DEFAULT_HAMMER_REPAIR,
repair_cost = DEFAULT_HAMMER_REPAIR_COST,
})
-- Hammer recipes
minetest.register_craft({
output = "xdecor:hammer",
recipe = {
{"default:steel_ingot", "group:stick", "default:steel_ingot"},
{"", "group:stick", ""}
}
})