mirror of
https://github.com/luanti-org/luanti.git
synced 2025-11-06 10:15:19 +01:00
Merge branch 'master' into doc-refactor-2
This commit is contained in:
@@ -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")
|
||||
|
||||
@@ -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 <command>")
|
||||
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 | <cmd>]"),
|
||||
description = core.gettext("Get help for commands"),
|
||||
params = core.gettext("[all | <cmd>] [-t]"),
|
||||
description = core.gettext("Get help for commands (-t: output in chat)"),
|
||||
func = function(param)
|
||||
return do_help_cmd(nil, param)
|
||||
end,
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -7182,6 +7182,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
|
||||
@@ -7220,6 +7222,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`
|
||||
|
||||
@@ -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 = 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,
|
||||
@@ -8703,6 +8711,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.
|
||||
@@ -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 converts one or more inputs to one output itemstack of arbitrary
|
||||
|
||||
@@ -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,
|
||||
})
|
||||
|
||||
@@ -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 = "<saturation>",
|
||||
description = "Set the saturation for current player.",
|
||||
|
||||
@@ -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<s32> progressrect2 = progressrect;
|
||||
progressrect2.LowerRightCorner.X = progressmid;
|
||||
|
||||
@@ -131,6 +131,15 @@ struct ItemStack
|
||||
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)
|
||||
// Returns true if the item is (was) a tool
|
||||
bool addWear(s32 amount, const IItemDefManager *itemdef)
|
||||
|
||||
@@ -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>();
|
||||
pointabilities->deSerialize(tmp_is);
|
||||
}
|
||||
|
||||
if (readU8(is)) {
|
||||
wear_bar_params = WearBarParams::deserialize(is);
|
||||
}
|
||||
} catch(SerializationError &e) {};
|
||||
}
|
||||
|
||||
|
||||
@@ -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<WearBarParams> wear_bar_params;
|
||||
|
||||
ItemGroupList groups;
|
||||
SoundSpec sound_place;
|
||||
SoundSpec sound_place_failed;
|
||||
|
||||
@@ -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 <algorithm>
|
||||
#include <optional>
|
||||
|
||||
#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, "");
|
||||
}
|
||||
|
||||
@@ -22,13 +22,17 @@ with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
#include "metadata.h"
|
||||
#include "tool.h"
|
||||
|
||||
#include <optional>
|
||||
|
||||
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<WearBarParams> &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<WearBarParams> wear_bar_override;
|
||||
};
|
||||
|
||||
42
src/map.cpp
42
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<v3s16, u8>::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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -71,6 +71,9 @@ std::unique_ptr<MapBlock> 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<MapBlock>(blockpos_map, m_gamedef);
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
|
||||
@@ -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; }
|
||||
|
||||
@@ -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 <SColor.h>
|
||||
#include <json/json.h>
|
||||
|
||||
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<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)
|
||||
{
|
||||
@@ -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 ¶ms)
|
||||
{
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<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
|
||||
// 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),
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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<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)
|
||||
{
|
||||
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}
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<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)
|
||||
{
|
||||
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());
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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: "<<dtime<<std::endl;
|
||||
@@ -357,14 +360,14 @@ void PlayerSAO::setPos(const v3f &pos)
|
||||
|
||||
// Send mapblock of target location
|
||||
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()->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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -1834,8 +1826,8 @@ void ServerEnvironment::getSelectedActiveObjects(
|
||||
const std::optional<Pointabilities> &pointabilities)
|
||||
{
|
||||
std::vector<ServerActiveObject *> 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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
124
src/tool.cpp
124
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 <json/json.h>
|
||||
|
||||
|
||||
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<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)
|
||||
{
|
||||
if (uses == 0) {
|
||||
|
||||
40
src/tool.h
40
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 <string>
|
||||
#include <iostream>
|
||||
#include "itemgroup.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;
|
||||
|
||||
@@ -82,6 +87,37 @@ struct ToolCapabilities
|
||||
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
|
||||
{
|
||||
bool diggable;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user