From e1f6108789a7d65b5b8bd011d7faecbbb4f3f727 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 30 Jan 2024 17:03:29 +0100 Subject: [PATCH 1/7] Revert class forward declaration in {client,server}opcodes.h closes #14324 --- src/network/clientopcodes.h | 3 ++- src/network/serveropcodes.h | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/network/clientopcodes.h b/src/network/clientopcodes.h index 683d3534df..20bfc66974 100644 --- a/src/network/clientopcodes.h +++ b/src/network/clientopcodes.h @@ -20,10 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "client/client.h" #include "networkprotocol.h" class NetworkPacket; -class Client; +// Note: don't forward-declare Client here (#14324) enum ToClientConnectionState { TOCLIENT_STATE_NOT_CONNECTED, diff --git a/src/network/serveropcodes.h b/src/network/serveropcodes.h index c461da44a4..275270ab91 100644 --- a/src/network/serveropcodes.h +++ b/src/network/serveropcodes.h @@ -20,10 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once +#include "server.h" #include "networkprotocol.h" class NetworkPacket; -class Server; +// Note: don't forward-declare Server here (#14324) enum ToServerConnectionState { TOSERVER_STATE_NOT_CONNECTED, From 9da1354f3ae5bb62f9cf067c3ae38d87d956d603 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 30 Jan 2024 21:51:51 +0100 Subject: [PATCH 2/7] Fix missing limit check for block y pos (#14320) --- src/map.cpp | 42 ++++++++++++++++-------------------------- src/mapsector.cpp | 3 +++ 2 files changed, 19 insertions(+), 26 deletions(-) diff --git a/src/map.cpp b/src/map.cpp index d0b3e046de..07819c2148 100644 --- a/src/map.cpp +++ b/src/map.cpp @@ -1506,12 +1506,11 @@ MapSector *ServerMap::createSector(v2s16 p2d) Do not create over max mapgen limit */ if (blockpos_over_max_limit(v3s16(p2d.X, 0, p2d.Y))) - throw InvalidPositionException("createSector(): pos. over max mapgen limit"); + throw InvalidPositionException("createSector(): pos over max mapgen limit"); /* Generate blank sector */ - sector = new MapSector(this, p2d, m_gamedef); /* @@ -1524,20 +1523,11 @@ MapSector *ServerMap::createSector(v2s16 p2d) MapBlock * ServerMap::createBlock(v3s16 p) { - /* - Do not create over max mapgen limit - */ - if (blockpos_over_max_limit(p)) - throw InvalidPositionException("createBlock(): pos. over max mapgen limit"); - v2s16 p2d(p.X, p.Z); s16 block_y = p.Y; + /* This will create or load a sector if not found in memory. - If block exists on disk, it will be loaded. - - NOTE: On old save formats, this will be slow, as it generates - lighting on blocks for them. */ MapSector *sector; try { @@ -1552,11 +1542,16 @@ MapBlock * ServerMap::createBlock(v3s16 p) */ MapBlock *block = sector->getBlockNoCreateNoEx(block_y); - if (block) { + if (block) return block; - } + // Create blank - block = sector->createBlankBlock(block_y); + try { + block = sector->createBlankBlock(block_y); + } catch (InvalidPositionException &e) { + infostream << "createBlock: createBlankBlock() failed" << std::endl; + throw e; + } return block; } @@ -1576,10 +1571,10 @@ MapBlock * ServerMap::emergeBlock(v3s16 p, bool create_blank) } if (create_blank) { - MapSector *sector = createSector(v2s16(p.X, p.Z)); - MapBlock *block = sector->createBlankBlock(p.Y); - - return block; + try { + MapSector *sector = createSector(v2s16(p.X, p.Z)); + return sector->createBlankBlock(p.Y); + } catch (InvalidPositionException &e) {} } return NULL; @@ -2000,9 +1995,7 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, u8 flags = 0; MapBlock *block; v3s16 p(x,y,z); - std::map::iterator n; - n = m_loaded_blocks.find(p); - if(n != m_loaded_blocks.end()) + if (m_loaded_blocks.count(p) > 0) continue; bool block_data_inexistent = false; @@ -2020,10 +2013,7 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max, { if (load_if_inexistent && !blockpos_over_max_limit(p)) { - ServerMap *svrmap = (ServerMap *)m_map; - block = svrmap->emergeBlock(p, false); - if (block == NULL) - block = svrmap->createBlock(p); + block = m_map->emergeBlock(p, true); block->copyTo(*this); } else { flags |= VMANIP_BLOCK_DATA_INEXIST; diff --git a/src/mapsector.cpp b/src/mapsector.cpp index 0eaf81ed44..e3fc3cfee8 100644 --- a/src/mapsector.cpp +++ b/src/mapsector.cpp @@ -71,6 +71,9 @@ std::unique_ptr MapSector::createBlankBlockNoInsert(s16 y) { assert(getBlockBuffered(y) == nullptr); // Pre-condition + if (blockpos_over_max_limit(v3s16(0, y, 0))) + throw InvalidPositionException("createBlankBlockNoInsert(): pos over max mapgen limit"); + v3s16 blockpos_map(m_pos.X, y, m_pos.Y); return std::make_unique(blockpos_map, m_gamedef); From e10d8080ba6e53e0f3c4b20b32304f8bb36e5958 Mon Sep 17 00:00:00 2001 From: sfan5 Date: Tue, 30 Jan 2024 21:52:04 +0100 Subject: [PATCH 3/7] Add flag to control mgv6 temple generation (#14293) --- builtin/mainmenu/dlg_create_world.lua | 10 +++++++--- builtin/settingtypes.txt | 3 ++- src/mapgen/mapgen_v6.cpp | 7 +++++-- src/mapgen/mapgen_v6.h | 1 + 4 files changed, 15 insertions(+), 6 deletions(-) diff --git a/builtin/mainmenu/dlg_create_world.lua b/builtin/mainmenu/dlg_create_world.lua index b844923ed5..2fd7dc4218 100644 --- a/builtin/mainmenu/dlg_create_world.lua +++ b/builtin/mainmenu/dlg_create_world.lua @@ -70,6 +70,8 @@ local flag_checkboxes = { { "trees", fgettext("Trees and jungle grass") }, { "flat", fgettext("Flat terrain") }, { "mudflow", fgettext("Mud flow"), fgettext("Terrain surface erosion") }, + { "temples", fgettext("Desert temples"), + fgettext("Different dungeon variant generated in desert biomes (only if dungeons enabled)") }, -- Biome settings are in mgv6_biomes below }, } @@ -279,7 +281,7 @@ local function create_world_formspec(dialogdata) end local retval = - "size[12.25,7,true]" .. + "size[12.25,7.4,true]" .. -- Left side "container[0,0]".. @@ -321,8 +323,10 @@ local function create_world_formspec(dialogdata) "container_end[]".. -- Menu buttons - "button[3.25,6.5;3,0.5;world_create_confirm;" .. fgettext("Create") .. "]" .. - "button[6.25,6.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" + "container[0,6.9]".. + "button[3.25,0;3,0.5;world_create_confirm;" .. fgettext("Create") .. "]" .. + "button[6.25,0;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" .. + "container_end[]" return retval diff --git a/builtin/settingtypes.txt b/builtin/settingtypes.txt index af821b5be2..42e19c3852 100644 --- a/builtin/settingtypes.txt +++ b/builtin/settingtypes.txt @@ -1068,7 +1068,8 @@ mgv5_np_dungeons (Dungeon noise) noise_params_3d 0.9, 0.5, (500, 500, 500), 0, 2 # The 'snowbiomes' flag enables the new 5 biome system. # When the 'snowbiomes' flag is enabled jungles are automatically enabled and # the 'jungles' flag is ignored. -mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees jungles,biomeblend,mudflow,snowbiomes,flat,trees,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees +# The 'temples' flag disables generation of desert temples. Normal dungeons will appear instead. +mgv6_spflags (Mapgen V6 specific flags) flags jungles,biomeblend,mudflow,snowbiomes,noflat,trees,temples jungles,biomeblend,mudflow,snowbiomes,flat,trees,temples,nojungles,nobiomeblend,nomudflow,nosnowbiomes,noflat,notrees,notemples # Deserts occur when np_biome exceeds this value. # When the 'snowbiomes' flag is enabled, this is ignored. diff --git a/src/mapgen/mapgen_v6.cpp b/src/mapgen/mapgen_v6.cpp index 4db8b25ef3..80a3d3be33 100644 --- a/src/mapgen/mapgen_v6.cpp +++ b/src/mapgen/mapgen_v6.cpp @@ -47,6 +47,7 @@ FlagDesc flagdesc_mapgen_v6[] = { {"snowbiomes", MGV6_SNOWBIOMES}, {"flat", MGV6_FLAT}, {"trees", MGV6_TREES}, + {"temples", MGV6_TEMPLES}, {NULL, 0} }; @@ -225,7 +226,8 @@ void MapgenV6Params::writeParams(Settings *settings) const void MapgenV6Params::setDefaultSettings(Settings *settings) { settings->setDefault("mgv6_spflags", flagdesc_mapgen_v6, MGV6_JUNGLES | - MGV6_SNOWBIOMES | MGV6_TREES | MGV6_BIOMEBLEND | MGV6_MUDFLOW); + MGV6_SNOWBIOMES | MGV6_TREES | MGV6_BIOMEBLEND | MGV6_MUDFLOW | + MGV6_TEMPLES); } @@ -578,7 +580,8 @@ void MapgenV6::makeChunk(BlockMakeData *data) dp.np_alt_wall = NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); - if (getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) { + if ((spflags & MGV6_TEMPLES) && + getBiome(0, v2s16(node_min.X, node_min.Z)) == BT_DESERT) { dp.c_wall = c_desert_stone; dp.c_alt_wall = CONTENT_IGNORE; dp.c_stair = c_stair_desert_stone; diff --git a/src/mapgen/mapgen_v6.h b/src/mapgen/mapgen_v6.h index 5122bf3657..4b439810cc 100644 --- a/src/mapgen/mapgen_v6.h +++ b/src/mapgen/mapgen_v6.h @@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #define MGV6_SNOWBIOMES 0x08 #define MGV6_FLAT 0x10 #define MGV6_TREES 0x20 +#define MGV6_TEMPLES 0x40 extern FlagDesc flagdesc_mapgen_v6[]; From 176e674a51bb27f3c239dd217214dca0a2f2a1d1 Mon Sep 17 00:00:00 2001 From: techno-sam <77073745+techno-sam@users.noreply.github.com> Date: Fri, 2 Feb 2024 12:21:00 -0800 Subject: [PATCH 4/7] Add wear bar color API (#13328) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --------- Co-authored-by: Muhammad Rifqi Priyo Susanto Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com> Co-authored-by: grorp --- doc/lua_api.md | 59 ++++++++++ games/devtest/mods/basetools/init.lua | 137 +++++++++++++++++++--- games/devtest/mods/util_commands/init.lua | 23 ++++ src/client/hud.cpp | 23 ++-- src/inventory.h | 9 ++ src/itemdef.cpp | 14 +++ src/itemdef.h | 3 + src/itemstackmetadata.cpp | 29 +++++ src/itemstackmetadata.h | 17 ++- src/script/common/c_content.cpp | 78 ++++++++++++ src/script/common/c_content.h | 3 + src/script/lua_api/l_item.cpp | 17 +++ src/script/lua_api/l_item.h | 5 + src/script/lua_api/l_itemstackmeta.cpp | 17 +++ src/script/lua_api/l_itemstackmeta.h | 11 ++ src/tool.cpp | 124 ++++++++++++++++++++ src/tool.h | 40 ++++++- src/util/string.cpp | 14 +++ src/util/string.h | 1 + 19 files changed, 598 insertions(+), 26 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index a38b5be0d0..b4ce001ddc 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -7273,6 +7273,8 @@ an itemstring, a table or `nil`. the item breaks after `max_uses` times * Valid `max_uses` range is [0,65536] * Does nothing if item is not a tool or if `max_uses` is 0 +* `get_wear_bar_params()`: returns the wear bar parameters of the item, + or nil if none are defined for this item type or in the stack's meta * `add_item(item)`: returns leftover `ItemStack` * Put some item or stack onto this stack * `item_fits(item)`: returns `true` if item or stack can be fully added to @@ -7314,6 +7316,10 @@ Can be obtained via `item:get_meta()`. * Overrides the item's tool capabilities * A nil value will clear the override data and restore the original behavior. +* `set_wear_bar_params([wear_bar_params])` + * Overrides the item's wear bar parameters (see "Wear Bar Color" section) + * A nil value will clear the override data and restore the original + behavior. `MetaDataRef` ------------- @@ -8815,6 +8821,19 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and -- fallback behavior. }, + -- Set wear bar color of the tool by setting color stops and blend mode + -- See "Wear Bar Color" section for further explanation including an example + wear_color = { + -- interpolation mode: 'constant' or 'linear' + -- (nil defaults to 'constant') + blend = "linear", + color_stops = { + [0.0] = "#ff0000", + [0.5] = "#ffff00", + [1.0] = "#00ff00", + } + }, + node_placement_prediction = nil, -- If nil and item is node, prediction is made automatically. -- If nil and item is not a node, no prediction is made. @@ -9382,6 +9401,46 @@ Used by `minetest.register_node`. } ``` +Wear Bar Color +-------------- + +'Wear Bar' is a property of items that defines the coloring +of the bar that appears under damaged tools. +If it is absent, the default behavior of green-yellow-red is +used. + +### Wear bar colors definition + +#### Syntax + +```lua +{ + -- 'constant' or 'linear' + -- (nil defaults to 'constant') + blend = "linear", + color_stops = { + [0.0] = "#ff0000", + [0.5] = "slateblue", + [1.0] = {r=0, g=255, b=0, a=150}, + } +} +``` + +#### Blend mode `blend` + +* `linear`: blends smoothly between each defined color point. +* `constant`: each color starts at its defined point, and continues up to the next point + +#### Color stops `color_stops` + +Specified as `ColorSpec` color values assigned to `float` durability keys. + +"Durability" is defined as `1 - (wear / 65535)`. + +#### Shortcut usage + +Wear bar color can also be specified as a single `ColorSpec` instead of a table. + Crafting recipes ---------------- diff --git a/games/devtest/mods/basetools/init.lua b/games/devtest/mods/basetools/init.lua index c0bd1d00bc..aa91d5e924 100644 --- a/games/devtest/mods/basetools/init.lua +++ b/games/devtest/mods/basetools/init.lua @@ -420,36 +420,141 @@ minetest.register_tool("basetools:dagger_steel", { } }) --- Test tool uses and punch_attack_uses -local uses = { 1, 2, 3, 5, 10, 50, 100, 1000, 10000, 65535 } -for i=1, #uses do - local u = uses[i] - local ustring - if i == 1 then - ustring = u.."-Use" - else - ustring = u.."-Uses" - end - local color = string.format("#FF00%02X", math.floor(((i-1)/#uses) * 255)) - minetest.register_tool("basetools:pick_uses_"..string.format("%05d", u), { +-- Test tool uses, punch_attack_uses, and wear bar coloring +local tool_params = { + {uses = 1}, + {uses = 2}, + {uses = 3}, + { + uses = 5, + wear_color = "#5865f2", + wear_description = "Solid color: #5865f2", + }, + { + uses = 10, + wear_color = "slateblue", + wear_description = "Solid color: slateblue", + }, + { + uses = 50, + wear_color = { + color_stops = { + [0] = "red", + [0.5] = "yellow", + [1.0] = "blue" + }, + blend = "linear" + }, + wear_description = "Ranges from blue to yellow to red", + }, + { + uses = 100, + wear_color = { + color_stops = { + [0] = "#ffff00", + [0.2] = "#ff00ff", + [0.3] = "#ffff00", + [0.45] = "#c0ffee", + [0.6] = {r=255, g=255, b=0, a=100}, -- continues until the end + }, + blend = "constant" + }, + wear_description = "Misc. colors, constant interpolation", + }, + {uses = 1e3}, + {uses = 1e4}, + {uses = 65535}, +} + +for i, params in ipairs(tool_params) do + local uses = params.uses + local ustring = uses.."-Use"..(uses == 1 and "" or "s") + local color = string.format("#FF00%02X", math.floor(((i-1)/#tool_params) * 255)) + minetest.register_tool("basetools:pick_uses_"..string.format("%05d", uses), { description = ustring.." Pickaxe".."\n".. - "Digs cracky=3", + "Digs cracky=3".. + (params.wear_description and "\n".."Wear bar: " .. params.wear_description or ""), inventory_image = "basetools_usespick.png^[colorize:"..color..":127", tool_capabilities = { max_drop_level=0, groupcaps={ - cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=u, maxlevel=0} + cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=uses, maxlevel=0} }, }, + wear_color = params.wear_color }) - minetest.register_tool("basetools:sword_uses_"..string.format("%05d", u), { + minetest.register_tool("basetools:sword_uses_"..string.format("%05d", uses), { description = ustring.." Sword".."\n".. "Damage: fleshy=1", inventory_image = "basetools_usessword.png^[colorize:"..color..":127", tool_capabilities = { damage_groups = {fleshy=1}, - punch_attack_uses = u, + punch_attack_uses = uses, }, }) end + +minetest.register_chatcommand("wear_color", { + params = "[idx]", + description = "Set wear bar color override", + func = function(player_name, param) + local player = minetest.get_player_by_name(player_name) + if not player then return end + + local wear_color = nil + local wear_desc = "Reset override" + + if param ~= "" then + local params = tool_params[tonumber(param)] + if not params then + return false, "idx out of bounds" + end + wear_color = params.wear_color + wear_desc = "Set override: "..(params.wear_description or "Default behavior") + end + local tool = player:get_wielded_item() + if tool:get_count() == 0 then + return false, "Tool not found" + end + tool:get_meta():set_wear_bar_params(wear_color) + player:set_wielded_item(tool) + return true, wear_desc + end +}) + +-- Punch handler to set random color & wear +local wear_on_use = function(itemstack, user, pointed_thing) + local meta = itemstack:get_meta() + local color = math.random(0, 0xFFFFFF) + local colorstr = string.format("#%06x", color) + meta:set_wear_bar_params(colorstr) + minetest.log("action", "[basetool] Wear bar color of "..itemstack:get_name().." changed to "..colorstr) + itemstack:set_wear(math.random(0, 65535)) + return itemstack +end + +-- Place handler to clear item metadata color +local wear_on_place = function(itemstack, user, pointed_thing) + local meta = itemstack:get_meta() + meta:set_wear_bar_params(nil) + return itemstack +end + +minetest.register_tool("basetools:random_wear_bar", { + description = "Wear Bar Color Test\n" .. + "Punch: Set random color & wear\n" .. + "Place: Clear color", + -- Base texture: A grayscale square (can be colorized) + inventory_image = "basetools_usespick.png^[colorize:#FFFFFF:127", + tool_capabilities = { + max_drop_level=0, + groupcaps={ + cracky={times={[3]=0.1, [2]=0.2, [1]=0.3}, uses=1000, maxlevel=0} + }, + }, + + on_use = wear_on_use, + on_place = wear_on_place, + on_secondary_use = wear_on_place, +}) diff --git a/games/devtest/mods/util_commands/init.lua b/games/devtest/mods/util_commands/init.lua index 48cd47f107..6285d4754f 100644 --- a/games/devtest/mods/util_commands/init.lua +++ b/games/devtest/mods/util_commands/init.lua @@ -210,6 +210,29 @@ minetest.register_chatcommand("dump_item", { end, }) +minetest.register_chatcommand("dump_itemdef", { + params = "", + description = "Prints a dump of the wielded item's definition in table form", + func = function(name, param) + local player = minetest.get_player_by_name(name) + local str = dump(player:get_wielded_item():get_definition()) + print(str) + return true, str + end, +}) + +minetest.register_chatcommand("dump_wear_bar", { + params = "", + description = "Prints a dump of the wielded item's wear bar parameters in table form", + func = function(name, param) + local player = minetest.get_player_by_name(name) + local item = player:get_wielded_item() + local str = dump(item:get_wear_bar_params()) + print(str) + return true, str + end, +}) + core.register_chatcommand("set_saturation", { params = "", description = "Set the saturation for current player.", diff --git a/src/client/hud.cpp b/src/client/hud.cpp index 1618b757f7..1201b95b92 100644 --- a/src/client/hud.cpp +++ b/src/client/hud.cpp @@ -1179,17 +1179,26 @@ void drawItemStack( (1 - wear) * progressrect.LowerRightCorner.X; // Compute progressbar color + // default scheme: // wear = 0.0: green // wear = 0.5: yellow // wear = 1.0: red - video::SColor color(255, 255, 255, 255); - int wear_i = MYMIN(std::floor(wear * 600), 511); - wear_i = MYMIN(wear_i + 10, 511); - if (wear_i <= 255) - color.set(255, wear_i, 255, 0); - else - color.set(255, 255, 511 - wear_i, 0); + video::SColor color; + auto barParams = item.getWearBarParams(client->idef()); + if (barParams.has_value()) { + f32 durabilityPercent = 1.0 - wear; + color = barParams->getWearBarColor(durabilityPercent); + } else { + color = video::SColor(255, 255, 255, 255); + int wear_i = MYMIN(std::floor(wear * 600), 511); + wear_i = MYMIN(wear_i + 10, 511); + + if (wear_i <= 255) + color.set(255, wear_i, 255, 0); + else + color.set(255, 255, 511 - wear_i, 0); + } core::rect progressrect2 = progressrect; progressrect2.LowerRightCorner.X = progressmid; diff --git a/src/inventory.h b/src/inventory.h index 64ca857fca..e92893a8ea 100644 --- a/src/inventory.h +++ b/src/inventory.h @@ -131,6 +131,15 @@ struct ItemStack return metadata.getToolCapabilities(*item_cap); // Check for override } + const std::optional &getWearBarParams( + const IItemDefManager *itemdef) const + { + auto &meta_override = metadata.getWearBarParamOverride(); + if (meta_override.has_value()) + return meta_override; + return itemdef->get(name).wear_bar_params; + } + // Wear out (only tools) // Returns true if the item is (was) a tool bool addWear(s32 amount, const IItemDefManager *itemdef) diff --git a/src/itemdef.cpp b/src/itemdef.cpp index ee3e3829d9..5c63270c00 100644 --- a/src/itemdef.cpp +++ b/src/itemdef.cpp @@ -125,6 +125,7 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def) pointabilities = def.pointabilities; if (def.tool_capabilities) tool_capabilities = new ToolCapabilities(*def.tool_capabilities); + wear_bar_params = def.wear_bar_params; groups = def.groups; node_placement_prediction = def.node_placement_prediction; place_param2 = def.place_param2; @@ -149,6 +150,7 @@ void ItemDefinition::resetInitial() { // Initialize pointers to NULL so reset() does not delete undefined pointers tool_capabilities = NULL; + wear_bar_params = std::nullopt; reset(); } @@ -171,6 +173,7 @@ void ItemDefinition::reset() pointabilities = std::nullopt; delete tool_capabilities; tool_capabilities = NULL; + wear_bar_params.reset(); groups.clear(); sound_place = SoundSpec(); sound_place_failed = SoundSpec(); @@ -251,6 +254,13 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const pointabilities_s = tmp_os.str(); } os << serializeString16(pointabilities_s); + + if (wear_bar_params.has_value()) { + writeU8(os, 1); + wear_bar_params->serialize(os); + } else { + writeU8(os, 0); + } } void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version) @@ -333,6 +343,10 @@ void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version) pointabilities = std::make_optional(); pointabilities->deSerialize(tmp_is); } + + if (readU8(is)) { + wear_bar_params = WearBarParams::deserialize(is); + } } catch(SerializationError &e) {}; } diff --git a/src/itemdef.h b/src/itemdef.h index 192e900952..f8cb1b6136 100644 --- a/src/itemdef.h +++ b/src/itemdef.h @@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemgroup.h" #include "sound.h" #include "texture_override.h" // TextureOverride +#include "tool.h" #include "util/pointabilities.h" class IGameDef; class Client; @@ -103,6 +104,8 @@ struct ItemDefinition // They may be NULL. If non-NULL, deleted by destructor ToolCapabilities *tool_capabilities; + std::optional wear_bar_params; + ItemGroupList groups; SoundSpec sound_place; SoundSpec sound_place_failed; diff --git a/src/itemstackmetadata.cpp b/src/itemstackmetadata.cpp index c8ce2449f6..bf4a7b2ac7 100644 --- a/src/itemstackmetadata.cpp +++ b/src/itemstackmetadata.cpp @@ -21,7 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "itemstackmetadata.h" #include "util/serialize.h" #include "util/strfnd.h" + #include +#include #define DESERIALIZE_START '\x01' #define DESERIALIZE_KV_DELIM '\x02' @@ -31,11 +33,13 @@ with this program; if not, write to the Free Software Foundation, Inc., #define DESERIALIZE_PAIR_DELIM_STR "\x03" #define TOOLCAP_KEY "tool_capabilities" +#define WEAR_BAR_KEY "wear_color" void ItemStackMetadata::clear() { SimpleMetadata::clear(); updateToolCapabilities(); + updateWearBarParams(); } static void sanitize_string(std::string &str) @@ -55,6 +59,8 @@ bool ItemStackMetadata::setString(const std::string &name, const std::string &va bool result = SimpleMetadata::setString(clean_name, clean_var); if (clean_name == TOOLCAP_KEY) updateToolCapabilities(); + else if (clean_name == WEAR_BAR_KEY) + updateWearBarParams(); return result; } @@ -91,6 +97,7 @@ void ItemStackMetadata::deSerialize(std::istream &is) } } updateToolCapabilities(); + updateWearBarParams(); } void ItemStackMetadata::updateToolCapabilities() @@ -116,3 +123,25 @@ void ItemStackMetadata::clearToolCapabilities() { setString(TOOLCAP_KEY, ""); } + +void ItemStackMetadata::updateWearBarParams() +{ + if (contains(WEAR_BAR_KEY)) { + std::istringstream is(getString(WEAR_BAR_KEY)); + wear_bar_override = WearBarParams::deserializeJson(is); + } else { + wear_bar_override.reset(); + } +} + +void ItemStackMetadata::setWearBarParams(const WearBarParams ¶ms) +{ + std::ostringstream os; + params.serializeJson(os); + setString(WEAR_BAR_KEY, os.str()); +} + +void ItemStackMetadata::clearWearBarParams() +{ + setString(WEAR_BAR_KEY, ""); +} diff --git a/src/itemstackmetadata.h b/src/itemstackmetadata.h index 48a029c51f..e0615916ed 100644 --- a/src/itemstackmetadata.h +++ b/src/itemstackmetadata.h @@ -22,13 +22,17 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "metadata.h" #include "tool.h" +#include + class Inventory; class IItemDefManager; class ItemStackMetadata : public SimpleMetadata { public: - ItemStackMetadata() : toolcaps_overridden(false) {} + ItemStackMetadata(): + toolcaps_overridden(false) + {} // Overrides void clear() override; @@ -46,9 +50,20 @@ public: void setToolCapabilities(const ToolCapabilities &caps); void clearToolCapabilities(); + const std::optional &getWearBarParamOverride() const + { + return wear_bar_override; + } + + + void setWearBarParams(const WearBarParams ¶ms); + void clearWearBarParams(); + private: void updateToolCapabilities(); + void updateWearBarParams(); bool toolcaps_overridden; ToolCapabilities toolcaps_override; + std::optional wear_bar_override; }; diff --git a/src/script/common/c_content.cpp b/src/script/common/c_content.cpp index 5259d3f384..aab91f35c1 100644 --- a/src/script/common/c_content.cpp +++ b/src/script/common/c_content.cpp @@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "server/player_sao.h" #include "util/pointedthing.h" #include "debug.h" // For FATAL_ERROR +#include #include struct EnumString es_TileAnimationType[] = @@ -94,6 +95,15 @@ void read_item_definition(lua_State* L, int index, def.tool_capabilities = new ToolCapabilities( read_tool_capabilities(L, -1)); } + lua_getfield(L, index, "wear_color"); + if (lua_istable(L, -1)) { + def.wear_bar_params = read_wear_bar_params(L, -1); + } else if (lua_isstring(L, -1)) { + video::SColor color; + read_color(L, -1, &color); + def.wear_bar_params = WearBarParams({{0.0, color}}, + WearBarParams::BLEND_MODE_CONSTANT); + } // If name is "" (hand), ensure there are ToolCapabilities // because it will be looked up there whenever any other item has @@ -213,6 +223,10 @@ void push_item_definition_full(lua_State *L, const ItemDefinition &i) push_tool_capabilities(L, *i.tool_capabilities); lua_setfield(L, -2, "tool_capabilities"); } + if (i.wear_bar_params.has_value()) { + push_wear_bar_params(L, *i.wear_bar_params); + lua_setfield(L, -2, "wear_color"); + } push_groups(L, i.groups); lua_setfield(L, -2, "groups"); push_simplesoundspec(L, i.sound_place); @@ -1454,6 +1468,22 @@ void push_tool_capabilities(lua_State *L, lua_setfield(L, -2, "damage_groups"); } +/******************************************************************************/ +void push_wear_bar_params(lua_State *L, + const WearBarParams ¶ms) +{ + lua_newtable(L); + setstringfield(L, -1, "blend", WearBarParams::es_BlendMode[params.blend].str); + + lua_newtable(L); + for (const std::pair item: params.colorStops) { + lua_pushnumber(L, item.first); // key + push_ARGB8(L, item.second); + lua_rawset(L, -3); + } + lua_setfield(L, -2, "color_stops"); +} + /******************************************************************************/ void push_inventory_list(lua_State *L, const InventoryList &invlist) { @@ -1732,6 +1762,54 @@ void push_pointabilities(lua_State *L, const Pointabilities &pointabilities) } } +/******************************************************************************/ +WearBarParams read_wear_bar_params( + lua_State *L, int stack_idx) +{ + if (lua_isstring(L, stack_idx)) { + video::SColor color; + read_color(L, stack_idx, &color); + return WearBarParams(color); + } + + if (!lua_istable(L, stack_idx)) + throw LuaError("Expected wear bar color table or colorstring"); + + lua_getfield(L, stack_idx, "color_stops"); + if (!check_field_or_nil(L, -1, LUA_TTABLE, "color_stops")) + throw LuaError("color_stops must be a table"); + + std::map colorStops; + // color stops table is on the stack + int table_values = lua_gettop(L); + lua_pushnil(L); + while (lua_next(L, table_values) != 0) { + // key at index -2 and value at index -1 within table_values + f32 point = luaL_checknumber(L, -2); + if (point < 0 || point > 1) + throw LuaError("Wear bar color stop key out of range"); + video::SColor color; + read_color(L, -1, &color); + colorStops.emplace(point, color); + + // removes value, keeps key for next iteration + lua_pop(L, 1); + } + lua_pop(L, 1); // pop color stops table + + auto blend = WearBarParams::BLEND_MODE_CONSTANT; + lua_getfield(L, stack_idx, "blend"); + if (check_field_or_nil(L, -1, LUA_TSTRING, "blend")) { + int blendInt; + if (!string_to_enum(WearBarParams::es_BlendMode, blendInt, std::string(lua_tostring(L, -1)))) + throw LuaError("Invalid wear bar color blend mode"); + blend = static_cast(blendInt); + } + lua_pop(L, 1); + + return WearBarParams(colorStops, blend); +} + /******************************************************************************/ void push_dig_params(lua_State *L,const DigParams ¶ms) { diff --git a/src/script/common/c_content.h b/src/script/common/c_content.h index 6f54b7b23a..7aa7dca8d8 100644 --- a/src/script/common/c_content.h +++ b/src/script/common/c_content.h @@ -116,6 +116,9 @@ void push_pointabilities (lua_State *L, const Pointabilities ToolCapabilities read_tool_capabilities (lua_State *L, int table); void push_tool_capabilities (lua_State *L, const ToolCapabilities &prop); +WearBarParams read_wear_bar_params (lua_State *L, int table); +void push_wear_bar_params (lua_State *L, + const WearBarParams &prop); void read_item_definition (lua_State *L, int index, const ItemDefinition &default_def, ItemDefinition &def); diff --git a/src/script/lua_api/l_item.cpp b/src/script/lua_api/l_item.cpp index c68046b5d7..875e5dedd4 100644 --- a/src/script/lua_api/l_item.cpp +++ b/src/script/lua_api/l_item.cpp @@ -376,6 +376,22 @@ int LuaItemStack::l_add_wear_by_uses(lua_State *L) return 1; } +// get_wear_bar_params(self) -> table +// Returns the effective wear bar parameters. +// Returns nil if this item has none associated. +int LuaItemStack::l_get_wear_bar_params(lua_State *L) +{ + NO_MAP_LOCK_REQUIRED; + LuaItemStack *o = checkObject(L, 1); + ItemStack &item = o->m_stack; + auto params = item.getWearBarParams(getGameDef(L)->idef()); + if (params.has_value()) { + push_wear_bar_params(L, *params); + return 1; + } + return 0; +} + // add_item(self, itemstack or itemstring or table or nil) -> itemstack // Returns leftover item stack int LuaItemStack::l_add_item(lua_State *L) @@ -551,6 +567,7 @@ const luaL_Reg LuaItemStack::methods[] = { luamethod(LuaItemStack, get_tool_capabilities), luamethod(LuaItemStack, add_wear), luamethod(LuaItemStack, add_wear_by_uses), + luamethod(LuaItemStack, get_wear_bar_params), luamethod(LuaItemStack, add_item), luamethod(LuaItemStack, item_fits), luamethod(LuaItemStack, take_item), diff --git a/src/script/lua_api/l_item.h b/src/script/lua_api/l_item.h index 55333ec73b..243d17d721 100644 --- a/src/script/lua_api/l_item.h +++ b/src/script/lua_api/l_item.h @@ -125,6 +125,11 @@ private: // Returns true if the item is (or was) a tool. static int l_add_wear_by_uses(lua_State *L); + // get_wear_bar_params(self) -> table + // Returns the effective wear bar parameters. + // Returns nil if this item has none associated. + static int l_get_wear_bar_params(lua_State *L); + // add_item(self, itemstack or itemstring or table or nil) -> itemstack // Returns leftover item stack static int l_add_item(lua_State *L); diff --git a/src/script/lua_api/l_itemstackmeta.cpp b/src/script/lua_api/l_itemstackmeta.cpp index 8ce9673db4..ebabf7baec 100644 --- a/src/script/lua_api/l_itemstackmeta.cpp +++ b/src/script/lua_api/l_itemstackmeta.cpp @@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "lua_api/l_itemstackmeta.h" #include "lua_api/l_internal.h" #include "common/c_content.h" +#include "common/c_converter.h" +#include "tool.h" /* ItemStackMetaRef @@ -58,6 +60,20 @@ int ItemStackMetaRef::l_set_tool_capabilities(lua_State *L) return 0; } +int ItemStackMetaRef::l_set_wear_bar_params(lua_State *L) +{ + ItemStackMetaRef *metaref = checkObject(L, 1); + if (lua_isnoneornil(L, 2)) { + metaref->clearWearBarParams(); + } else if (lua_istable(L, 2) || lua_isstring(L, 2)) { + metaref->setWearBarParams(read_wear_bar_params(L, 2)); + } else { + luaL_typerror(L, 2, "table, ColorString, or nil"); + } + + return 0; +} + ItemStackMetaRef::ItemStackMetaRef(LuaItemStack *istack): istack(istack) { istack->grab(); @@ -102,5 +118,6 @@ const luaL_Reg ItemStackMetaRef::methods[] = { luamethod(MetaDataRef, from_table), luamethod(MetaDataRef, equals), luamethod(ItemStackMetaRef, set_tool_capabilities), + luamethod(ItemStackMetaRef, set_wear_bar_params), {0,0} }; diff --git a/src/script/lua_api/l_itemstackmeta.h b/src/script/lua_api/l_itemstackmeta.h index ac27bcc2ca..27adc57e09 100644 --- a/src/script/lua_api/l_itemstackmeta.h +++ b/src/script/lua_api/l_itemstackmeta.h @@ -49,8 +49,19 @@ private: istack->getItem().metadata.clearToolCapabilities(); } + void setWearBarParams(const WearBarParams ¶ms) + { + istack->getItem().metadata.setWearBarParams(params); + } + + void clearWearBarParams() + { + istack->getItem().metadata.clearWearBarParams(); + } + // Exported functions static int l_set_tool_capabilities(lua_State *L); + static int l_set_wear_bar_params(lua_State *L); public: // takes a reference ItemStackMetaRef(LuaItemStack *istack); diff --git a/src/tool.cpp b/src/tool.cpp index 220df24ac1..36ad1c608c 100644 --- a/src/tool.cpp +++ b/src/tool.cpp @@ -26,7 +26,10 @@ with this program; if not, write to the Free Software Foundation, Inc., #include "convert_json.h" #include "util/serialize.h" #include "util/numeric.h" +#include "util/hex.h" +#include "common/c_content.h" #include + void ToolGroupCap::toJson(Json::Value &object) const { @@ -183,6 +186,127 @@ void ToolCapabilities::deserializeJson(std::istream &is) } } +void WearBarParams::serialize(std::ostream &os) const +{ + writeU8(os, 1); // Version for future-proofing + writeU8(os, blend); + writeU16(os, colorStops.size()); + for (const std::pair item : colorStops) { + writeF32(os, item.first); + writeARGB8(os, item.second); + } +} + +WearBarParams WearBarParams::deserialize(std::istream &is) +{ + u8 version = readU8(is); + if (version > 1) + throw SerializationError("unsupported WearBarParams version"); + + auto blend = static_cast(readU8(is)); + if (blend >= BlendMode_END) + throw SerializationError("invalid blend mode"); + u16 count = readU16(is); + if (count == 0) + throw SerializationError("no stops"); + std::map colorStops; + for (u16 i = 0; i < count; i++) { + f32 key = readF32(is); + if (key < 0 || key > 1) + throw SerializationError("key out of range"); + video::SColor color = readARGB8(is); + colorStops.emplace(key, color); + } + return WearBarParams(colorStops, blend); +} + +void WearBarParams::serializeJson(std::ostream &os) const +{ + Json::Value root; + Json::Value color_stops; + for (const std::pair item : colorStops) { + color_stops[ftos(item.first)] = encodeHexColorString(item.second); + } + root["color_stops"] = color_stops; + root["blend"] = WearBarParams::es_BlendMode[blend].str; + + fastWriteJson(root, os); +} + +std::optional WearBarParams::deserializeJson(std::istream &is) +{ + Json::Value root; + is >> root; + if (!root.isObject() || !root["color_stops"].isObject() || !root["blend"].isString()) + return std::nullopt; + + int blendInt; + WearBarParams::BlendMode blend; + if (string_to_enum(WearBarParams::es_BlendMode, blendInt, root["blend"].asString())) + blend = static_cast(blendInt); + else + return std::nullopt; + + const Json::Value &color_stops_object = root["color_stops"]; + std::map colorStops; + for (const std::string &key : color_stops_object.getMemberNames()) { + f32 stop = stof(key); + if (stop < 0 || stop > 1) + return std::nullopt; + const Json::Value &value = color_stops_object[key]; + if (value.isString()) { + video::SColor color; + parseColorString(value.asString(), color, false); + colorStops.emplace(stop, color); + } + } + if (colorStops.empty()) + return std::nullopt; + return WearBarParams(colorStops, blend); +} + +video::SColor WearBarParams::getWearBarColor(f32 durabilityPercent) { + if (colorStops.empty()) + return video::SColor(); + + /* + * Strategy: + * Find upper bound of durabilityPercent + * + * if it == stops.end() -> return last color in the map + * if it == stops.begin() -> return first color in the map + * + * else: + * lower_bound = it - 1 + * interpolate/do constant + */ + auto upper = colorStops.upper_bound(durabilityPercent); + + if (upper == colorStops.end()) // durability is >= the highest defined color stop + return std::prev(colorStops.end())->second; // return last element of the map + + if (upper == colorStops.begin()) // durability is <= the lowest defined color stop + return upper->second; + + auto lower = std::prev(upper); + f32 lower_bound = lower->first; + video::SColor lower_color = lower->second; + f32 upper_bound = upper->first; + video::SColor upper_color = upper->second; + + f32 progress = (durabilityPercent - lower_bound) / (upper_bound - lower_bound); + + switch (blend) { + case BLEND_MODE_CONSTANT: + return lower_color; + case BLEND_MODE_LINEAR: + return upper_color.getInterpolated(lower_color, progress); + case BlendMode_END: + throw std::logic_error("dummy value"); + } + throw std::logic_error("invalid blend value"); +} + u32 calculateResultWear(const u32 uses, const u16 initial_wear) { if (uses == 0) { diff --git a/src/tool.h b/src/tool.h index 8a22fca7b9..3121e907d4 100644 --- a/src/tool.h +++ b/src/tool.h @@ -20,10 +20,15 @@ with this program; if not, write to the Free Software Foundation, Inc., #pragma once #include "irrlichttypes.h" -#include -#include #include "itemgroup.h" #include "json-forwards.h" +#include "common/c_types.h" +#include +#include + +#include +#include +#include struct ItemDefinition; @@ -82,6 +87,37 @@ struct ToolCapabilities void deserializeJson(std::istream &is); }; +struct WearBarParams +{ + std::map colorStops; + enum BlendMode: u8 { + BLEND_MODE_CONSTANT, + BLEND_MODE_LINEAR, + BlendMode_END // Dummy for validity check + }; + constexpr const static EnumString es_BlendMode[3] = { + {WearBarParams::BLEND_MODE_CONSTANT, "constant"}, + {WearBarParams::BLEND_MODE_LINEAR, "linear"}, + {0, NULL} + }; + BlendMode blend; + + WearBarParams(const std::map &colorStops, BlendMode blend): + colorStops(colorStops), + blend(blend) + {} + + WearBarParams(const video::SColor color): + WearBarParams({{0.0, color}}, WearBarParams::BLEND_MODE_CONSTANT) + {}; + + void serialize(std::ostream &os) const; + static WearBarParams deserialize(std::istream &is); + void serializeJson(std::ostream &os) const; + static std::optional deserializeJson(std::istream &is); + video::SColor getWearBarColor(f32 durabilityPercent); +}; + struct DigParams { bool diggable; diff --git a/src/util/string.cpp b/src/util/string.cpp index b4af7a404d..efb07c1d18 100644 --- a/src/util/string.cpp +++ b/src/util/string.cpp @@ -574,6 +574,20 @@ bool parseColorString(const std::string &value, video::SColor &color, bool quiet return success; } +std::string encodeHexColorString(const video::SColor &color) +{ + std::string color_string = "#"; + const char red = color.getRed(); + const char green = color.getGreen(); + const char blue = color.getBlue(); + const char alpha = color.getAlpha(); + color_string += hex_encode(&red, 1); + color_string += hex_encode(&green, 1); + color_string += hex_encode(&blue, 1); + color_string += hex_encode(&alpha, 1); + return color_string; +} + void str_replace(std::string &str, char from, char to) { std::replace(str.begin(), str.end(), from, to); diff --git a/src/util/string.h b/src/util/string.h index 5d49cc77d5..f5382200db 100644 --- a/src/util/string.h +++ b/src/util/string.h @@ -88,6 +88,7 @@ char *mystrtok_r(char *s, const char *sep, char **lasts) noexcept; u64 read_seed(const char *str); bool parseColorString(const std::string &value, video::SColor &color, bool quiet, unsigned char default_alpha = 0xff); +std::string encodeHexColorString(const video::SColor &color); /** From 893594d81a3daedc084fefc42ea26b5801772bd2 Mon Sep 17 00:00:00 2001 From: Zemtzov7 <72821250+zmv7@users.noreply.github.com> Date: Sat, 3 Feb 2024 02:12:59 +0500 Subject: [PATCH 5/7] Add help formspec for CSM commands (#13937) --- builtin/client/init.lua | 1 + builtin/common/chatcommands.lua | 6 +-- builtin/common/information_formspecs.lua | 63 +++++++++++++++++------- 3 files changed, 48 insertions(+), 22 deletions(-) diff --git a/builtin/client/init.lua b/builtin/client/init.lua index 68fb169f04..301a8050c4 100644 --- a/builtin/client/init.lua +++ b/builtin/client/init.lua @@ -7,6 +7,7 @@ dofile(clientpath .. "register.lua") dofile(commonpath .. "after.lua") dofile(commonpath .. "mod_storage.lua") dofile(commonpath .. "chatcommands.lua") +dofile(commonpath .. "information_formspecs.lua") dofile(clientpath .. "chatcommands.lua") dofile(clientpath .. "death_formspec.lua") dofile(clientpath .. "misc.lua") diff --git a/builtin/common/chatcommands.lua b/builtin/common/chatcommands.lua index 7c3da0601c..7a8a49558f 100644 --- a/builtin/common/chatcommands.lua +++ b/builtin/common/chatcommands.lua @@ -89,7 +89,7 @@ local function do_help_cmd(name, param) if #args > 1 then return false, S("Too many arguments, try using just /help ") end - local use_gui = INIT ~= "client" and core.get_player_by_name(name) + local use_gui = INIT == "client" or core.get_player_by_name(name) use_gui = use_gui and not opts:find("t") if #args == 0 and not use_gui then @@ -163,8 +163,8 @@ end if INIT == "client" then core.register_chatcommand("help", { - params = core.gettext("[all | ]"), - description = core.gettext("Get help for commands"), + params = core.gettext("[all | ] [-t]"), + description = core.gettext("Get help for commands (-t: output in chat)"), func = function(param) return do_help_cmd(nil, param) end, diff --git a/builtin/common/information_formspecs.lua b/builtin/common/information_formspecs.lua index 3405263bf4..3fa397d256 100644 --- a/builtin/common/information_formspecs.lua +++ b/builtin/common/information_formspecs.lua @@ -61,15 +61,20 @@ local function build_chatcommands_formspec(name, sel, copy) for i, data in ipairs(mod_cmds) do rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. "," for j, cmds in ipairs(data[2]) do - local has_priv = check_player_privs(name, cmds[2].privs) + local has_priv = INIT == "client" or check_player_privs(name, cmds[2].privs) rows[#rows + 1] = ("%s,1,%s,%s"):format( has_priv and COLOR_GREEN or COLOR_GRAY, cmds[1], F(cmds[2].params)) if sel == #rows then description = cmds[2].description if copy then - core.chat_send_player(name, S("Command: @1 @2", - core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params)) + local msg = S("Command: @1 @2", + core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params) + if INIT == "client" then + core.display_chat_message(msg) + else + core.chat_send_player(name, msg) + end end end end @@ -111,26 +116,46 @@ end -- DETAILED CHAT COMMAND INFORMATION +if INIT == "client" then + core.register_on_formspec_input(function(formname, fields) + if formname ~= "__builtin:help_cmds" or fields.quit then + return + end -core.register_on_player_receive_fields(function(player, formname, fields) - if formname ~= "__builtin:help_cmds" or fields.quit then - return - end + local event = core.explode_table_event(fields.list) + if event.type ~= "INV" then + core.show_formspec("__builtin:help_cmds", + build_chatcommands_formspec(nil, event.row, event.type == "DCL")) + end + end) +else + core.register_on_player_receive_fields(function(player, formname, fields) + if formname ~= "__builtin:help_cmds" or fields.quit then + return + end - local event = core.explode_table_event(fields.list) - if event.type ~= "INV" then - local name = player:get_player_name() - core.show_formspec(name, "__builtin:help_cmds", - build_chatcommands_formspec(name, event.row, event.type == "DCL")) - end -end) + local event = core.explode_table_event(fields.list) + if event.type ~= "INV" then + local name = player:get_player_name() + core.show_formspec(name, "__builtin:help_cmds", + build_chatcommands_formspec(name, event.row, event.type == "DCL")) + end + end) +end function core.show_general_help_formspec(name) - core.show_formspec(name, "__builtin:help_cmds", - build_chatcommands_formspec(name)) + if INIT == "client" then + core.show_formspec("__builtin:help_cmds", + build_chatcommands_formspec(name)) + else + core.show_formspec(name, "__builtin:help_cmds", + build_chatcommands_formspec(name)) + end end -function core.show_privs_help_formspec(name) - core.show_formspec(name, "__builtin:help_privs", - build_privs_formspec(name)) +if INIT ~= "client" then + function core.show_privs_help_formspec(name) + core.show_formspec(name, "__builtin:help_privs", + build_privs_formspec(name)) + end end From e7dbd325d2e50e7bc3a509ec6a7eaeee1ec2ae8a Mon Sep 17 00:00:00 2001 From: SmallJoker Date: Fri, 2 Feb 2024 22:13:24 +0100 Subject: [PATCH 6/7] RemotePlayer: make peer ID always reflect the validity of PlayerSAO (#14317) Upon disconnect, RemotePlayer still had a peer ID assigned even though the PlayerSAO object was maked as gone (for removal). This commit makes that the following always holds true: (!sao || sao->isGone()) === (peer_id == PEER_ID_INEXISTENT) --- src/network/serverpackethandler.cpp | 14 ++++---- src/remoteplayer.cpp | 5 +++ src/remoteplayer.h | 3 +- src/script/lua_api/l_object.cpp | 2 +- src/server.cpp | 53 ++++++++++++++++++++--------- src/server.h | 6 ++-- src/server/player_sao.cpp | 45 +++++++++++++----------- src/server/player_sao.h | 5 +-- src/serverenvironment.cpp | 12 ++----- src/serverenvironment.h | 2 -- 10 files changed, 86 insertions(+), 61 deletions(-) diff --git a/src/network/serverpackethandler.cpp b/src/network/serverpackethandler.cpp index 3c6b635008..146f4cfb32 100644 --- a/src/network/serverpackethandler.cpp +++ b/src/network/serverpackethandler.cpp @@ -512,7 +512,7 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao, if (playersao->checkMovementCheat()) { // Call callbacks m_script->on_cheat(playersao, "moved_too_fast"); - SendMovePlayer(pkt->getPeerId()); + SendMovePlayer(playersao); } } @@ -993,7 +993,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) return; } - playersao->getPlayer()->setWieldIndex(item_i); + player->setWieldIndex(item_i); // Get pointed to object (NULL if not POINTEDTYPE_OBJECT) ServerActiveObject *pointed_object = NULL; @@ -1161,7 +1161,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Get player's wielded item // See also: Game::handleDigging ItemStack selected_item, hand_item; - playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item); + player->getWieldedItem(&selected_item, &hand_item); // Get diggability and expected digging time DigParams params = getDigParams(m_nodedef->get(n).groups, @@ -1253,7 +1253,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Do stuff if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) - SendInventory(playersao, true); + SendInventory(player, true); } pointed_object->rightClick(playersao); @@ -1262,7 +1262,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) // Apply returned ItemStack if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) - SendInventory(playersao, true); + SendInventory(player, true); } if (pointed.type != POINTEDTHING_NODE) @@ -1296,7 +1296,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (m_script->item_OnUse(selected_item, playersao, pointed)) { // Apply returned ItemStack if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) - SendInventory(playersao, true); + SendInventory(player, true); } return; @@ -1315,7 +1315,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt) if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { // Apply returned ItemStack if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) - SendInventory(playersao, true); + SendInventory(player, true); } return; diff --git a/src/remoteplayer.cpp b/src/remoteplayer.cpp index 20be7a8c81..9658dca066 100644 --- a/src/remoteplayer.cpp +++ b/src/remoteplayer.cpp @@ -69,6 +69,11 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef): m_star_params = SkyboxDefaults::getStarDefaults(); } +RemotePlayer::~RemotePlayer() +{ + if (m_sao) + m_sao->setPlayer(nullptr); +} RemotePlayerChatResult RemotePlayer::canSendChatMessage() { diff --git a/src/remoteplayer.h b/src/remoteplayer.h index 0ab33adfe1..a38f31731a 100644 --- a/src/remoteplayer.h +++ b/src/remoteplayer.h @@ -42,7 +42,7 @@ class RemotePlayer : public Player public: RemotePlayer(const char *name, IItemDefManager *idef); - virtual ~RemotePlayer() = default; + virtual ~RemotePlayer(); PlayerSAO *getPlayerSAO() { return m_sao; } void setPlayerSAO(PlayerSAO *sao) { m_sao = sao; } @@ -135,6 +135,7 @@ public: u16 protocol_version = 0; u16 formspec_version = 0; + /// returns PEER_ID_INEXISTENT when PlayerSAO is not ready session_t getPeerId() const { return m_peer_id; } void setPeerId(session_t peer_id) { m_peer_id = peer_id; } diff --git a/src/script/lua_api/l_object.cpp b/src/script/lua_api/l_object.cpp index 764cf87fe1..c291e605a8 100644 --- a/src/script/lua_api/l_object.cpp +++ b/src/script/lua_api/l_object.cpp @@ -326,7 +326,7 @@ int ObjectRef::l_set_wielded_item(lua_State *L) bool success = sao->setWieldedItem(item); if (success && sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) { - getServer(L)->SendInventory((PlayerSAO *)sao, true); + getServer(L)->SendInventory(((PlayerSAO *)sao)->getPlayer(), true); } lua_pushboolean(L, success); return 1; diff --git a/src/server.cpp b/src/server.cpp index baacce3f6b..1c92525814 100644 --- a/src/server.cpp +++ b/src/server.cpp @@ -343,7 +343,7 @@ Server::~Server() kick_msg = g_settings->get("kick_msg_shutdown"); } m_env->saveLoadedPlayers(true); - m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, + kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, kick_msg, reconnect); } @@ -590,7 +590,7 @@ void Server::step() std::string async_err = m_async_fatal_error.get(); if (!async_err.empty()) { if (!m_simple_singleplayer_mode) { - m_env->kickAllPlayers(SERVER_ACCESSDENIED_CRASH, + kickAllPlayers(SERVER_ACCESSDENIED_CRASH, g_settings->get("kick_msg_crash"), g_settings->getBool("ask_reconnect_on_crash")); } @@ -1105,7 +1105,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) /* Send complete position information */ - SendMovePlayer(peer_id); + SendMovePlayer(playersao); // Send privileges SendPlayerPrivileges(peer_id); @@ -1114,7 +1114,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id) SendPlayerInventoryFormspec(peer_id); // Send inventory - SendInventory(playersao, false); + SendInventory(player, false); // Send HP SendPlayerHP(playersao, false); @@ -1458,10 +1458,8 @@ void Server::SendNodeDef(session_t peer_id, Non-static send methods */ -void Server::SendInventory(PlayerSAO *sao, bool incremental) +void Server::SendInventory(RemotePlayer *player, bool incremental) { - RemotePlayer *player = sao->getPlayer(); - // Do not send new format to old clients incremental &= player->protocol_version >= 38; @@ -1471,11 +1469,11 @@ void Server::SendInventory(PlayerSAO *sao, bool incremental) Serialize it */ - NetworkPacket pkt(TOCLIENT_INVENTORY, 0, sao->getPeerID()); + NetworkPacket pkt(TOCLIENT_INVENTORY, 0, player->getPeerId()); std::ostringstream os(std::ios::binary); - sao->getInventory()->serialize(os, incremental); - sao->getInventory()->setModified(false); + player->inventory.serialize(os, incremental); + player->inventory.setModified(false); player->setModified(true); const std::string &s = os.str(); @@ -1900,17 +1898,12 @@ void Server::SendPlayerBreath(PlayerSAO *sao) SendBreath(sao->getPeerID(), sao->getBreath()); } -void Server::SendMovePlayer(session_t peer_id) +void Server::SendMovePlayer(PlayerSAO *sao) { - RemotePlayer *player = m_env->getPlayer(peer_id); - assert(player); - PlayerSAO *sao = player->getPlayerSAO(); - assert(sao); - // Send attachment updates instantly to the client prior updating position sao->sendOutdatedData(); - NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, peer_id); + NetworkPacket pkt(TOCLIENT_MOVE_PLAYER, sizeof(v3f) + sizeof(f32) * 2, sao->getPeerID()); pkt << sao->getBasePosition() << sao->getLookPitch() << sao->getRotation().Y; { @@ -2877,6 +2870,15 @@ void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason, DisconnectPeer(peer_id); } +void Server::kickAllPlayers(AccessDeniedCode reason, + const std::string &str_reason, bool reconnect) +{ + std::vector clients = m_clients.getClientIDs(); + for (const session_t client_id : clients) { + DenyAccess(client_id, reason, str_reason, reconnect); + } +} + void Server::DisconnectPeer(session_t peer_id) { m_modchannel_mgr->leaveAllChannels(peer_id); @@ -3929,6 +3931,23 @@ PlayerSAO* Server::emergePlayer(const char *name, session_t peer_id, u16 proto_v return NULL; } + /* + Object construction sequence/hierarchy + -------------------------------------- + 1. RemoteClient (tightly connection-bound) + 2. RemotePlayer (controls, in-game appearance) + 3. PlayerSAO (movable object in-game) + PlayerSAO controls the peer_id assignment of RemotePlayer, + indicating whether the player is ready + + Destruction sequence + -------------------- + 1. PlayerSAO pending removal flag + 2. PlayerSAO save data before free + 3. RemotePlayer, then PlayerSAO freed + 4. RemoteClient freed + */ + if (!player) { player = new RemotePlayer(name, idef()); } diff --git a/src/server.h b/src/server.h index c57dde0d73..5a6b6b7cdc 100644 --- a/src/server.h +++ b/src/server.h @@ -352,6 +352,8 @@ public: void DenySudoAccess(session_t peer_id); void DenyAccess(session_t peer_id, AccessDeniedCode reason, const std::string &custom_reason = "", bool reconnect = false); + void kickAllPlayers(AccessDeniedCode reason, + const std::string &str_reason, bool reconnect); void acceptAuth(session_t peer_id, bool forSudoMode); void DisconnectPeer(session_t peer_id); bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval); @@ -363,8 +365,8 @@ public: void HandlePlayerHPChange(PlayerSAO *sao, const PlayerHPChangeReason &reason); void SendPlayerHP(PlayerSAO *sao, bool effect); void SendPlayerBreath(PlayerSAO *sao); - void SendInventory(PlayerSAO *playerSAO, bool incremental); - void SendMovePlayer(session_t peer_id); + void SendInventory(RemotePlayer *player, bool incremental); + void SendMovePlayer(PlayerSAO *sao); void SendMovePlayerRel(session_t peer_id, const v3f &added_pos); void SendPlayerSpeed(session_t peer_id, const v3f &added_vel); void SendPlayerFov(session_t peer_id); diff --git a/src/server/player_sao.cpp b/src/server/player_sao.cpp index 688dcde4e0..d72383382b 100644 --- a/src/server/player_sao.cpp +++ b/src/server/player_sao.cpp @@ -29,10 +29,10 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p bool is_singleplayer): UnitSAO(env_, v3f(0,0,0)), m_player(player_), - m_peer_id(peer_id_), + m_peer_id_initial(peer_id_), m_is_singleplayer(is_singleplayer) { - SANITY_CHECK(m_peer_id != PEER_ID_INEXISTENT); + SANITY_CHECK(m_peer_id_initial != PEER_ID_INEXISTENT); m_prop.hp_max = PLAYER_MAX_HP_DEFAULT; m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT; @@ -88,7 +88,8 @@ void PlayerSAO::addedToEnvironment(u32 dtime_s) ServerActiveObject::addedToEnvironment(dtime_s); ServerActiveObject::setBasePosition(m_base_position); m_player->setPlayerSAO(this); - m_player->setPeerId(m_peer_id); + m_player->setPeerId(m_peer_id_initial); + m_peer_id_initial = PEER_ID_INEXISTENT; // don't try to use it again. m_last_good_position = m_base_position; } @@ -96,11 +97,13 @@ void PlayerSAO::addedToEnvironment(u32 dtime_s) void PlayerSAO::removingFromEnvironment() { ServerActiveObject::removingFromEnvironment(); - if (m_player->getPlayerSAO() == this) { - unlinkPlayerSessionAndSave(); - for (u32 attached_particle_spawner : m_attached_particle_spawners) { - m_env->deleteParticleSpawner(attached_particle_spawner, false); - } + + // If this fails, fix the ActiveObjectMgr code in ServerEnvironment + SANITY_CHECK(m_player->getPlayerSAO() == this); + + unlinkPlayerSessionAndSave(); + for (u32 attached_particle_spawner : m_attached_particle_spawners) { + m_env->deleteParticleSpawner(attached_particle_spawner, false); } } @@ -236,7 +239,7 @@ void PlayerSAO::step(float dtime, bool send_recommended) " is attached to nonexistent parent. This is a bug." << std::endl; clearParentAttachment(); setBasePosition(m_last_good_position); - m_env->getGameDef()->SendMovePlayer(m_peer_id); + m_env->getGameDef()->SendMovePlayer(this); } //dstream<<"PlayerSAO::step: dtime: "<getGameDef()->SendBlock(m_peer_id, blockpos); + m_env->getGameDef()->SendBlock(getPeerID(), blockpos); setBasePosition(pos); // Movement caused by this command is always valid m_last_good_position = getBasePosition(); m_move_pool.empty(); m_time_from_last_teleport = 0.0; - m_env->getGameDef()->SendMovePlayer(m_peer_id); + m_env->getGameDef()->SendMovePlayer(this); } void PlayerSAO::addPos(const v3f &added_pos) @@ -381,14 +384,14 @@ void PlayerSAO::addPos(const v3f &added_pos) // Send mapblock of target location v3f pos = getBasePosition() + added_pos; v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE); - m_env->getGameDef()->SendBlock(m_peer_id, blockpos); + m_env->getGameDef()->SendBlock(getPeerID(), blockpos); setBasePosition(pos); // Movement caused by this command is always valid m_last_good_position = getBasePosition(); m_move_pool.empty(); m_time_from_last_teleport = 0.0; - m_env->getGameDef()->SendMovePlayerRel(m_peer_id, added_pos); + m_env->getGameDef()->SendMovePlayerRel(getPeerID(), added_pos); } void PlayerSAO::moveTo(v3f pos, bool continuous) @@ -401,7 +404,7 @@ void PlayerSAO::moveTo(v3f pos, bool continuous) m_last_good_position = getBasePosition(); m_move_pool.empty(); m_time_from_last_teleport = 0.0; - m_env->getGameDef()->SendMovePlayer(m_peer_id); + m_env->getGameDef()->SendMovePlayer(this); } void PlayerSAO::setPlayerYaw(const float yaw) @@ -433,7 +436,7 @@ void PlayerSAO::setWantedRange(const s16 range) void PlayerSAO::setPlayerYawAndSend(const float yaw) { setPlayerYaw(yaw); - m_env->getGameDef()->SendMovePlayer(m_peer_id); + m_env->getGameDef()->SendMovePlayer(this); } void PlayerSAO::setLookPitch(const float pitch) @@ -447,7 +450,7 @@ void PlayerSAO::setLookPitch(const float pitch) void PlayerSAO::setLookPitchAndSend(const float pitch) { setLookPitch(pitch); - m_env->getGameDef()->SendMovePlayer(m_peer_id); + m_env->getGameDef()->SendMovePlayer(this); } u32 PlayerSAO::punch(v3f dir, @@ -578,16 +581,20 @@ bool PlayerSAO::setWieldedItem(const ItemStack &item) void PlayerSAO::disconnected() { - m_peer_id = PEER_ID_INEXISTENT; markForRemoval(); + m_player->setPeerId(PEER_ID_INEXISTENT); +} + +session_t PlayerSAO::getPeerID() const +{ + // Before adding `this` to the server env, m_player is still nullptr. + return m_player ? m_player->getPeerId() : PEER_ID_INEXISTENT; } void PlayerSAO::unlinkPlayerSessionAndSave() { assert(m_player->getPlayerSAO() == this); - m_player->setPeerId(PEER_ID_INEXISTENT); m_env->savePlayer(m_player); - m_player->setPlayerSAO(NULL); m_env->removePlayer(m_player); } diff --git a/src/server/player_sao.h b/src/server/player_sao.h index 8544645083..b26304589f 100644 --- a/src/server/player_sao.h +++ b/src/server/player_sao.h @@ -142,8 +142,9 @@ public: void disconnected(); + void setPlayer(RemotePlayer *player) { m_player = player; } RemotePlayer *getPlayer() { return m_player; } - session_t getPeerID() const { return m_peer_id; } + session_t getPeerID() const; // Cheat prevention @@ -193,7 +194,7 @@ private: std::string generateUpdatePhysicsOverrideCommand() const; RemotePlayer *m_player = nullptr; - session_t m_peer_id = 0; + session_t m_peer_id_initial = 0; ///< only used to initialize RemotePlayer // Cheat prevention LagPool m_dig_pool; diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index e0cf993788..0d003e078b 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -625,13 +625,6 @@ bool ServerEnvironment::removePlayerFromDatabase(const std::string &name) return m_player_database->removePlayer(name); } -void ServerEnvironment::kickAllPlayers(AccessDeniedCode reason, - const std::string &str_reason, bool reconnect) -{ - for (RemotePlayer *player : m_players) - m_server->DenyAccess(player->getPeerId(), reason, str_reason, reconnect); -} - void ServerEnvironment::saveLoadedPlayers(bool force) { for (RemotePlayer *player : m_players) { @@ -1643,9 +1636,8 @@ void ServerEnvironment::step(float dtime) if (player->getPeerId() == PEER_ID_INEXISTENT) continue; - PlayerSAO *sao = player->getPlayerSAO(); - if (sao && player->inventory.checkModified()) - m_server->SendInventory(sao, true); + if (player->inventory.checkModified()) + m_server->SendInventory(player, true); } // Send outdated detached inventories diff --git a/src/serverenvironment.h b/src/serverenvironment.h index ad6a3acc5d..6238b2ae2f 100644 --- a/src/serverenvironment.h +++ b/src/serverenvironment.h @@ -239,8 +239,6 @@ public: float getSendRecommendedInterval() { return m_recommended_send_interval; } - void kickAllPlayers(AccessDeniedCode reason, - const std::string &str_reason, bool reconnect); // Save players void saveLoadedPlayers(bool force = false); void savePlayer(RemotePlayer *player); From 1d9c9710d70c8d48de362f13ab97de5a6215a5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lars=20M=C3=BCller?= <34514239+appgurueu@users.noreply.github.com> Date: Sun, 4 Feb 2024 14:04:05 +0100 Subject: [PATCH 7/7] Fix short raycasts missing large objects (#14339) Increases the tolerance from one node to five nodes. Also optimizes the "sphere" used for pre-filtering entities to start in the middle of the line segment rather than at the start. --- doc/lua_api.md | 2 ++ src/serverenvironment.cpp | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/doc/lua_api.md b/doc/lua_api.md index b4ce001ddc..469292a726 100644 --- a/doc/lua_api.md +++ b/doc/lua_api.md @@ -8403,6 +8403,8 @@ Player properties need to be saved manually. -- If `rotate = false`, the selection box will not rotate with the object itself, remaining fixed to the axes. -- If `rotate = true`, it will match the object's rotation and any attachment rotations. -- Raycasts use the selection box and object's rotation, but do *not* obey attachment rotations. + -- For server-side raycasts to work correctly, + -- the selection box should extend at most 5 units in each direction. pointable = true, diff --git a/src/serverenvironment.cpp b/src/serverenvironment.cpp index 0d003e078b..7b188f60fc 100644 --- a/src/serverenvironment.cpp +++ b/src/serverenvironment.cpp @@ -1826,8 +1826,8 @@ void ServerEnvironment::getSelectedActiveObjects( const std::optional &pointabilities) { std::vector objs; - getObjectsInsideRadius(objs, shootline_on_map.start, - shootline_on_map.getLength() + 10.0f, nullptr); + getObjectsInsideRadius(objs, shootline_on_map.getMiddle(), + 0.5 * shootline_on_map.getLength() + 5 * BS, nullptr); const v3f line_vector = shootline_on_map.getVector(); for (auto obj : objs) {