1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-08-01 17:38:41 +00: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:
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

View file

@ -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;

View file

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

View file

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

View file

@ -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;

View file

@ -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, "");
}

View file

@ -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;
};

View file

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

View file

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

View file

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

View file

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

View file

@ -22,6 +22,8 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "lua_api/l_itemstackmeta.h"
#include "lua_api/l_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}
};

View file

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

View file

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

View file

@ -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;

View file

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

View file

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