1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00

Add node texture variants

This commit is contained in:
Jude Melton-Houghton 2022-11-03 21:18:49 -04:00 committed by SFENCE
parent 81d62d01d1
commit e638072e15
29 changed files with 984 additions and 208 deletions

View file

@ -180,6 +180,25 @@ function core.strip_param2_color(param2, paramtype2)
end end
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 -- Content ID caching
local old_get_content_id = core.get_content_id local old_get_content_id = core.get_content_id

View file

@ -47,6 +47,7 @@ core.features = {
particle_blend_clip = true, particle_blend_clip = true,
remove_item_match_meta = true, remove_item_match_meta = true,
httpfetch_additional_methods = true, httpfetch_additional_methods = true,
texture_variants = true,
} }
function core.has_feature(arg) function core.has_feature(arg)

View file

@ -52,14 +52,18 @@ function core.get_node_drops(node, toolname)
local ptype = def and def.paramtype2 local ptype = def and def.paramtype2
-- get color, if there is color (otherwise nil) -- get color, if there is color (otherwise nil)
local palette_index = core.strip_param2_color(param2, ptype) 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 if drop == nil then
-- default drop -- default drop
local itemstring = nodename
if palette_index then if palette_index then
local stack = ItemStack(nodename) itemstring = core.itemstring_with_palette(itemstring, palette_index)
stack:get_meta():set_int("palette_index", palette_index)
return {stack:to_string()}
end end
return {nodename} if variant > 0 then
itemstring = core.itemstring_with_variant(itemstring, variant)
end
return {itemstring}
elseif type(drop) == "string" then elseif type(drop) == "string" then
-- itemstring drop -- itemstring drop
return drop ~= "" and {drop} or {} return drop ~= "" and {drop} or {}
@ -118,9 +122,10 @@ function core.get_node_drops(node, toolname)
for _, add_item in ipairs(item.items) do for _, add_item in ipairs(item.items) do
-- add color, if necessary -- add color, if necessary
if item.inherit_color and palette_index then if item.inherit_color and palette_index then
local stack = ItemStack(add_item) add_item = core.itemstring_with_palette(add_item, palette_index)
stack:get_meta():set_int("palette_index", palette_index) end
add_item = stack:to_string() if item.inherit_variant then
add_item = core.itemstring_with_variant(add_item, variant)
end end
got_items[#got_items+1] = add_item got_items[#got_items+1] = add_item
end end
@ -270,6 +275,13 @@ function core.item_place_node(itemstack, placer, pointed_thing, param2,
end end
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 -- Check if the node is attached and if it can be placed there
local an = core.get_item_group(def.name, "attached_node") local an = core.get_item_group(def.name, "attached_node")
if an ~= 0 and if an ~= 0 and
@ -604,6 +616,12 @@ function core.itemstring_with_color(item, colorstring)
return stack:to_string() return stack:to_string()
end 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 -- 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 -- NOTE: This is not the preferred way. Preferred way is to provide enough
-- callbacks to not require redefining global functions. -celeron55 -- callbacks to not require redefining global functions. -celeron55
@ -625,6 +643,7 @@ core.nodedef_default = {
-- name intentionally not defined here -- name intentionally not defined here
description = "", description = "",
groups = {}, groups = {},
variant_count = 1,
inventory_image = "", inventory_image = "",
wield_image = "", wield_image = "",
wield_scale = vector.new(1, 1, 1), 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}, post_effect_color = {a=0, r=0, g=0, b=0},
paramtype = "none", paramtype = "none",
paramtype2 = "none", paramtype2 = "none",
param2_variant = {width = 0, offset = 0},
is_ground_content = true, is_ground_content = true,
sunlight_propagates = false, sunlight_propagates = false,
walkable = true, walkable = true,
@ -680,6 +700,7 @@ core.craftitemdef_default = {
-- name intentionally not defined here -- name intentionally not defined here
description = "", description = "",
groups = {}, groups = {},
variant_count = 1,
inventory_image = "", inventory_image = "",
wield_image = "", wield_image = "",
wield_scale = vector.new(1, 1, 1), wield_scale = vector.new(1, 1, 1),
@ -700,6 +721,7 @@ core.tooldef_default = {
-- name intentionally not defined here -- name intentionally not defined here
description = "", description = "",
groups = {}, groups = {},
variant_count = 1,
inventory_image = "", inventory_image = "",
wield_image = "", wield_image = "",
wield_scale = vector.new(1, 1, 1), 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 -- name intentionally not defined here
description = "", description = "",
groups = {}, groups = {},
variant_count = 1,
inventory_image = "", inventory_image = "",
wield_image = "", wield_image = "",
wield_scale = vector.new(1, 1, 1), wield_scale = vector.new(1, 1, 1),

View file

@ -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 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 For entities with rotated selection boxes, this will be rotated properly
by the entity's rotation - it will always be in absolute world space. 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 = <integer bit width>, offset = <offset from least significant bit>}
@ -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. * `color`: A `ColorString`, which sets the stack's color.
* `palette_index`: If the item has a palette, this is used to get the * `palette_index`: If the item has a palette, this is used to get the
current color from the palette. 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_meta`: Replace the displayed count with any string.
* `count_alignment`: Set the alignment of the displayed count value. This is an * `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 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, remove_item_match_meta = true,
-- The HTTP API supports the HEAD and PATCH methods (5.12.0) -- The HTTP API supports the HEAD and PATCH methods (5.12.0)
httpfetch_additional_methods = true, 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. given `param2` value.
* Returns `nil` if the given `paramtype2` does not contain color * Returns `nil` if the given `paramtype2` does not contain color
information. information.
<<<<<<< HEAD
* `core.get_node_drops(node, toolname[, tool, digger, pos])` * `core.get_node_drops(node, toolname[, tool, digger, pos])`
* Returns list of itemstrings that are dropped by `node` when dug with the * Returns list of itemstrings that are dropped by `node` when dug with the
item `toolname` (not limited to tools). The default implementation doesn't item `toolname` (not limited to tools). The default implementation doesn't
use `tool`, `digger`, and `pos`, but these are provided by `core.node_dig` use `tool`, `digger`, and `pos`, but these are provided by `core.node_dig`
since 5.12.0 for games/mods implementing customized drops. 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 * `node`: node as table or node name
* `toolname`: name of the item used to dig (can be `nil`) * `toolname`: name of the item used to dig (can be `nil`)
* `tool`: `ItemStack` 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, * `item`: the item stack which becomes colored. Can be in string,
table and native form. table and native form.
* `colorstring`: the new color of the item stack * `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 Rollback
-------- --------
@ -9779,6 +9864,9 @@ Used by `core.register_node`, `core.register_craftitem`, and
-- {bendy = 2, snappy = 1}, -- {bendy = 2, snappy = 1},
-- {hard = 1, metal = 1, spikes = 1} -- {hard = 1, metal = 1, spikes = 1}
variant_count = 1,
-- The number item variants, a positive integer.
inventory_image = "", inventory_image = "",
-- Texture shown in the inventory GUI -- Texture shown in the inventory GUI
-- Defaults to a 3D rendering of the node if left empty. -- Defaults to a 3D rendering of the node if left empty.
@ -10003,6 +10091,9 @@ Used by `core.register_node`.
{ {
-- <all fields allowed in item definitions> -- <all fields allowed in item definitions>
param2_variant = BitField,
-- The part of param2 from which to read the variant number.
drawtype = "normal", -- See "Node drawtypes" drawtype = "normal", -- See "Node drawtypes"
visual_scale = 1.0, visual_scale = 1.0,
@ -10016,6 +10107,7 @@ Used by `core.register_node`.
tiles = {tile definition 1, def2, def3, def4, def5, def6}, tiles = {tile definition 1, def2, def3, def4, def5, def6},
-- Textures of node; +Y, -Y, +X, -X, +Z, -Z -- Textures of node; +Y, -Y, +X, -X, +Z, -Z
-- List can be shortened to needed length. -- 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}, overlay_tiles = {tile definition 1, def2, def3, def4, def5, def6},
-- Same as `tiles`, but these textures are drawn on top of the base -- 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 -- 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 -- drawn. Since such tiles are drawn twice, it is not recommended to use
-- overlays on very common nodes. -- 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_tiles = {tile definition 1, Tile definition 2},
-- Special textures of node; used rarely. -- Special textures of node; used rarely.
-- List can be shortened to needed length. -- 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, color = ColorSpec,
-- The node's original color will be multiplied with this color. -- 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. -- hardware coloring palette color from the dug node.
-- Default is 'false'. -- Default is 'false'.
inherit_color = true, inherit_color = true,
-- variant of the dug node.
-- Default is 'false'.
inherit_variant = true,
}, },
{ {
-- Only drop if using an item whose name contains -- Only drop if using an item whose name contains

View file

@ -25,6 +25,23 @@ core.register_node("testnodes:normal", {
groups = { dig_immediate = 3 }, 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 -- Standard glasslike node
core.register_node("testnodes:glasslike", { core.register_node("testnodes:glasslike", {
description = S("\"glasslike\" Drawtype Test Node").."\n".. description = S("\"glasslike\" Drawtype Test Node").."\n"..

View file

@ -10,4 +10,5 @@ dofile(path.."/liquids.lua")
dofile(path.."/light.lua") dofile(path.."/light.lua")
dofile(path.."/textures.lua") dofile(path.."/textures.lua")
dofile(path.."/overlays.lua") dofile(path.."/overlays.lua")
dofile(path.."/variants.lua")
dofile(path.."/commands.lua") dofile(path.."/commands.lua")

View file

@ -7,6 +7,20 @@ core.register_node("testnodes:overlay", {
overlay_tiles = {{name = "testnodes_overlay.png"}}, overlay_tiles = {{name = "testnodes_overlay.png"}},
groups = { dig_immediate = 2 }, 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", { core.register_node("testnodes:overlay_color_all", {
description = S("Texture Overlay Test Node, Colorized") .. "\n" .. description = S("Texture Overlay Test Node, Colorized") .. "\n" ..
S("param2 changes color"), S("param2 changes color"),

View file

@ -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"}},
},
})

View file

@ -98,7 +98,7 @@ void MapblockMeshGenerator::getTile(v3s16 direction, TileSpec *tile_ret)
// Returns a special tile, ready for use, non-rotated. // Returns a special tile, ready for use, non-rotated.
void MapblockMeshGenerator::getSpecialTile(int index, TileSpec *tile_ret, bool apply_crack) 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; TileLayer *top_layer = nullptr;
for (auto &layernum : tile_ret->layers) { for (auto &layernum : tile_ret->layers) {
@ -1023,7 +1023,7 @@ void MapblockMeshGenerator::drawGlasslikeFramedNode()
// Optionally render internal liquid level defined by param2 // Optionally render internal liquid level defined by param2
// Liquid is textured with 1 tile defined in nodedef 'special_tiles' // Liquid is textured with 1 tile defined in nodedef 'special_tiles'
if (param2 > 0 && cur_node.f->param_type_2 == CPT2_GLASSLIKE_LIQUID_LEVEL && 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, // Internal liquid level has param2 range 0 .. 63,
// convert it to -0.5 .. 0.5 // convert it to -0.5 .. 0.5
float vlev = (param2 / 63.0f) * 2.0f - 1.0f; float vlev = (param2 / 63.0f) * 2.0f - 1.0f;

View file

@ -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 // Add node to client map
try { try {
LocalPlayer *player = client->getEnv().getLocalPlayer(); LocalPlayer *player = client->getEnv().getLocalPlayer();

View file

@ -15,8 +15,8 @@ ItemVisualsManager::ItemVisuals::~ItemVisuals() {
wield_mesh.mesh->drop(); wield_mesh.mesh->drop();
} }
ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const ItemStack &item, ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals(
Client *client) const const ItemStack &item, Client *client, u16 &variant_count) const
{ {
// This is not thread-safe // This is not thread-safe
sanity_check(std::this_thread::get_id() == m_main_thread); sanity_check(std::this_thread::get_id() == m_main_thread);
@ -32,6 +32,8 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It
if (!inventory_overlay.empty()) if (!inventory_overlay.empty())
cache_key += ":" + inventory_overlay; cache_key += ":" + inventory_overlay;
variant_count = client->ndef()->get(cache_key).variant_count;
// Skip if already in cache // Skip if already in cache
auto it = m_cached_item_visuals.find(cache_key); auto it = m_cached_item_visuals.find(cache_key);
if (it != m_cached_item_visuals.end()) if (it != m_cached_item_visuals.end())
@ -43,14 +45,23 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It
ITextureSource *tsrc = client->getTextureSource(); ITextureSource *tsrc = client->getTextureSource();
// Create new ItemVisuals // Create new ItemVisuals
auto cc = std::make_unique<ItemVisuals>(); auto cc = std::make_unique<ItemVisuals[]>(variant_count);
cc->inventory_texture = NULL; for (u16 v = 0; v < variant_count; v++) {
if (!inventory_image.empty()) // Create an inventory texture
cc->inventory_texture = tsrc->getTexture(inventory_image); cc[v].inventory_texture = NULL;
getItemMesh(client, item, &(cc->wield_mesh)); 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 // Put in cache
ItemVisuals *ptr = cc.get(); ItemVisuals *ptr = cc.get();
@ -59,25 +70,29 @@ ItemVisualsManager::ItemVisuals *ItemVisualsManager::createItemVisuals( const It
} }
video::ITexture* ItemVisualsManager::getInventoryTexture(const ItemStack &item, video::ITexture* ItemVisualsManager::getInventoryTexture(const ItemStack &item,
Client *client) const u16 variant, Client *client) const
{ {
ItemVisuals *iv = createItemVisuals(item, client); u16 variant_count;
if (!iv) ItemVisuals *iv = createItemVisuals(item, client, variant_count);
if (!iv || (variant >= variant_count))
return nullptr; 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); u16 variant_count;
if (!iv) ItemVisuals *iv = createItemVisuals(item, client, variant_count);
if (!iv || (variant >= variant_count))
return nullptr; return nullptr;
return &(iv->wield_mesh); return &(iv[variant].wield_mesh);
} }
Palette* ItemVisualsManager::getPalette(const ItemStack &item, Client *client) const 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) if (!iv)
return nullptr; return nullptr;
return iv->palette; return iv->palette;

View file

@ -30,11 +30,11 @@ struct ItemVisualsManager
} }
// Get item inventory texture // 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 // Get item wield mesh
// Once said to return nullptr if there is an inventory image, but this is wrong // 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 // Get item palette
Palette* getPalette(const ItemStack &item, Client *client) const; 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 // The id of the thread that is allowed to use irrlicht directly
std::thread::id m_main_thread; std::thread::id m_main_thread;
// Cached textures and meshes // Cached textures and meshes
mutable std::unordered_map<std::string, std::unique_ptr<ItemVisuals>> m_cached_item_visuals; mutable std::unordered_map<std::string, std::unique_ptr<ItemVisuals[]>> m_cached_item_visuals;
ItemVisuals* createItemVisuals(const ItemStack &item, Client *client) const; ItemVisuals* createItemVisuals(const ItemStack &item, Client *client, u16 &variant_count) const;
}; };

View file

@ -344,7 +344,7 @@ void getNodeTileN(MapNode mn, const v3s16 &p, u8 tileindex, MeshMakeData *data,
{ {
const NodeDefManager *ndef = data->m_nodedef; const NodeDefManager *ndef = data->m_nodedef;
const ContentFeatures &f = ndef->get(mn); 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; bool has_crack = p == data->m_crack_pos_relative;
for (TileLayer &layer : tile.layers) { for (TileLayer &layer : tile.layers) {
if (layer.empty()) if (layer.empty())

View file

@ -403,7 +403,9 @@ void Minimap::blitMinimapPixelsToImageSurface(
MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.map_size]; MinimapPixel *mmpixel = &data->minimap_scan[x + z * data->mode.map_size];
const ContentFeatures &f = m_ndef->get(mmpixel->n); 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) // Color of the 0th tile (mostly this is the topmost)
if(tile->has_color) if(tile->has_color)
@ -411,9 +413,9 @@ void Minimap::blitMinimapPixelsToImageSurface(
else else
mmpixel->n.getColor(f, &tilecolor); mmpixel->n.getColor(f, &tilecolor);
tilecolor.setRed(tilecolor.getRed() * f.minimap_color.getRed() / 255); tilecolor.setRed(tilecolor.getRed() * minimap_color.getRed() / 255);
tilecolor.setGreen(tilecolor.getGreen() * f.minimap_color.getGreen() / 255); tilecolor.setGreen(tilecolor.getGreen() * minimap_color.getGreen() / 255);
tilecolor.setBlue(tilecolor.getBlue() * f.minimap_color.getBlue() / 255); tilecolor.setBlue(tilecolor.getBlue() * minimap_color.getBlue() / 255);
tilecolor.setAlpha(240); tilecolor.setAlpha(240);
map_image->setPixel(x, data->mode.map_size - z - 1, tilecolor); map_image->setPixel(x, data->mode.map_size - z - 1, tilecolor);

View file

@ -867,7 +867,7 @@ bool ParticleManager::getNodeParticleParams(const MapNode &n,
texid = tilenum - 1; texid = tilenum - 1;
else else
texid = myrand_range(0,5); 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; p.animation.type = TAT_NONE;
// Only use first frame of animated texture // Only use first frame of animated texture

View file

@ -17,6 +17,7 @@
#include "client/texturesource.h" #include "client/texturesource.h"
#include "log.h" #include "log.h"
#include "util/numeric.h" #include "util/numeric.h"
#include "util/string.h"
#include <map> #include <map>
#include <IMeshManipulator.h> #include <IMeshManipulator.h>
#include "client/renderingengine.h" #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<ItemMeshBufferInfo> *buffer_info, const ContentFeatures &f) std::vector<ItemMeshBufferInfo> *buffer_info, const ContentFeatures &f)
{ {
n.setParam1(0xff); n.setParam1(0xff);
@ -310,6 +311,9 @@ static scene::SMesh *createGenericNodeMesh(Client *client, MapNode n,
n.setParam2(1); n.setParam2(1);
} }
if (f.variant_count > 1)
n.setParam2(f.param2_variant.set(n.getParam2(), variant));
MeshCollector collector(v3f(0), v3f()); MeshCollector collector(v3f(0), v3f());
{ {
MeshMakeData mmd(client->ndef(), 1, MeshGrid{1}); 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 ItemDefinition &def = item.getDefinition(idef);
const ContentFeatures &f = ndef->get(def.name); const ContentFeatures &f = ndef->get(def.name);
content_t id = ndef->getId(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; scene::SMesh *mesh = nullptr;
@ -395,8 +401,8 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
v3f wscale = wield_scale; v3f wscale = wield_scale;
if (f.drawtype == NDT_FLOWINGLIQUID) if (f.drawtype == NDT_FLOWINGLIQUID)
wscale.Z *= 0.1f; wscale.Z *= 0.1f;
const TileLayer &l0 = f.tiles[0].layers[0]; const TileLayer &l0 = f.tiles[variant][0].layers[0];
const TileLayer &l1 = f.tiles[0].layers[1]; const TileLayer &l1 = f.tiles[variant][0].layers[1];
setExtruded(tsrc->getTextureName(l0.texture_id), setExtruded(tsrc->getTextureName(l0.texture_id),
tsrc->getTextureName(l1.texture_id), tsrc->getTextureName(l1.texture_id),
wscale, tsrc, wscale, tsrc,
@ -408,7 +414,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
} }
case NDT_PLANTLIKE_ROOTED: { case NDT_PLANTLIKE_ROOTED: {
// use the plant tile // 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), setExtruded(tsrc->getTextureName(l0.texture_id),
"", wield_scale, tsrc, "", wield_scale, tsrc,
l0.animation_frame_count); l0.animation_frame_count);
@ -421,7 +427,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client, bool che
if (def.place_param2) if (def.place_param2)
n.setParam2(*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); changeToMesh(mesh);
mesh->drop(); mesh->drop();
m_meshnode->setScale( m_meshnode->setScale(
@ -544,6 +550,8 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
const ItemDefinition &def = item.getDefinition(idef); const ItemDefinition &def = item.getDefinition(idef);
const ContentFeatures &f = ndef->get(def.name); const ContentFeatures &f = ndef->get(def.name);
content_t id = ndef->getId(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"); 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) { } else if (def.type == ITEM_NODE) {
switch (f.drawtype) { switch (f.drawtype) {
case NDT_PLANTLIKE: { case NDT_PLANTLIKE: {
const TileLayer &l0 = f.tiles[0].layers[0]; const TileLayer &l0 = f.tiles[variant][0].layers[0];
const TileLayer &l1 = f.tiles[0].layers[1]; const TileLayer &l1 = f.tiles[variant][0].layers[1];
mesh = getExtrudedMesh(tsrc, mesh = getExtrudedMesh(tsrc,
tsrc->getTextureName(l0.texture_id), tsrc->getTextureName(l0.texture_id),
tsrc->getTextureName(l1.texture_id)); tsrc->getTextureName(l1.texture_id));
@ -581,7 +589,7 @@ void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
} }
case NDT_PLANTLIKE_ROOTED: { case NDT_PLANTLIKE_ROOTED: {
// Use the plant tile // 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, mesh = getExtrudedMesh(tsrc,
tsrc->getTextureName(l0.texture_id), ""); tsrc->getTextureName(l0.texture_id), "");
result->buffer_info.emplace_back(l0.has_color, l0.color); 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) if (def.place_param2)
n.setParam2(*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)); scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
break; break;
} }

View file

@ -45,6 +45,9 @@ void drawItemStack(
auto *idef = client->idef(); auto *idef = client->idef();
const ItemDefinition &def = item.getDefinition(idef); const ItemDefinition &def = item.getDefinition(idef);
ItemVisualsManager* item_visuals = client->getItemVisualsManager(); 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; bool draw_overlay = false;
@ -60,7 +63,7 @@ void drawItemStack(
// Render as mesh if animated or no inventory image // Render as mesh if animated or no inventory image
if ((enable_animations && rotation_kind < IT_ROT_NONE) || inventory_image.empty()) { 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; has_mesh = imesh && imesh->mesh;
} }
if (has_mesh) { if (has_mesh) {
@ -155,7 +158,7 @@ void drawItemStack(
draw_overlay = def.type == ITEM_NODE && inventory_image.empty(); draw_overlay = def.type == ITEM_NODE && inventory_image.empty();
} else { // Otherwise just draw as 2D } 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; video::SColor color;
if (texture) { if (texture) {
color = item_visuals->getItemstackColor(item, client); color = item_visuals->getItemstackColor(item, client);

View file

@ -38,6 +38,11 @@ void MapNode::getColor(const ContentFeatures &f, video::SColor *color) const
*color = f.color; *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, u8 MapNode::getFaceDir(const NodeDefManager *nodemgr,
bool allow_wallmounted) const bool allow_wallmounted) const
{ {

View file

@ -196,6 +196,12 @@ struct alignas(u32) MapNode
*/ */
void getColor(const ContentFeatures &f, video::SColor *color) const; 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 inline void setLight(LightBank bank, u8 a_light, ContentLightingFlags f) noexcept
{ {
// If node doesn't contain light data, ignore this // If node doesn't contain light data, ignore this

View file

@ -316,12 +316,16 @@ ContentFeatures::ContentFeatures()
ContentFeatures::~ContentFeatures() ContentFeatures::~ContentFeatures()
{ {
#if CHECK_CLIENT_BUILD() #if CHECK_CLIENT_BUILD()
for (u16 j = 0; j < 6; j++) { for (auto &t : tiles) {
delete tiles[j].layers[0].frames; for (u16 j = 0; j < 6; j++) {
delete tiles[j].layers[1].frames; 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 #endif
} }
@ -331,6 +335,8 @@ void ContentFeatures::reset()
Cached stuff Cached stuff
*/ */
#if CHECK_CLIENT_BUILD() #if CHECK_CLIENT_BUILD()
tiles = std::vector<std::array<TileSpec, 6> >(1);
special_tiles = std::vector<std::array<TileSpec, CF_SPECIAL_COUNT> >(1);
solidness = 2; solidness = 2;
visual_solidness = 0; visual_solidness = 0;
backface_culling = true; backface_culling = true;
@ -355,17 +361,18 @@ void ContentFeatures::reset()
mesh.clear(); mesh.clear();
#if CHECK_CLIENT_BUILD() #if CHECK_CLIENT_BUILD()
mesh_ptr = nullptr; mesh_ptr = nullptr;
minimap_color = video::SColor(0, 0, 0, 0); minimap_color = std::vector<video::SColor>(1, video::SColor(0, 0, 0, 0));
#endif #endif
visual_scale = 1.0; visual_scale = 1.0;
for (auto &i : tiledef) tiledef = std::vector<std::array<TileDef, 6> >(1);
i = TileDef(); tiledef_overlay = std::vector<std::array<TileDef, 6> >(1);
for (auto &j : tiledef_special) tiledef_special = std::vector<std::array<TileDef, CF_SPECIAL_COUNT> >(1);
j = TileDef();
alpha = ALPHAMODE_OPAQUE; alpha = ALPHAMODE_OPAQUE;
post_effect_color = video::SColor(0, 0, 0, 0); post_effect_color = video::SColor(0, 0, 0, 0);
param_type = CPT_NONE; param_type = CPT_NONE;
param_type_2 = CPT2_NONE; param_type_2 = CPT2_NONE;
variant_count = 1;
param2_variant = BitField<u8>();
is_ground_content = false; is_ground_content = false;
light_propagates = false; light_propagates = false;
sunlight_propagates = false; sunlight_propagates = false;
@ -457,12 +464,12 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
os << serializeString16(mesh); os << serializeString16(mesh);
writeF32(os, visual_scale); writeF32(os, visual_scale);
writeU8(os, 6); writeU8(os, 6);
for (const TileDef &td : tiledef) for (const TileDef &td : tiledef[0])
td.serialize(os, protocol_version); td.serialize(os, protocol_version);
for (const TileDef &td : tiledef_overlay) for (const TileDef &td : tiledef_overlay[0])
td.serialize(os, protocol_version); td.serialize(os, protocol_version);
writeU8(os, CF_SPECIAL_COUNT); writeU8(os, CF_SPECIAL_COUNT);
for (const TileDef &td : tiledef_special) { for (const TileDef &td : tiledef_special[0]) {
td.serialize(os, protocol_version); td.serialize(os, protocol_version);
} }
writeU8(os, getAlphaForLegacy()); writeU8(os, getAlphaForLegacy());
@ -535,6 +542,21 @@ void ContentFeatures::serialize(std::ostream &os, u16 protocol_version) const
writeU8(os, move_resistance); writeU8(os, move_resistance);
writeU8(os, liquid_move_physics); writeU8(os, liquid_move_physics);
writeU8(os, post_effect_color_shaded); 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) 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); visual_scale = readF32(is);
if (readU8(is) != 6) if (readU8(is) != 6)
throw SerializationError("unsupported tile count"); throw SerializationError("unsupported tile count");
for (TileDef &td : tiledef) for (TileDef &td : tiledef[0])
td.deSerialize(is, drawtype, protocol_version); td.deSerialize(is, drawtype, protocol_version);
for (TileDef &td : tiledef_overlay) for (TileDef &td : tiledef_overlay[0])
td.deSerialize(is, drawtype, protocol_version); td.deSerialize(is, drawtype, protocol_version);
if (readU8(is) != CF_SPECIAL_COUNT) if (readU8(is) != CF_SPECIAL_COUNT)
throw SerializationError("unsupported 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); td.deSerialize(is, drawtype, protocol_version);
setAlphaFromLegacy(readU8(is)); setAlphaFromLegacy(readU8(is));
color.setRed(readU8(is)); color.setRed(readU8(is));
@ -665,6 +687,43 @@ void ContentFeatures::deSerialize(std::istream &is, u16 protocol_version)
if (is.eof()) if (is.eof())
throw SerializationError(""); throw SerializationError("");
post_effect_color_shaded = tmp; 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<u8>(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) {}; } catch (SerializationError &e) {};
} }
@ -763,22 +822,27 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings) scene::IMeshManipulator *meshmanip, Client *client, const TextureSettings &tsettings)
{ {
// Figure out the actual tiles to use // Figure out the actual tiles to use
TileDef tdef[6]; std::vector<std::array<TileDef, 6> > tdef(variant_count);
for (u32 j = 0; j < 6; j++) { for (u16 v = 0; v < variant_count; v++) {
tdef[j] = tiledef[j]; for (u32 j = 0; j < 6; j++) {
if (tdef[j].name.empty()) { tdef[v][j] = tiledef[v][j];
tdef[j].name = "no_texture.png"; if (tdef[v][j].name.empty()) {
tdef[j].backface_culling = false; tdef[v][j].name = "no_texture.png";
tdef[v][j].backface_culling = false;
}
} }
} }
// also the overlay tiles // also the overlay tiles
TileDef tdef_overlay[6]; std::vector<std::array<TileDef, 6> > tdef_overlay(variant_count);
for (u32 j = 0; j < 6; j++) for (u16 v = 0; v < variant_count; v++) {
tdef_overlay[j] = tiledef_overlay[j]; for (u32 j = 0; j < 6; j++)
tdef_overlay[v][j] = tiledef_overlay[v][j];
}
// also the special tiles // also the special tiles
TileDef tdef_spec[6]; std::vector<std::array<TileDef, CF_SPECIAL_COUNT> > tdef_spec(variant_count);
for (u32 j = 0; j < CF_SPECIAL_COUNT; j++) { for (u16 v = 0; v < variant_count; v++) {
tdef_spec[j] = tiledef_special[j]; for (u32 j = 0; j < CF_SPECIAL_COUNT; j++)
tdef_spec[v][j] = tiledef_special[v][j];
} }
bool is_liquid = false; bool is_liquid = false;
@ -830,9 +894,11 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
solidness = 0; solidness = 0;
visual_solidness = 1; visual_solidness = 1;
} else if (tsettings.leaves_style == LEAVES_SIMPLE) { } else if (tsettings.leaves_style == LEAVES_SIMPLE) {
for (u32 j = 0; j < 6; j++) { for (u16 v = 0; v < variant_count; v++) {
if (!tdef_spec[j].name.empty()) for (u32 j = 0; j < 6; j++) {
tdef[j].name = tdef_spec[j].name; if (!tdef_spec[v][j].name.empty())
tdef[v][j].name = tdef_spec[v][j].name;
}
} }
drawtype = NDT_GLASSLIKE; drawtype = NDT_GLASSLIKE;
solidness = 0; solidness = 0;
@ -847,8 +913,10 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
drawtype = NDT_NORMAL; drawtype = NDT_NORMAL;
solidness = 2; solidness = 2;
} }
for (TileDef &td : tdef) for (u16 v = 0; v < variant_count; v++) {
td.name += std::string("^[noalpha"); for (TileDef &td : tdef[v])
td.name += std::string("^[noalpha");
}
} }
if (waving >= 1) if (waving >= 1)
material_type = TILE_MATERIAL_WAVING_LEAVES; 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); u32 overlay_shader = shdsrc->getShader("nodes_shader", overlay_material, drawtype);
// minimap pixel color = average color of top tile // minimap pixel color = average color of top tile
if (tsettings.enable_minimap && !tdef[0].name.empty() && drawtype != NDT_AIRLIKE) if (tsettings.enable_minimap && !tdef[0][0].name.empty() && drawtype != NDT_AIRLIKE) {
minimap_color = tsrc->getTextureAverageColor(tdef[0].name); minimap_color = std::vector<video::SColor>(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[]) // Tiles (fill in f->tiles[])
bool any_polygon_offset = false; bool any_polygon_offset = false;
for (u16 j = 0; j < 6; j++) { tiles = std::vector<std::array<TileSpec, 6> >(variant_count);
tiles[j].world_aligned = isWorldAligned(tdef[j].align_style, for (u16 v = 0; v < variant_count; v++) {
tsettings.world_aligned_mode, drawtype); for (u16 j = 0; j < 6; j++) {
fillTileAttribs(tsrc, &tiles[j].layers[0], tiles[j], tdef[j], tiles[v][j].world_aligned = isWorldAligned(tdef[v][j].align_style,
color, material_type, tile_shader, tsettings.world_aligned_mode, drawtype);
tdef[j].backface_culling, tsettings); fillTileAttribs(tsrc, &tiles[v][j].layers[0], tiles[v][j], tdef[v][j],
if (!tdef_overlay[j].name.empty()) color, material_type, tile_shader,
fillTileAttribs(tsrc, &tiles[j].layers[1], tiles[j], tdef_overlay[j], tdef[v][j].backface_culling, tsettings);
color, overlay_material, overlay_shader, if (!tdef_overlay[v][j].name.empty())
tdef[j].backface_culling, tsettings); 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(); tiles[v][j].layers[0].need_polygon_offset = !tiles[v][j].layers[1].empty();
any_polygon_offset |= tiles[j].layers[0].need_polygon_offset; any_polygon_offset |= tiles[v][j].layers[0].need_polygon_offset;
} }
if (drawtype == NDT_MESH && any_polygon_offset) { if (drawtype == NDT_MESH && any_polygon_offset) {
// Our per-tile polygon offset enablement workaround works fine for normal // Our per-tile polygon offset enablement workaround works fine for normal
// nodes and anything else, where we know that different tiles are different // nodes and anything else, where we know that different tiles are different
// faces that couldn't possibly conflict with each other. // faces that couldn't possibly conflict with each other.
// We can't assume this for mesh nodes, so apply it to all tiles (= materials) // We can't assume this for mesh nodes, so apply it to all tiles (= materials)
// then. // then.
for (u16 j = 0; j < 6; j++) for (u16 j = 0; j < 6; j++)
tiles[j].layers[0].need_polygon_offset = true; tiles[v][j].layers[0].need_polygon_offset = true;
}
} }
MaterialType special_material = material_type; 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); u32 special_shader = shdsrc->getShader("nodes_shader", special_material, drawtype);
// Special tiles (fill in f->special_tiles[]) // Special tiles (fill in f->special_tiles[])
for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) special_tiles = std::vector<std::array<TileSpec, CF_SPECIAL_COUNT> >(variant_count);
fillTileAttribs(tsrc, &special_tiles[j].layers[0], special_tiles[j], tdef_spec[j], for (u16 v = 0; v < variant_count; v++) {
color, special_material, special_shader, for (u16 j = 0; j < CF_SPECIAL_COUNT; j++)
tdef_spec[j].backface_culling, tsettings); 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 || if (param_type_2 == CPT2_COLOR ||
param_type_2 == CPT2_COLORED_FACEDIR || param_type_2 == CPT2_COLORED_FACEDIR ||
@ -1043,7 +1123,7 @@ void NodeDefManager::clear()
ContentFeatures f; ContentFeatures f;
f.name = "unknown"; f.name = "unknown";
for (int t = 0; t < 6; t++) 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 // Insert directly into containers
content_t c = CONTENT_UNKNOWN; content_t c = CONTENT_UNKNOWN;
m_content_features[c] = f; m_content_features[c] = f;
@ -1409,52 +1489,55 @@ void NodeDefManager::applyTextureOverrides(const std::vector<TextureOverride> &o
ContentFeatures &nodedef = m_content_features[id]; ContentFeatures &nodedef = m_content_features[id];
auto apply = [&] (TileDef &tile) { // For now this works with tiledef_special since CF_SPECIAL_COUNT == 6.
tile.name = texture_override.texture; auto apply = [&] (std::vector<std::array<TileDef, 6> > &tiledef, u32 i) {
if (texture_override.world_scale > 0) { for (u16 v = 0; v < nodedef.variant_count; v++) {
tile.align_style = ALIGN_STYLE_WORLD; tiledef[v][i].name = texture_override.texture;
tile.scale = texture_override.world_scale; if (texture_override.world_scale > 0) {
tiledef[v][i].align_style = ALIGN_STYLE_WORLD;
tiledef[v][i].scale = texture_override.world_scale;
}
} }
}; };
// Override tiles // Override tiles
if (texture_override.hasTarget(OverrideTarget::TOP)) if (texture_override.hasTarget(OverrideTarget::TOP))
apply(nodedef.tiledef[0]); apply(nodedef.tiledef, 0);
if (texture_override.hasTarget(OverrideTarget::BOTTOM)) if (texture_override.hasTarget(OverrideTarget::BOTTOM))
apply(nodedef.tiledef[1]); apply(nodedef.tiledef, 1);
if (texture_override.hasTarget(OverrideTarget::RIGHT)) if (texture_override.hasTarget(OverrideTarget::RIGHT))
apply(nodedef.tiledef[2]); apply(nodedef.tiledef, 2);
if (texture_override.hasTarget(OverrideTarget::LEFT)) if (texture_override.hasTarget(OverrideTarget::LEFT))
apply(nodedef.tiledef[3]); apply(nodedef.tiledef, 3);
if (texture_override.hasTarget(OverrideTarget::BACK)) if (texture_override.hasTarget(OverrideTarget::BACK))
apply(nodedef.tiledef[4]); apply(nodedef.tiledef, 4);
if (texture_override.hasTarget(OverrideTarget::FRONT)) if (texture_override.hasTarget(OverrideTarget::FRONT))
apply(nodedef.tiledef[5]); apply(nodedef.tiledef, 5);
// Override special tiles, if applicable // Override special tiles, if applicable
if (texture_override.hasTarget(OverrideTarget::SPECIAL_1)) if (texture_override.hasTarget(OverrideTarget::SPECIAL_1))
apply(nodedef.tiledef_special[0]); apply(nodedef.tiledef_special, 0);
if (texture_override.hasTarget(OverrideTarget::SPECIAL_2)) if (texture_override.hasTarget(OverrideTarget::SPECIAL_2))
apply(nodedef.tiledef_special[1]); apply(nodedef.tiledef_special, 1);
if (texture_override.hasTarget(OverrideTarget::SPECIAL_3)) if (texture_override.hasTarget(OverrideTarget::SPECIAL_3))
apply(nodedef.tiledef_special[2]); apply(nodedef.tiledef_special, 2);
if (texture_override.hasTarget(OverrideTarget::SPECIAL_4)) if (texture_override.hasTarget(OverrideTarget::SPECIAL_4))
apply(nodedef.tiledef_special[3]); apply(nodedef.tiledef_special, 3);
if (texture_override.hasTarget(OverrideTarget::SPECIAL_5)) if (texture_override.hasTarget(OverrideTarget::SPECIAL_5))
apply(nodedef.tiledef_special[4]); apply(nodedef.tiledef_special, 4);
if (texture_override.hasTarget(OverrideTarget::SPECIAL_6)) if (texture_override.hasTarget(OverrideTarget::SPECIAL_6))
apply(nodedef.tiledef_special[5]); apply(nodedef.tiledef_special, 5);
} }
} }

View file

@ -9,6 +9,7 @@
#include <iostream> #include <iostream>
#include <memory> // shared_ptr #include <memory> // shared_ptr
#include <map> #include <map>
#include <array>
#include "mapnode.h" #include "mapnode.h"
#include "nameidmapping.h" #include "nameidmapping.h"
#if CHECK_CLIENT_BUILD() #if CHECK_CLIENT_BUILD()
@ -22,6 +23,7 @@ class Client;
#include "texture_override.h" // TextureOverride #include "texture_override.h" // TextureOverride
#include "tileanimation.h" #include "tileanimation.h"
#include "util/pointabilities.h" #include "util/pointabilities.h"
#include "util/numeric.h"
class IItemDefManager; class IItemDefManager;
class ITextureSource; class ITextureSource;
@ -308,9 +310,9 @@ struct ContentFeatures
#if CHECK_CLIENT_BUILD() #if CHECK_CLIENT_BUILD()
// 0 1 2 3 4 5 // 0 1 2 3 4 5
// up down right left back front // up down right left back front
TileSpec tiles[6]; std::vector<std::array<TileSpec, 6> > tiles; // Length is variant count
// Special tiles // Special tiles
TileSpec special_tiles[CF_SPECIAL_COUNT]; std::vector<std::array<TileSpec, CF_SPECIAL_COUNT> > special_tiles;
u8 solidness; // Used when choosing which face is drawn u8 solidness; // Used when choosing which face is drawn
u8 visual_solidness; // When solidness=0, this tells how it looks like u8 visual_solidness; // When solidness=0, this tells how it looks like
bool backface_culling; bool backface_culling;
@ -336,6 +338,10 @@ struct ContentFeatures
ContentParamType param_type; ContentParamType param_type;
// Type of MapNode::param2 // Type of MapNode::param2
ContentParamType2 param_type_2; ContentParamType2 param_type_2;
// Number of node variants
u16 variant_count = 1;
// Bit field for variant in param2
BitField<u8> param2_variant;
// --- VISUAL PROPERTIES --- // --- VISUAL PROPERTIES ---
@ -343,13 +349,13 @@ struct ContentFeatures
std::string mesh; std::string mesh;
#if CHECK_CLIENT_BUILD() #if CHECK_CLIENT_BUILD()
scene::SMesh *mesh_ptr; // mesh in case of mesh node scene::SMesh *mesh_ptr; // mesh in case of mesh node
video::SColor minimap_color; std::vector<video::SColor> minimap_color; // Length is variant count
#endif #endif
float visual_scale; // Misc. scale parameter float visual_scale; // Misc. scale parameter
TileDef tiledef[6]; std::vector<std::array<TileDef, 6> > tiledef; // Length is variant count
// These will be drawn over the base tiles. // These will be drawn over the base tiles.
TileDef tiledef_overlay[6]; std::vector<std::array<TileDef, 6> > tiledef_overlay; // Length is variant count
TileDef tiledef_special[CF_SPECIAL_COUNT]; // eg. flowing liquid std::vector<std::array<TileDef, CF_SPECIAL_COUNT> > tiledef_special; // eg. flowing liquid
AlphaMode alpha; AlphaMode alpha;
// The color of the node. // The color of the node.
video::SColor color; video::SColor color;

View file

@ -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)); 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_newtable(L);
lua_pushstring(L, i.name.c_str()); lua_pushstring(L, i.name.c_str());
lua_setfield(L, -2, "name"); lua_setfield(L, -2, "name");
@ -660,6 +662,8 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
// color = ... // color = ...
lua_getfield(L, index, "color"); lua_getfield(L, index, "color");
tiledef.has_color = read_color(L, -1, &tiledef.color); tiledef.has_color = read_color(L, -1, &tiledef.color);
if (!tiledef.has_color)
tiledef.color = video::SColor();
lua_pop(L, 1); lua_pop(L, 1);
// animation = {} // animation = {}
lua_getfield(L, index, "animation"); lua_getfield(L, index, "animation");
@ -673,12 +677,76 @@ TileDef read_tiledef(lua_State *L, int index, u8 drawtype, bool special)
return tiledef; return tiledef;
} }
void read_tiledefs(lua_State *L, int index, u8 drawtype, bool special,
std::array<TileDef, 6> &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<std::array<TileDef, 6> > &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) void read_content_features(lua_State *L, ContentFeatures &f, int index)
{ {
if(index < 0) if(index < 0)
index = lua_gettop(L) + 1 + index; 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<std::array<TileDef, 6> >(variant_count);
f.tiledef_overlay = std::vector<std::array<TileDef, 6> >(variant_count);
f.tiledef_special =
std::vector<std::array<TileDef, CF_SPECIAL_COUNT> >(variant_count);
}
f.variant_count = variant_count;
}
lua_getfield(L, index, "param2_variant");
f.param2_variant = read_bitfield<u8>(L, -1);
lua_pop(L, 1);
/* Cache existence of some callbacks */ /* Cache existence of some callbacks */
lua_getfield(L, index, "on_construct"); lua_getfield(L, index, "on_construct");
f.has_on_construct = !lua_isnil(L, -1); f.has_on_construct = !lua_isnil(L, -1);
@ -718,76 +786,45 @@ void read_content_features(lua_State *L, ContentFeatures &f, int index)
// tiles = {} // tiles = {}
lua_getfield(L, index, "tiles"); lua_getfield(L, index, "tiles");
if(lua_istable(L, -1)){ read_default_tiledefs(L, -1, f.drawtype, false, f.tiledef);
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++;
}
}
}
lua_pop(L, 1); lua_pop(L, 1);
// overlay_tiles = {} // overlay_tiles = {}
lua_getfield(L, index, "overlay_tiles"); lua_getfield(L, index, "overlay_tiles");
if (lua_istable(L, -1)) { read_default_tiledefs(L, -1, f.drawtype, false, f.tiledef_overlay);
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++;
}
}
}
lua_pop(L, 1); lua_pop(L, 1);
// special_tiles = {} // special_tiles = {}
lua_getfield(L, index, "special_tiles"); lua_getfield(L, index, "special_tiles");
if(lua_istable(L, -1)){ // This works as long as CF_SPECIAL_COUNT == 6.
int table = lua_gettop(L); 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); lua_pushnil(L);
int i = 0; while (lua_next(L, variants_index) != 0) {
while(lua_next(L, table) != 0){ size_t v = lua_tointeger(L, -2);
// Read tiledef from value if (v < f.variant_count && lua_istable(L, -1)) {
f.tiledef_special[i] = read_tiledef(L, -1, f.drawtype, true); // 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 // removes value, keeps key for next iteration
lua_pop(L, 1); lua_pop(L, 1);
i++;
if(i==CF_SPECIAL_COUNT){
lua_pop(L, 1);
break;
}
} }
} }
lua_pop(L, 1); 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 drawtype(enum_to_string(ScriptApiNode::es_DrawType, c.drawtype));
std::string liquid_type(enum_to_string(ScriptApiNode::es_LiquidType, c.liquid_type)); 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_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_pushboolean(L, c.has_on_construct);
lua_setfield(L, -2, "has_on_construct"); lua_setfield(L, -2, "has_on_construct");
lua_pushboolean(L, c.has_on_destruct); 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"); lua_setfield(L, -2, "mesh");
} }
#if CHECK_CLIENT_BUILD() #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! lua_setfield(L, -2, "minimap_color"); // but the people need to know!
#endif #endif
lua_pushnumber(L, c.visual_scale); lua_pushnumber(L, c.visual_scale);

View file

@ -369,6 +369,15 @@ void push_aabb3f(lua_State *L, aabb3f box, f32 divisor)
lua_rawseti(L, -2, 6); 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<aabb3f> read_aabb3f_vector(lua_State *L, int index, f32 scale) std::vector<aabb3f> read_aabb3f_vector(lua_State *L, int index, f32 scale)
{ {
std::vector<aabb3f> boxes; std::vector<aabb3f> boxes;
@ -431,6 +440,16 @@ size_t read_stringlist(lua_State *L, int index, std::vector<std::string> *result
return num_strings; 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 Table field getters
*/ */

View file

@ -16,6 +16,8 @@
#include <string_view> #include <string_view>
#include "irrlichttypes_bloated.h" #include "irrlichttypes_bloated.h"
//#include "common/c_types.h"
#include "util/numeric.h"
extern "C" { extern "C" {
#include <lua.h> #include <lua.h>
@ -108,6 +110,15 @@ aabb3f read_aabb3f(lua_State *L, int index, f32 scale);
std::vector<aabb3f> read_aabb3f_vector (lua_State *L, int index, f32 scale); std::vector<aabb3f> read_aabb3f_vector (lua_State *L, int index, f32 scale);
size_t read_stringlist(lua_State *L, int index, size_t read_stringlist(lua_State *L, int index,
std::vector<std::string> *result); std::vector<std::string> *result);
void read_bitfield_parts (lua_State *L, int index, u8 *width, u8 *offset);
template<typename T>
BitField<T> read_bitfield(lua_State *L, int index)
{
u8 width, offset;
read_bitfield_parts(L, index, &width, &offset);
return BitField<T>(width, offset);
}
void push_v2s16(lua_State *L, v2s16 p); void push_v2s16(lua_State *L, v2s16 p);
void push_v2s32(lua_State *L, v2s32 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_v2f(lua_State *L, v2f p);
void push_aabb3f_vector(lua_State *L, const std::vector<aabb3f> &boxes, void push_aabb3f_vector(lua_State *L, const std::vector<aabb3f> &boxes,
f32 divisor = 1.0f); f32 divisor = 1.0f);
void push_bitfield_parts (lua_State *L, u8 width, u8 offset);
template<typename T>
void push_bitfield(lua_State *L, const BitField<T> &bitfield)
{
push_bitfield_parts(L, bitfield.getWidth(), bitfield.getOffset());
}
void warn_if_field_exists(lua_State *L, int table, const char *fieldname, void warn_if_field_exists(lua_State *L, int table, const char *fieldname,
std::string_view name, std::string_view message); std::string_view name, std::string_view message);

View file

@ -80,7 +80,7 @@ void TestGameDef::defineSomeNodes()
"{default_stone.png"; "{default_stone.png";
f = ContentFeatures(); f = ContentFeatures();
f.name = itemdef.name; f.name = itemdef.name;
for (TileDef &tiledef : f.tiledef) for (TileDef &tiledef : f.tiledef[0])
tiledef.name = "default_stone.png"; tiledef.name = "default_stone.png";
f.is_ground_content = true; f.is_ground_content = true;
idef->registerItem(itemdef); idef->registerItem(itemdef);
@ -98,10 +98,10 @@ void TestGameDef::defineSomeNodes()
"{default_dirt.png&default_grass_side.png"; "{default_dirt.png&default_grass_side.png";
f = ContentFeatures(); f = ContentFeatures();
f.name = itemdef.name; f.name = itemdef.name;
f.tiledef[0].name = "default_grass.png"; f.tiledef[0][0].name = "default_grass.png";
f.tiledef[1].name = "default_dirt.png"; f.tiledef[0][1].name = "default_dirt.png";
for(int i = 2; i < 6; i++) 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; f.is_ground_content = true;
idef->registerItem(itemdef); idef->registerItem(itemdef);
t_CONTENT_GRASS = ndef->set(f.name, f); t_CONTENT_GRASS = ndef->set(f.name, f);
@ -137,7 +137,7 @@ void TestGameDef::defineSomeNodes()
f.liquid_viscosity = 4; f.liquid_viscosity = 4;
f.is_ground_content = true; f.is_ground_content = true;
f.groups["liquids"] = 3; f.groups["liquids"] = 3;
for (TileDef &tiledef : f.tiledef) for (TileDef &tiledef : f.tiledef[0])
tiledef.name = "default_water.png"; tiledef.name = "default_water.png";
idef->registerItem(itemdef); idef->registerItem(itemdef);
t_CONTENT_WATER = ndef->set(f.name, f); t_CONTENT_WATER = ndef->set(f.name, f);
@ -159,7 +159,7 @@ void TestGameDef::defineSomeNodes()
f.light_source = LIGHT_MAX-1; f.light_source = LIGHT_MAX-1;
f.is_ground_content = true; f.is_ground_content = true;
f.groups["liquids"] = 3; f.groups["liquids"] = 3;
for (TileDef &tiledef : f.tiledef) for (TileDef &tiledef : f.tiledef[0])
tiledef.name = "default_lava.png"; tiledef.name = "default_lava.png";
idef->registerItem(itemdef); idef->registerItem(itemdef);
t_CONTENT_LAVA = ndef->set(f.name, f); t_CONTENT_LAVA = ndef->set(f.name, f);
@ -177,7 +177,7 @@ void TestGameDef::defineSomeNodes()
"{default_brick.png"; "{default_brick.png";
f = ContentFeatures(); f = ContentFeatures();
f.name = itemdef.name; f.name = itemdef.name;
for (TileDef &tiledef : f.tiledef) for (TileDef &tiledef : f.tiledef[0])
tiledef.name = "default_brick.png"; tiledef.name = "default_brick.png";
f.is_ground_content = true; f.is_ground_content = true;
idef->registerItem(itemdef); idef->registerItem(itemdef);

View file

@ -62,9 +62,9 @@ public:
f.drawtype = NDT_NORMAL; f.drawtype = NDT_NORMAL;
f.solidness = 2; f.solidness = 2;
f.alpha = ALPHAMODE_OPAQUE; f.alpha = ALPHAMODE_OPAQUE;
for (TileDef &tiledef : f.tiledef) for (TileDef &tiledef : f.tiledef[0])
tiledef.name = name + ".png"; tiledef.name = name + ".png";
for (TileSpec &tile : f.tiles) for (TileSpec &tile : f.tiles[0])
tile.layers[0].texture_id = texture; tile.layers[0].texture_id = texture;
return registerNode(itemdef, f); return registerNode(itemdef, f);
@ -89,9 +89,9 @@ public:
f.groups["liquids"] = 3; f.groups["liquids"] = 3;
f.liquid_alternative_source = "test:" + name + "_source"; f.liquid_alternative_source = "test:" + name + "_source";
f.liquid_alternative_flowing = "test:" + name + "_flowing"; f.liquid_alternative_flowing = "test:" + name + "_flowing";
for (TileDef &tiledef : f.tiledef) for (TileDef &tiledef : f.tiledef[0])
tiledef.name = name + ".png"; tiledef.name = name + ".png";
for (TileSpec &tile : f.tiles) for (TileSpec &tile : f.tiles[0])
tile.layers[0].texture_id = texture; tile.layers[0].texture_id = texture;
return registerNode(itemdef, f); return registerNode(itemdef, f);
@ -116,10 +116,10 @@ public:
f.groups["liquids"] = 3; f.groups["liquids"] = 3;
f.liquid_alternative_source = "test:" + name + "_source"; f.liquid_alternative_source = "test:" + name + "_source";
f.liquid_alternative_flowing = "test:" + name + "_flowing"; f.liquid_alternative_flowing = "test:" + name + "_flowing";
f.tiledef_special[0].name = name + "_top.png"; f.tiledef_special[0][0].name = name + "_top.png";
f.tiledef_special[1].name = name + "_side.png"; f.tiledef_special[0][1].name = name + "_side.png";
f.special_tiles[0].layers[0].texture_id = texture_top; f.special_tiles[0][0].layers[0].texture_id = texture_top;
f.special_tiles[1].layers[0].texture_id = texture_side; f.special_tiles[1][1].layers[0].texture_id = texture_side;
return registerNode(itemdef, f); return registerNode(itemdef, f);
} }

View file

@ -19,7 +19,8 @@ TEST_CASE("Given a node definition, "
{ {
ContentFeatures f; ContentFeatures f;
f.name = "default:stone"; f.name = "default:stone";
for (TileDef &tiledef : f.tiledef) f.tiledef.emplace_back();
for (TileDef &tiledef : f.tiledef[0])
tiledef.name = "default_stone.png"; tiledef.name = "default_stone.png";
f.is_ground_content = true; f.is_ground_content = true;

View file

@ -50,6 +50,7 @@ public:
void testSanitizeUntrusted(); void testSanitizeUntrusted();
void testReadSeed(); void testReadSeed();
void testMyDoubleStringConversions(); void testMyDoubleStringConversions();
void testBitField();
}; };
static TestUtilities g_test_instance; static TestUtilities g_test_instance;
@ -87,6 +88,7 @@ void TestUtilities::runTests(IGameDef *gamedef)
TEST(testSanitizeUntrusted); TEST(testSanitizeUntrusted);
TEST(testReadSeed); TEST(testReadSeed);
TEST(testMyDoubleStringConversions); TEST(testMyDoubleStringConversions);
TEST(testBitField);
} }
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
@ -800,3 +802,23 @@ void TestUtilities::testMyDoubleStringConversions()
test_round_trip(0.3); test_round_trip(0.3);
test_round_trip(0.1 + 0.2); test_round_trip(0.1 + 0.2);
} }
void TestUtilities::testBitField()
{
UASSERTEQ(int, BitField<u16>(5, 4).getWidth(), 5);
UASSERTEQ(int, BitField<u16>(5, 4).getOffset(), 4);
UASSERTEQ(u16, BitField<u16>(5, 4).get(0xEFDCU), 0x1DU);
UASSERTEQ(u16, BitField<u16>(5, 4).set(0xEFDCU, 0xF0FU), 0xEEFCU);
UASSERTEQ(int, BitField<u16>(17, 0).getWidth(), 16);
UASSERTEQ(int, BitField<u16>(17, 0).getOffset(), 0);
UASSERTEQ(u16, BitField<u16>(17, 0).get(0xEFDCU), 0xEFDCU);
UASSERTEQ(u16, BitField<u16>(17, 0).set(0xEFDCU, 0xFFFFU), 0xFFFFU);
UASSERTEQ(int, BitField<u16>(16, 17).getWidth(), 0);
UASSERTEQ(int, BitField<u16>(16, 17).getOffset(), 0);
UASSERTEQ(u16, BitField<u16>(16, 17).get(0xEFDCU), 0);
UASSERTEQ(u16, BitField<u16>(16, 17).set(0xEFDCU, 0), 0xEFDCU);
UASSERTEQ(int, BitField<u16>(8, 15).getWidth(), 1);
UASSERTEQ(int, BitField<u16>(8, 15).getOffset(), 15);
UASSERTEQ(u16, BitField<u16>(8, 15).get(0xEFDCU), 1);
UASSERTEQ(u16, BitField<u16>(8, 15).set(0xEFDCU, 0), 0x6FDCU);
}

View file

@ -14,6 +14,10 @@
#include <matrix4.h> #include <matrix4.h>
#include <cmath> #include <cmath>
#include <algorithm> #include <algorithm>
#include <climits>
#if _MSC_VER
#include <intrin.h> // For popcnt
#endif
// Like std::clamp but allows mismatched types // Like std::clamp but allows mismatched types
template <typename T, typename T2, typename T3> template <typename T, typename T2, typename T3>
@ -277,6 +281,53 @@ inline void set_bits(u32 *x, u32 pos, u32 len, u32 val)
*x |= (val & mask) << pos; *x |= (val & mask) << pos;
} }
inline unsigned popcount(unsigned b)
{
#if _MSC_VER
return __popcnt(b);
#else
return __builtin_popcount(b);
#endif
}
template<typename T>
class BitField {
public:
static_assert(static_cast<T>(-1) > static_cast<T>(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) inline u32 calc_parity(u32 v)
{ {
v ^= v >> 16; v ^= v >> 16;