mirror of
https://github.com/minetest/minetest.git
synced 2024-12-22 22:22:23 +01:00
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:
parent
e10d8080ba
commit
176e674a51
@ -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 ¶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;
|
||||
};
|
||||
|
@ -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);
|
||||
|
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);
|
||||
|
||||
|
||||
/**
|
||||
|
Loading…
Reference in New Issue
Block a user