Mineclonia/mods/ITEMS/mcl_banners/init.lua
Wuzzy 267a697fab Banners: Respawn entity if it got lost
Entity is respawned on load (in an LBM) or when the banner node is punched.
Also, the banner drop is now handled in the node instead of the entity.
2019-02-18 23:32:18 +01:00

520 lines
19 KiB
Lua

local node_sounds
if minetest.get_modpath("mcl_sounds") then
node_sounds = mcl_sounds.node_sound_wood_defaults()
end
-- Helper function
local function round(num, idp)
local mult = 10^(idp or 0)
return math.floor(num * mult + 0.5) / mult
end
mcl_banners = {}
mcl_banners.colors = {
-- Format:
-- [ID] = { banner description, wool, unified dyes color group, overlay color, dye, color name for emblazonings }
["unicolor_white"] = {"white", "White Banner", "mcl_wool:white", "#FFFFFF", "mcl_dye:white", "White" },
["unicolor_darkgrey"] = {"grey", "Grey Banner", "mcl_wool:grey", "#303030", "mcl_dye:dark_grey", "Grey" },
["unicolor_grey"] = {"silver", "Light Grey Banner", "mcl_wool:silver", "#5B5B5B", "mcl_dye:grey", "Light Grey" },
["unicolor_black"] = {"black", "Black Banner", "mcl_wool:black", "#000000", "mcl_dye:black", "Black" },
["unicolor_red"] = {"red", "Red Banner", "mcl_wool:red", "#BC0000", "mcl_dye:red", "Red" },
["unicolor_yellow"] = {"yellow", "Yellow Banner", "mcl_wool:yellow", "#E6CD00", "mcl_dye:yellow", "Yellow" },
["unicolor_dark_green"] = {"green", "Green Banner", "mcl_wool:green", "#006000", "mcl_dye:dark_green", "Green" },
["unicolor_cyan"] = {"cyan", "Cyan Banner", "mcl_wool:cyan", "#00ACAC", "mcl_dye:cyan", "Cyan" },
["unicolor_blue"] = {"blue", "Blue Banner", "mcl_wool:blue", "#0000AC", "mcl_dye:blue", "Blue" },
["unicolor_red_violet"] = {"magenta", "Magenta Banner", "mcl_wool:magenta", "#AC007C", "mcl_dye:magenta", "Magenta"},
["unicolor_orange"] = {"orange", "Orange Banner", "mcl_wool:orange", "#E67300", "mcl_dye:orange", "Orange" },
["unicolor_violet"] = {"purple", "Purple Banner", "mcl_wool:purple", "#6400AC", "mcl_dye:violet", "Violet" },
["unicolor_brown"] = {"brown", "Brown Banner", "mcl_wool:brown", "#603000", "mcl_dye:brown", "Brown" },
["unicolor_pink"] = {"pink", "Pink Banner", "mcl_wool:pink", "#DE557C", "mcl_dye:pink", "Pink" },
["unicolor_lime"] = {"lime", "Lime Banner", "mcl_wool:lime", "#30AC00", "mcl_dye:green", "Lime" },
["unicolor_light_blue"] = {"light_blue", "Light Blue Banner", "mcl_wool:light_blue", "#4040CF", "mcl_dye:lightblue", "Light Blue" },
}
local colors_reverse = {}
for k,v in pairs(mcl_banners.colors) do
colors_reverse["mcl_banners:banner_item_"..v[1]] = k
end
-- Add pattern/emblazoning crafting recipes
dofile(minetest.get_modpath("mcl_banners").."/patterncraft.lua")
-- Overlay ratios (0-255)
local base_color_ratio = 224
local layer_ratio = 255
local standing_banner_entity_offset = { x=0, y=-0.499, z=0 }
local hanging_banner_entity_offset = { x=0, y=-1.7, z=0 }
local on_destruct_banner = function(pos, hanging)
local offset, nodename
if hanging then
offset = hanging_banner_entity_offset
nodename = "mcl_banners:hanging_banner"
else
offset = standing_banner_entity_offset
nodename = "mcl_banners:standing_banner"
end
-- Find this node's banner entity and make it drop as an item
local checkpos = vector.add(pos, offset)
local objects = minetest.get_objects_inside_radius(checkpos, 0.5)
for _, v in ipairs(objects) do
local ent = v:get_luaentity()
if ent and ent.name == nodename then
v:remove()
end
end
-- Drop item
local meta = minetest.get_meta(pos)
local item = meta:get_inventory():get_stack("banner", 1)
if not item:is_empty() then
minetest.add_item(pos, item)
else
minetest.add_item(pos, "mcl_banners:banner_item_white")
end
end
local on_destruct_standing_banner = function(pos)
return on_destruct_banner(pos, false)
end
local on_destruct_hanging_banner = function(pos)
return on_destruct_banner(pos, true)
end
local make_banner_texture = function(base_color, layers)
local colorize
if mcl_banners.colors[base_color] then
colorize = mcl_banners.colors[base_color][4]
end
if colorize then
-- Base texture with base color
local base = "(mcl_banners_banner_base.png^[mask:mcl_banners_base_inverted.png)^((mcl_banners_banner_base.png^[colorize:"..colorize..":"..base_color_ratio..")^[mask:mcl_banners_base.png)"
-- Optional pattern layers
if layers then
local finished_banner = base
for l=1, #layers do
local layerinfo = layers[l]
local pattern = "mcl_banners_" .. layerinfo.pattern .. ".png"
local color = mcl_banners.colors[layerinfo.color][4]
-- Generate layer texture
local layer = "(("..pattern.."^[colorize:"..color..":"..layer_ratio..")^[mask:"..pattern..")"
finished_banner = finished_banner .. "^" .. layer
end
return { finished_banner }
end
return { base }
else
return { "mcl_banners_banner_base.png" }
end
end
local spawn_banner_entity = function(pos, hanging, itemstack)
local banner
if hanging then
banner = minetest.add_entity(pos, "mcl_banners:hanging_banner")
else
banner = minetest.add_entity(pos, "mcl_banners:standing_banner")
end
if banner == nil then
return banner
end
local imeta = itemstack:get_meta()
local layers_raw = imeta:get_string("layers")
local layers = minetest.deserialize(layers_raw)
local colorid = colors_reverse[itemstack:get_name()]
banner:get_luaentity():_set_textures(colorid, layers)
local mname = imeta:get_string("name")
if mname ~= nil and mname ~= "" then
banner:get_luaentity()._item_name = mname
banner:get_luaentity()._item_description = imeta:get_string("description")
end
return banner
end
local respawn_banner_entity = function(pos, node)
local hanging = node.name == "mcl_banners:hanging_banner"
local offset
if hanging then
offset = hanging_banner_entity_offset
else
offset = standing_banner_entity_offset
end
-- Check if a banner entity already exists
local bpos = vector.add(pos, offset)
local objects = minetest.get_objects_inside_radius(bpos, 0.5)
for _, v in ipairs(objects) do
local ent = v:get_luaentity()
if ent and (ent.name == "mcl_banners:standing_banner" or ent.name == "mcl_banners:hanging_banner") then
return
end
end
-- Spawn new entity
local meta = minetest.get_meta(pos)
local banner_item = meta:get_inventory():get_stack("banner", 1)
local banner_entity = spawn_banner_entity(bpos, hanging, banner_item)
-- Set rotation
local final_yaw
local rotation_level = meta:get_int("rotation_level")
final_yaw = (rotation_level * (math.pi/8)) + math.pi
banner_entity:set_yaw(final_yaw)
end
local on_rotate
if minetest.get_modpath("screwdriver") then
on_rotate = screwdriver.disallow
end
-- Banner nodes.
-- These are an invisible nodes which are only used to destroy the banner entity.
-- All the important banner information (such as color) is stored in the entity.
-- It is used only used internally.
-- Standing banner node
-- This one is also used for the help entry to avoid spamming the help with 16 entries.
minetest.register_node("mcl_banners:standing_banner", {
_doc_items_entry_name = "Banner",
_doc_items_image = "mcl_banners_item_base.png^mcl_banners_item_overlay.png",
_doc_items_longdesc = "Banners are tall colorful decorative blocks. They can be placed on the floor and at walls. Banners can be emblazoned with a variety of patterns using a lot of dye in crafting.",
_doc_items_usagehelp = "Use crafting to draw a pattern on top of the banner. Emblazoned banners can be emblazoned again to combine various patterns. You can draw up to 6 layers on a banner that way. You can copy the pattern of a banner by placing two banners of the same color in the crafting grid—one needs to be emblazoned, the other one must be clean. Finally, you can use a banner on a cauldron with water to wash off its top-most layer.",
walkable = false,
is_ground_content = false,
paramtype = "light",
sunlight_propagates = true,
drawtype = "nodebox",
-- Nodebox is drawn as fallback when the entity is missing, so that the
-- banner node is never truly invisible.
-- If the entity is drawn, the nodebox disappears within the real banner mesh.
node_box = {
type = "fixed",
fixed = { -1/32, -0.49, -1/32, 1/32, 1.49, 1/32 },
},
-- This texture is based on the banner base texture
tiles = { "mcl_banners_fallback_wood.png" },
inventory_image = "mcl_banners_item_base.png",
wield_image = "mcl_banners_item_base.png",
selection_box = {type = "fixed", fixed= {-0.3, -0.5, -0.3, 0.3, 0.5, 0.3} },
groups = {axey=1,handy=1, attached_node = 1, not_in_creative_inventory = 1, not_in_craft_guide = 1, material_wood=1 },
stack_max = 16,
sounds = node_sounds,
drop = "", -- Item drops are handled in entity code
on_destruct = on_destruct_standing_banner,
on_punch = function(pos, node)
respawn_banner_entity(pos, node)
end,
_mcl_hardness = 1,
_mcl_blast_resistance = 5,
})
-- Hanging banner node
minetest.register_node("mcl_banners:hanging_banner", {
walkable = false,
is_ground_content = false,
paramtype = "light",
paramtype2 = "wallmounted",
sunlight_propagates = true,
drawtype = "nodebox",
inventory_image = "mcl_banners_item_base.png",
wield_image = "mcl_banners_item_base.png",
tiles = { "mcl_banners_fallback_wood.png" },
node_box = {
type = "wallmounted",
wall_side = { -0.49, 0.41, -0.49, -0.41, 0.49, 0.49 },
wall_top = { -0.49, 0.41, -0.49, -0.41, 0.49, 0.49 },
wall_bottom = { -0.49, -0.49, -0.49, -0.41, -0.41, 0.49 },
},
selection_box = {type = "wallmounted", wall_side = {-0.5, -0.5, -0.5, -4/16, 0.5, 0.5} },
groups = {axey=1,handy=1, attached_node = 1, not_in_creative_inventory = 1, not_in_craft_guide = 1, material_wood=1 },
stack_max = 16,
sounds = node_sounds,
drop = "", -- Item drops are handled in entity code
on_destruct = on_destruct_hanging_banner,
on_punch = function(pos, node)
respawn_banner_entity(pos, node)
end,
_mcl_hardness = 1,
_mcl_blast_resistance = 5,
on_rotate = on_rotate,
})
for colorid, colortab in pairs(mcl_banners.colors) do
local itemid = colortab[1]
local desc = colortab[2]
local wool = colortab[3]
local colorize = colortab[4]
local itemstring = "mcl_banners:banner_item_"..itemid
local inv
if colorize then
inv = "mcl_banners_item_base.png^(mcl_banners_item_overlay.png^[colorize:"..colorize..")"
else
inv = "mcl_banners_item_base.png^mcl_banners_item_overlay.png"
end
-- Banner items.
-- This is the player-visible banner item. It comes in 16 base colors.
-- The multiple items are really only needed for the different item images.
-- TODO: Combine the items into only 1 item.
minetest.register_craftitem(itemstring, {
description = desc,
_doc_items_create_entry = false,
inventory_image = inv,
wield_image = inv,
-- Banner group groups together the banner items, but not the nodes.
-- Used for crafting.
groups = { banner = 1, deco_block = 1, },
stack_max = 16,
on_place = function(itemstack, placer, pointed_thing)
local above = pointed_thing.above
local under = pointed_thing.under
local node_under = minetest.get_node(under)
if placer and not placer:get_player_control().sneak then
-- Use pointed node's on_rightclick function first, if present
if minetest.registered_nodes[node_under.name] and minetest.registered_nodes[node_under.name].on_rightclick then
return minetest.registered_nodes[node_under.name].on_rightclick(under, node_under, placer, itemstack) or itemstack
end
if minetest.get_modpath("mcl_cauldrons") then
-- Use banner on cauldron to remove the top-most layer. This reduces the water level by 1.
local new_node
if node_under.name == "mcl_cauldrons:cauldron_3" then
new_node = "mcl_cauldrons:cauldron_2"
elseif node_under.name == "mcl_cauldrons:cauldron_2" then
new_node = "mcl_cauldrons:cauldron_1"
elseif node_under.name == "mcl_cauldrons:cauldron_1" then
new_node = "mcl_cauldrons:cauldron"
elseif node_under.name == "mcl_cauldrons:cauldron_3r" then
new_node = "mcl_cauldrons:cauldron_2r"
elseif node_under.name == "mcl_cauldrons:cauldron_2r" then
new_node = "mcl_cauldrons:cauldron_1r"
elseif node_under.name == "mcl_cauldrons:cauldron_1r" then
new_node = "mcl_cauldrons:cauldron"
end
if new_node then
local imeta = itemstack:get_meta()
local layers_raw = imeta:get_string("layers")
local layers = minetest.deserialize(layers_raw)
if type(layers) == "table" and #layers > 0 then
table.remove(layers)
imeta:set_string("layers", minetest.serialize(layers))
local newdesc = mcl_banners.make_advanced_banner_description(itemstack:get_definition().description, layers)
local mname = imeta:get_string("name")
-- Don't change description if item has a name
if mname == "" then
imeta:set_string("description", newdesc)
end
end
-- Washing off reduces the water level by 1.
-- (It is possible to waste water if the banner had 0 layers.)
minetest.set_node(pointed_thing.under, {name=new_node})
-- Play sound (from mcl_potions mod)
minetest.sound_play("mcl_potions_bottle_pour", {pos=pointed_thing.under, gain=0.5, max_hear_range=16})
return itemstack
end
end
end
-- Place the node!
local hanging = false
-- Standing or hanging banner. The placement rules are enforced by the node definitions
local _, success = minetest.item_place_node(ItemStack("mcl_banners:standing_banner"), placer, pointed_thing)
if not success then
-- Forbidden on ceiling
if pointed_thing.under.y ~= pointed_thing.above.y then
return itemstack
end
_, success = minetest.item_place_node(ItemStack("mcl_banners:hanging_banner"), placer, pointed_thing)
if not success then
return itemstack
end
hanging = true
end
local place_pos
if minetest.registered_nodes[node_under.name].buildable_to then
place_pos = under
else
place_pos = above
end
local bnode = minetest.get_node(place_pos)
if bnode.name ~= "mcl_banners:standing_banner" and bnode.name ~= "mcl_banners:hanging_banner" then
minetest.log("error", "[mcl_banners] The placed banner node is not what the mod expected!")
return itemstack
end
local meta = minetest.get_meta(place_pos)
local inv = meta:get_inventory()
inv:set_size("banner", 1)
local store_stack = ItemStack(itemstack)
store_stack:set_count(1)
inv:set_stack("banner", 1, store_stack)
-- Spawn entity
local entity_place_pos
if hanging then
entity_place_pos = vector.add(place_pos, hanging_banner_entity_offset)
else
entity_place_pos = vector.add(place_pos, standing_banner_entity_offset)
end
local banner_entity = spawn_banner_entity(entity_place_pos, hanging, itemstack)
-- Set rotation
local final_yaw, rotation_level
if hanging then
local pdir = vector.direction(pointed_thing.under, pointed_thing.above)
final_yaw = minetest.dir_to_yaw(pdir)
if pdir.x > 0 then
rotation_level = 4
elseif pdir.z > 0 then
rotation_level = 8
elseif pdir.x < 0 then
rotation_level = 12
else
rotation_level = 0
end
else
-- Determine the rotation based on player's yaw
local yaw = placer:get_look_horizontal()
-- Select one of 16 possible rotations (0-15)
rotation_level = round((yaw / (math.pi*2)) * 16)
if rotation_level >= 16 then
rotation_level = 0
end
final_yaw = (rotation_level * (math.pi/8)) + math.pi
end
meta:set_int("rotation_level", rotation_level)
if banner_entity ~= nil then
banner_entity:set_yaw(final_yaw)
end
if not minetest.settings:get_bool("creative_mode") then
itemstack:take_item()
end
minetest.sound_play({name="default_place_node_hard", gain=1.0}, {pos = place_pos})
return itemstack
end,
_mcl_generate_description = function(itemstack)
local meta = itemstack:get_meta()
local layers_raw = meta:get_string("layers")
if not layers_raw then
return nil
end
local layers = minetest.deserialize(layers_raw)
local desc = itemstack:get_definition().description
local newdesc = mcl_banners.make_advanced_banner_description(desc, layers)
meta:set_string("description", newdesc)
return newdesc
end,
})
if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_wool") then
minetest.register_craft({
output = itemstring,
recipe = {
{ wool, wool, wool },
{ wool, wool, wool },
{ "", "mcl_core:stick", "" },
}
})
end
if minetest.get_modpath("doc") then
-- Add item to node alias
doc.add_entry_alias("nodes", "mcl_banners:standing_banner", "craftitems", itemstring)
end
end
if minetest.get_modpath("doc") then
-- Add item to node alias
doc.add_entry_alias("nodes", "mcl_banners:standing_banner", "nodes", "mcl_banners:hanging_banner")
end
-- Banner entities.
local entity_standing = {
physical = false,
collide_with_objects = false,
visual = "mesh",
mesh = "amc_banner.b3d",
visual_size = { x=2.499, y=2.499 },
textures = make_banner_texture(),
collisionbox = { 0, 0, 0, 0, 0, 0 },
_base_color = nil, -- base color of banner
_layers = nil, -- table of layers painted over the base color.
-- This is a table of tables with each table having the following fields:
-- color: layer color ID (see colors table above)
-- pattern: name of pattern (see list above)
get_staticdata = function(self)
local out = { _base_color = self._base_color, _layers = self._layers, _name = self._name }
return minetest.serialize(out)
end,
on_activate = function(self, staticdata)
if staticdata and staticdata ~= "" then
local inp = minetest.deserialize(staticdata)
self._base_color = inp._base_color
self._layers = inp._layers
self._name = inp._name
self.object:set_properties({
textures = make_banner_texture(self._base_color, self._layers),
})
end
-- Make banner slowly swing
self.object:set_animation({x=0, y=80}, 25)
self.object:set_armor_groups({immortal=1})
end,
-- Set the banner textures. This function can be used by external mods.
-- Meaning of parameters:
-- * self: Lua entity reference to entity.
-- * other parameters: Same meaning as in make_banner_texture
_set_textures = function(self, base_color, layers)
if base_color then
self._base_color = base_color
end
if layers then
self._layers = layers
end
self.object:set_properties({textures = make_banner_texture(self._base_color, self._layers)})
end,
}
minetest.register_entity("mcl_banners:standing_banner", entity_standing)
local entity_hanging = table.copy(entity_standing)
entity_hanging.mesh = "amc_banner_hanging.b3d"
minetest.register_entity("mcl_banners:hanging_banner", entity_hanging)
-- FIXME: Prevent entity destruction by /clearobjects
minetest.register_lbm({
label = "Respawn banner entities",
name = "mcl_banners:respawn_entities",
run_at_every_load = true,
nodenames = {"mcl_banners:standing_banner", "mcl_banners:hanging_banner"},
action = function(pos, node)
respawn_banner_entity(pos, node)
end,
})
minetest.register_craft({
type = "fuel",
recipe = "group:banner",
burntime = 15,
})