1
0
mirror of https://github.com/luanti-org/luanti.git synced 2025-11-06 18:25:21 +01:00

Merge branch 'master' into doc-refactor-2

This commit is contained in:
Bradley Pierce
2024-02-04 15:22:22 -05:00
40 changed files with 772 additions and 145 deletions

View File

@@ -7,6 +7,7 @@ dofile(clientpath .. "register.lua")
dofile(commonpath .. "after.lua") dofile(commonpath .. "after.lua")
dofile(commonpath .. "mod_storage.lua") dofile(commonpath .. "mod_storage.lua")
dofile(commonpath .. "chatcommands.lua") dofile(commonpath .. "chatcommands.lua")
dofile(commonpath .. "information_formspecs.lua")
dofile(clientpath .. "chatcommands.lua") dofile(clientpath .. "chatcommands.lua")
dofile(clientpath .. "death_formspec.lua") dofile(clientpath .. "death_formspec.lua")
dofile(clientpath .. "misc.lua") dofile(clientpath .. "misc.lua")

View File

@@ -89,7 +89,7 @@ local function do_help_cmd(name, param)
if #args > 1 then if #args > 1 then
return false, S("Too many arguments, try using just /help <command>") return false, S("Too many arguments, try using just /help <command>")
end 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") use_gui = use_gui and not opts:find("t")
if #args == 0 and not use_gui then if #args == 0 and not use_gui then
@@ -163,8 +163,8 @@ end
if INIT == "client" then if INIT == "client" then
core.register_chatcommand("help", { core.register_chatcommand("help", {
params = core.gettext("[all | <cmd>]"), params = core.gettext("[all | <cmd>] [-t]"),
description = core.gettext("Get help for commands"), description = core.gettext("Get help for commands (-t: output in chat)"),
func = function(param) func = function(param)
return do_help_cmd(nil, param) return do_help_cmd(nil, param)
end, end,

View File

@@ -61,15 +61,20 @@ local function build_chatcommands_formspec(name, sel, copy)
for i, data in ipairs(mod_cmds) do for i, data in ipairs(mod_cmds) do
rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. "," rows[#rows + 1] = COLOR_BLUE .. ",0," .. F(data[1]) .. ","
for j, cmds in ipairs(data[2]) do 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( rows[#rows + 1] = ("%s,1,%s,%s"):format(
has_priv and COLOR_GREEN or COLOR_GRAY, has_priv and COLOR_GREEN or COLOR_GRAY,
cmds[1], F(cmds[2].params)) cmds[1], F(cmds[2].params))
if sel == #rows then if sel == #rows then
description = cmds[2].description description = cmds[2].description
if copy then if copy then
core.chat_send_player(name, S("Command: @1 @2", local msg = S("Command: @1 @2",
core.colorize("#0FF", "/" .. cmds[1]), cmds[2].params)) 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 end
end end
@@ -111,7 +116,19 @@ end
-- DETAILED CHAT COMMAND INFORMATION -- 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
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) core.register_on_player_receive_fields(function(player, formname, fields)
if formname ~= "__builtin:help_cmds" or fields.quit then if formname ~= "__builtin:help_cmds" or fields.quit then
return return
@@ -124,13 +141,21 @@ core.register_on_player_receive_fields(function(player, formname, fields)
build_chatcommands_formspec(name, event.row, event.type == "DCL")) build_chatcommands_formspec(name, event.row, event.type == "DCL"))
end end
end) end)
end
function core.show_general_help_formspec(name) function core.show_general_help_formspec(name)
if INIT == "client" then
core.show_formspec("__builtin:help_cmds",
build_chatcommands_formspec(name))
else
core.show_formspec(name, "__builtin:help_cmds", core.show_formspec(name, "__builtin:help_cmds",
build_chatcommands_formspec(name)) build_chatcommands_formspec(name))
end end
end
if INIT ~= "client" then
function core.show_privs_help_formspec(name) function core.show_privs_help_formspec(name)
core.show_formspec(name, "__builtin:help_privs", core.show_formspec(name, "__builtin:help_privs",
build_privs_formspec(name)) build_privs_formspec(name))
end end
end

View File

@@ -70,6 +70,8 @@ local flag_checkboxes = {
{ "trees", fgettext("Trees and jungle grass") }, { "trees", fgettext("Trees and jungle grass") },
{ "flat", fgettext("Flat terrain") }, { "flat", fgettext("Flat terrain") },
{ "mudflow", fgettext("Mud flow"), fgettext("Terrain surface erosion") }, { "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 -- Biome settings are in mgv6_biomes below
}, },
} }
@@ -279,7 +281,7 @@ local function create_world_formspec(dialogdata)
end end
local retval = local retval =
"size[12.25,7,true]" .. "size[12.25,7.4,true]" ..
-- Left side -- Left side
"container[0,0]".. "container[0,0]"..
@@ -321,8 +323,10 @@ local function create_world_formspec(dialogdata)
"container_end[]".. "container_end[]"..
-- Menu buttons -- Menu buttons
"button[3.25,6.5;3,0.5;world_create_confirm;" .. fgettext("Create") .. "]" .. "container[0,6.9]"..
"button[6.25,6.5;3,0.5;world_create_cancel;" .. fgettext("Cancel") .. "]" "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 return retval

View File

@@ -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. # The 'snowbiomes' flag enables the new 5 biome system.
# When the 'snowbiomes' flag is enabled jungles are automatically enabled and # When the 'snowbiomes' flag is enabled jungles are automatically enabled and
# the 'jungles' flag is ignored. # 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. # Deserts occur when np_biome exceeds this value.
# When the 'snowbiomes' flag is enabled, this is ignored. # When the 'snowbiomes' flag is enabled, this is ignored.

View File

@@ -7182,6 +7182,8 @@ an itemstring, a table or `nil`.
the item breaks after `max_uses` times the item breaks after `max_uses` times
* Valid `max_uses` range is [0,65536] * Valid `max_uses` range is [0,65536]
* Does nothing if item is not a tool or if `max_uses` is 0 * 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` * `add_item(item)`: returns leftover `ItemStack`
* Put some item or stack onto this stack * Put some item or stack onto this stack
* `item_fits(item)`: returns `true` if item or stack can be fully added to * `item_fits(item)`: returns `true` if item or stack can be fully added to
@@ -7220,6 +7222,10 @@ Can be obtained via `item:get_meta()`.
* Overrides the item's tool capabilities * Overrides the item's tool capabilities
* A nil value will clear the override data and restore the original * A nil value will clear the override data and restore the original
behavior. 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` ## `MetaDataRef`
@@ -8291,6 +8297,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 = 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. -- 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. -- 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, pointable = true,
@@ -8703,6 +8711,19 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- fallback behavior. -- 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, node_placement_prediction = nil,
-- If nil and item is node, prediction is made automatically. -- If nil and item is node, prediction is made automatically.
-- If nil and item is not a node, no prediction is made. -- If nil and item is not a node, no prediction is made.
@@ -9269,6 +9290,44 @@ 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 ## Crafting Recipes
Crafting converts one or more inputs to one output itemstack of arbitrary Crafting converts one or more inputs to one output itemstack of arbitrary

View File

@@ -420,36 +420,141 @@ minetest.register_tool("basetools:dagger_steel", {
} }
}) })
-- Test tool uses and punch_attack_uses -- Test tool uses, punch_attack_uses, and wear bar coloring
local uses = { 1, 2, 3, 5, 10, 50, 100, 1000, 10000, 65535 } local tool_params = {
for i=1, #uses do {uses = 1},
local u = uses[i] {uses = 2},
local ustring {uses = 3},
if i == 1 then {
ustring = u.."-Use" uses = 5,
else wear_color = "#5865f2",
ustring = u.."-Uses" wear_description = "Solid color: #5865f2",
end },
local color = string.format("#FF00%02X", math.floor(((i-1)/#uses) * 255)) {
minetest.register_tool("basetools:pick_uses_"..string.format("%05d", u), { 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".. 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", inventory_image = "basetools_usespick.png^[colorize:"..color..":127",
tool_capabilities = { tool_capabilities = {
max_drop_level=0, max_drop_level=0,
groupcaps={ 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".. description = ustring.." Sword".."\n"..
"Damage: fleshy=1", "Damage: fleshy=1",
inventory_image = "basetools_usessword.png^[colorize:"..color..":127", inventory_image = "basetools_usessword.png^[colorize:"..color..":127",
tool_capabilities = { tool_capabilities = {
damage_groups = {fleshy=1}, damage_groups = {fleshy=1},
punch_attack_uses = u, punch_attack_uses = uses,
}, },
}) })
end 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,
})

View File

@@ -210,6 +210,29 @@ minetest.register_chatcommand("dump_item", {
end, 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", { core.register_chatcommand("set_saturation", {
params = "<saturation>", params = "<saturation>",
description = "Set the saturation for current player.", description = "Set the saturation for current player.",

View File

@@ -1179,10 +1179,18 @@ void drawItemStack(
(1 - wear) * progressrect.LowerRightCorner.X; (1 - wear) * progressrect.LowerRightCorner.X;
// Compute progressbar color // Compute progressbar color
// default scheme:
// wear = 0.0: green // wear = 0.0: green
// wear = 0.5: yellow // wear = 0.5: yellow
// wear = 1.0: red // wear = 1.0: red
video::SColor color(255, 255, 255, 255);
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); int wear_i = MYMIN(std::floor(wear * 600), 511);
wear_i = MYMIN(wear_i + 10, 511); wear_i = MYMIN(wear_i + 10, 511);
@@ -1190,6 +1198,7 @@ void drawItemStack(
color.set(255, wear_i, 255, 0); color.set(255, wear_i, 255, 0);
else else
color.set(255, 255, 511 - wear_i, 0); color.set(255, 255, 511 - wear_i, 0);
}
core::rect<s32> progressrect2 = progressrect; core::rect<s32> progressrect2 = progressrect;
progressrect2.LowerRightCorner.X = progressmid; progressrect2.LowerRightCorner.X = progressmid;

View File

@@ -131,6 +131,15 @@ struct ItemStack
return metadata.getToolCapabilities(*item_cap); // Check for override return metadata.getToolCapabilities(*item_cap); // Check for override
} }
const std::optional<WearBarParams> &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) // Wear out (only tools)
// Returns true if the item is (was) a tool // Returns true if the item is (was) a tool
bool addWear(s32 amount, const IItemDefManager *itemdef) bool addWear(s32 amount, const IItemDefManager *itemdef)

View File

@@ -125,6 +125,7 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
pointabilities = def.pointabilities; pointabilities = def.pointabilities;
if (def.tool_capabilities) if (def.tool_capabilities)
tool_capabilities = new ToolCapabilities(*def.tool_capabilities); tool_capabilities = new ToolCapabilities(*def.tool_capabilities);
wear_bar_params = def.wear_bar_params;
groups = def.groups; groups = def.groups;
node_placement_prediction = def.node_placement_prediction; node_placement_prediction = def.node_placement_prediction;
place_param2 = def.place_param2; place_param2 = def.place_param2;
@@ -149,6 +150,7 @@ void ItemDefinition::resetInitial()
{ {
// Initialize pointers to NULL so reset() does not delete undefined pointers // Initialize pointers to NULL so reset() does not delete undefined pointers
tool_capabilities = NULL; tool_capabilities = NULL;
wear_bar_params = std::nullopt;
reset(); reset();
} }
@@ -171,6 +173,7 @@ void ItemDefinition::reset()
pointabilities = std::nullopt; pointabilities = std::nullopt;
delete tool_capabilities; delete tool_capabilities;
tool_capabilities = NULL; tool_capabilities = NULL;
wear_bar_params.reset();
groups.clear(); groups.clear();
sound_place = SoundSpec(); sound_place = SoundSpec();
sound_place_failed = SoundSpec(); sound_place_failed = SoundSpec();
@@ -251,6 +254,13 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
pointabilities_s = tmp_os.str(); pointabilities_s = tmp_os.str();
} }
os << serializeString16(pointabilities_s); 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) 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>(); pointabilities = std::make_optional<Pointabilities>();
pointabilities->deSerialize(tmp_is); pointabilities->deSerialize(tmp_is);
} }
if (readU8(is)) {
wear_bar_params = WearBarParams::deserialize(is);
}
} catch(SerializationError &e) {}; } catch(SerializationError &e) {};
} }

