Add wear bar color API (#13328)

---------

Co-authored-by: Muhammad Rifqi Priyo Susanto <muhammadrifqipriyosusanto@gmail.com>
Co-authored-by: Lars Müller <34514239+appgurueu@users.noreply.github.com>
Co-authored-by: grorp <gregor.parzefall@posteo.de>
This commit is contained in:
techno-sam 2024-02-02 12:21:00 -08:00 committed by GitHub
parent e10d8080ba
commit 176e674a51
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 598 additions and 26 deletions

@ -7273,6 +7273,8 @@ an itemstring, a table or `nil`.
the item breaks after `max_uses` times
* Valid `max_uses` range is [0,65536]
* Does nothing if item is not a tool or if `max_uses` is 0
* `get_wear_bar_params()`: returns the wear bar parameters of the item,
or nil if none are defined for this item type or in the stack's meta
* `add_item(item)`: returns leftover `ItemStack`
* Put some item or stack onto this stack
* `item_fits(item)`: returns `true` if item or stack can be fully added to
@ -7314,6 +7316,10 @@ Can be obtained via `item:get_meta()`.
* Overrides the item's tool capabilities
* A nil value will clear the override data and restore the original
behavior.
* `set_wear_bar_params([wear_bar_params])`
* Overrides the item's wear bar parameters (see "Wear Bar Color" section)
* A nil value will clear the override data and restore the original
behavior.
`MetaDataRef`
-------------
@ -8815,6 +8821,19 @@ Used by `minetest.register_node`, `minetest.register_craftitem`, and
-- fallback behavior.
},
-- Set wear bar color of the tool by setting color stops and blend mode
-- See "Wear Bar Color" section for further explanation including an example
wear_color = {
-- interpolation mode: 'constant' or 'linear'
-- (nil defaults to 'constant')
blend = "linear",
color_stops = {
[0.0] = "#ff0000",
[0.5] = "#ffff00",
[1.0] = "#00ff00",
}
},
node_placement_prediction = nil,
-- If nil and item is node, prediction is made automatically.
-- If nil and item is not a node, no prediction is made.
@ -9382,6 +9401,46 @@ Used by `minetest.register_node`.
}
```
Wear Bar Color
--------------
'Wear Bar' is a property of items that defines the coloring
of the bar that appears under damaged tools.
If it is absent, the default behavior of green-yellow-red is
used.
### Wear bar colors definition
#### Syntax
```lua
{
-- 'constant' or 'linear'
-- (nil defaults to 'constant')
blend = "linear",
color_stops = {
[0.0] = "#ff0000",
[0.5] = "slateblue",
[1.0] = {r=0, g=255, b=0, a=150},
}
}
```
#### Blend mode `blend`
* `linear`: blends smoothly between each defined color point.
* `constant`: each color starts at its defined point, and continues up to the next point
#### Color stops `color_stops`
Specified as `ColorSpec` color values assigned to `float` durability keys.
"Durability" is defined as `1 - (wear / 65535)`.
#### Shortcut usage
Wear bar color can also be specified as a single `ColorSpec` instead of a table.
Crafting recipes
----------------

@ -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 &params)
{
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 &params);
void clearWearBarParams();
private:
void updateToolCapabilities();
void updateWearBarParams();
bool toolcaps_overridden;
ToolCapabilities toolcaps_override;
std::optional<WearBarParams> wear_bar_override;
};

@ -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 &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)
{
@ -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)
{

@ -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 &params)
{
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);

@ -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) {

@ -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);
/**