diff --git a/builtin/common/item_s.lua b/builtin/common/item_s.lua index 2761e41dd..bcb3669e2 100644 --- a/builtin/common/item_s.lua +++ b/builtin/common/item_s.lua @@ -180,6 +180,25 @@ function core.strip_param2_color(param2, paramtype2) end end +function core.strip_param2_variant(param2, def) + if not def or def.variant_count <= 1 or not def.param2_variant then + return 0 + end + local bf = def.param2_variant + local right_mask = bit.lshift(1, bf.width) - 1 + return bit.band(bit.rshift(param2, bf.offset), right_mask) % def.variant_count +end + +function core.set_param2_variant(param2, variant, def) + if not def or not def.param2_variant then + return param2 + end + local bf = def.param2_variant + local mask = bit.lshift(bit.lshift(1, bf.width) - 1, bf.offset) + local new_bits = bit.band(bit.lshift(variant, bf.offset), mask) + return bit.bor(bit.band(param2, bit.bnot(mask)), new_bits) +end + -- Content ID caching local old_get_content_id = core.get_content_id diff --git a/builtin/game/features.lua b/builtin/game/features.lua index 1b329c7a3..086e436b4 100644 --- a/builtin/game/features.lua +++ b/builtin/game/features.lua @@ -47,6 +47,7 @@ core.features = { particle_blend_clip = true, remove_item_match_meta = true, httpfetch_additional_methods = true, + texture_variants = true, } function core.has_feature(arg) diff --git a/builtin/game/item.lua b/builtin/game/item.lua index 4d68f1136..03ffacd3e 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -52,14 +52,18 @@ function core.get_node_drops(node, toolname) local ptype = def and def.paramtype2 -- get color, if there is color (otherwise nil) local palette_index = core.strip_param2_color(param2, ptype) + -- get variant (always a number) + local variant = core.strip_param2_variant(param2, def) if drop == nil then -- default drop + local itemstring = nodename if palette_index then - local stack = ItemStack(nodename) - stack:get_meta():set_int("palette_index", palette_index) - return {stack:to_string()} + itemstring = core.itemstring_with_palette(itemstring, palette_index) end - return {nodename} + if variant > 0 then + itemstring = core.itemstring_with_variant(itemstring, variant) + end + return {itemstring} elseif type(drop) == "string" then -- itemstring drop return drop ~= "" and {drop} or {} @@ -118,9 +122,10 @@ function core.get_node_drops(node, toolname) for _, add_item in ipairs(item.items) do -- add color, if necessary if item.inherit_color and palette_index then - local stack = ItemStack(add_item) - stack:get_meta():set_int("palette_index", palette_index) - add_item = stack:to_string() + add_item = core.itemstring_with_palette(add_item, palette_index) + end + if item.inherit_variant then + add_item = core.itemstring_with_variant(add_item, variant) end got_items[#got_items+1] = add_item end @@ -270,6 +275,13 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2, end end + -- Transfer variant + if not def.place_param2 and def.variant_count > 1 then + local variant = math.min(math.max(math.floor(metatable.variant or 0), 0), + def.variant_count - 1) + newnode.param2 = core.set_param2_variant(newnode.param2, variant, def) + end + -- Check if the node is attached and if it can be placed there local an = core.get_item_group(def.name, "attached_node") if an ~= 0 and @@ -604,6 +616,12 @@ function core.itemstring_with_color(item, colorstring) return stack:to_string() end +function core.itemstring_with_variant(item, variant) + local stack = ItemStack(item) -- convert to ItemStack + stack:get_meta():set_string("variant", variant > 0 and variant or "") + return stack:to_string() +end + -- This is used to allow mods to redefine core.item_place and so on -- NOTE: This is not the preferred way. Preferred way is to provide enough -- callbacks to not require redefining global functions. -celeron55 @@ -625,6 +643,7 @@ core.nodedef_default = { -- name intentionally not defined here description = "", groups = {}, + variant_count = 1, inventory_image = "", wield_image = "", wield_scale = vector.new(1, 1, 1), @@ -655,6 +674,7 @@ core.nodedef_default = { post_effect_color = {a=0, r=0, g=0, b=0}, paramtype = "none", paramtype2 = "none", + param2_variant = {width = 0, offset = 0}, is_ground_content = true, sunlight_propagates = false, walkable = true, @@ -680,6 +700,7 @@ core.craftitemdef_default = { -- name intentionally not defined here description = "", groups = {}, + variant_count = 1, inventory_image = "", wield_image = "", wield_scale = vector.new(1, 1, 1), @@ -700,6 +721,7 @@ core.tooldef_default = { -- name intentionally not defined here description = "", groups = {}, + variant_count = 1, inventory_image = "", wield_image = "", wield_scale = vector.new(1, 1, 1), @@ -720,6 +742,7 @@ core.noneitemdef_default = { -- This is used for the hand and unknown items -- name intentionally not defined here description = "", groups = {}, + variant_count = 1, inventory_image = "", wield_image = "", wield_scale = vector.new(1, 1, 1), diff --git a/doc/lua_api.md b/doc/lua_api.md index ca3a5fee7..a988f8804 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -1109,6 +1109,63 @@ core.register_node("default:dirt_with_grass", { }) ``` +Variants +-------- + +Items can have "variants", which are numbered states that can determine certain +properties. The number of variants is specified with the item definition field +`variant_count`. Each variant is numbered from 0 to `variant_count` - 1. Every +item has at least one variant (numbered 0). Nodes can specify a part of param2 +to read as the variant number by setting the `param2_variant` field to a +`BitField`. + +The currently supported variant properties are: +* `tiles` +* `overlay_tiles` +* `special_tiles` +The `variants` table in the item definition is a mapping from +variant numbers to variant tables. These tables can include the aforementioned +fields to set the properties for particular variants. Variants not present in +the mapping default to the values of the aforementioned fields specified in the +item definition table. Old clients will receive only the variant 0 tiles. + +Items with multiple variants can specify a variant number with the "variant" key +in their metadata. The absence of the key indicates a variant number of 0, and +this is the canonical representation of the 0 variant. The helper function +`core.itemstring_with_variant` is a shortcut for creating such itemstrings. +The variant is preserved when a node is placed or dug. Custom drops will inherit +the variant only if `inherit_variant` is set to `true` in their specification. + +Example node with variants: + + core.register_node("mod:grass", { + description = "Grass", + drawtype = "plantlike", + paramtype = "light", + sunlight_propagates = true, + walkable = false, + groups = {dig_immediate = 3}, + -- There are 4 variants numbered 0 to 3. + variant_count = 4, + -- The lowest 2 bits store the variant number. + param2_variant = {width = 2, offset = 0}, + -- These tiles will be used for variants not otherwise specified, + -- in this case variant 0. + tiles = {"mod_grass1.png"}, + -- Tiles for variants 1, 2, and 3 are specified here. + variants = { + {tiles = {"mod_grass2.png"}}, + {tiles = {"mod_grass3.png"}}, + {tiles = {"mod_grass4.png"}}, + }, + drop = { + items = { + -- The seeds will inherit the variant of the grass. + {items = {"mod:seeds"}, inherit_variant = true}, + } + } + }) + Sounds @@ -1991,6 +2048,13 @@ Exact pointing location (currently only `Raycast` supports these fields): For entities with rotated selection boxes, this will be rotated properly by the entity's rotation - it will always be in absolute world space. +`BitField` +---------- + +A `BitField` specifies a part of an unsigned integer whose bits represent +another unsigned integer. A `BitField` is represented as follows: + + {width = , offset = } @@ -2715,6 +2779,8 @@ Some of the values in the key-value store are handled specially: * `color`: A `ColorString`, which sets the stack's color. * `palette_index`: If the item has a palette, this is used to get the current color from the palette. +* `variant`: If the item has more than one variant, this is the variant number. + The canonical form of variant 0 is the absence of this key. * `count_meta`: Replace the displayed count with any string. * `count_alignment`: Set the alignment of the displayed count value. This is an int value. The lowest 2 bits specify the alignment in x-direction, the 3rd and @@ -5810,6 +5876,8 @@ Utilities remove_item_match_meta = true, -- The HTTP API supports the HEAD and PATCH methods (5.12.0) httpfetch_additional_methods = true, + -- Node/Item texture variants is supported (5.13.0) + texture_variants = true, } ``` @@ -6962,11 +7030,23 @@ Item handling given `param2` value. * Returns `nil` if the given `paramtype2` does not contain color information. +<<<<<<< HEAD * `core.get_node_drops(node, toolname[, tool, digger, pos])` * Returns list of itemstrings that are dropped by `node` when dug with the item `toolname` (not limited to tools). The default implementation doesn't use `tool`, `digger`, and `pos`, but these are provided by `core.node_dig` since 5.12.0 for games/mods implementing customized drops. +======= +* `core.strip_param2_variant(param2, def)` + * Returns the variant from `param2` with the given node definition `def`. + * Always returns a non-negative integer less than `def.variant_count`. +* `core.set_param2_variant(param2, variant, def)` + * Returns a modified `param2` with the variant bitfield set to `variant` + with the given node definition `def`. +* `core.get_node_drops(node, toolname)` + * Returns list of itemstrings that are dropped by `node` when dug + with the item `toolname` (not limited to tools). +>>>>>>> 324240af9 (Add node texture variants) * `node`: node as table or node name * `toolname`: name of the item used to dig (can be `nil`) * `tool`: `ItemStack` used to dig (can be `nil`) @@ -7035,6 +7115,11 @@ Item handling * `item`: the item stack which becomes colored. Can be in string, table and native form. * `colorstring`: the new color of the item stack +* `core.itemstring_with_variant(item, variant)`: returns an item string + * Creates an item string with an associated item variant. + * `item`: the item stack which is given the variant. Can be in string, + table or native form. + * `variant`: the new variant of the item stack Rollback -------- @@ -9779,6 +9864,9 @@ Used by `core.register_node`, `core.register_craftitem`, and -- {bendy = 2, snappy = 1}, -- {hard = 1, metal = 1, spikes = 1} + variant_count = 1, + -- The number item variants, a positive integer. + inventory_image = "", -- Texture shown in the inventory GUI -- Defaults to a 3D rendering of the node if left empty. @@ -10003,6 +10091,9 @@ Used by `core.register_node`. { -- + param2_variant = BitField, + -- The part of param2 from which to read the variant number. + drawtype = "normal", -- See "Node drawtypes" visual_scale = 1.0, @@ -10016,6 +10107,7 @@ Used by `core.register_node`. tiles = {tile definition 1, def2, def3, def4, def5, def6}, -- Textures of node; +Y, -Y, +X, -X, +Z, -Z -- List can be shortened to needed length. + -- This field is also used for Variant number 0, see "Variants" for details. overlay_tiles = {tile definition 1, def2, def3, def4, def5, def6}, -- Same as `tiles`, but these textures are drawn on top of the base @@ -10023,10 +10115,37 @@ Used by `core.register_node`. -- texture. If the texture name is an empty string, that overlay is not -- drawn. Since such tiles are drawn twice, it is not recommended to use -- overlays on very common nodes. + -- This field is also used for Variant number 0, see "Variants" for details. special_tiles = {tile definition 1, Tile definition 2}, -- Special textures of node; used rarely. -- List can be shortened to needed length. + -- This field is also used for Variant number 0, see "Variants" for details. + + -- See "Variants" + -- This field is optional. + variants = { + -- Variant number 0 is created from fields + -- tiles, overlay_tiles and special_tiles + -- defined above (outside from variants table). + { -- Variant number 1. + -- this field is reused from above definition if is not specified. + tiles = {tile definition 1, def2, def3, def4, def5, def6}, + + -- this field is reused from above definition if is not specified. + overlay_tiles = {def1, def2, def3, def4, def5, def6}, + + -- this field is reused from above definition if is not specified. + special_tiles = {def1, def2}, + }, + { -- Variant number 2. + -- reuse tiles and special_tiles from variant number 0 + -- no overlay_tiles + overlay_tiles = {}, + ... + }, + ... + }, color = ColorSpec, -- The node's original color will be multiplied with this color. @@ -10311,6 +10430,9 @@ Used by `core.register_node`. -- hardware coloring palette color from the dug node. -- Default is 'false'. inherit_color = true, + -- variant of the dug node. + -- Default is 'false'. + inherit_variant = true, }, { -- Only drop if using an item whose name contains diff --git a/games/devtest/mods/testnodes/drawtypes.lua b/games/devtest/mods/testnodes/drawtypes.lua index 95dae96dc..5e24e3eeb 100644 --- a/games/devtest/mods/testnodes/drawtypes.lua +++ b/games/devtest/mods/testnodes/drawtypes.lua @@ -25,6 +25,23 @@ core.register_node("testnodes:normal", { groups = { dig_immediate = 3 }, }) +-- A regular cube with tiles using color +core.register_node("testnodes:normal_with_color", { + description = S("\"normal\" Drawtype Test Node").."\n".. + S("Opaque texture with color parameter"), + drawtype = "normal", + tiles = { + { name = "testnodes_normal.png", color = "#A00" }, + { name = "testnodes_normal.png", color = "#00B" }, + { name = "testnodes_normal.png", color = "#0C0" }, + { name = "testnodes_normal.png", color = "#DD0" }, + { name = "testnodes_normal.png", color = "#0EE" }, + { name = "testnodes_normal.png", color = "#F0F" }, + }, + + groups = { dig_immediate = 3 }, +}) + -- Standard glasslike node core.register_node("testnodes:glasslike", { description = S("\"glasslike\" Drawtype Test Node").."\n".. diff --git a/games/devtest/mods/testnodes/init.lua b/games/devtest/mods/testnodes/init.lua index 49a45e08c..7394cd657 100644 --- a/games/devtest/mods/testnodes/init.lua +++ b/games/devtest/mods/testnodes/init.lua @@ -10,4 +10,5 @@ dofile(path.."/liquids.lua") dofile(path.."/light.lua") dofile(path.."/textures.lua") dofile(path.."/overlays.lua") +dofile(path.."/variants.lua") dofile(path.."/commands.lua") diff --git a/games/devtest/mods/testnodes/overlays.lua b/games/devtest/mods/testnodes/overlays.lua index 7f7a1cd72..0a1ac1d8f 100644 --- a/games/devtest/mods/testnodes/overlays.lua +++ b/games/devtest/mods/testnodes/overlays.lua @@ -7,6 +7,20 @@ core.register_node("testnodes:overlay", { overlay_tiles = {{name = "testnodes_overlay.png"}}, groups = { dig_immediate = 2 }, }) +core.register_node("testnodes:overlay_tile_colors", { + description = S("Texture Overlay Test Node, Tile Colors") .. "\n" .. + S("Uncolorized"), + tiles = {{name = "testnodes_overlayable.png"}}, + overlay_tiles = { + {name = "testnodes_overlay.png", color = "#F00"}, + {name = "testnodes_overlay.png", color = "#0F0"}, + {name = "testnodes_overlay.png", color = "#00F"}, + {name = "testnodes_overlay.png", color = "#FF0"}, + {name = "testnodes_overlay.png", color = "#0FF"}, + {name = "testnodes_overlay.png", color = "#F0F"}, + }, + groups = { dig_immediate = 2 }, +}) core.register_node("testnodes:overlay_color_all", { description = S("Texture Overlay Test Node, Colorized") .. "\n" .. S("param2 changes color"), diff --git a/games/devtest/mods/testnodes/variants.lua b/games/devtest/mods/testnodes/variants.lua new file mode 100644 index 000000000..9ce9d54fd --- /dev/null +++ b/games/devtest/mods/testnodes/variants.lua @@ -0,0 +1,290 @@ +-- This file is for variant properties. + +local S = core.get_translator("testnodes") + +local animated_tile = { + name = "testnodes_anim.png", + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 4.0, + }, +} +core.register_node("testnodes:variant_animated", { + description = S("Variant Animated Test Node").."\n".. + S("Tiles animate from A to D in 4s cycle").."\n".. + S("Has two variants"), + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 2, + param2_variant = {width = 1, offset = 0}, + tiles = { + "testnodes_node.png", animated_tile, + "testnodes_node.png", animated_tile, + "testnodes_node.png", animated_tile, + }, + variants = { + { + tiles = { + animated_tile, "testnodes_node.png", + animated_tile, "testnodes_node.png", + animated_tile, "testnodes_node.png", + }, + }, + }, +}) + +core.register_node("testnodes:variant_color", { + description = S("Variant Color Test Node").."\n".. + S("param2 = color + 3 bit variant").."\n".. + S("Has six unique variants").."\n".. + S("Variants 6 and 7 are same as variant 0"), + paramtype2 = "color", + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 8, -- Last two variants are the same as the first. + param2_variant = {width = 3, offset = 0}, + tiles = {"testnodes_1g.png"}, + variants = { + {tiles = {"testnodes_2g.png"}}, + {tiles = {"testnodes_3g.png"}}, + {tiles = {"testnodes_4g.png"}}, + {tiles = {"testnodes_5g.png"}}, + {tiles = {"testnodes_6g.png"}}, + }, + palette = "testnodes_palette_wallmounted.png", +}) + +core.register_node("testnodes:variant_tile_color", { + description = S("Variant Tile Color Test Node").."\n".. + S("Has seven unique variants").."\n".. + S("Variant 7 is same as variant 0 (red colored)"), + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 8, -- Last one variant are the same as the first. + param2_variant = {width = 3, offset = 0}, + tiles = {{ name = "testnodes_1g.png", color = "#F00"}}, + variants = { + {tiles = {{ name = "testnodes_1g.png", color = "#0F0"}}}, + {tiles = {{ name = "testnodes_1g.png", color = "#00F"}}}, + {tiles = {{ name = "testnodes_1g.png", color = "#FF0"}}}, + {tiles = {{ name = "testnodes_1g.png", color = "#0FF"}}}, + {tiles = {{ name ="testnodes_1g.png", color = "#F0F"}}}, + {tiles = {{ name ="testnodes_1g.png"}}}, + }, +}) + +core.register_node("testnodes:variant_drop", { + description = S("Variant Drop Test Node").."\n".. + S("Has five variants").."\n".. + S("Variiant 1 is copied from variant 0").."\n".. + S("Drops one node with an inherited variant and one without"), + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 5, + param2_variant = {width = 3, offset = 0}, + tiles = {"testnodes_1.png"}, + variants = { + nil, + {tiles = {"testnodes_2.png"}}, + {tiles = {"testnodes_3.png"}}, + {tiles = {"testnodes_4.png"}}, + }, + drop = { + max_items = 2, + items = { + {items = {"testnodes:variant_drop"}, inherit_variant = true}, + {items = {"testnodes:variant_drop"}, inherit_variant = false}, + }, + }, +}) + +core.register_node("testnodes:variant_facedir", { + description = S("Variant Facedir Test Node").."\n".. + S("param2 = 3 bit variant + facedir").."\n".. + S("Has six variants"), + paramtype2 = "facedir", + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 6, + param2_variant = {width = 3, offset = 5}, + tiles = {"testnodes_1f.png"}, + variants = { + {tiles = {"testnodes_2f.png"}}, + {tiles = {"testnodes_3f.png"}}, + {tiles = {"testnodes_4f.png"}}, + {tiles = {"testnodes_5f.png"}}, + {tiles = {"testnodes_6f.png"}}, + }, +}) + +core.register_node("testnodes:variant_falling", { + description = S("Variant Falling Test Node").."\n".. + S("Has six variants"), + is_ground_content = false, + groups = {dig_immediate = 3, falling_node = 1}, + variant_count = 6, + param2_variant = {width = 3, offset = 0}, + tiles = {"testnodes_1.png"}, + variants = { + {tiles = {"testnodes_2.png"}}, + {tiles = {"testnodes_3.png"}}, + {tiles = {"testnodes_4.png"}}, + {tiles = {"testnodes_5.png"}}, + {tiles = {"testnodes_6.png"}}, + }, +}) + +core.register_node("testnodes:variant_falling_torchlike", { + description = S("Variant Falling Torchlike Test Node").."\n".. + S("Has six variants"), + paramtype = "light", + sunlight_propagates = true, + is_ground_content = false, + groups = {dig_immediate = 3, falling_node = 1}, + variant_count = 6, + param2_variant = {width = 3, offset = 0}, + drawtype = "torchlike", + tiles = {"testnodes_1.png"}, + variants = { + {tiles = {"testnodes_2.png"}}, + {tiles = {"testnodes_3.png"}}, + {tiles = {"testnodes_4.png"}}, + {tiles = {"testnodes_5.png"}}, + {tiles = {"testnodes_6.png"}}, + }, +}) + +core.register_node("testnodes:variant_mesh", { + description = S("Variant Mesh Test Node").."\n".. + S("Has ten variants"), + paramtype = "light", + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 10, + param2_variant = {width = 8, offset = 0}, + drawtype = "mesh", + mesh = "testnodes_ocorner.obj", + tiles = {"testnodes_mesh_stripes.png"}, + variants = { + {tiles = {"testnodes_mesh_stripes2.png"}}, + {tiles = {"testnodes_mesh_stripes3.png"}}, + {tiles = {"testnodes_mesh_stripes4.png"}}, + {tiles = {"testnodes_mesh_stripes5.png"}}, + {tiles = {"testnodes_mesh_stripes6.png"}}, + {tiles = {"testnodes_mesh_stripes7.png"}}, + {tiles = {"testnodes_mesh_stripes8.png"}}, + {tiles = {"testnodes_mesh_stripes9.png"}}, + {tiles = {"testnodes_mesh_stripes10.png"}}, + }, +}) + +core.register_node("testnodes:variant_overlay", { + description = S("Variant Overlay Test Node").."\n".. + S("Has four variants"), + S("Variant 1 is same as variant 0"), + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 4, + param2_variant = {width = 2, offset = 0}, + tiles = {"testnodes_node.png"}, + overlay_tiles = {{name = "testnodes_overlay.png", color = "#F00"}}, + variants = { + { + tiles = {"testnodes_node.png"}, + }, + { + tiles = {"testnodes_node.png"}, + overlay_tiles = {{name = "testnodes_overlay.png", color = "#0FF"}}, + }, + { + tiles = {"testnodes_node.png"}, + overlay_tiles = {}, + }, + }, +}) + +core.register_node("testnodes:variant_overlay_color", { + description = S("Variant Overlay Test Node With Node Color").."\n".. + S("Has four variants"), + S("Variant 1 is same as variant 1"), + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 4, + param2_variant = {width = 2, offset = 0}, + tiles = {"testnodes_node.png"}, + overlay_tiles = {{name = "testnodes_overlay.png", color = "#F00"}}, + color = "#00F", + variants = { + { + tiles = {"testnodes_node.png"}, + }, + { + tiles = {"testnodes_node.png"}, + overlay_tiles = {{name = "testnodes_overlay.png", color = "#0FF"}}, + }, + { + tiles = {"testnodes_node.png"}, + overlay_tiles = {}, + }, + }, +}) + +core.register_node("testnodes:variant_plantlike_rooted", { + description = S("Variant Plantlike Rooted Test Node").."\n".. + S("Has six variants"), + paramtype = "light", + is_ground_content = false, + groups = {dig_immediate = 3}, + drawtype = "plantlike_rooted", + variant_count = 8, + param2_variant = {width = 8, offset = 0}, + tiles = {"testnodes_1.png"}, + special_tiles = {"testnodes_6.png"}, + variants = { + {tiles = {"testnodes_2.png"}, special_tiles = {"testnodes_5.png"}}, + {tiles = {"testnodes_3.png"}, special_tiles = {"testnodes_4.png"}}, + {tiles = {"testnodes_4.png"}, special_tiles = {"testnodes_3.png"}}, + {tiles = {"testnodes_5.png"}, special_tiles = {"testnodes_2.png"}}, + {tiles = {"testnodes_6.png"}, special_tiles = {"testnodes_1.png"}}, + {tiles = {"testnodes_6.png"}}, + {special_tiles = {"testnodes_1.png"}}, + }, +}) + +core.register_node("testnodes:variant_raillike", { + description = S("Variant Raillike Test Node").."\n".. + S("Has four variants"), + paramtype = "light", + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 4, + param2_variant = {width = 2, offset = 0}, + drawtype = "raillike", + variants = { + [0] = {tiles = {"testnodes_1.png"}}, + {tiles = {"testnodes_2.png"}}, + {tiles = {"testnodes_3.png"}}, + {tiles = {"testnodes_4.png"}}, + }, +}) + +core.register_node("testnodes:variant_wallmounted", { + description = S("Variant Wallmounted Test Node").."\n".. + S("Has six variants corresponding to direction"), + paramtype = "light", + paramtype2 = "wallmounted", + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 6, + param2_variant = {width = 3, offset = 0}, + tiles = {"testnodes_1w.png"}, + variants = { + {tiles = {"testnodes_2w.png"}}, + {tiles = {"testnodes_3w.png"}}, + {tiles = {"testnodes_4w.png"}}, + {tiles = {"testnodes_5w.png"}}, + {tiles = {"testnodes_6w.png"}}, + }, +}) diff --git a/src/client/content_mapblock.cpp b/src/client/content_mapblock.cpp index 6c1cfea33..fbf20d308 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -98,7 +98,7 @@ void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile_ret) // Returns a special tile, ready for use, non-rotated. void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile_ret, bool apply_crack) { - *tile_ret = cur_node.f->special_tiles[index]; + *tile_ret = cur_node.f->special_tiles[cur_node.n.getVariant(*cur_node.f)][index]; TileLayer *top_layer = nullptr; for (auto &layernum : tile_ret->layers) { @@ -1023,7 +1023,7 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode() // Optionally render internal liquid level defined by param2 // Liquid is textured with 1 tile defined in nodedef 'special_tiles' if (param2 > 0 && cur_node.f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL && - cur_node.f->special_tiles[0].layers[0].texture) { + cur_node.f->special_tiles[cur_node.n.getVariant(*cur_node.f)][0].layers[0].texture) { // Internal liquid level has param2 range 0 .. 63, // convert it to -0.5 .. 0.5 float vlev = (param2 / 63.0f) * 2.0f - 1.0f; diff --git a/src/client/game.cpp b/src/client/game.cpp index f9a52c43f..f8225d23a 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3648,6 +3648,14 @@ bool Game::nodePlacement(const ItemDefinition &selected_def, } } + // Apply variant + if (!place_param2 && predicted_f.variant_count > 1) { + u16 variant = mystoi(selected_item.metadata.getString("variant"), + 0, predicted_f.variant_count - 1); + predicted_node.setParam2( + predicted_f.param2_variant.set(predicted_node.getParam2(), variant)); + } + // Add node to client map try { LocalPlayer *player = client->getEnv().getLocalPlayer(); diff --git a/src/client/item_visuals_manager.cpp b/src/client/item_visuals_manager.cpp index 7503d496b..6910edb92 100644 --- a/src/client/item_visuals_manager.cpp +++ b/src/client/item_visuals_manager.cpp @@ -15,8 +15,8 @@ ItemVisualsManager::ItemVisuals::~ItemVisuals() { wield_mesh.mesh->drop(); } -ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const ItemStack &item, - Client *client) const +ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( + const ItemStack &item, Client *client, u16 &variant_count) const { // This is not thread-safe sanity_check(std::this_thread::get_id() == m_main_thread); @@ -32,6 +32,8 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It if (!inventory_overlay.empty()) cache_key += ":" + inventory_overlay; + variant_count = client->ndef()->get(cache_key).variant_count; + // Skip if already in cache auto it = m_cached_item_visuals.find(cache_key); if (it != m_cached_item_visuals.end()) @@ -43,14 +45,23 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It ITextureSource *tsrc = client->getTextureSource(); // Create new ItemVisuals - auto cc = std::make_unique(); + auto cc = std::make_unique(variant_count); - cc->inventory_texture = NULL; - if (!inventory_image.empty()) - cc->inventory_texture = tsrc->getTexture(inventory_image); - getItemMesh(client, item, &(cc->wield_mesh)); + for (u16 v = 0; v < variant_count; v++) { + // Create an inventory texture + cc[v].inventory_texture = NULL; + if (!inventory_image.empty()) + cc[v].inventory_texture = tsrc->getTexture(inventory_image); - cc->palette = tsrc->getPalette(def.palette_image); + ItemStack item = ItemStack(); + item.name = def.name; + if (v > 0) + item.metadata.setString("variant", std::to_string(v)); + + getItemMesh(client, item, &(cc[v].wield_mesh)); + + cc[v].palette = tsrc->getPalette(def.palette_image); + } // Put in cache ItemVisuals *ptr = cc.get(); @@ -59,25 +70,29 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It } video::ITexture* ItemVisualsManager::getInventoryTexture(const ItemStack &item, - Client *client) const + u16 variant, Client *client) const { - ItemVisuals *iv = createItemVisuals(item, client); - if (!iv) + u16 variant_count; + ItemVisuals *iv = createItemVisuals(item, client, variant_count); + if (!iv || (variant >= variant_count)) return nullptr; - return iv->inventory_texture; + return iv[variant].inventory_texture; } -ItemMesh* ItemVisualsManager::getWieldMesh(const ItemStack &item, Client *client) const +ItemMesh* ItemVisualsManager::getWieldMesh(const ItemStack &item, u16 variant, + Client *client) const { - ItemVisuals *iv = createItemVisuals(item, client); - if (!iv) + u16 variant_count; + ItemVisuals *iv = createItemVisuals(item, client, variant_count); + if (!iv || (variant >= variant_count)) return nullptr; - return &(iv->wield_mesh); + return &(iv[variant].wield_mesh); } Palette* ItemVisualsManager::getPalette(const ItemStack &item, Client *client) const { - ItemVisuals *iv = createItemVisuals(item, client); + u16 variant_count; + ItemVisuals *iv = createItemVisuals(item, client, variant_count); if (!iv) return nullptr; return iv->palette; diff --git a/src/client/item_visuals_manager.h b/src/client/item_visuals_manager.h index 36604f0c7..743334724 100644 --- a/src/client/item_visuals_manager.h +++ b/src/client/item_visuals_manager.h @@ -30,11 +30,11 @@ struct ItemVisualsManager } // Get item inventory texture - video::ITexture* getInventoryTexture(const ItemStack &item, Client *client) const; + video::ITexture* getInventoryTexture(const ItemStack &item, u16 variant, Client *client) const; // Get item wield mesh // Once said to return nullptr if there is an inventory image, but this is wrong - ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const; + ItemMesh* getWieldMesh(const ItemStack &item, u16 variant, Client *client) const; // Get item palette Palette* getPalette(const ItemStack &item, Client *client) const; @@ -63,7 +63,7 @@ private: // The id of the thread that is allowed to use irrlicht directly std::thread::id m_main_thread; // Cached textures and meshes - mutable std::unordered_map> m_cached_item_visuals; + mutable std::unordered_map> m_cached_item_visuals; - ItemVisuals* createItemVisuals(const ItemStack &item, Client *client) const; + ItemVisuals* createItemVisuals(const ItemStack &item, Client *client, u16 &variant_count) const; }; diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 07381b473..f5f6ec3c6 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -344,7 +344,7 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, { const NodeDefManager *ndef = data->m_nodedef; const ContentFeatures &f = ndef->get(mn); - tile = f.tiles[tileindex]; + tile = f.tiles[mn.getVariant(f)][tileindex]; bool has_crack = p == data->m_crack_pos_relative; for (TileLayer &layer : tile.layers) { if (layer.empty()) diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index ae6244b93..50463d8dd 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -403,7 +403,9 @@ void Minimap::blitMinimapPixelsToImageSurface( MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.map_size]; const ContentFeatures &f = m_ndef->get(mmpixel->n); - const TileDef *tile = &f.tiledef[0]; + u16 v = mmpixel->n.getVariant(f); + const TileDef *tile = &f.tiledef[v][0]; + video::SColor minimap_color = f.minimap_color[v]; // Color of the 0th tile (mostly this is the topmost) if(tile->has_color) @@ -411,9 +413,9 @@ void Minimap::blitMinimapPixelsToImageSurface( else mmpixel->n.getColor(f, &tilecolor); - tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255); - tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() / 255); - tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255); + tilecolor.setRed(tilecolor.getRed() * minimap_color.getRed() / 255); + tilecolor.setGreen(tilecolor.getGreen() * minimap_color.getGreen() / 255); + tilecolor.setBlue(tilecolor.getBlue() * minimap_color.getBlue() / 255); tilecolor.setAlpha(240); map_image->setPixel(x, data->mode.map_size - z - 1, tilecolor); diff --git a/src/client/particles.cpp b/src/client/particles.cpp index 179c9c465..7aa35636c 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -867,7 +867,7 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n, texid = tilenum - 1; else texid = myrand_range(0,5); - const TileLayer &tile = f.tiles[texid].layers[0]; + const TileLayer &tile = f.tiles[n.getVariant(f)][texid].layers[0]; p.animation.type = TAT_NONE; // Only use first frame of animated texture diff --git a/src/client/wieldmesh.cpp b/src/client/wieldmesh.cpp index bdd24a727..516a1c0bf 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -17,6 +17,7 @@ #include "client/texturesource.h" #include "log.h" #include "util/numeric.h" +#include "util/string.h" #include #include #include "client/renderingengine.h" @@ -292,7 +293,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, } } -static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n, +static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n, u16 variant, std::vector *buffer_info, const ContentFeatures &f) { n.setParam1(0xff); @@ -310,6 +311,9 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n, n.setParam2(1); } + if (f.variant_count > 1) + n.setParam2(f.param2_variant.set(n.getParam2(), variant)); + MeshCollector collector(v3f(0), v3f()); { MeshMakeData mmd(client->ndef(), 1, MeshGrid{1}); @@ -354,6 +358,8 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che const ItemDefinition &def = item.getDefinition(idef); const ContentFeatures &f = ndef->get(def.name); content_t id = ndef->getId(def.name); + u16 variant = f.variant_count > 1 ? + mystoi(item.metadata.getString("variant"), 0, f.variant_count - 1) : 0; scene::SMesh *mesh = nullptr; @@ -395,8 +401,8 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che v3f wscale = wield_scale; if (f.drawtype == NDT_FLOWINGLIQUID) wscale.Z *= 0.1f; - const TileLayer &l0 = f.tiles[0].layers[0]; - const TileLayer &l1 = f.tiles[0].layers[1]; + const TileLayer &l0 = f.tiles[variant][0].layers[0]; + const TileLayer &l1 = f.tiles[variant][0].layers[1]; setExtruded(tsrc->getTextureName(l0.texture_id), tsrc->getTextureName(l1.texture_id), wscale, tsrc, @@ -408,7 +414,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che } case NDT_PLANTLIKE_ROOTED: { // use the plant tile - const TileLayer &l0 = f.special_tiles[0].layers[0]; + const TileLayer &l0 = f.special_tiles[variant][0].layers[0]; setExtruded(tsrc->getTextureName(l0.texture_id), "", wield_scale, tsrc, l0.animation_frame_count); @@ -421,7 +427,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che if (def.place_param2) n.setParam2(*def.place_param2); - mesh = createGenericNodeMesh(client, n, &m_buffer_info, f); + mesh = createGenericNodeMesh(client, n, variant, &m_buffer_info, f); changeToMesh(mesh); mesh->drop(); m_meshnode->setScale( @@ -544,6 +550,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) const ItemDefinition &def = item.getDefinition(idef); const ContentFeatures &f = ndef->get(def.name); content_t id = ndef->getId(def.name); + u16 variant = f.variant_count > 1 ? + mystoi(item.metadata.getString("variant"), 0, f.variant_count - 1) : 0; FATAL_ERROR_IF(!g_extrusion_mesh_cache, "Extrusion mesh cache is not yet initialized"); @@ -569,8 +577,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) } else if (def.type == ITEM_NODE) { switch (f.drawtype) { case NDT_PLANTLIKE: { - const TileLayer &l0 = f.tiles[0].layers[0]; - const TileLayer &l1 = f.tiles[0].layers[1]; + const TileLayer &l0 = f.tiles[variant][0].layers[0]; + const TileLayer &l1 = f.tiles[variant][0].layers[1]; mesh = getExtrudedMesh(tsrc, tsrc->getTextureName(l0.texture_id), tsrc->getTextureName(l1.texture_id)); @@ -581,7 +589,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) } case NDT_PLANTLIKE_ROOTED: { // Use the plant tile - const TileLayer &l0 = f.special_tiles[0].layers[0]; + const TileLayer &l0 = f.special_tiles[variant][0].layers[0]; mesh = getExtrudedMesh(tsrc, tsrc->getTextureName(l0.texture_id), ""); result->buffer_info.emplace_back(l0.has_color, l0.color); @@ -593,7 +601,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) if (def.place_param2) n.setParam2(*def.place_param2); - mesh = createGenericNodeMesh(client, n, &result->buffer_info, f); + mesh = createGenericNodeMesh(client, n, variant, &result->buffer_info, f); scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); break; } diff --git a/src/gui/drawItemStack.cpp b/src/gui/drawItemStack.cpp index 111109fd4..5ad2a2af5 100644 --- a/src/gui/drawItemStack.cpp +++ b/src/gui/drawItemStack.cpp @@ -45,6 +45,9 @@ void drawItemStack( auto *idef = client->idef(); const ItemDefinition &def = item.getDefinition(idef); ItemVisualsManager* item_visuals = client->getItemVisualsManager(); + const ContentFeatures &f = client->ndef()->get(def.name); + u16 variant = f.variant_count > 1 ? + mystoi(item.metadata.getString("variant"), 0, f.variant_count - 1) : 0; bool draw_overlay = false; @@ -60,7 +63,7 @@ void drawItemStack( // Render as mesh if animated or no inventory image if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) { - imesh = item_visuals->getWieldMesh(item, client); + imesh = item_visuals->getWieldMesh(item, variant, client); has_mesh = imesh && imesh->mesh; } if (has_mesh) { @@ -155,7 +158,7 @@ void drawItemStack( draw_overlay = def.type == ITEM_NODE && inventory_image.empty(); } else { // Otherwise just draw as 2D - video::ITexture *texture = item_visuals->getInventoryTexture(item, client); + video::ITexture *texture = item_visuals->getInventoryTexture(item, variant, client); video::SColor color; if (texture) { color = item_visuals->getItemstackColor(item, client); diff --git a/src/mapnode.cpp b/src/mapnode.cpp index a82cbe041..0e23eec87 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -38,6 +38,11 @@ void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const *color = f.color; } +u16 MapNode::getVariant(const ContentFeatures &f) const +{ + return f.variant_count > 1 ? f.param2_variant.get(param2) % f.variant_count : 0; +} + u8 MapNode::getFaceDir(const NodeDefManager *nodemgr, bool allow_wallmounted) const { diff --git a/src/mapnode.h b/src/mapnode.h index 5366caea2..5c81f0f9a 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -196,6 +196,12 @@ struct alignas(u32) MapNode */ void getColor(const ContentFeatures &f, video::SColor *color) const; + /*! + * Returns the variant of the node. + * \param f content features of this node + */ + u16 getVariant(const ContentFeatures &f) const; + inline void setLight(LightBank bank, u8 a_light, ContentLightingFlags f) noexcept { // If node doesn't contain light data, ignore this diff --git a/src/nodedef.cpp b/src/nodedef.cpp index 9d54c3957..c8f740203 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -316,12 +316,16 @@ ContentFeatures::ContentFeatures() ContentFeatures::~ContentFeatures() { #if CHECK_CLIENT_BUILD() - for (u16 j = 0; j < 6; j++) { - delete tiles[j].layers[0].frames; - delete tiles[j].layers[1].frames; + for (auto &t : tiles) { + for (u16 j = 0; j < 6; j++) { + delete t[j].layers[0].frames; + delete t[j].layers[1].frames; + } + } + for (auto &t : special_tiles) { + for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) + delete t[j].layers[0].frames; } - for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) - delete special_tiles[j].layers[0].frames; #endif } @@ -331,6 +335,8 @@ void ContentFeatures::reset() Cached stuff */ #if CHECK_CLIENT_BUILD() + tiles = std::vector >(1); + special_tiles = std::vector >(1); solidness = 2; visual_solidness = 0; backface_culling = true; @@ -355,17 +361,18 @@ void ContentFeatures::reset() mesh.clear(); #if CHECK_CLIENT_BUILD() mesh_ptr = nullptr; - minimap_color = video::SColor(0, 0, 0, 0); + minimap_color = std::vector(1, video::SColor(0, 0, 0, 0)); #endif visual_scale = 1.0; - for (auto &i : tiledef) - i = TileDef(); - for (auto &j : tiledef_special) - j = TileDef(); + tiledef = std::vector >(1); + tiledef_overlay = std::vector >(1); + tiledef_special = std::vector >(1); alpha = ALPHAMODE_OPAQUE; post_effect_color = video::SColor(0, 0, 0, 0); param_type = CPT_NONE; param_type_2 = CPT2_NONE; + variant_count = 1; + param2_variant = BitField(); is_ground_content = false; light_propagates = false; sunlight_propagates = false; @@ -457,12 +464,12 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const os << serializeString16(mesh); writeF32(os, visual_scale); writeU8(os, 6); - for (const TileDef &td : tiledef) + for (const TileDef &td : tiledef[0]) td.serialize(os, protocol_version); - for (const TileDef &td : tiledef_overlay) + for (const TileDef &td : tiledef_overlay[0]) td.serialize(os, protocol_version); writeU8(os, CF_SPECIAL_COUNT); - for (const TileDef &td : tiledef_special) { + for (const TileDef &td : tiledef_special[0]) { td.serialize(os, protocol_version); } writeU8(os, getAlphaForLegacy()); @@ -535,6 +542,21 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const writeU8(os, move_resistance); writeU8(os, liquid_move_physics); writeU8(os, post_effect_color_shaded); + writeU16(os, variant_count); + writeU8(os, param2_variant.getWidth()); + writeU8(os, param2_variant.getOffset()); + for (u16 v = 1; v < variant_count; v++) { + for (const TileDef &td : tiledef[v]) + td.serialize(os, protocol_version); + } + for (u16 v = 1; v < variant_count; v++) { + for (const TileDef &td : tiledef_overlay[v]) + td.serialize(os, protocol_version); + } + for (u16 v = 1; v < variant_count; v++) { + for (const TileDef &td : tiledef_special[v]) + td.serialize(os, protocol_version); + } } void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version) @@ -568,13 +590,13 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version) visual_scale = readF32(is); if (readU8(is) != 6) throw SerializationError("unsupported tile count"); - for (TileDef &td : tiledef) + for (TileDef &td : tiledef[0]) td.deSerialize(is, drawtype, protocol_version); - for (TileDef &td : tiledef_overlay) + for (TileDef &td : tiledef_overlay[0]) td.deSerialize(is, drawtype, protocol_version); if (readU8(is) != CF_SPECIAL_COUNT) throw SerializationError("unsupported CF_SPECIAL_COUNT"); - for (TileDef &td : tiledef_special) + for (TileDef &td : tiledef_special[0]) td.deSerialize(is, drawtype, protocol_version); setAlphaFromLegacy(readU8(is)); color.setRed(readU8(is)); @@ -665,6 +687,43 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version) if (is.eof()) throw SerializationError(""); post_effect_color_shaded = tmp; + + u16 tmp_variant_count = readU16(is); + if (is.eof()) + throw SerializationError(""); + + // Reserve space first for memory safety. + tiledef.reserve(variant_count); + tiledef_overlay.reserve(variant_count); + tiledef_special.reserve(variant_count); + for (u16 v = 1; v < tmp_variant_count; v++) { + tiledef.emplace_back(); + tiledef_overlay.emplace_back(); + tiledef_special.emplace_back(); + } + + variant_count = tmp_variant_count; + + tmp = readU8(is); + u8 tmp2 = readU8(is); + if (is.eof()) + throw SerializationError(""); + param2_variant = BitField(tmp, tmp2); + + for (u16 v = 1; v < variant_count; v++) { + for (TileDef &td : tiledef[v]) + td.deSerialize(is, drawtype, protocol_version); + } + + for (u16 v = 1; v < variant_count; v++) { + for (TileDef &td : tiledef_overlay[v]) + td.deSerialize(is, drawtype, protocol_version); + } + + for (u16 v = 1; v < variant_count; v++) { + for (TileDef &td : tiledef_special[v]) + td.deSerialize(is, drawtype, protocol_version); + } } catch (SerializationError &e) {}; } @@ -763,22 +822,27 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings) { // Figure out the actual tiles to use - TileDef tdef[6]; - for (u32 j = 0; j < 6; j++) { - tdef[j] = tiledef[j]; - if (tdef[j].name.empty()) { - tdef[j].name = "no_texture.png"; - tdef[j].backface_culling = false; + std::vector > tdef(variant_count); + for (u16 v = 0; v < variant_count; v++) { + for (u32 j = 0; j < 6; j++) { + tdef[v][j] = tiledef[v][j]; + if (tdef[v][j].name.empty()) { + tdef[v][j].name = "no_texture.png"; + tdef[v][j].backface_culling = false; + } } } // also the overlay tiles - TileDef tdef_overlay[6]; - for (u32 j = 0; j < 6; j++) - tdef_overlay[j] = tiledef_overlay[j]; + std::vector > tdef_overlay(variant_count); + for (u16 v = 0; v < variant_count; v++) { + for (u32 j = 0; j < 6; j++) + tdef_overlay[v][j] = tiledef_overlay[v][j]; + } // also the special tiles - TileDef tdef_spec[6]; - for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) { - tdef_spec[j] = tiledef_special[j]; + std::vector > tdef_spec(variant_count); + for (u16 v = 0; v < variant_count; v++) { + for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) + tdef_spec[v][j] = tiledef_special[v][j]; } bool is_liquid = false; @@ -830,9 +894,11 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc solidness = 0; visual_solidness = 1; } else if (tsettings.leaves_style == LEAVES_SIMPLE) { - for (u32 j = 0; j < 6; j++) { - if (!tdef_spec[j].name.empty()) - tdef[j].name = tdef_spec[j].name; + for (u16 v = 0; v < variant_count; v++) { + for (u32 j = 0; j < 6; j++) { + if (!tdef_spec[v][j].name.empty()) + tdef[v][j].name = tdef_spec[v][j].name; + } } drawtype = NDT_GLASSLIKE; solidness = 0; @@ -847,8 +913,10 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc drawtype = NDT_NORMAL; solidness = 2; } - for (TileDef &td : tdef) - td.name += std::string("^[noalpha"); + for (u16 v = 0; v < variant_count; v++) { + for (TileDef &td : tdef[v]) + td.name += std::string("^[noalpha"); + } } if (waving >= 1) material_type = TILE_MATERIAL_WAVING_LEAVES; @@ -907,34 +975,42 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc u32 overlay_shader = shdsrc->getShader("nodes_shader", overlay_material, drawtype); // minimap pixel color = average color of top tile - if (tsettings.enable_minimap && !tdef[0].name.empty() && drawtype != NDT_AIRLIKE) - minimap_color = tsrc->getTextureAverageColor(tdef[0].name); + if (tsettings.enable_minimap && !tdef[0][0].name.empty() && drawtype != NDT_AIRLIKE) { + minimap_color = std::vector(variant_count); + for (u16 v = 0; v < variant_count; v++) { + if (!tdef[v][0].name.empty()) + minimap_color[v] = tsrc->getTextureAverageColor(tdef[v][0].name); + } + } // Tiles (fill in f->tiles[]) bool any_polygon_offset = false; - for (u16 j = 0; j < 6; j++) { - tiles[j].world_aligned = isWorldAligned(tdef[j].align_style, - tsettings.world_aligned_mode, drawtype); - fillTileAttribs(tsrc, &tiles[j].layers[0], tiles[j], tdef[j], - color, material_type, tile_shader, - tdef[j].backface_culling, tsettings); - if (!tdef_overlay[j].name.empty()) - fillTileAttribs(tsrc, &tiles[j].layers[1], tiles[j], tdef_overlay[j], - color, overlay_material, overlay_shader, - tdef[j].backface_culling, tsettings); + tiles = std::vector >(variant_count); + for (u16 v = 0; v < variant_count; v++) { + for (u16 j = 0; j < 6; j++) { + tiles[v][j].world_aligned = isWorldAligned(tdef[v][j].align_style, + tsettings.world_aligned_mode, drawtype); + fillTileAttribs(tsrc, &tiles[v][j].layers[0], tiles[v][j], tdef[v][j], + color, material_type, tile_shader, + tdef[v][j].backface_culling, tsettings); + if (!tdef_overlay[v][j].name.empty()) + fillTileAttribs(tsrc, &tiles[v][j].layers[1], tiles[v][j], tdef_overlay[v][j], + color, overlay_material, overlay_shader, + tdef[v][j].backface_culling, tsettings); - tiles[j].layers[0].need_polygon_offset = !tiles[j].layers[1].empty(); - any_polygon_offset |= tiles[j].layers[0].need_polygon_offset; - } + tiles[v][j].layers[0].need_polygon_offset = !tiles[v][j].layers[1].empty(); + any_polygon_offset |= tiles[v][j].layers[0].need_polygon_offset; + } - if (drawtype == NDT_MESH && any_polygon_offset) { - // Our per-tile polygon offset enablement workaround works fine for normal - // nodes and anything else, where we know that different tiles are different - // faces that couldn't possibly conflict with each other. - // We can't assume this for mesh nodes, so apply it to all tiles (= materials) - // then. - for (u16 j = 0; j < 6; j++) - tiles[j].layers[0].need_polygon_offset = true; + if (drawtype == NDT_MESH && any_polygon_offset) { + // Our per-tile polygon offset enablement workaround works fine for normal + // nodes and anything else, where we know that different tiles are different + // faces that couldn't possibly conflict with each other. + // We can't assume this for mesh nodes, so apply it to all tiles (= materials) + // then. + for (u16 j = 0; j < 6; j++) + tiles[v][j].layers[0].need_polygon_offset = true; + } } MaterialType special_material = material_type; @@ -947,10 +1023,14 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc u32 special_shader = shdsrc->getShader("nodes_shader", special_material, drawtype); // Special tiles (fill in f->special_tiles[]) - for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) - fillTileAttribs(tsrc, &special_tiles[j].layers[0], special_tiles[j], tdef_spec[j], - color, special_material, special_shader, - tdef_spec[j].backface_culling, tsettings); + special_tiles = std::vector >(variant_count); + for (u16 v = 0; v < variant_count; v++) { + for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) + fillTileAttribs(tsrc, + &special_tiles[v][j].layers[0], special_tiles[v][j], tdef_spec[v][j], + color, special_material, special_shader, + tdef_spec[v][j].backface_culling, tsettings); + } if (param_type_2 == CPT2_COLOR || param_type_2 == CPT2_COLORED_FACEDIR || @@ -1043,7 +1123,7 @@ void NodeDefManager::clear() ContentFeatures f; f.name = "unknown"; for (int t = 0; t < 6; t++) - f.tiledef[t].name = "unknown_node.png"; + f.tiledef[0][t].name = "unknown_node.png"; // Insert directly into containers content_t c = CONTENT_UNKNOWN; m_content_features[c] = f; @@ -1409,52 +1489,55 @@ void NodeDefManager::applyTextureOverrides(const std::vector &o ContentFeatures &nodedef = m_content_features[id]; - auto apply = [&] (TileDef &tile) { - tile.name = texture_override.texture; - if (texture_override.world_scale > 0) { - tile.align_style = ALIGN_STYLE_WORLD; - tile.scale = texture_override.world_scale; + // For now this works with tiledef_special since CF_SPECIAL_COUNT == 6. + auto apply = [&] (std::vector > &tiledef, u32 i) { + for (u16 v = 0; v < nodedef.variant_count; v++) { + tiledef[v][i].name = texture_override.texture; + if (texture_override.world_scale > 0) { + tiledef[v][i].align_style = ALIGN_STYLE_WORLD; + tiledef[v][i].scale = texture_override.world_scale; + } } }; // Override tiles if (texture_override.hasTarget(OverrideTarget::TOP)) - apply(nodedef.tiledef[0]); + apply(nodedef.tiledef, 0); if (texture_override.hasTarget(OverrideTarget::BOTTOM)) - apply(nodedef.tiledef[1]); + apply(nodedef.tiledef, 1); if (texture_override.hasTarget(OverrideTarget::RIGHT)) - apply(nodedef.tiledef[2]); + apply(nodedef.tiledef, 2); if (texture_override.hasTarget(OverrideTarget::LEFT)) - apply(nodedef.tiledef[3]); + apply(nodedef.tiledef, 3); if (texture_override.hasTarget(OverrideTarget::BACK)) - apply(nodedef.tiledef[4]); + apply(nodedef.tiledef, 4); if (texture_override.hasTarget(OverrideTarget::FRONT)) - apply(nodedef.tiledef[5]); + apply(nodedef.tiledef, 5); // Override special tiles, if applicable if (texture_override.hasTarget(OverrideTarget::SPECIAL_1)) - apply(nodedef.tiledef_special[0]); + apply(nodedef.tiledef_special, 0); if (texture_override.hasTarget(OverrideTarget::SPECIAL_2)) - apply(nodedef.tiledef_special[1]); + apply(nodedef.tiledef_special, 1); if (texture_override.hasTarget(OverrideTarget::SPECIAL_3)) - apply(nodedef.tiledef_special[2]); + apply(nodedef.tiledef_special, 2); if (texture_override.hasTarget(OverrideTarget::SPECIAL_4)) - apply(nodedef.tiledef_special[3]); + apply(nodedef.tiledef_special, 3); if (texture_override.hasTarget(OverrideTarget::SPECIAL_5)) - apply(nodedef.tiledef_special[4]); + apply(nodedef.tiledef_special, 4); if (texture_override.hasTarget(OverrideTarget::SPECIAL_6)) - apply(nodedef.tiledef_special[5]); + apply(nodedef.tiledef_special, 5); } } diff --git a/src/nodedef.h b/src/nodedef.h index 967e3fcd4..5c3580e29 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -9,6 +9,7 @@ #include #include // shared_ptr #include +#include #include "mapnode.h" #include "nameidmapping.h" #if CHECK_CLIENT_BUILD() @@ -22,6 +23,7 @@ class Client; #include "texture_override.h" // TextureOverride #include "tileanimation.h" #include "util/pointabilities.h" +#include "util/numeric.h" class IItemDefManager; class ITextureSource; @@ -308,9 +310,9 @@ struct ContentFeatures #if CHECK_CLIENT_BUILD() // 0 1 2 3 4 5 // up down right left back front - TileSpec tiles[6]; + std::vector > tiles; // Length is variant count // Special tiles - TileSpec special_tiles[CF_SPECIAL_COUNT]; + std::vector > special_tiles; u8 solidness; // Used when choosing which face is drawn u8 visual_solidness; // When solidness=0, this tells how it looks like bool backface_culling; @@ -336,6 +338,10 @@ struct ContentFeatures ContentParamType param_type; // Type of MapNode::param2 ContentParamType2 param_type_2; + // Number of node variants + u16 variant_count = 1; + // Bit field for variant in param2 + BitField param2_variant; // --- VISUAL PROPERTIES --- @@ -343,13 +349,13 @@ struct ContentFeatures std::string mesh; #if CHECK_CLIENT_BUILD() scene::SMesh *mesh_ptr; // mesh in case of mesh node - video::SColor minimap_color; + std::vector minimap_color; // Length is variant count #endif float visual_scale; // Misc. scale parameter - TileDef tiledef[6]; + std::vector > tiledef; // Length is variant count // These will be drawn over the base tiles. - TileDef tiledef_overlay[6]; - TileDef tiledef_special[CF_SPECIAL_COUNT]; // eg. flowing liquid + std::vector > tiledef_overlay; // Length is variant count + std::vector > tiledef_special; // eg. flowing liquid AlphaMode alpha; // The color of the node. video::SColor color; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 91aa5b405..89daf76a8 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -201,6 +201,8 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i) { std::string type(enum_to_string(es_ItemType, i.type)); + // variant_count is presently not included, unlike in the server-side definition table. + lua_newtable(L); lua_pushstring(L, i.name.c_str()); lua_setfield(L, -2, "name"); @@ -660,6 +662,8 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special) // color = ... lua_getfield(L, index, "color"); tiledef.has_color = read_color(L, -1, &tiledef.color); + if (!tiledef.has_color) + tiledef.color = video::SColor(); lua_pop(L, 1); // animation = {} lua_getfield(L, index, "animation"); @@ -673,12 +677,76 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special) return tiledef; } +void read_tiledefs(lua_State *L, int index, u8 drawtype, bool special, + std::array &tiledefs) +{ + if(index < 0) + index = lua_gettop(L) + 1 + index; + + if (lua_istable(L, index)) { + lua_pushnil(L); + int i = 0; + while (lua_next(L, index) != 0) { + // Read tiledef from value + tiledefs[i] = read_tiledef(L, -1, drawtype, special); + // removes value, keeps key for next iteration + lua_pop(L, 1); + i++; + if (i == 6) { + lua_pop(L, 1); + break; + } + } + // Allow empthy {} definition of tile + if (i==0) { + tiledefs[0] = TileDef(); + i++; + } + // Copy last value to all remaining textures + if (i >= 1) { + TileDef lasttile = tiledefs[i - 1]; + while (i < 6) { + tiledefs[i] = lasttile; + i++; + } + } + } +} + +void read_default_tiledefs(lua_State *L, int index, u8 drawtype, bool special, + std::vector > &tiledefs) +{ + read_tiledefs(L, index, drawtype, special, tiledefs[0]); + for (u16 v = 1; v < tiledefs.size(); v++) + tiledefs[v] = tiledefs[0]; +} + /******************************************************************************/ void read_content_features(lua_State *L, ContentFeatures &f, int index) { if(index < 0) index = lua_gettop(L) + 1 + index; + { + int variant_count = getintfield_default(L, index, "variant_count", 1); + if (variant_count < 1 || variant_count > UINT16_MAX) + throw LuaError("Invalid variant_count " + variant_count); + + if (variant_count > 1) { + // Reserve memory first to avoid out-of-bounds indexing. + f.tiledef = std::vector >(variant_count); + f.tiledef_overlay = std::vector >(variant_count); + f.tiledef_special = + std::vector >(variant_count); + } + + f.variant_count = variant_count; + } + + lua_getfield(L, index, "param2_variant"); + f.param2_variant = read_bitfield(L, -1); + lua_pop(L, 1); + /* Cache existence of some callbacks */ lua_getfield(L, index, "on_construct"); f.has_on_construct = !lua_isnil(L, -1); @@ -718,76 +786,45 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index) // tiles = {} lua_getfield(L, index, "tiles"); - if(lua_istable(L, -1)){ - int table = lua_gettop(L); - lua_pushnil(L); - int i = 0; - while(lua_next(L, table) != 0){ - // Read tiledef from value - f.tiledef[i] = read_tiledef(L, -1, f.drawtype, false); - // removes value, keeps key for next iteration - lua_pop(L, 1); - i++; - if(i==6){ - lua_pop(L, 1); - break; - } - } - // Copy last value to all remaining textures - if(i >= 1){ - TileDef lasttile = f.tiledef[i-1]; - while(i < 6){ - f.tiledef[i] = lasttile; - i++; - } - } - } + read_default_tiledefs(L, -1, f.drawtype, false, f.tiledef); lua_pop(L, 1); // overlay_tiles = {} lua_getfield(L, index, "overlay_tiles"); - if (lua_istable(L, -1)) { - int table = lua_gettop(L); - lua_pushnil(L); - int i = 0; - while (lua_next(L, table) != 0) { - // Read tiledef from value - f.tiledef_overlay[i] = read_tiledef(L, -1, f.drawtype, false); - // removes value, keeps key for next iteration - lua_pop(L, 1); - i++; - if (i == 6) { - lua_pop(L, 1); - break; - } - } - // Copy last value to all remaining textures - if (i >= 1) { - TileDef lasttile = f.tiledef_overlay[i - 1]; - while (i < 6) { - f.tiledef_overlay[i] = lasttile; - i++; - } - } - } + read_default_tiledefs(L, -1, f.drawtype, false, f.tiledef_overlay); lua_pop(L, 1); // special_tiles = {} lua_getfield(L, index, "special_tiles"); - if(lua_istable(L, -1)){ - int table = lua_gettop(L); + // This works as long as CF_SPECIAL_COUNT == 6. + read_default_tiledefs(L, -1, f.drawtype, true, f.tiledef_special); + lua_pop(L, 1); + + // variants = {} + lua_getfield(L, index, "variants"); + if (lua_istable(L, -1)) { + int variants_index = lua_gettop(L); lua_pushnil(L); - int i = 0; - while(lua_next(L, table) != 0){ - // Read tiledef from value - f.tiledef_special[i] = read_tiledef(L, -1, f.drawtype, true); + while (lua_next(L, variants_index) != 0) { + size_t v = lua_tointeger(L, -2); + if (v < f.variant_count && lua_istable(L, -1)) { + // tiles = {} + lua_getfield(L, -1, "tiles"); + read_tiledefs(L, -1, f.drawtype, false, f.tiledef[v]); + lua_pop(L, 1); + + // overlay_tiles = {} + lua_getfield(L, -1, "overlay_tiles"); + read_tiledefs(L, -1, f.drawtype, false, f.tiledef_overlay[v]); + lua_pop(L, 1); + + // special_tiles = {} + lua_getfield(L, -1, "special_tiles"); + read_tiledefs(L, -1, f.drawtype, true, f.tiledef_special[v]); + lua_pop(L, 1); + } // removes value, keeps key for next iteration lua_pop(L, 1); - i++; - if(i==CF_SPECIAL_COUNT){ - lua_pop(L, 1); - break; - } } } lua_pop(L, 1); @@ -1022,9 +1059,14 @@ void push_content_features(lua_State *L, const ContentFeatures &c) std::string drawtype(enum_to_string(ScriptApiNode::es_DrawType, c.drawtype)); std::string liquid_type(enum_to_string(ScriptApiNode::es_LiquidType, c.liquid_type)); - /* Missing "tiles" because I don't see a usecase (at least not yet). */ + // Missing "tiles" because I don't see a usecase (at least not yet). + // "variants" is also missing as the only variant properties are tiles. lua_newtable(L); + lua_pushinteger(L, c.variant_count); + lua_setfield(L, -2, "variant_count"); + push_bitfield(L, c.param2_variant); + lua_setfield(L, -2, "param2_variant"); lua_pushboolean(L, c.has_on_construct); lua_setfield(L, -2, "has_on_construct"); lua_pushboolean(L, c.has_on_destruct); @@ -1046,7 +1088,7 @@ void push_content_features(lua_State *L, const ContentFeatures &c) lua_setfield(L, -2, "mesh"); } #if CHECK_CLIENT_BUILD() - push_ARGB8(L, c.minimap_color); // I know this is not set-able w/ register_node, + push_ARGB8(L, c.minimap_color[0]); // I know this is not set-able w/ register_node, lua_setfield(L, -2, "minimap_color"); // but the people need to know! #endif lua_pushnumber(L, c.visual_scale); diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 2b711bfda..77b4f684f 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -369,6 +369,15 @@ void push_aabb3f(lua_State *L, aabb3f box, f32 divisor) lua_rawseti(L, -2, 6); } +void push_bitfield_parts(lua_State *L, u8 width, u8 offset) +{ + lua_createtable(L, 0, 2); + lua_pushinteger(L, width); + lua_setfield(L, -2, "width"); + lua_pushinteger(L, offset); + lua_setfield(L, -2, "offset"); +} + std::vector read_aabb3f_vector(lua_State *L, int index, f32 scale) { std::vector boxes; @@ -431,6 +440,16 @@ size_t read_stringlist(lua_State *L, int index, std::vector *result return num_strings; } +void read_bitfield_parts(lua_State *L, int index, u8 *width, u8 *offset) +{ + *width = 0; + *offset = 0; + if (lua_istable(L, index)) { + getintfield(L, index, "width", *width); + getintfield(L, index, "offset", *offset); + } +} + /* Table field getters */ diff --git a/src/script/common/c_converter.h b/src/script/common/c_converter.h index 2744fa0b5..460d24f3b 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -16,6 +16,8 @@ #include #include "irrlichttypes_bloated.h" +//#include "common/c_types.h" +#include "util/numeric.h" extern "C" { #include @@ -108,6 +110,15 @@ aabb3f read_aabb3f(lua_State *L, int index, f32 scale); std::vector read_aabb3f_vector (lua_State *L, int index, f32 scale); size_t read_stringlist(lua_State *L, int index, std::vector *result); +void read_bitfield_parts (lua_State *L, int index, u8 *width, u8 *offset); + +template +BitField read_bitfield(lua_State *L, int index) +{ + u8 width, offset; + read_bitfield_parts(L, index, &width, &offset); + return BitField(width, offset); +} void push_v2s16(lua_State *L, v2s16 p); void push_v2s32(lua_State *L, v2s32 p); @@ -120,6 +131,13 @@ void push_v3f(lua_State *L, v3f p); void push_v2f(lua_State *L, v2f p); void push_aabb3f_vector(lua_State *L, const std::vector &boxes, f32 divisor = 1.0f); +void push_bitfield_parts (lua_State *L, u8 width, u8 offset); + +template +void push_bitfield(lua_State *L, const BitField &bitfield) +{ + push_bitfield_parts(L, bitfield.getWidth(), bitfield.getOffset()); +} void warn_if_field_exists(lua_State *L, int table, const char *fieldname, std::string_view name, std::string_view message); diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 8f0eaf855..def22366f 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -80,7 +80,7 @@ void TestGameDef::defineSomeNodes() "{default_stone.png"; f = ContentFeatures(); f.name = itemdef.name; - for (TileDef &tiledef : f.tiledef) + for (TileDef &tiledef : f.tiledef[0]) tiledef.name = "default_stone.png"; f.is_ground_content = true; idef->registerItem(itemdef); @@ -98,10 +98,10 @@ void TestGameDef::defineSomeNodes() "{default_dirt.png&default_grass_side.png"; f = ContentFeatures(); f.name = itemdef.name; - f.tiledef[0].name = "default_grass.png"; - f.tiledef[1].name = "default_dirt.png"; + f.tiledef[0][0].name = "default_grass.png"; + f.tiledef[0][1].name = "default_dirt.png"; for(int i = 2; i < 6; i++) - f.tiledef[i].name = "default_dirt.png^default_grass_side.png"; + f.tiledef[0][i].name = "default_dirt.png^default_grass_side.png"; f.is_ground_content = true; idef->registerItem(itemdef); t_CONTENT_GRASS = ndef->set(f.name, f); @@ -137,7 +137,7 @@ void TestGameDef::defineSomeNodes() f.liquid_viscosity = 4; f.is_ground_content = true; f.groups["liquids"] = 3; - for (TileDef &tiledef : f.tiledef) + for (TileDef &tiledef : f.tiledef[0]) tiledef.name = "default_water.png"; idef->registerItem(itemdef); t_CONTENT_WATER = ndef->set(f.name, f); @@ -159,7 +159,7 @@ void TestGameDef::defineSomeNodes() f.light_source = LIGHT_MAX-1; f.is_ground_content = true; f.groups["liquids"] = 3; - for (TileDef &tiledef : f.tiledef) + for (TileDef &tiledef : f.tiledef[0]) tiledef.name = "default_lava.png"; idef->registerItem(itemdef); t_CONTENT_LAVA = ndef->set(f.name, f); @@ -177,7 +177,7 @@ void TestGameDef::defineSomeNodes() "{default_brick.png"; f = ContentFeatures(); f.name = itemdef.name; - for (TileDef &tiledef : f.tiledef) + for (TileDef &tiledef : f.tiledef[0]) tiledef.name = "default_brick.png"; f.is_ground_content = true; idef->registerItem(itemdef); diff --git a/src/unittest/test_content_mapblock.cpp b/src/unittest/test_content_mapblock.cpp index 68f66eabf..058c359ca 100644 --- a/src/unittest/test_content_mapblock.cpp +++ b/src/unittest/test_content_mapblock.cpp @@ -62,9 +62,9 @@ public: f.drawtype = NDT_NORMAL; f.solidness = 2; f.alpha = ALPHAMODE_OPAQUE; - for (TileDef &tiledef : f.tiledef) + for (TileDef &tiledef : f.tiledef[0]) tiledef.name = name + ".png"; - for (TileSpec &tile : f.tiles) + for (TileSpec &tile : f.tiles[0]) tile.layers[0].texture_id = texture; return registerNode(itemdef, f); @@ -89,9 +89,9 @@ public: f.groups["liquids"] = 3; f.liquid_alternative_source = "test:" + name + "_source"; f.liquid_alternative_flowing = "test:" + name + "_flowing"; - for (TileDef &tiledef : f.tiledef) + for (TileDef &tiledef : f.tiledef[0]) tiledef.name = name + ".png"; - for (TileSpec &tile : f.tiles) + for (TileSpec &tile : f.tiles[0]) tile.layers[0].texture_id = texture; return registerNode(itemdef, f); @@ -116,10 +116,10 @@ public: f.groups["liquids"] = 3; f.liquid_alternative_source = "test:" + name + "_source"; f.liquid_alternative_flowing = "test:" + name + "_flowing"; - f.tiledef_special[0].name = name + "_top.png"; - f.tiledef_special[1].name = name + "_side.png"; - f.special_tiles[0].layers[0].texture_id = texture_top; - f.special_tiles[1].layers[0].texture_id = texture_side; + f.tiledef_special[0][0].name = name + "_top.png"; + f.tiledef_special[0][1].name = name + "_side.png"; + f.special_tiles[0][0].layers[0].texture_id = texture_top; + f.special_tiles[1][1].layers[0].texture_id = texture_side; return registerNode(itemdef, f); } diff --git a/src/unittest/test_nodedef.cpp b/src/unittest/test_nodedef.cpp index 5841bf003..02cf6b57d 100644 --- a/src/unittest/test_nodedef.cpp +++ b/src/unittest/test_nodedef.cpp @@ -19,7 +19,8 @@ TEST_CASE("Given a node definition, " { ContentFeatures f; f.name = "default:stone"; - for (TileDef &tiledef : f.tiledef) + f.tiledef.emplace_back(); + for (TileDef &tiledef : f.tiledef[0]) tiledef.name = "default_stone.png"; f.is_ground_content = true; diff --git a/src/unittest/test_utilities.cpp b/src/unittest/test_utilities.cpp index 093dcf1ef..95e6bb253 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -50,6 +50,7 @@ public: void testSanitizeUntrusted(); void testReadSeed(); void testMyDoubleStringConversions(); + void testBitField(); }; static TestUtilities g_test_instance; @@ -87,6 +88,7 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testSanitizeUntrusted); TEST(testReadSeed); TEST(testMyDoubleStringConversions); + TEST(testBitField); } //////////////////////////////////////////////////////////////////////////////// @@ -800,3 +802,23 @@ void TestUtilities::testMyDoubleStringConversions() test_round_trip(0.3); test_round_trip(0.1 + 0.2); } + +void TestUtilities::testBitField() +{ + UASSERTEQ(int, BitField(5, 4).getWidth(), 5); + UASSERTEQ(int, BitField(5, 4).getOffset(), 4); + UASSERTEQ(u16, BitField(5, 4).get(0xEFDCU), 0x1DU); + UASSERTEQ(u16, BitField(5, 4).set(0xEFDCU, 0xF0FU), 0xEEFCU); + UASSERTEQ(int, BitField(17, 0).getWidth(), 16); + UASSERTEQ(int, BitField(17, 0).getOffset(), 0); + UASSERTEQ(u16, BitField(17, 0).get(0xEFDCU), 0xEFDCU); + UASSERTEQ(u16, BitField(17, 0).set(0xEFDCU, 0xFFFFU), 0xFFFFU); + UASSERTEQ(int, BitField(16, 17).getWidth(), 0); + UASSERTEQ(int, BitField(16, 17).getOffset(), 0); + UASSERTEQ(u16, BitField(16, 17).get(0xEFDCU), 0); + UASSERTEQ(u16, BitField(16, 17).set(0xEFDCU, 0), 0xEFDCU); + UASSERTEQ(int, BitField(8, 15).getWidth(), 1); + UASSERTEQ(int, BitField(8, 15).getOffset(), 15); + UASSERTEQ(u16, BitField(8, 15).get(0xEFDCU), 1); + UASSERTEQ(u16, BitField(8, 15).set(0xEFDCU, 0), 0x6FDCU); +} diff --git a/src/util/numeric.h b/src/util/numeric.h index b79ef2aef..01c4f362a 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -14,6 +14,10 @@ #include #include #include +#include +#if _MSC_VER +#include // For popcnt +#endif // Like std::clamp but allows mismatched types template @@ -277,6 +281,53 @@ inline void set_bits(u32 *x, u32 pos, u32 len, u32 val) *x |= (val & mask) << pos; } +inline unsigned popcount(unsigned b) +{ +#if _MSC_VER + return __popcnt(b); +#else + return __builtin_popcount(b); +#endif +} + +template +class BitField { +public: + static_assert(static_cast(-1) > static_cast(0), + "Bit fields only work with unsigned integer types"); + + BitField(): m_mask(0), m_offset(0) {} + + BitField(u8 width, u8 offset) + { + // Truncate to the type width to avoid undefined behavior. + unsigned bit_count = sizeof(T) * CHAR_BIT; + offset = MYMIN(offset, bit_count); + width = MYMIN(width, bit_count - offset); + m_mask = ((width < bit_count ? 1 << width : 0) - 1) << (offset % bit_count); + m_offset = offset % bit_count; + } + + inline u8 getWidth() const { return popcount(m_mask); } + inline u8 getOffset() const { return m_offset; } + + inline T get(T bits) const + { + return (bits & m_mask) >> m_offset; + } + + inline T set(T bits, T value) const + { + bits &= ~m_mask; + bits |= (value << m_offset) & m_mask; + return bits; + } + +private: + T m_mask; + u8 m_offset; +}; + inline u32 calc_parity(u32 v) { v ^= v >> 16;