Add inner nodeboxes for banner nodes
[MineClone/MineClone2.git] / mods / ITEMS / mcl_banners / init.lua
blob610f8b7116fa2ca00233f56d19d9da5311d1d9ec
1 local node_sounds
2 if minetest.get_modpath("mcl_sounds") then
3 node_sounds = mcl_sounds.node_sound_wood_defaults()
4 end
6 -- Helper function
7 local function round(num, idp)
8 local mult = 10^(idp or 0)
9 return math.floor(num * mult + 0.5) / mult
10 end
12 mcl_banners = {}
14 mcl_banners.colors = {
15 -- Format:
16 -- [ID] = { banner description, wool, unified dyes color group, overlay color, dye, color name for emblazonings }
17 ["unicolor_white"] = {"white", "White Banner", "mcl_wool:white", "#FFFFFF", "mcl_dye:white", "White" },
18 ["unicolor_darkgrey"] = {"grey", "Grey Banner", "mcl_wool:grey", "#303030", "mcl_dye:dark_grey", "Grey" },
19 ["unicolor_grey"] = {"silver", "Light Grey Banner", "mcl_wool:silver", "#5B5B5B", "mcl_dye:grey", "Light Grey" },
20 ["unicolor_black"] = {"black", "Black Banner", "mcl_wool:black", "#000000", "mcl_dye:black", "Black" },
21 ["unicolor_red"] = {"red", "Red Banner", "mcl_wool:red", "#BC0000", "mcl_dye:red", "Red" },
22 ["unicolor_yellow"] = {"yellow", "Yellow Banner", "mcl_wool:yellow", "#E6CD00", "mcl_dye:yellow", "Yellow" },
23 ["unicolor_dark_green"] = {"green", "Green Banner", "mcl_wool:green", "#006000", "mcl_dye:dark_green", "Green" },
24 ["unicolor_cyan"] = {"cyan", "Cyan Banner", "mcl_wool:cyan", "#00ACAC", "mcl_dye:cyan", "Cyan" },
25 ["unicolor_blue"] = {"blue", "Blue Banner", "mcl_wool:blue", "#0000AC", "mcl_dye:blue", "Blue" },
26 ["unicolor_red_violet"] = {"magenta", "Magenta Banner", "mcl_wool:magenta", "#AC007C", "mcl_dye:magenta", "Magenta"},
27 ["unicolor_orange"] = {"orange", "Orange Banner", "mcl_wool:orange", "#E67300", "mcl_dye:orange", "Orange" },
28 ["unicolor_violet"] = {"purple", "Purple Banner", "mcl_wool:purple", "#6400AC", "mcl_dye:violet", "Violet" },
29 ["unicolor_brown"] = {"brown", "Brown Banner", "mcl_wool:brown", "#603000", "mcl_dye:brown", "Brown" },
30 ["unicolor_pink"] = {"pink", "Pink Banner", "mcl_wool:pink", "#DE557C", "mcl_dye:pink", "Pink" },
31 ["unicolor_lime"] = {"lime", "Lime Banner", "mcl_wool:lime", "#30AC00", "mcl_dye:green", "Lime" },
32 ["unicolor_light_blue"] = {"light_blue", "Light Blue Banner", "mcl_wool:light_blue", "#4040CF", "mcl_dye:lightblue", "Light Blue" },
35 -- Add pattern/emblazoning crafting recipes
36 dofile(minetest.get_modpath("mcl_banners").."/patterncraft.lua")
38 -- Overlay ratios (0-255)
39 local base_color_ratio = 224
40 local layer_ratio = 255
42 local standing_banner_entity_offset = { x=0, y=-0.499, z=0 }
43 local hanging_banner_entity_offset = { x=0, y=-1.7, z=0 }
45 local on_destruct_standing_banner = function(pos)
46 -- Find this node's banner entity and make it drop as an item
47 local checkpos = vector.add(pos, standing_banner_entity_offset)
48 local objects = minetest.get_objects_inside_radius(checkpos, 0.5)
49 for _, v in ipairs(objects) do
50 local ent = v:get_luaentity()
51 if ent and ent.name == "mcl_banners:standing_banner" then
52 v:get_luaentity():_drop()
53 end
54 end
55 end
57 local on_destruct_hanging_banner = function(pos)
58 -- Find this node's banner entity and make it drop as an item
59 local checkpos = vector.add(pos, hanging_banner_entity_offset)
60 local objects = minetest.get_objects_inside_radius(checkpos, 0.5)
61 for _, v in ipairs(objects) do
62 local ent = v:get_luaentity()
63 if ent and ent.name == "mcl_banners:hanging_banner" then
64 v:get_luaentity():_drop()
65 end
66 end
67 end
69 local make_banner_texture = function(base_color, layers)
70 local colorize
71 if mcl_banners.colors[base_color] then
72 colorize = mcl_banners.colors[base_color][4]
73 end
74 if colorize then
75 -- Base texture with base color
76 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)"
78 -- Optional pattern layers
79 if layers then
80 local finished_banner = base
81 for l=1, #layers do
82 local layerinfo = layers[l]
83 local pattern = "mcl_banners_" .. layerinfo.pattern .. ".png"
84 local color = mcl_banners.colors[layerinfo.color][4]
86 -- Generate layer texture
87 local layer = "(("..pattern.."^[colorize:"..color..":"..layer_ratio..")^[mask:"..pattern..")"
89 finished_banner = finished_banner .. "^" .. layer
90 end
91 return { finished_banner }
92 end
93 return { base }
94 else
95 return { "mcl_banners_banner_base.png" }
96 end
97 end
99 local on_rotate
100 if minetest.get_modpath("screwdriver") then
101 on_rotate = screwdriver.disallow
104 -- Banner nodes.
105 -- These are an invisible nodes which are only used to destroy the banner entity.
106 -- All the important banner information (such as color) is stored in the entity.
107 -- It is used only used internally.
109 -- Standing banner node
110 -- This one is also used for the help entry to avoid spamming the help with 16 entries.
111 minetest.register_node("mcl_banners:standing_banner", {
112 _doc_items_entry_name = "Banner",
113 _doc_items_image = "mcl_banners_item_base.png^mcl_banners_item_overlay.png",
114 _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.",
115 _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.",
116 walkable = false,
117 is_ground_content = false,
118 paramtype = "light",
119 sunlight_propagates = true,
120 drawtype = "nodebox",
121 -- Nodebox is drawn as fallback when the entity is missing, so that the
122 -- banner node is never truly invisible.
123 -- If the entity is drawn, the nodebox disappears within the real banner mesh.
124 node_box = {
125 type = "fixed",
126 fixed = { -1/32, -0.49, -1/32, 1/32, 1.49, 1/32 },
128 -- This texture is based on the banner base texture
129 tiles = { "mcl_banners_fallback_wood.png" },
131 inventory_image = "mcl_banners_item_base.png",
132 wield_image = "mcl_banners_item_base.png",
134 selection_box = {type = "fixed", fixed= {-0.3, -0.5, -0.3, 0.3, 0.5, 0.3} },
135 groups = {axey=1,handy=1, attached_node = 1, not_in_creative_inventory = 1, not_in_craft_guide = 1, material_wood=1 },
136 stack_max = 16,
137 sounds = node_sounds,
138 drop = "", -- Item drops are handled in entity code
140 on_destruct = on_destruct_standing_banner,
141 _mcl_hardness = 1,
142 _mcl_blast_resistance = 5,
145 -- Hanging banner node
146 minetest.register_node("mcl_banners:hanging_banner", {
147 walkable = false,
148 is_ground_content = false,
149 paramtype = "light",
150 paramtype2 = "wallmounted",
151 sunlight_propagates = true,
152 drawtype = "nodebox",
153 inventory_image = "mcl_banners_item_base.png",
154 wield_image = "mcl_banners_item_base.png",
155 tiles = { "mcl_banners_fallback_wood.png" },
156 node_box = {
157 type = "wallmounted",
158 wall_side = { -0.49, 0.41, -0.49, -0.41, 0.49, 0.49 },
159 wall_top = { -0.49, 0.41, -0.49, -0.41, 0.49, 0.49 },
160 wall_bottom = { -0.49, -0.49, -0.49, -0.41, -0.41, 0.49 },
162 selection_box = {type = "wallmounted", wall_side = {-0.5, -0.5, -0.5, -4/16, 0.5, 0.5} },
163 groups = {axey=1,handy=1, attached_node = 1, not_in_creative_inventory = 1, not_in_craft_guide = 1, material_wood=1 },
164 stack_max = 16,
165 sounds = node_sounds,
166 drop = "", -- Item drops are handled in entity code
168 on_destruct = on_destruct_hanging_banner,
169 _mcl_hardness = 1,
170 _mcl_blast_resistance = 5,
171 on_rotate = on_rotate,
174 for colorid, colortab in pairs(mcl_banners.colors) do
175 local itemid = colortab[1]
176 local desc = colortab[2]
177 local wool = colortab[3]
178 local colorize = colortab[4]
180 local itemstring = "mcl_banners:banner_item_"..itemid
181 local inv
182 if colorize then
183 inv = "mcl_banners_item_base.png^(mcl_banners_item_overlay.png^[colorize:"..colorize..")"
184 else
185 inv = "mcl_banners_item_base.png^mcl_banners_item_overlay.png"
188 -- Banner items.
189 -- This is the player-visible banner item. It comes in 16 base colors.
190 -- The multiple items are really only needed for the different item images.
191 -- TODO: Combine the items into only 1 item.
192 minetest.register_craftitem(itemstring, {
193 description = desc,
194 _doc_items_create_entry = false,
195 inventory_image = inv,
196 wield_image = inv,
197 -- Banner group groups together the banner items, but not the nodes.
198 -- Used for crafting.
199 groups = { banner = 1, deco_block = 1, },
200 stack_max = 16,
202 on_place = function(itemstack, placer, pointed_thing)
203 local above = pointed_thing.above
204 local under = pointed_thing.under
206 local node_under = minetest.get_node(under)
207 if placer and not placer:get_player_control().sneak then
208 -- Use pointed node's on_rightclick function first, if present
209 if minetest.registered_nodes[node_under.name] and minetest.registered_nodes[node_under.name].on_rightclick then
210 return minetest.registered_nodes[node_under.name].on_rightclick(under, node_under, placer, itemstack) or itemstack
213 if minetest.get_modpath("mcl_cauldrons") then
214 -- Use banner on cauldron to remove the top-most layer. This reduces the water level by 1.
215 local new_node
216 if node_under.name == "mcl_cauldrons:cauldron_3" then
217 new_node = "mcl_cauldrons:cauldron_2"
218 elseif node_under.name == "mcl_cauldrons:cauldron_2" then
219 new_node = "mcl_cauldrons:cauldron_1"
220 elseif node_under.name == "mcl_cauldrons:cauldron_1" then
221 new_node = "mcl_cauldrons:cauldron"
222 elseif node_under.name == "mcl_cauldrons:cauldron_3r" then
223 new_node = "mcl_cauldrons:cauldron_2r"
224 elseif node_under.name == "mcl_cauldrons:cauldron_2r" then
225 new_node = "mcl_cauldrons:cauldron_1r"
226 elseif node_under.name == "mcl_cauldrons:cauldron_1r" then
227 new_node = "mcl_cauldrons:cauldron"
229 if new_node then
230 local imeta = itemstack:get_meta()
231 local layers_raw = imeta:get_string("layers")
232 local layers = minetest.deserialize(layers_raw)
233 if type(layers) == "table" and #layers > 0 then
234 table.remove(layers)
235 imeta:set_string("layers", minetest.serialize(layers))
236 local newdesc = mcl_banners.make_advanced_banner_description(itemstack:get_definition().description, layers)
237 local mname = imeta:get_string("name")
238 -- Don't change description if item has a name
239 if mname == "" then
240 imeta:set_string("description", newdesc)
244 -- Washing off reduces the water level by 1.
245 -- (It is possible to waste water if the banner had 0 layers.)
246 minetest.set_node(pointed_thing.under, {name=new_node})
248 -- Play sound (from mcl_potions mod)
249 minetest.sound_play("mcl_potions_bottle_pour", {pos=pointed_thing.under, gain=0.5, max_hear_range=16})
251 return itemstack
256 -- Place the node!
257 local hanging = false
259 -- Standing or hanging banner. The placement rules are enforced by the node definitions
260 local _, success = minetest.item_place_node(ItemStack("mcl_banners:standing_banner"), placer, pointed_thing)
261 if not success then
262 -- Forbidden on ceiling
263 if pointed_thing.under.y ~= pointed_thing.above.y then
264 return itemstack
266 _, success = minetest.item_place_node(ItemStack("mcl_banners:hanging_banner"), placer, pointed_thing)
267 if not success then
268 return itemstack
270 hanging = true
273 local place_pos
274 if minetest.registered_nodes[node_under.name].buildable_to then
275 place_pos = under
276 else
277 place_pos = above
279 if hanging then
280 place_pos = vector.add(place_pos, hanging_banner_entity_offset)
281 else
282 place_pos = vector.add(place_pos, standing_banner_entity_offset)
285 local banner
286 if hanging then
287 banner = minetest.add_entity(place_pos, "mcl_banners:hanging_banner")
288 else
289 banner = minetest.add_entity(place_pos, "mcl_banners:standing_banner")
291 local imeta = itemstack:get_meta()
292 local layers_raw = imeta:get_string("layers")
293 local layers = minetest.deserialize(layers_raw)
294 banner:get_luaentity():_set_textures(colorid, layers)
295 local mname = imeta:get_string("name")
296 if mname ~= nil and mname ~= "" then
297 banner:get_luaentity()._item_name = mname
298 banner:get_luaentity()._item_description = imeta:get_string("description")
301 -- Set rotation
302 local final_yaw
303 if hanging then
304 local pdir = vector.direction(pointed_thing.under, pointed_thing.above)
305 final_yaw = minetest.dir_to_yaw(pdir)
306 else
307 -- Determine the rotation based on player's yaw
308 local yaw = placer:get_look_horizontal()
309 -- Select one of 16 possible rotations (0-15)
310 local rotation_level = round((yaw / (math.pi*2)) * 16)
311 final_yaw = (rotation_level * (math.pi/8)) + math.pi
313 banner:set_yaw(final_yaw)
315 if not minetest.settings:get_bool("creative_mode") then
316 itemstack:take_item()
318 minetest.sound_play({name="default_place_node_hard", gain=1.0}, {pos = place_pos})
320 return itemstack
321 end,
323 _mcl_generate_description = function(itemstack)
324 local meta = itemstack:get_meta()
325 local layers_raw = meta:get_string("layers")
326 if not layers_raw then
327 return nil
329 local layers = minetest.deserialize(layers_raw)
330 local desc = itemstack:get_definition().description
331 local newdesc = mcl_banners.make_advanced_banner_description(desc, layers)
332 meta:set_string("description", newdesc)
333 return newdesc
334 end,
337 if minetest.get_modpath("mcl_core") and minetest.get_modpath("mcl_wool") then
338 minetest.register_craft({
339 output = itemstring,
340 recipe = {
341 { wool, wool, wool },
342 { wool, wool, wool },
343 { "", "mcl_core:stick", "" },
348 if minetest.get_modpath("doc") then
349 -- Add item to node alias
350 doc.add_entry_alias("nodes", "mcl_banners:standing_banner", "craftitems", itemstring)
354 if minetest.get_modpath("doc") then
355 -- Add item to node alias
356 doc.add_entry_alias("nodes", "mcl_banners:standing_banner", "nodes", "mcl_banners:hanging_banner")
360 -- Banner entities.
361 local entity_standing = {
362 physical = false,
363 collide_with_objects = false,
364 visual = "mesh",
365 mesh = "amc_banner.b3d",
366 visual_size = { x=2.499, y=2.499 },
367 textures = make_banner_texture(),
368 collisionbox = { 0, 0, 0, 0, 0, 0 },
370 _base_color = nil, -- base color of banner
371 _layers = nil, -- table of layers painted over the base color.
372 -- This is a table of tables with each table having the following fields:
373 -- color: layer color ID (see colors table above)
374 -- pattern: name of pattern (see list above)
376 get_staticdata = function(self)
377 local out = { _base_color = self._base_color, _layers = self._layers, _name = self._name }
378 return minetest.serialize(out)
379 end,
380 on_activate = function(self, staticdata)
381 if staticdata and staticdata ~= "" then
382 local inp = minetest.deserialize(staticdata)
383 self._base_color = inp._base_color
384 self._layers = inp._layers
385 self._name = inp._name
386 self.object:set_properties({
387 textures = make_banner_texture(self._base_color, self._layers),
390 -- Make banner slowly swing
391 self.object:set_animation({x=0, y=80}, 25)
392 self.object:set_armor_groups({immortal=1})
393 end,
395 -- This is a custom function which causes the banner to be dropped as item and destroys the entity.
396 _drop = function(self)
397 local pos = self.object:getpos()
398 pos.y = pos.y + 1
400 if not minetest.settings:get_bool("creative_mode") and self._base_color then
401 -- Spawn item
402 local banner = ItemStack("mcl_banners:banner_item_"..mcl_banners.colors[self._base_color][1])
403 local meta = banner:get_meta()
404 meta:set_string("layers", minetest.serialize(self._layers))
405 if self._item_name ~= nil and self._item_name ~= "" then
406 meta:set_string("description", self._item_description)
407 meta:set_string("name", self._item_name)
408 else
409 meta:set_string("description", mcl_banners.make_advanced_banner_description(banner:get_definition().description, self._layers))
412 minetest.add_item(pos, banner)
415 -- Destroy entity
416 self.object:remove()
417 end,
419 -- Set the banner textures. This function can be used by external mods.
420 -- Meaning of parameters:
421 -- * self: Lua entity reference to entity.
422 -- * other parameters: Same meaning as in make_banner_texture
423 _set_textures = function(self, base_color, layers)
424 if base_color then
425 self._base_color = base_color
427 if layers then
428 self._layers = layers
430 self.object:set_properties({textures = make_banner_texture(self._base_color, self._layers)})
431 end,
433 minetest.register_entity("mcl_banners:standing_banner", entity_standing)
435 local entity_hanging = table.copy(entity_standing)
436 entity_hanging.mesh = "amc_banner_hanging.b3d"
437 minetest.register_entity("mcl_banners:hanging_banner", entity_hanging)
439 minetest.register_craft({
440 type = "fuel",
441 recipe = "group:banner",
442 burntime = 15,