Skip to content

Commit

Permalink
Add wear bar color API (#13328)
Browse files Browse the repository at this point in the history

---------

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>
  • Loading branch information
4 people committed Feb 2, 2024
1 parent e10d808 commit 176e674
Show file tree
Hide file tree
Showing 19 changed files with 598 additions and 26 deletions.
59 changes: 59 additions & 0 deletions doc/lua_api.md
Expand Up @@ -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
Expand Down Expand Up @@ -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`
-------------
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
----------------

Expand Down
137 changes: 121 additions & 16 deletions games/devtest/mods/basetools/init.lua
Expand Up @@ -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,
})
23 changes: 23 additions & 0 deletions games/devtest/mods/util_commands/init.lua
Expand Up @@ -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.",
Expand Down
23 changes: 16 additions & 7 deletions src/client/hud.cpp
Expand Up @@ -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;
Expand Down
9 changes: 9 additions & 0 deletions src/inventory.h
Expand Up @@ -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)
Expand Down
14 changes: 14 additions & 0 deletions src/itemdef.cpp
Expand Up @@ -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;
Expand All @@ -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();
}

Expand All @@ -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();
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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) {};
}

Expand Down
3 changes: 3 additions & 0 deletions src/itemdef.h
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down

0 comments on commit 176e674

Please sign in to comment.