View File

@@ -28,6 +28,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "itemgroup.h" #include "itemgroup.h"
#include "sound.h" #include "sound.h"
#include "texture_override.h" // TextureOverride #include "texture_override.h" // TextureOverride
#include "tool.h"
#include "util/pointabilities.h" #include "util/pointabilities.h"
class IGameDef; class IGameDef;
class Client; class Client;
@@ -103,6 +104,8 @@ struct ItemDefinition
// They may be NULL. If non-NULL, deleted by destructor // They may be NULL. If non-NULL, deleted by destructor
ToolCapabilities *tool_capabilities; ToolCapabilities *tool_capabilities;
std::optional<WearBarParams> wear_bar_params;
ItemGroupList groups; ItemGroupList groups;
SoundSpec sound_place; SoundSpec sound_place;
SoundSpec sound_place_failed; SoundSpec sound_place_failed;

View File

@@ -21,7 +21,9 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "itemstackmetadata.h" #include "itemstackmetadata.h"
#include "util/serialize.h" #include "util/serialize.h"
#include "util/strfnd.h" #include "util/strfnd.h"
#include <algorithm> #include <algorithm>
#include <optional>
#define DESERIALIZE_START '\x01' #define DESERIALIZE_START '\x01'
#define DESERIALIZE_KV_DELIM '\x02' #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 DESERIALIZE_PAIR_DELIM_STR "\x03"
#define TOOLCAP_KEY "tool_capabilities" #define TOOLCAP_KEY "tool_capabilities"
#define WEAR_BAR_KEY "wear_color"
void ItemStackMetadata::clear() void ItemStackMetadata::clear()
{ {
SimpleMetadata::clear(); SimpleMetadata::clear();
updateToolCapabilities(); updateToolCapabilities();
updateWearBarParams();
} }
static void sanitize_string(std::string &str) 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); bool result = SimpleMetadata::setString(clean_name, clean_var);
if (clean_name == TOOLCAP_KEY) if (clean_name == TOOLCAP_KEY)
updateToolCapabilities(); updateToolCapabilities();
else if (clean_name == WEAR_BAR_KEY)
updateWearBarParams();
return result; return result;
} }
@@ -91,6 +97,7 @@ void ItemStackMetadata::deSerialize(std::istream &is)
} }
} }
updateToolCapabilities(); updateToolCapabilities();
updateWearBarParams();
} }
void ItemStackMetadata::updateToolCapabilities() void ItemStackMetadata::updateToolCapabilities()
@@ -116,3 +123,25 @@ void ItemStackMetadata::clearToolCapabilities()
{ {
setString(TOOLCAP_KEY, ""); 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 &params)
{
std::ostringstream os;
params.serializeJson(os);
setString(WEAR_BAR_KEY, os.str());
}
void ItemStackMetadata::clearWearBarParams()
{
setString(WEAR_BAR_KEY, "");
}

View File

@@ -22,13 +22,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "metadata.h" #include "metadata.h"
#include "tool.h" #include "tool.h"
#include <optional>
class Inventory; class Inventory;
class IItemDefManager; class IItemDefManager;
class ItemStackMetadata : public SimpleMetadata class ItemStackMetadata : public SimpleMetadata
{ {
public: public:
ItemStackMetadata() : toolcaps_overridden(false) {} ItemStackMetadata():
toolcaps_overridden(false)
{}
// Overrides // Overrides
void clear() override; void clear() override;
@@ -46,9 +50,20 @@ public:
void setToolCapabilities(const ToolCapabilities &caps); void setToolCapabilities(const ToolCapabilities &caps);
void clearToolCapabilities(); void clearToolCapabilities();
const std::optional<WearBarParams> &getWearBarParamOverride() const
{
return wear_bar_override;
}
void setWearBarParams(const WearBarParams &params);
void clearWearBarParams();
private: private:
void updateToolCapabilities(); void updateToolCapabilities();
void updateWearBarParams();
bool toolcaps_overridden; bool toolcaps_overridden;
ToolCapabilities toolcaps_override; ToolCapabilities toolcaps_override;
std::optional<WearBarParams> wear_bar_override;
}; };

View File

@@ -1506,12 +1506,11 @@ MapSector *ServerMap::createSector(v2s16 p2d)
Do not create over max mapgen limit Do not create over max mapgen limit
*/ */
if (blockpos_over_max_limit(v3s16(p2d.X, 0, p2d.Y))) 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 Generate blank sector
*/ */
sector = new MapSector(this, p2d, m_gamedef); sector = new MapSector(this, p2d, m_gamedef);
/* /*
@@ -1524,20 +1523,11 @@ MapSector *ServerMap::createSector(v2s16 p2d)
MapBlock * ServerMap::createBlock(v3s16 p) 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); v2s16 p2d(p.X, p.Z);
s16 block_y = p.Y; s16 block_y = p.Y;
/* /*
This will create or load a sector if not found in memory. 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; MapSector *sector;
try { try {
@@ -1552,11 +1542,16 @@ MapBlock * ServerMap::createBlock(v3s16 p)
*/ */
MapBlock *block = sector->getBlockNoCreateNoEx(block_y); MapBlock *block = sector->getBlockNoCreateNoEx(block_y);
if (block) { if (block)
return block; return block;
}
// Create blank // Create blank
try {
block = sector->createBlankBlock(block_y); block = sector->createBlankBlock(block_y);
} catch (InvalidPositionException &e) {
infostream << "createBlock: createBlankBlock() failed" << std::endl;
throw e;
}
return block; return block;
} }
@@ -1576,10 +1571,10 @@ MapBlock * ServerMap::emergeBlock(v3s16 p, bool create_blank)
} }
if (create_blank) { if (create_blank) {
try {
MapSector *sector = createSector(v2s16(p.X, p.Z)); MapSector *sector = createSector(v2s16(p.X, p.Z));
MapBlock *block = sector->createBlankBlock(p.Y); return sector->createBlankBlock(p.Y);
} catch (InvalidPositionException &e) {}
return block;
} }
return NULL; return NULL;
@@ -2000,9 +1995,7 @@ void MMVManip::initialEmerge(v3s16 blockpos_min, v3s16 blockpos_max,
u8 flags = 0; u8 flags = 0;
MapBlock *block; MapBlock *block;
v3s16 p(x,y,z); v3s16 p(x,y,z);
std::map<v3s16, u8>::iterator n; if (m_loaded_blocks.count(p) > 0)
n = m_loaded_blocks.find(p);
if(n != m_loaded_blocks.end())
continue; continue;
bool block_data_inexistent = false; 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)) { if (load_if_inexistent && !blockpos_over_max_limit(p)) {
ServerMap *svrmap = (ServerMap *)m_map; block = m_map->emergeBlock(p, true);
block = svrmap->emergeBlock(p, false);
if (block == NULL)
block = svrmap->createBlock(p);
block->copyTo(*this); block->copyTo(*this);
} else { } else {
flags |= VMANIP_BLOCK_DATA_INEXIST; flags |= VMANIP_BLOCK_DATA_INEXIST;

View File

@@ -47,6 +47,7 @@ FlagDesc flagdesc_mapgen_v6[] = {
{"snowbiomes", MGV6_SNOWBIOMES}, {"snowbiomes", MGV6_SNOWBIOMES},
{"flat", MGV6_FLAT}, {"flat", MGV6_FLAT},
{"trees", MGV6_TREES}, {"trees", MGV6_TREES},
{"temples", MGV6_TEMPLES},
{NULL, 0} {NULL, 0}
}; };
@@ -225,7 +226,8 @@ void MapgenV6Params::writeParams(Settings *settings) const
void MapgenV6Params::setDefaultSettings(Settings *settings) void MapgenV6Params::setDefaultSettings(Settings *settings)
{ {
settings->setDefault("mgv6_spflags", flagdesc_mapgen_v6, MGV6_JUNGLES | 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 dp.np_alt_wall
= NoiseParams(-0.4, 1.0, v3f(40.0, 40.0, 40.0), 32474, 6, 1.1, 2.0); = 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_wall = c_desert_stone;
dp.c_alt_wall = CONTENT_IGNORE; dp.c_alt_wall = CONTENT_IGNORE;
dp.c_stair = c_stair_desert_stone; dp.c_stair = c_stair_desert_stone;

View File

@@ -39,6 +39,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#define MGV6_SNOWBIOMES 0x08 #define MGV6_SNOWBIOMES 0x08
#define MGV6_FLAT 0x10 #define MGV6_FLAT 0x10
#define MGV6_TREES 0x20 #define MGV6_TREES 0x20
#define MGV6_TEMPLES 0x40
extern FlagDesc flagdesc_mapgen_v6[]; extern FlagDesc flagdesc_mapgen_v6[];

View File

@@ -71,6 +71,9 @@ std::unique_ptr<MapBlock> MapSector::createBlankBlockNoInsert(s16 y)
{ {
assert(getBlockBuffered(y) == nullptr); // Pre-condition 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); v3s16 blockpos_map(m_pos.X, y, m_pos.Y);
return std::make_unique<MapBlock>(blockpos_map, m_gamedef); return std::make_unique<MapBlock>(blockpos_map, m_gamedef);

View File

@@ -20,10 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once #pragma once
#include "client/client.h"
#include "networkprotocol.h" #include "networkprotocol.h"
class NetworkPacket; class NetworkPacket;
class Client; // Note: don't forward-declare Client here (#14324)
enum ToClientConnectionState { enum ToClientConnectionState {
TOCLIENT_STATE_NOT_CONNECTED, TOCLIENT_STATE_NOT_CONNECTED,

View File

@@ -20,10 +20,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once #pragma once
#include "server.h"
#include "networkprotocol.h" #include "networkprotocol.h"
class NetworkPacket; class NetworkPacket;
class Server; // Note: don't forward-declare Server here (#14324)
enum ToServerConnectionState { enum ToServerConnectionState {
TOSERVER_STATE_NOT_CONNECTED, TOSERVER_STATE_NOT_CONNECTED,

View File

@@ -512,7 +512,7 @@ void Server::process_PlayerPos(RemotePlayer *player, PlayerSAO *playersao,
if (playersao->checkMovementCheat()) { if (playersao->checkMovementCheat()) {
// Call callbacks // Call callbacks
m_script->on_cheat(playersao, "moved_too_fast"); m_script->on_cheat(playersao, "moved_too_fast");
SendMovePlayer(pkt->getPeerId()); SendMovePlayer(playersao);
} }
} }
@@ -993,7 +993,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
return; return;
} }
playersao->getPlayer()->setWieldIndex(item_i); player->setWieldIndex(item_i);
// Get pointed to object (NULL if not POINTEDTYPE_OBJECT) // Get pointed to object (NULL if not POINTEDTYPE_OBJECT)
ServerActiveObject *pointed_object = NULL; ServerActiveObject *pointed_object = NULL;
@@ -1161,7 +1161,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
// Get player's wielded item // Get player's wielded item
// See also: Game::handleDigging // See also: Game::handleDigging
ItemStack selected_item, hand_item; ItemStack selected_item, hand_item;
playersao->getPlayer()->getWieldedItem(&selected_item, &hand_item); player->getWieldedItem(&selected_item, &hand_item);
// Get diggability and expected digging time // Get diggability and expected digging time
DigParams params = getDigParams(m_nodedef->get(n).groups, DigParams params = getDigParams(m_nodedef->get(n).groups,
@@ -1253,7 +1253,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
// Do stuff // Do stuff
if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) {
if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
SendInventory(playersao, true); SendInventory(player, true);
} }
pointed_object->rightClick(playersao); pointed_object->rightClick(playersao);
@@ -1262,7 +1262,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
// Apply returned ItemStack // Apply returned ItemStack
if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
SendInventory(playersao, true); SendInventory(player, true);
} }
if (pointed.type != POINTEDTHING_NODE) if (pointed.type != POINTEDTHING_NODE)
@@ -1296,7 +1296,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
if (m_script->item_OnUse(selected_item, playersao, pointed)) { if (m_script->item_OnUse(selected_item, playersao, pointed)) {
// Apply returned ItemStack // Apply returned ItemStack
if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
SendInventory(playersao, true); SendInventory(player, true);
} }
return; return;
@@ -1315,7 +1315,7 @@ void Server::handleCommand_Interact(NetworkPacket *pkt)
if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) { if (m_script->item_OnSecondaryUse(selected_item, playersao, pointed)) {
// Apply returned ItemStack // Apply returned ItemStack
if (selected_item.has_value() && playersao->setWieldedItem(*selected_item)) if (selected_item.has_value() && playersao->setWieldedItem(*selected_item))
SendInventory(playersao, true); SendInventory(player, true);
} }
return; return;

View File

@@ -69,6 +69,11 @@ RemotePlayer::RemotePlayer(const char *name, IItemDefManager *idef):
m_star_params = SkyboxDefaults::getStarDefaults(); m_star_params = SkyboxDefaults::getStarDefaults();
} }
RemotePlayer::~RemotePlayer()
{
if (m_sao)
m_sao->setPlayer(nullptr);
}
RemotePlayerChatResult RemotePlayer::canSendChatMessage() RemotePlayerChatResult RemotePlayer::canSendChatMessage()
{ {

View File

@@ -42,7 +42,7 @@ class RemotePlayer : public Player
public: public:
RemotePlayer(const char *name, IItemDefManager *idef); RemotePlayer(const char *name, IItemDefManager *idef);
virtual ~RemotePlayer() = default; virtual ~RemotePlayer();
PlayerSAO *getPlayerSAO() { return m_sao; } PlayerSAO *getPlayerSAO() { return m_sao; }
void setPlayerSAO(PlayerSAO *sao) { m_sao = sao; } void setPlayerSAO(PlayerSAO *sao) { m_sao = sao; }
@@ -135,6 +135,7 @@ public:
u16 protocol_version = 0; u16 protocol_version = 0;
u16 formspec_version = 0; u16 formspec_version = 0;
/// returns PEER_ID_INEXISTENT when PlayerSAO is not ready
session_t getPeerId() const { return m_peer_id; } session_t getPeerId() const { return m_peer_id; }
void setPeerId(session_t peer_id) { m_peer_id = peer_id; } void setPeerId(session_t peer_id) { m_peer_id = peer_id; }

View File

@@ -35,6 +35,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "server/player_sao.h" #include "server/player_sao.h"
#include "util/pointedthing.h" #include "util/pointedthing.h"
#include "debug.h" // For FATAL_ERROR #include "debug.h" // For FATAL_ERROR
#include <SColor.h>
#include <json/json.h> #include <json/json.h>
struct EnumString es_TileAnimationType[] = struct EnumString es_TileAnimationType[] =
@@ -94,6 +95,15 @@ void read_item_definition(lua_State* L, int index,
def.tool_capabilities = new ToolCapabilities( def.tool_capabilities = new ToolCapabilities(
read_tool_capabilities(L, -1)); 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 // If name is "" (hand), ensure there are ToolCapabilities
// because it will be looked up there whenever any other item has // 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); push_tool_capabilities(L, *i.tool_capabilities);
lua_setfield(L, -2, "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); push_groups(L, i.groups);
lua_setfield(L, -2, "groups"); lua_setfield(L, -2, "groups");
push_simplesoundspec(L, i.sound_place); push_simplesoundspec(L, i.sound_place);
@@ -1454,6 +1468,22 @@ void push_tool_capabilities(lua_State *L,
lua_setfield(L, -2, "damage_groups"); lua_setfield(L, -2, "damage_groups");
} }
/******************************************************************************/
void push_wear_bar_params(lua_State *L,
const WearBarParams &params)
{
lua_newtable(L);
setstringfield(L, -1, "blend", WearBarParams::es_BlendMode[params.blend].str);
lua_newtable(L);
for (const std::pair<const f32, const video::SColor> 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) 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<f32, video::SColor> 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<WearBarParams::BlendMode>(blendInt);
}
lua_pop(L, 1);
return WearBarParams(colorStops, blend);
}
/******************************************************************************/ /******************************************************************************/
void push_dig_params(lua_State *L,const DigParams &params) void push_dig_params(lua_State *L,const DigParams &params)
{ {

View File

@@ -116,6 +116,9 @@ void push_pointabilities (lua_State *L, const Pointabilities
ToolCapabilities read_tool_capabilities (lua_State *L, int table); ToolCapabilities read_tool_capabilities (lua_State *L, int table);
void push_tool_capabilities (lua_State *L, void push_tool_capabilities (lua_State *L,
const ToolCapabilities &prop); 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, void read_item_definition (lua_State *L, int index, const ItemDefinition &default_def,
ItemDefinition &def); ItemDefinition &def);

View File

@@ -376,6 +376,22 @@ int LuaItemStack::l_add_wear_by_uses(lua_State *L)
return 1; 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<LuaItemStack>(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 // add_item(self, itemstack or itemstring or table or nil) -> itemstack
// Returns leftover item stack // Returns leftover item stack
int LuaItemStack::l_add_item(lua_State *L) int LuaItemStack::l_add_item(lua_State *L)
@@ -551,6 +567,7 @@ const luaL_Reg LuaItemStack::methods[] = {
luamethod(LuaItemStack, get_tool_capabilities), luamethod(LuaItemStack, get_tool_capabilities),
luamethod(LuaItemStack, add_wear), luamethod(LuaItemStack, add_wear),
luamethod(LuaItemStack, add_wear_by_uses), luamethod(LuaItemStack, add_wear_by_uses),
luamethod(LuaItemStack, get_wear_bar_params),
luamethod(LuaItemStack, add_item), luamethod(LuaItemStack, add_item),
luamethod(LuaItemStack, item_fits), luamethod(LuaItemStack, item_fits),
luamethod(LuaItemStack, take_item), luamethod(LuaItemStack, take_item),

View File

@@ -125,6 +125,11 @@ private:
// Returns true if the item is (or was) a tool. // Returns true if the item is (or was) a tool.
static int l_add_wear_by_uses(lua_State *L); 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 // add_item(self, itemstack or itemstring or table or nil) -> itemstack
// Returns leftover item stack // Returns leftover item stack
static int l_add_item(lua_State *L); static int l_add_item(lua_State *L);

View File

@@ -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_itemstackmeta.h"
#include "lua_api/l_internal.h" #include "lua_api/l_internal.h"
#include "common/c_content.h" #include "common/c_content.h"
#include "common/c_converter.h"
#include "tool.h"
/* /*
ItemStackMetaRef ItemStackMetaRef
@@ -58,6 +60,20 @@ int ItemStackMetaRef::l_set_tool_capabilities(lua_State *L)
return 0; return 0;
} }
int ItemStackMetaRef::l_set_wear_bar_params(lua_State *L)
{
ItemStackMetaRef *metaref = checkObject<ItemStackMetaRef>(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) ItemStackMetaRef::ItemStackMetaRef(LuaItemStack *istack): istack(istack)
{ {
istack->grab(); istack->grab();
@@ -102,5 +118,6 @@ const luaL_Reg ItemStackMetaRef::methods[] = {
luamethod(MetaDataRef, from_table), luamethod(MetaDataRef, from_table),
luamethod(MetaDataRef, equals), luamethod(MetaDataRef, equals),
luamethod(ItemStackMetaRef, set_tool_capabilities), luamethod(ItemStackMetaRef, set_tool_capabilities),
luamethod(ItemStackMetaRef, set_wear_bar_params),
{0,0} {0,0}
}; };

View File

@@ -49,8 +49,19 @@ private:
istack->getItem().metadata.clearToolCapabilities(); istack->getItem().metadata.clearToolCapabilities();
} }
void setWearBarParams(const WearBarParams &params)
{
istack->getItem().metadata.setWearBarParams(params);
}
void clearWearBarParams()
{
istack->getItem().metadata.clearWearBarParams();
}
// Exported functions // Exported functions
static int l_set_tool_capabilities(lua_State *L); static int l_set_tool_capabilities(lua_State *L);
static int l_set_wear_bar_params(lua_State *L);
public: public:
// takes a reference // takes a reference
ItemStackMetaRef(LuaItemStack *istack); ItemStackMetaRef(LuaItemStack *istack);

View File

@@ -326,7 +326,7 @@ int ObjectRef::l_set_wielded_item(lua_State *L)
bool success = sao->setWieldedItem(item); bool success = sao->setWieldedItem(item);
if (success && sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) { if (success && sao->getType() == ACTIVEOBJECT_TYPE_PLAYER) {
getServer(L)->SendInventory((PlayerSAO *)sao, true); getServer(L)->SendInventory(((PlayerSAO *)sao)->getPlayer(), true);
} }
lua_pushboolean(L, success); lua_pushboolean(L, success);
return 1; return 1;

View File

@@ -343,7 +343,7 @@ Server::~Server()
kick_msg = g_settings->get("kick_msg_shutdown"); kick_msg = g_settings->get("kick_msg_shutdown");
} }
m_env->saveLoadedPlayers(true); m_env->saveLoadedPlayers(true);
m_env->kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN, kickAllPlayers(SERVER_ACCESSDENIED_SHUTDOWN,
kick_msg, reconnect); kick_msg, reconnect);
} }
@@ -590,7 +590,7 @@ void Server::step()
std::string async_err = m_async_fatal_error.get(); std::string async_err = m_async_fatal_error.get();
if (!async_err.empty()) { if (!async_err.empty()) {
if (!m_simple_singleplayer_mode) { if (!m_simple_singleplayer_mode) {
m_env->kickAllPlayers(SERVER_ACCESSDENIED_CRASH, kickAllPlayers(SERVER_ACCESSDENIED_CRASH,
g_settings->get("kick_msg_crash"), g_settings->get("kick_msg_crash"),
g_settings->getBool("ask_reconnect_on_crash")); g_settings->getBool("ask_reconnect_on_crash"));
} }
@@ -1105,7 +1105,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id)
/* /*
Send complete position information Send complete position information
*/ */
SendMovePlayer(peer_id); SendMovePlayer(playersao);
// Send privileges // Send privileges
SendPlayerPrivileges(peer_id); SendPlayerPrivileges(peer_id);
@@ -1114,7 +1114,7 @@ PlayerSAO* Server::StageTwoClientInit(session_t peer_id)
SendPlayerInventoryFormspec(peer_id); SendPlayerInventoryFormspec(peer_id);
// Send inventory // Send inventory
SendInventory(playersao, false); SendInventory(player, false);
// Send HP // Send HP
SendPlayerHP(playersao, false); SendPlayerHP(playersao, false);
@@ -1458,10 +1458,8 @@ void Server::SendNodeDef(session_t peer_id,
Non-static send methods 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 // Do not send new format to old clients
incremental &= player->protocol_version >= 38; incremental &= player->protocol_version >= 38;
@@ -1471,11 +1469,11 @@ void Server::SendInventory(PlayerSAO *sao, bool incremental)
Serialize it Serialize it
*/ */
NetworkPacket pkt(TOCLIENT_INVENTORY, 0, sao->getPeerID()); NetworkPacket pkt(TOCLIENT_INVENTORY, 0, player->getPeerId());
std::ostringstream os(std::ios::binary); std::ostringstream os(std::ios::binary);
sao->getInventory()->serialize(os, incremental); player->inventory.serialize(os, incremental);
sao->getInventory()->setModified(false); player->inventory.setModified(false);
player->setModified(true); player->setModified(true);
const std::string &s = os.str(); const std::string &s = os.str();
@@ -1900,17 +1898,12 @@ void Server::SendPlayerBreath(PlayerSAO *sao)
SendBreath(sao->getPeerID(), sao->getBreath()); 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 // Send attachment updates instantly to the client prior updating position
sao->sendOutdatedData(); 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; pkt << sao->getBasePosition() << sao->getLookPitch() << sao->getRotation().Y;
{ {
@@ -2877,6 +2870,15 @@ void Server::DenyAccess(session_t peer_id, AccessDeniedCode reason,
DisconnectPeer(peer_id); DisconnectPeer(peer_id);
} }
void Server::kickAllPlayers(AccessDeniedCode reason,
const std::string &str_reason, bool reconnect)
{
std::vector<session_t> 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) void Server::DisconnectPeer(session_t peer_id)
{ {
m_modchannel_mgr->leaveAllChannels(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; 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) { if (!player) {
player = new RemotePlayer(name, idef()); player = new RemotePlayer(name, idef());
} }

View File

@@ -352,6 +352,8 @@ public:
void DenySudoAccess(session_t peer_id); void DenySudoAccess(session_t peer_id);
void DenyAccess(session_t peer_id, AccessDeniedCode reason, void DenyAccess(session_t peer_id, AccessDeniedCode reason,
const std::string &custom_reason = "", bool reconnect = false); 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 acceptAuth(session_t peer_id, bool forSudoMode);
void DisconnectPeer(session_t peer_id); void DisconnectPeer(session_t peer_id);
bool getClientConInfo(session_t peer_id, con::rtt_stat_type type, float *retval); 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 HandlePlayerHPChange(PlayerSAO *sao, const PlayerHPChangeReason &reason);
void SendPlayerHP(PlayerSAO *sao, bool effect); void SendPlayerHP(PlayerSAO *sao, bool effect);
void SendPlayerBreath(PlayerSAO *sao); void SendPlayerBreath(PlayerSAO *sao);
void SendInventory(PlayerSAO *playerSAO, bool incremental); void SendInventory(RemotePlayer *player, bool incremental);
void SendMovePlayer(session_t peer_id); void SendMovePlayer(PlayerSAO *sao);
void SendMovePlayerRel(session_t peer_id, const v3f &added_pos); void SendMovePlayerRel(session_t peer_id, const v3f &added_pos);
void SendPlayerSpeed(session_t peer_id, const v3f &added_vel); void SendPlayerSpeed(session_t peer_id, const v3f &added_vel);
void SendPlayerFov(session_t peer_id); void SendPlayerFov(session_t peer_id);

View File

@@ -29,10 +29,10 @@ PlayerSAO::PlayerSAO(ServerEnvironment *env_, RemotePlayer *player_, session_t p
bool is_singleplayer): bool is_singleplayer):
UnitSAO(env_, v3f(0,0,0)), UnitSAO(env_, v3f(0,0,0)),
m_player(player_), m_player(player_),
m_peer_id(peer_id_), m_peer_id_initial(peer_id_),
m_is_singleplayer(is_singleplayer) 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.hp_max = PLAYER_MAX_HP_DEFAULT;
m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT; m_prop.breath_max = PLAYER_MAX_BREATH_DEFAULT;
@@ -88,7 +88,8 @@ void PlayerSAO::addedToEnvironment(u32 dtime_s)
ServerActiveObject::addedToEnvironment(dtime_s); ServerActiveObject::addedToEnvironment(dtime_s);
ServerActiveObject::setBasePosition(m_base_position); ServerActiveObject::setBasePosition(m_base_position);
m_player->setPlayerSAO(this); 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; m_last_good_position = m_base_position;
} }
@@ -96,13 +97,15 @@ void PlayerSAO::addedToEnvironment(u32 dtime_s)
void PlayerSAO::removingFromEnvironment() void PlayerSAO::removingFromEnvironment()
{ {
ServerActiveObject::removingFromEnvironment(); ServerActiveObject::removingFromEnvironment();
if (m_player->getPlayerSAO() == this) {
// If this fails, fix the ActiveObjectMgr code in ServerEnvironment
SANITY_CHECK(m_player->getPlayerSAO() == this);
unlinkPlayerSessionAndSave(); unlinkPlayerSessionAndSave();
for (u32 attached_particle_spawner : m_attached_particle_spawners) { for (u32 attached_particle_spawner : m_attached_particle_spawners) {
m_env->deleteParticleSpawner(attached_particle_spawner, false); m_env->deleteParticleSpawner(attached_particle_spawner, false);
} }
} }
}
std::string PlayerSAO::getClientInitializationData(u16 protocol_version) std::string PlayerSAO::getClientInitializationData(u16 protocol_version)
{ {
@@ -236,7 +239,7 @@ void PlayerSAO::step(float dtime, bool send_recommended)
" is attached to nonexistent parent. This is a bug." << std::endl; " is attached to nonexistent parent. This is a bug." << std::endl;
clearParentAttachment(); clearParentAttachment();
setBasePosition(m_last_good_position); setBasePosition(m_last_good_position);
m_env->getGameDef()->SendMovePlayer(m_peer_id); m_env->getGameDef()->SendMovePlayer(this);
} }
//dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl; //dstream<<"PlayerSAO::step: dtime: "<<dtime<<std::endl;
@@ -357,14 +360,14 @@ void PlayerSAO::setPos(const v3f &pos)
// Send mapblock of target location // Send mapblock of target location
v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE); 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); setBasePosition(pos);
// Movement caused by this command is always valid // Movement caused by this command is always valid
m_last_good_position = getBasePosition(); m_last_good_position = getBasePosition();
m_move_pool.empty(); m_move_pool.empty();
m_time_from_last_teleport = 0.0; 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) void PlayerSAO::addPos(const v3f &added_pos)
@@ -381,14 +384,14 @@ void PlayerSAO::addPos(const v3f &added_pos)
// Send mapblock of target location // Send mapblock of target location
v3f pos = getBasePosition() + added_pos; v3f pos = getBasePosition() + added_pos;
v3s16 blockpos = v3s16(pos.X / MAP_BLOCKSIZE, pos.Y / MAP_BLOCKSIZE, pos.Z / MAP_BLOCKSIZE); 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); setBasePosition(pos);
// Movement caused by this command is always valid // Movement caused by this command is always valid
m_last_good_position = getBasePosition(); m_last_good_position = getBasePosition();
m_move_pool.empty(); m_move_pool.empty();
m_time_from_last_teleport = 0.0; 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) void PlayerSAO::moveTo(v3f pos, bool continuous)
@@ -401,7 +404,7 @@ void PlayerSAO::moveTo(v3f pos, bool continuous)
m_last_good_position = getBasePosition(); m_last_good_position = getBasePosition();
m_move_pool.empty(); m_move_pool.empty();
m_time_from_last_teleport = 0.0; 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) void PlayerSAO::setPlayerYaw(const float yaw)
@@ -433,7 +436,7 @@ void PlayerSAO::setWantedRange(const s16 range)
void PlayerSAO::setPlayerYawAndSend(const float yaw) void PlayerSAO::setPlayerYawAndSend(const float yaw)
{ {
setPlayerYaw(yaw); setPlayerYaw(yaw);
m_env->getGameDef()->SendMovePlayer(m_peer_id); m_env->getGameDef()->SendMovePlayer(this);
} }
void PlayerSAO::setLookPitch(const float pitch) void PlayerSAO::setLookPitch(const float pitch)
@@ -447,7 +450,7 @@ void PlayerSAO::setLookPitch(const float pitch)
void PlayerSAO::setLookPitchAndSend(const float pitch) void PlayerSAO::setLookPitchAndSend(const float pitch)
{ {
setLookPitch(pitch); setLookPitch(pitch);
m_env->getGameDef()->SendMovePlayer(m_peer_id); m_env->getGameDef()->SendMovePlayer(this);
} }
u32 PlayerSAO::punch(v3f dir, u32 PlayerSAO::punch(v3f dir,
@@ -578,16 +581,20 @@ bool PlayerSAO::setWieldedItem(const ItemStack &item)
void PlayerSAO::disconnected() void PlayerSAO::disconnected()
{ {
m_peer_id = PEER_ID_INEXISTENT;
markForRemoval(); 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() void PlayerSAO::unlinkPlayerSessionAndSave()
{ {
assert(m_player->getPlayerSAO() == this); assert(m_player->getPlayerSAO() == this);
m_player->setPeerId(PEER_ID_INEXISTENT);
m_env->savePlayer(m_player); m_env->savePlayer(m_player);
m_player->setPlayerSAO(NULL);
m_env->removePlayer(m_player); m_env->removePlayer(m_player);
} }

View File

@@ -142,8 +142,9 @@ public:
void disconnected(); void disconnected();
void setPlayer(RemotePlayer *player) { m_player = player; }
RemotePlayer *getPlayer() { return m_player; } RemotePlayer *getPlayer() { return m_player; }
session_t getPeerID() const { return m_peer_id; } session_t getPeerID() const;
// Cheat prevention // Cheat prevention
@@ -193,7 +194,7 @@ private:
std::string generateUpdatePhysicsOverrideCommand() const; std::string generateUpdatePhysicsOverrideCommand() const;
RemotePlayer *m_player = nullptr; RemotePlayer *m_player = nullptr;
session_t m_peer_id = 0; session_t m_peer_id_initial = 0; ///< only used to initialize RemotePlayer
// Cheat prevention // Cheat prevention
LagPool m_dig_pool; LagPool m_dig_pool;

View File

@@ -625,13 +625,6 @@ bool ServerEnvironment::removePlayerFromDatabase(const std::string &name)
return m_player_database->removePlayer(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) void ServerEnvironment::saveLoadedPlayers(bool force)
{ {
for (RemotePlayer *player : m_players) { for (RemotePlayer *player : m_players) {
@@ -1643,9 +1636,8 @@ void ServerEnvironment::step(float dtime)
if (player->getPeerId() == PEER_ID_INEXISTENT) if (player->getPeerId() == PEER_ID_INEXISTENT)
continue; continue;
PlayerSAO *sao = player->getPlayerSAO(); if (player->inventory.checkModified())
if (sao && player->inventory.checkModified()) m_server->SendInventory(player, true);
m_server->SendInventory(sao, true);
} }
// Send outdated detached inventories // Send outdated detached inventories
@@ -1834,8 +1826,8 @@ void ServerEnvironment::getSelectedActiveObjects(
const std::optional<Pointabilities> &pointabilities) const std::optional<Pointabilities> &pointabilities)
{ {
std::vector<ServerActiveObject *> objs; std::vector<ServerActiveObject *> objs;
getObjectsInsideRadius(objs, shootline_on_map.start, getObjectsInsideRadius(objs, shootline_on_map.getMiddle(),
shootline_on_map.getLength() + 10.0f, nullptr); 0.5 * shootline_on_map.getLength() + 5 * BS, nullptr);
const v3f line_vector = shootline_on_map.getVector(); const v3f line_vector = shootline_on_map.getVector();
for (auto obj : objs) { for (auto obj : objs) {

View File

@@ -239,8 +239,6 @@ public:
float getSendRecommendedInterval() float getSendRecommendedInterval()
{ return m_recommended_send_interval; } { return m_recommended_send_interval; }
void kickAllPlayers(AccessDeniedCode reason,
const std::string &str_reason, bool reconnect);
// Save players // Save players
void saveLoadedPlayers(bool force = false); void saveLoadedPlayers(bool force = false);
void savePlayer(RemotePlayer *player); void savePlayer(RemotePlayer *player);

View File

@@ -26,8 +26,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "convert_json.h" #include "convert_json.h"
#include "util/serialize.h" #include "util/serialize.h"
#include "util/numeric.h" #include "util/numeric.h"
#include "util/hex.h"
#include "common/c_content.h"
#include <json/json.h> #include <json/json.h>
void ToolGroupCap::toJson(Json::Value &object) const void ToolGroupCap::toJson(Json::Value &object) const
{ {
object["maxlevel"] = maxlevel; object["maxlevel"] = maxlevel;
@@ -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<f32, video::SColor> 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<WearBarParams::BlendMode>(readU8(is));
if (blend >= BlendMode_END)
throw SerializationError("invalid blend mode");
u16 count = readU16(is);
if (count == 0)
throw SerializationError("no stops");
std::map<f32, video::SColor> 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<f32, video::SColor> 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> 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<WearBarParams::BlendMode>(blendInt);
else
return std::nullopt;
const Json::Value &color_stops_object = root["color_stops"];
std::map<f32, video::SColor> 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) u32 calculateResultWear(const u32 uses, const u16 initial_wear)
{ {
if (uses == 0) { if (uses == 0) {

View File

@@ -20,10 +20,15 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#pragma once #pragma once
#include "irrlichttypes.h" #include "irrlichttypes.h"
#include <string>
#include <iostream>
#include "itemgroup.h" #include "itemgroup.h"
#include "json-forwards.h" #include "json-forwards.h"
#include "common/c_types.h"
#include <json/json.h>
#include <SColor.h>
#include <string>
#include <iostream>
#include <optional>
struct ItemDefinition; struct ItemDefinition;
@@ -82,6 +87,37 @@ struct ToolCapabilities
void deserializeJson(std::istream &is); void deserializeJson(std::istream &is);
}; };
struct WearBarParams
{
std::map<f32, video::SColor> 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<f32, video::SColor> &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<WearBarParams> deserializeJson(std::istream &is);
video::SColor getWearBarColor(f32 durabilityPercent);
};
struct DigParams struct DigParams
{ {
bool diggable; bool diggable;

View File

@@ -574,6 +574,20 @@ bool parseColorString(const std::string &value, video::SColor &color, bool quiet
return success; 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) void str_replace(std::string &str, char from, char to)
{ {
std::replace(str.begin(), str.end(), from, to); std::replace(str.begin(), str.end(), from, to);

View File

@@ -88,6 +88,7 @@ char *mystrtok_r(char *s, const char *sep, char **lasts) noexcept;
u64 read_seed(const char *str); u64 read_seed(const char *str);
bool parseColorString(const std::string &value, video::SColor &color, bool quiet, bool parseColorString(const std::string &value, video::SColor &color, bool quiet,
unsigned char default_alpha = 0xff); unsigned char default_alpha = 0xff);
std::string encodeHexColorString(const video::SColor &color);
/** /**