From 7d783905d589402a66fba27ed6ee78bd0920e398 Mon Sep 17 00:00:00 2001 From: Jude Melton-Houghton Date: Thu, 3 Nov 2022 21:18:49 -0400 Subject: [PATCH] Add node texture variants --- builtin/common/item_s.lua | 19 ++ builtin/game/falling.lua | 9 +- builtin/game/item.lua | 37 +++- doc/lua_api.md | 95 +++++++++ games/devtest/mods/testnodes/init.lua | 1 + games/devtest/mods/testnodes/variants.lua | 232 ++++++++++++++++++++++ src/client/content_mapblock.cpp | 4 +- src/client/game.cpp | 8 + src/client/hud.cpp | 8 +- src/client/mapblock_mesh.cpp | 2 +- src/client/minimap.cpp | 10 +- src/client/particles.cpp | 2 +- src/client/wieldmesh.cpp | 53 ++--- src/client/wieldmesh.h | 6 +- src/itemdef.cpp | 35 ++-- src/itemdef.h | 10 +- src/mapnode.cpp | 5 + src/mapnode.h | 6 + src/nodedef.cpp | 217 +++++++++++++------- src/nodedef.h | 18 +- src/script/common/c_content.cpp | 157 +++++++++------ src/script/common/c_content.h | 6 + src/script/common/c_converter.cpp | 19 ++ src/script/common/c_converter.h | 19 ++ src/unittest/test.cpp | 14 +- src/unittest/test_nodedef.cpp | 3 +- src/unittest/test_utilities.cpp | 22 ++ src/util/numeric.h | 51 +++++ 28 files changed, 866 insertions(+), 202 deletions(-) create mode 100644 games/devtest/mods/testnodes/variants.lua diff --git a/builtin/common/item_s.lua b/builtin/common/item_s.lua index f848ef6d8dabb..0a4433e9abfd0 100644 --- a/builtin/common/item_s.lua +++ b/builtin/common/item_s.lua @@ -180,6 +180,25 @@ function core.strip_param2_color(param2, paramtype2) return param2 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/falling.lua b/builtin/game/falling.lua index 717c0b748417a..dbabc4726f44d 100644 --- a/builtin/game/falling.lua +++ b/builtin/game/falling.lua @@ -80,10 +80,12 @@ core.register_entity(":__builtin:falling_node", { self.floats = core.get_item_group(node.name, "float") ~= 0 -- Set entity visuals + local variant = core.strip_param2_variant(node.param2, def) if def.drawtype == "torchlike" or def.drawtype == "signlike" then local textures - if def.tiles and def.tiles[1] then - local tile = def.tiles[1] + local tiles = def.variants and def.variants[variant] and def.variants[variant].tiles or def.tiles + if tiles and tiles[1] then + local tile = tiles[1] if type(tile) == "table" then tile = tile.name end @@ -110,6 +112,9 @@ core.register_entity(":__builtin:falling_node", { if core.is_colored_paramtype(def.paramtype2) then itemstring = core.itemstring_with_palette(itemstring, node.param2) end + if variant > 0 then + itemstring = core.itemstring_with_variant(itemstring, variant) + end -- FIXME: solution needed for paramtype2 == "leveled" -- Calculate size of falling node local s = {} diff --git a/builtin/game/item.lua b/builtin/game/item.lua index 17d081f3d3fe1..c46a3669dbb28 100644 --- a/builtin/game/item.lua +++ b/builtin/game/item.lua @@ -54,14 +54,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 {} @@ -120,9 +124,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 @@ -239,6 +244,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 @@ -573,6 +585,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 @@ -594,6 +612,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), @@ -624,6 +643,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, @@ -649,6 +669,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), @@ -669,6 +690,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), @@ -689,6 +711,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 c1cf1ea405404..18d0d9907fbb4 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -995,6 +995,60 @@ minetest.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`, and +`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 +`minetest.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: + + minetest.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 @@ -1819,6 +1873,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 = } @@ -2523,6 +2584,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 @@ -6289,6 +6352,12 @@ Item handling given `param2` value. * Returns `nil` if the given `paramtype2` does not contain color information. +* `minetest.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`. +* `minetest.set_param2_variant(param2, variant, def)` + * Returns a modified `param2` with the variant bitfield set to `variant` + with the given node definition `def`. * `minetest.get_node_drops(node, toolname)` * Returns list of itemstrings that are dropped by `node` when dug with the item `toolname` (not limited to tools). @@ -6357,6 +6426,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 +* `minetest.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 -------- @@ -8633,6 +8707,9 @@ Used by `minetest.register_node`, `minetest.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. @@ -8799,6 +8876,9 @@ Used by `minetest.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, @@ -8824,6 +8904,18 @@ Used by `minetest.register_node`. -- Special textures of node; used rarely. -- List can be shortened to needed length. + -- See "Variants" + variants = { + [variant number] = { + tiles = {tile definition 1, def2, def3, def4, def5, def6}, + + overlay_tiles = {def1, def2, def3, def4, def5, def6}, + + special_tiles = {def1, def2}, + }, + ... + }, + color = ColorSpec, -- The node's original color will be multiplied with this color. -- If the node has a palette, then this setting only has an effect in @@ -9084,6 +9176,9 @@ Used by `minetest.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/init.lua b/games/devtest/mods/testnodes/init.lua index 786485936cac3..51adf58fb39ff 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/variants.lua b/games/devtest/mods/testnodes/variants.lua new file mode 100644 index 0000000000000..86faee7e498eb --- /dev/null +++ b/games/devtest/mods/testnodes/variants.lua @@ -0,0 +1,232 @@ +-- This file is for variant properties. + +local S = minetest.get_translator("testnodes") + +local animated_tile = { + name = "testnodes_anim.png", + animation = { + type = "vertical_frames", + aspect_w = 16, + aspect_h = 16, + length = 4.0, + }, +} +minetest.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", + }, + }, + }, +}) + +minetest.register_node("testnodes:variant_color", { + description = S("Variant Color Test Node").."\n".. + S("param2 = color + 3 bit variant").."\n".. + S("Has six variants"), + 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", +}) + +minetest.register_node("testnodes:variant_drop", { + description = S("Variant Drop Test Node").."\n".. + S("Has four variants").."\n".. + S("Drops one node with an inherited variant and one without"), + is_ground_content = false, + groups = {dig_immediate = 3}, + variant_count = 4, + param2_variant = {width = 2, offset = 0}, + tiles = {"testnodes_1.png"}, + variants = { + {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}, + }, + }, +}) + +minetest.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"}}, + }, +}) + +minetest.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"}}, + }, +}) + +minetest.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"}}, + }, +}) + +minetest.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"}}, + }, +}) + +minetest.register_node("testnodes:variant_overlay", { + description = S("Variant Overlay Test Node").."\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"}, + variants = { + { + tiles = {"testnodes_node.png"}, + overlay_tiles = {{name = "testnodes_overlay.png", color = "#FFF"}}, + }, + }, + color = "#F00", +}) + +minetest.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 = 6, + 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"}}, + }, +}) + +minetest.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"}}, + }, +}) + +minetest.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 f4670e2c71390..31eb9d8de1521 100644 --- a/src/client/content_mapblock.cpp +++ b/src/client/content_mapblock.cpp @@ -115,7 +115,7 @@ void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile) // Returns a special tile, ready for use, non-rotated. void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile, bool apply_crack) { - *tile = cur_node.f->special_tiles[index]; + *tile = cur_node.f->special_tiles[cur_node.n.getVariant(*cur_node.f)][index]; TileLayer *top_layer = nullptr; for (auto &layernum : tile->layers) { @@ -980,7 +980,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 7bc2c8697fdf8..e880d5e4011ee 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -3713,6 +3713,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/hud.cpp b/src/client/hud.cpp index 5d3de7bfbf670..2c2d32f1beee9 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -1022,6 +1022,9 @@ void drawItemStack( auto *idef = client->idef(); const ItemDefinition &def = item.getDefinition(idef); + 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; @@ -1037,7 +1040,7 @@ void drawItemStack( // Render as mesh if animated or no inventory image if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) { - imesh = idef->getWieldMesh(item, client); + imesh = idef->getWieldMesh(item, variant, client); has_mesh = imesh && imesh->mesh; } if (has_mesh) { @@ -1128,7 +1131,8 @@ void drawItemStack( draw_overlay = def.type == ITEM_NODE && inventory_image.empty(); } else { // Otherwise just draw as 2D - video::ITexture *texture = client->idef()->getInventoryTexture(item, client); + video::ITexture *texture = + client->idef()->getInventoryTexture(item, variant, client); video::SColor color; if (texture) { color = client->idef()->getItemstackColor(item, client); diff --git a/src/client/mapblock_mesh.cpp b/src/client/mapblock_mesh.cpp index 09ca5262b9e9c..36b523b44edc9 100644 --- a/src/client/mapblock_mesh.cpp +++ b/src/client/mapblock_mesh.cpp @@ -362,7 +362,7 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data, { const NodeDefManager *ndef = data->m_client->ndef(); 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.texture_id == 0) diff --git a/src/client/minimap.cpp b/src/client/minimap.cpp index 8ebe2fb03cbe9..7ad2f258a6a1a 100644 --- a/src/client/minimap.cpp +++ b/src/client/minimap.cpp @@ -440,7 +440,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) @@ -448,9 +450,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 0697763ca9908..e03cab46359c2 100644 --- a/src/client/particles.cpp +++ b/src/client/particles.cpp @@ -811,7 +811,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 e83a79f92e140..a223557f93b68 100644 --- a/src/client/wieldmesh.cpp +++ b/src/client/wieldmesh.cpp @@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "client/tile.h" #include "log.h" #include "util/numeric.h" +#include "util/string.h" #include #include #include "client/renderingengine.h" @@ -242,13 +243,13 @@ WieldMeshSceneNode::~WieldMeshSceneNode() g_extrusion_mesh_cache = nullptr; } -void WieldMeshSceneNode::setCube(const ContentFeatures &f, +void WieldMeshSceneNode::setCube(const ContentFeatures &f, u16 variant, v3f wield_scale) { scene::IMesh *cubemesh = g_extrusion_mesh_cache->createCube(); scene::SMesh *copy = cloneMesh(cubemesh); cubemesh->drop(); - postProcessNodeMesh(copy, f, false, true, &m_material_type, &m_colors, true); + postProcessNodeMesh(copy, f, variant, false, true, &m_material_type, &m_colors, true); changeToMesh(copy); copy->drop(); m_meshnode->setScale(wield_scale * WIELD_SCALE_FACTOR); @@ -312,7 +313,7 @@ void WieldMeshSceneNode::setExtruded(const std::string &imagename, } } -static scene::SMesh *createSpecialNodeMesh(Client *client, MapNode n, +static scene::SMesh *createSpecialNodeMesh(Client *client, MapNode n, u16 variant, std::vector *colors, const ContentFeatures &f) { MeshMakeData mesh_make_data(client, false); @@ -334,6 +335,8 @@ static scene::SMesh *createSpecialNodeMesh(Client *client, MapNode n, } else if (f.drawtype == NDT_SIGNLIKE || f.drawtype == NDT_TORCHLIKE) { n.setParam2(1); } + if (f.variant_count > 1) + n.setParam2(f.param2_variant.set(n.getParam2(), variant)); gen.renderSingle(n.getContent(), n.getParam2()); colors->clear(); @@ -370,6 +373,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; @@ -418,30 +423,30 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che v3f wscale = wield_scale; if (f.drawtype == NDT_FLOWINGLIQUID) wscale.Z *= 0.1f; - setExtruded(tsrc->getTextureName(f.tiles[0].layers[0].texture_id), - tsrc->getTextureName(f.tiles[0].layers[1].texture_id), + setExtruded(tsrc->getTextureName(f.tiles[variant][0].layers[0].texture_id), + tsrc->getTextureName(f.tiles[variant][0].layers[1].texture_id), wscale, tsrc, - f.tiles[0].layers[0].animation_frame_count); + f.tiles[variant][0].layers[0].animation_frame_count); // Add color - const TileLayer &l0 = f.tiles[0].layers[0]; + const TileLayer &l0 = f.tiles[variant][0].layers[0]; m_colors.emplace_back(l0.has_color, l0.color); - const TileLayer &l1 = f.tiles[0].layers[1]; + const TileLayer &l1 = f.tiles[variant][0].layers[1]; m_colors.emplace_back(l1.has_color, l1.color); break; } case NDT_PLANTLIKE_ROOTED: { - setExtruded(tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), + setExtruded(tsrc->getTextureName(f.special_tiles[variant][0].layers[0].texture_id), "", wield_scale, tsrc, - f.special_tiles[0].layers[0].animation_frame_count); + f.special_tiles[variant][0].layers[0].animation_frame_count); // Add color - const TileLayer &l0 = f.special_tiles[0].layers[0]; + const TileLayer &l0 = f.special_tiles[variant][0].layers[0]; m_colors.emplace_back(l0.has_color, l0.color); break; } case NDT_NORMAL: case NDT_ALLFACES: case NDT_LIQUID: - setCube(f, wield_scale); + setCube(f, variant, wield_scale); break; default: { // Render non-trivial drawtypes like the actual node @@ -449,7 +454,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che if (def.place_param2) n.setParam2(*def.place_param2); - mesh = createSpecialNodeMesh(client, n, &m_colors, f); + mesh = createSpecialNodeMesh(client, n, variant, &m_colors, f); changeToMesh(mesh); mesh->drop(); m_meshnode->setScale( @@ -575,6 +580,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"); @@ -614,7 +621,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) } else scaleMesh(mesh, v3f(1.2, 1.2, 1.2)); // add overlays - postProcessNodeMesh(mesh, f, false, false, nullptr, + postProcessNodeMesh(mesh, f, variant, false, false, nullptr, &result->buffer_colors, true); if (f.drawtype == NDT_ALLFACES) scaleMesh(mesh, v3f(f.visual_scale)); @@ -622,20 +629,20 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) } case NDT_PLANTLIKE: { mesh = getExtrudedMesh(tsrc, - tsrc->getTextureName(f.tiles[0].layers[0].texture_id), - tsrc->getTextureName(f.tiles[0].layers[1].texture_id)); + tsrc->getTextureName(f.tiles[variant][0].layers[0].texture_id), + tsrc->getTextureName(f.tiles[variant][0].layers[1].texture_id)); // Add color - const TileLayer &l0 = f.tiles[0].layers[0]; + const TileLayer &l0 = f.tiles[variant][0].layers[0]; result->buffer_colors.emplace_back(l0.has_color, l0.color); - const TileLayer &l1 = f.tiles[0].layers[1]; + const TileLayer &l1 = f.tiles[variant][0].layers[1]; result->buffer_colors.emplace_back(l1.has_color, l1.color); break; } case NDT_PLANTLIKE_ROOTED: { mesh = getExtrudedMesh(tsrc, - tsrc->getTextureName(f.special_tiles[0].layers[0].texture_id), ""); + tsrc->getTextureName(f.special_tiles[variant][0].layers[0].texture_id), ""); // Add color - const TileLayer &l0 = f.special_tiles[0].layers[0]; + const TileLayer &l0 = f.special_tiles[variant][0].layers[0]; result->buffer_colors.emplace_back(l0.has_color, l0.color); break; } @@ -645,7 +652,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result) if (def.place_param2) n.setParam2(*def.place_param2); - mesh = createSpecialNodeMesh(client, n, &result->buffer_colors, f); + mesh = createSpecialNodeMesh(client, n, variant, &result->buffer_colors, f); scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); break; } @@ -718,7 +725,7 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, return mesh; } -void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, +void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, u16 variant, bool use_shaders, bool set_material, const video::E_MATERIAL_TYPE *mattype, std::vector *colors, bool apply_scale) { @@ -729,7 +736,7 @@ void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, colors->push_back(ItemPartColor()); for (u32 i = 0; i < mc; ++i) { - const TileSpec *tile = &(f.tiles[i]); + const TileSpec *tile = &(f.tiles[variant][i]); scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); for (int layernum = 0; layernum < MAX_TILE_LAYERS; layernum++) { const TileLayer *layer = &tile->layers[layernum]; diff --git a/src/client/wieldmesh.h b/src/client/wieldmesh.h index be0867e437e47..7e3d99aaf38ea 100644 --- a/src/client/wieldmesh.h +++ b/src/client/wieldmesh.h @@ -78,7 +78,7 @@ class WieldMeshSceneNode : public scene::ISceneNode WieldMeshSceneNode(scene::ISceneManager *mgr, s32 id = -1, bool lighting = false); virtual ~WieldMeshSceneNode(); - void setCube(const ContentFeatures &f, v3f wield_scale); + void setCube(const ContentFeatures &f, u16 variant, v3f wield_scale); void setExtruded(const std::string &imagename, const std::string &overlay_image, v3f wield_scale, ITextureSource *tsrc, u8 num_frames); void setItem(const ItemStack &item, Client *client, @@ -141,6 +141,6 @@ scene::SMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename * be NULL to leave the original material. * \param colors returns the colors of the mesh buffers in the mesh. */ -void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, bool use_shaders, - bool set_material, const video::E_MATERIAL_TYPE *mattype, +void postProcessNodeMesh(scene::SMesh *mesh, const ContentFeatures &f, u16 variant, + bool use_shaders, bool set_material, const video::E_MATERIAL_TYPE *mattype, std::vector *colors, bool apply_scale = false); diff --git a/src/itemdef.cpp b/src/itemdef.cpp index 07f07a53e8959..628d8fa7f0654 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -362,17 +362,27 @@ class CItemDefManager: public IWritableItemDefManager << cache_key << "\"" << std::endl; ITextureSource *tsrc = client->getTextureSource(); + + u16 variant_count = client->ndef()->get(cache_key).variant_count; // Create new ClientCached - auto cc = std::make_unique(); + auto cc = std::make_unique(variant_count); - // Create an inventory texture - 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 (!def.inventory_image.empty()) + cc[v].inventory_texture = tsrc->getTexture(def.inventory_image); + + ItemStack item = ItemStack(); + item.name = def.name; + if (v > 0) + item.metadata.setString("variant", std::to_string(v)); - cc->palette = tsrc->getPalette(def.palette_image); + getItemMesh(client, item, &cc[v].wield_mesh); + + cc[v].palette = tsrc->getPalette(def.palette_image); + } // Put in cache ClientCached *ptr = cc.get(); @@ -381,22 +391,23 @@ class CItemDefManager: public IWritableItemDefManager } // Get item inventory texture - virtual video::ITexture* getInventoryTexture(const ItemStack &item, + virtual video::ITexture* getInventoryTexture(const ItemStack &item, u16 variant, Client *client) const { ClientCached *cc = createClientCachedDirect(item, client); if (!cc) return nullptr; - return cc->inventory_texture; + return cc[variant].inventory_texture; } // Get item wield mesh - virtual ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const + virtual ItemMesh* getWieldMesh(const ItemStack &item, u16 variant, + Client *client) const { ClientCached *cc = createClientCachedDirect(item, client); if (!cc) return nullptr; - return &(cc->wield_mesh); + return &(cc[variant].wield_mesh); } // Get item palette @@ -572,7 +583,7 @@ class CItemDefManager: public IWritableItemDefManager // 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_clientcached; + mutable std::unordered_map> m_clientcached; #endif }; diff --git a/src/itemdef.h b/src/itemdef.h index 8ce6a15cd07a8..a84934c9d9b05 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -121,14 +121,18 @@ class IItemDefManager virtual bool isKnown(const std::string &name) const=0; #ifndef SERVER // Get item inventory texture - virtual video::ITexture* getInventoryTexture(const ItemStack &item, Client *client) const=0; - + // variant must be valid + virtual video::ITexture* getInventoryTexture(const ItemStack &item, u16 variant, + Client *client) const=0; /** * Get wield mesh * + * variant must be valid + * * Returns nullptr if there is an inventory image */ - virtual ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const = 0; + virtual ItemMesh* getWieldMesh(const ItemStack &item, u16 variant, + Client *client) const=0; // Get item palette virtual Palette* getPalette(const ItemStack &item, Client *client) const = 0; // Returns the base color of an item stack: the color of all diff --git a/src/mapnode.cpp b/src/mapnode.cpp index 76c050e7f8da2..e3b6bd90ed956 100644 --- a/src/mapnode.cpp +++ b/src/mapnode.cpp @@ -53,6 +53,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 c3137bbcb55c0..d7df260d88df9 100644 --- a/src/mapnode.h +++ b/src/mapnode.h @@ -207,6 +207,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 fa9198c5beac2..9f77c372da03a 100644 --- a/src/nodedef.cpp +++ b/src/nodedef.cpp @@ -325,12 +325,16 @@ ContentFeatures::ContentFeatures() ContentFeatures::~ContentFeatures() { #ifndef SERVER - 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 } @@ -340,6 +344,8 @@ void ContentFeatures::reset() Cached stuff */ #ifndef SERVER + tiles = std::vector >(1); + special_tiles = std::vector >(1); solidness = 2; visual_solidness = 0; backface_culling = true; @@ -363,17 +369,18 @@ void ContentFeatures::reset() #ifndef SERVER for (auto &i : mesh_ptr) i = NULL; - 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; @@ -465,12 +472,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()); @@ -543,6 +550,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)); @@ -663,6 +685,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) {}; } @@ -768,26 +827,36 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings) { // minimap pixel color - the average color of a texture - if (tsettings.enable_minimap && !tiledef[0].name.empty()) - minimap_color = tsrc->getTextureAverageColor(tiledef[0].name); + if (tsettings.enable_minimap) { + minimap_color = std::vector(variant_count); + for (u16 v = 0; v < variant_count; v++) { + if (!tiledef[v][0].name.empty()) + minimap_color[v] = tsrc->getTextureAverageColor(tiledef[v][0].name); + } + } // 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; @@ -839,9 +908,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; @@ -856,8 +927,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; @@ -916,16 +989,19 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc u32 overlay_shader = shdsrc->getShader("nodes_shader", overlay_material, drawtype); // Tiles (fill in f->tiles[]) - 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); + } } MaterialType special_material = material_type; @@ -938,10 +1014,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 || @@ -1047,7 +1127,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; @@ -1416,52 +1496,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 730a6cfc9b236..4469398164597 100644 --- a/src/nodedef.h +++ b/src/nodedef.h @@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include #include #include +#include #include "mapnode.h" #include "nameidmapping.h" #ifndef SERVER @@ -35,6 +36,7 @@ class Client; #include "constants.h" // BS #include "texture_override.h" // TextureOverride #include "tileanimation.h" +#include "util/numeric.h" class IItemDefManager; class ITextureSource; @@ -309,9 +311,9 @@ struct ContentFeatures #ifndef SERVER // 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; @@ -337,6 +339,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 --- @@ -344,13 +350,13 @@ struct ContentFeatures std::string mesh; #ifndef SERVER scene::IMesh *mesh_ptr[24]; - 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 1756c49c5c977..8f26174e8dfc6 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -152,6 +152,8 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i) { std::string type(es_ItemType[(int)i.type].str); + // 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"); @@ -579,12 +581,71 @@ 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; + } + } + // 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 = 2; 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"); if(!lua_isnil(L, -1)) f.has_on_construct = true; @@ -619,76 +680,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); - // removes value, keeps key for next iteration - lua_pop(L, 1); - i++; - if(i==CF_SPECIAL_COUNT){ + 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); - break; } + // removes value, keeps key for next iteration + lua_pop(L, 1); } } lua_pop(L, 1); @@ -907,9 +937,14 @@ void push_content_features(lua_State *L, const ContentFeatures &c) std::string drawtype(ScriptApiNode::es_DrawType[(int)c.drawtype].str); std::string liquid_type(ScriptApiNode::es_LiquidType[(int)c.liquid_type].str); - /* 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); @@ -931,7 +966,7 @@ void push_content_features(lua_State *L, const ContentFeatures &c) lua_setfield(L, -2, "mesh"); } #ifndef SERVER - 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_content.h b/src/script/common/c_content.h index 4335479c4ebcc..4d58b1004662e 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -90,6 +90,12 @@ void push_palette (lua_State *L, TileDef read_tiledef (lua_State *L, int index, u8 drawtype, bool special); +void read_tiledefs (lua_State *L, int index, + u8 drawtype, bool special, + std::array &tiledefs); +void read_default_tiledefs (lua_State *L, int index, + u8 drawtype, bool special, + std::vector > &tiledefs); void read_simplesoundspec (lua_State *L, int index, SoundSpec &spec); diff --git a/src/script/common/c_converter.cpp b/src/script/common/c_converter.cpp index 7924b9fc2b7c5..04d98b5efd420 100644 --- a/src/script/common/c_converter.cpp +++ b/src/script/common/c_converter.cpp @@ -381,6 +381,15 @@ void push_aabb3f(lua_State *L, aabb3f box) 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; @@ -433,6 +442,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 90358147ae013..15058c430bc3a 100644 --- a/src/script/common/c_converter.h +++ b/src/script/common/c_converter.h @@ -31,6 +31,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "irrlichttypes_bloated.h" #include "common/c_types.h" +#include "util/numeric.h" extern "C" { #include @@ -105,6 +106,16 @@ v3s16 read_v3s16 (lua_State *L, int index); 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); @@ -115,6 +126,14 @@ void push_ARGB8 (lua_State *L, video::SColor color); void pushFloatPos (lua_State *L, v3f p); void push_v3f (lua_State *L, v3f p); void push_v2f (lua_State *L, v2f p); +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, diff --git a/src/unittest/test.cpp b/src/unittest/test.cpp index 761c908135d57..2e642eda04450 100644 --- a/src/unittest/test.cpp +++ b/src/unittest/test.cpp @@ -88,7 +88,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); @@ -106,10 +106,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); @@ -145,7 +145,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); @@ -167,7 +167,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); @@ -185,7 +185,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_nodedef.cpp b/src/unittest/test_nodedef.cpp index acf6697832fd4..2cd0771bfe7e8 100644 --- a/src/unittest/test_nodedef.cpp +++ b/src/unittest/test_nodedef.cpp @@ -50,7 +50,8 @@ void TestNodeDef::testContentFeaturesSerialization() 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 0a19f1466567b..9ecb412408ac4 100644 --- a/src/unittest/test_utilities.cpp +++ b/src/unittest/test_utilities.cpp @@ -59,6 +59,7 @@ class TestUtilities : public TestBase { void testBase64(); void testSanitizeDirName(); void testIsBlockInSight(); + void testBitField(); }; static TestUtilities g_test_instance; @@ -92,6 +93,7 @@ void TestUtilities::runTests(IGameDef *gamedef) TEST(testBase64); TEST(testSanitizeDirName); TEST(testIsBlockInSight); + TEST(testBitField); } //////////////////////////////////////////////////////////////////////////////// @@ -704,3 +706,23 @@ void TestUtilities::testIsBlockInSight() UASSERT(isBlockInSight({-1, 0, 0}, cam_pos, cam_dir, fov, range)); } } + +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 d67dd0f5fb69b..c9e9cf026aa28 100644 --- a/src/util/numeric.h +++ b/src/util/numeric.h @@ -28,6 +28,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "SColor.h" #include #include +#include +#if _MSC_VER +#include // For popcnt +#endif #define rangelim(d, min, max) ((d) < (min) ? (min) : ((d) > (max) ? (max) : (d))) #define myfloor(x) ((x) < 0.0 ? (int)(x) - 1 : (int)(x)) @@ -267,6 +271,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;