1
0
Fork 0
mirror of https://github.com/luanti-org/luanti.git synced 2025-06-27 16:36:03 +00:00
luanti/src/itemdef.cpp
SmallJoker 4ef93fe25f
Allow place_param2 = 0 node placement predictions (#13787)
The placement prediction value 0 was accidentally ignored
and made the clients fall back to automatic rotation based
on the node paramtype2 value.

This now changes the internal representation to properly
indicate the disabled state (e.g. 'nil' in Lua).
2023-09-13 13:57:57 +02:00

582 lines
16 KiB
C++

/*
Minetest
Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
Copyright (C) 2013 Kahrl <kahrl@gmx.net>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "itemdef.h"
#include "nodedef.h"
#include "tool.h"
#include "inventory.h"
#ifndef SERVER
#include "client/mapblock_mesh.h"
#include "client/mesh.h"
#include "client/wieldmesh.h"
#include "client/tile.h"
#include "client/client.h"
#endif
#include "log.h"
#include "settings.h"
#include "util/serialize.h"
#include "util/container.h"
#include "util/thread.h"
#include <map>
#include <set>
/*
ItemDefinition
*/
ItemDefinition::ItemDefinition()
{
resetInitial();
}
ItemDefinition::ItemDefinition(const ItemDefinition &def)
{
resetInitial();
*this = def;
}
ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
{
if(this == &def)
return *this;
reset();
type = def.type;
name = def.name;
description = def.description;
short_description = def.short_description;
inventory_image = def.inventory_image;
inventory_overlay = def.inventory_overlay;
wield_image = def.wield_image;
wield_overlay = def.wield_overlay;
wield_scale = def.wield_scale;
stack_max = def.stack_max;
usable = def.usable;
liquids_pointable = def.liquids_pointable;
if (def.tool_capabilities)
tool_capabilities = new ToolCapabilities(*def.tool_capabilities);
groups = def.groups;
node_placement_prediction = def.node_placement_prediction;
place_param2 = def.place_param2;
sound_place = def.sound_place;
sound_place_failed = def.sound_place_failed;
sound_use = def.sound_use;
sound_use_air = def.sound_use_air;
range = def.range;
palette_image = def.palette_image;
color = def.color;
return *this;
}
ItemDefinition::~ItemDefinition()
{
reset();
}
void ItemDefinition::resetInitial()
{
// Initialize pointers to NULL so reset() does not delete undefined pointers
tool_capabilities = NULL;
reset();
}
void ItemDefinition::reset()
{
type = ITEM_NONE;
name.clear();
description.clear();
short_description.clear();
inventory_image.clear();
inventory_overlay.clear();
wield_image.clear();
wield_overlay.clear();
palette_image.clear();
color = video::SColor(0xFFFFFFFF);
wield_scale = v3f(1.0, 1.0, 1.0);
stack_max = 99;
usable = false;
liquids_pointable = false;
delete tool_capabilities;
tool_capabilities = NULL;
groups.clear();
sound_place = SoundSpec();
sound_place_failed = SoundSpec();
sound_use = SoundSpec();
sound_use_air = SoundSpec();
range = -1;
node_placement_prediction.clear();
place_param2.reset();
}
void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
{
// protocol_version >= 37
u8 version = 6;
writeU8(os, version);
writeU8(os, type);
os << serializeString16(name);
os << serializeString16(description);
os << serializeString16(inventory_image);
os << serializeString16(wield_image);
writeV3F32(os, wield_scale);
writeS16(os, stack_max);
writeU8(os, usable);
writeU8(os, liquids_pointable);
std::string tool_capabilities_s;
if (tool_capabilities) {
std::ostringstream tmp_os(std::ios::binary);
tool_capabilities->serialize(tmp_os, protocol_version);
tool_capabilities_s = tmp_os.str();
}
os << serializeString16(tool_capabilities_s);
writeU16(os, groups.size());
for (const auto &group : groups) {
os << serializeString16(group.first);
writeS16(os, group.second);
}
os << serializeString16(node_placement_prediction);
// Version from ContentFeatures::serialize to keep in sync
sound_place.serializeSimple(os, protocol_version);
sound_place_failed.serializeSimple(os, protocol_version);
writeF32(os, range);
os << serializeString16(palette_image);
writeARGB8(os, color);
os << serializeString16(inventory_overlay);
os << serializeString16(wield_overlay);
os << serializeString16(short_description);
if (protocol_version <= 43) {
// Uncertainity whether 0 is the specified prediction or means disabled
if (place_param2)
os << *place_param2;
else
os << (u8)0;
}
sound_use.serializeSimple(os, protocol_version);
sound_use_air.serializeSimple(os, protocol_version);
os << (u8)place_param2.has_value(); // protocol_version >= 43
if (place_param2)
os << *place_param2;
}
void ItemDefinition::deSerialize(std::istream &is, u16 protocol_version)
{
// Reset everything
reset();
// Deserialize
int version = readU8(is);
if (version < 6)
throw SerializationError("unsupported ItemDefinition version");
type = (enum ItemType)readU8(is);
name = deSerializeString16(is);
description = deSerializeString16(is);
inventory_image = deSerializeString16(is);
wield_image = deSerializeString16(is);
wield_scale = readV3F32(is);
stack_max = readS16(is);
usable = readU8(is);
liquids_pointable = readU8(is);
std::string tool_capabilities_s = deSerializeString16(is);
if (!tool_capabilities_s.empty()) {
std::istringstream tmp_is(tool_capabilities_s, std::ios::binary);
tool_capabilities = new ToolCapabilities;
tool_capabilities->deSerialize(tmp_is);
}
groups.clear();
u32 groups_size = readU16(is);
for(u32 i=0; i<groups_size; i++){
std::string name = deSerializeString16(is);
int value = readS16(is);
groups[name] = value;
}
node_placement_prediction = deSerializeString16(is);
sound_place.deSerializeSimple(is, protocol_version);
sound_place_failed.deSerializeSimple(is, protocol_version);
range = readF32(is);
palette_image = deSerializeString16(is);
color = readARGB8(is);
inventory_overlay = deSerializeString16(is);
wield_overlay = deSerializeString16(is);
// If you add anything here, insert it inside the try-catch
// block to not need to increase the version.
try {
short_description = deSerializeString16(is);
if (protocol_version <= 43) {
place_param2 = readU8(is);
// assume disabled prediction
if (place_param2 == 0)
place_param2.reset();
}
sound_use.deSerializeSimple(is, protocol_version);
sound_use_air.deSerializeSimple(is, protocol_version);
if (is.eof())
throw SerializationError("");
if (readU8(is)) // protocol_version >= 43
place_param2 = readU8(is);
} catch(SerializationError &e) {};
}
/*
CItemDefManager
*/
// SUGG: Support chains of aliases?
class CItemDefManager: public IWritableItemDefManager
{
#ifndef SERVER
struct ClientCached
{
video::ITexture *inventory_texture;
ItemMesh wield_mesh;
Palette *palette;
ClientCached():
inventory_texture(NULL),
palette(NULL)
{}
~ClientCached() {
if (wield_mesh.mesh)
wield_mesh.mesh->drop();
}
DISABLE_CLASS_COPY(ClientCached);
};
#endif
public:
CItemDefManager()
{
#ifndef SERVER
m_main_thread = std::this_thread::get_id();
#endif
clear();
}
virtual ~CItemDefManager()
{
for (auto &item_definition : m_item_definitions) {
delete item_definition.second;
}
m_item_definitions.clear();
}
virtual const ItemDefinition& get(const std::string &name_) const
{
// Convert name according to possible alias
std::string name = getAlias(name_);
// Get the definition
auto i = m_item_definitions.find(name);
if (i == m_item_definitions.cend())
i = m_item_definitions.find("unknown");
assert(i != m_item_definitions.cend());
return *(i->second);
}
virtual const std::string &getAlias(const std::string &name) const
{
auto it = m_aliases.find(name);
if (it != m_aliases.cend())
return it->second;
return name;
}
virtual void getAll(std::set<std::string> &result) const
{
result.clear();
for (const auto &item_definition : m_item_definitions) {
result.insert(item_definition.first);
}
for (const auto &alias : m_aliases) {
result.insert(alias.first);
}
}
virtual bool isKnown(const std::string &name_) const
{
// Convert name according to possible alias
std::string name = getAlias(name_);
// Get the definition
return m_item_definitions.find(name) != m_item_definitions.cend();
}
#ifndef SERVER
public:
ClientCached* createClientCachedDirect(const ItemStack &item, Client *client) const
{
// This is not thread-safe
sanity_check(std::this_thread::get_id() == m_main_thread);
const ItemDefinition &def = item.getDefinition(this);
std::string inventory_image = item.getInventoryImage(this);
std::string inventory_overlay = item.getInventoryOverlay(this);
std::string cache_key = def.name;
if (!inventory_image.empty())
cache_key += "/" + inventory_image;
if (!inventory_overlay.empty())
cache_key += ":" + inventory_overlay;
infostream << "Lazily creating item texture and mesh for \""
<< cache_key << "\""<<std::endl;
// Skip if already in cache
auto it = m_clientcached.find(cache_key);
if (it != m_clientcached.end())
return it->second.get();
ITextureSource *tsrc = client->getTextureSource();
// Create new ClientCached
auto cc = std::make_unique<ClientCached>();
// Create an inventory texture
cc->inventory_texture = NULL;
if (!inventory_image.empty())
cc->inventory_texture = tsrc->getTexture(inventory_image);
getItemMesh(client, item, &(cc->wield_mesh));
cc->palette = tsrc->getPalette(def.palette_image);
// Put in cache
ClientCached *ptr = cc.get();
m_clientcached[cache_key] = std::move(cc);
return ptr;
}
// Get item inventory texture
virtual video::ITexture* getInventoryTexture(const ItemStack &item,
Client *client) const
{
ClientCached *cc = createClientCachedDirect(item, client);
if (!cc)
return nullptr;
return cc->inventory_texture;
}
// Get item wield mesh
virtual ItemMesh* getWieldMesh(const ItemStack &item, Client *client) const
{
ClientCached *cc = createClientCachedDirect(item, client);
if (!cc)
return nullptr;
return &(cc->wield_mesh);
}
// Get item palette
virtual Palette* getPalette(const ItemStack &item, Client *client) const
{
ClientCached *cc = createClientCachedDirect(item, client);
if (!cc)
return nullptr;
return cc->palette;
}
virtual video::SColor getItemstackColor(const ItemStack &stack,
Client *client) const
{
// Look for direct color definition
const std::string &colorstring = stack.metadata.getString("color", 0);
video::SColor directcolor;
if (!colorstring.empty() && parseColorString(colorstring, directcolor, true))
return directcolor;
// See if there is a palette
Palette *palette = getPalette(stack, client);
const std::string &index = stack.metadata.getString("palette_index", 0);
if (palette && !index.empty())
return (*palette)[mystoi(index, 0, 255)];
// Fallback color
return get(stack.name).color;
}
#endif
void applyTextureOverrides(const std::vector<TextureOverride> &overrides)
{
infostream << "ItemDefManager::applyTextureOverrides(): Applying "
"overrides to textures" << std::endl;
for (const TextureOverride& texture_override : overrides) {
if (m_item_definitions.find(texture_override.id) == m_item_definitions.end()) {
continue; // Ignore unknown item
}
ItemDefinition* itemdef = m_item_definitions[texture_override.id];
if (texture_override.hasTarget(OverrideTarget::INVENTORY))
itemdef->inventory_image = texture_override.texture;
if (texture_override.hasTarget(OverrideTarget::WIELD))
itemdef->wield_image = texture_override.texture;
}
}
void clear()
{
for (auto &i : m_item_definitions)
{
delete i.second;
}
m_item_definitions.clear();
m_aliases.clear();
// Add the four builtin items:
// "" is the hand
// "unknown" is returned whenever an undefined item
// is accessed (is also the unknown node)
// "air" is the air node
// "ignore" is the ignore node
ItemDefinition* hand_def = new ItemDefinition;
hand_def->name.clear();
hand_def->wield_image = "wieldhand.png";
hand_def->tool_capabilities = new ToolCapabilities;
m_item_definitions.insert(std::make_pair("", hand_def));
ItemDefinition* unknown_def = new ItemDefinition;
unknown_def->type = ITEM_NODE;
unknown_def->name = "unknown";
m_item_definitions.insert(std::make_pair("unknown", unknown_def));
ItemDefinition* air_def = new ItemDefinition;
air_def->type = ITEM_NODE;
air_def->name = "air";
m_item_definitions.insert(std::make_pair("air", air_def));
ItemDefinition* ignore_def = new ItemDefinition;
ignore_def->type = ITEM_NODE;
ignore_def->name = "ignore";
m_item_definitions.insert(std::make_pair("ignore", ignore_def));
}
virtual void registerItem(const ItemDefinition &def)
{
TRACESTREAM(<< "ItemDefManager: registering " << def.name << std::endl);
// Ensure that the "" item (the hand) always has ToolCapabilities
if (def.name.empty())
FATAL_ERROR_IF(!def.tool_capabilities, "Hand does not have ToolCapabilities");
if(m_item_definitions.count(def.name) == 0)
m_item_definitions[def.name] = new ItemDefinition(def);
else
*(m_item_definitions[def.name]) = def;
// Remove conflicting alias if it exists
bool alias_removed = (m_aliases.erase(def.name) != 0);
if(alias_removed)
infostream<<"ItemDefManager: erased alias "<<def.name
<<" because item was defined"<<std::endl;
}
virtual void unregisterItem(const std::string &name)
{
verbosestream<<"ItemDefManager: unregistering \""<<name<<"\""<<std::endl;
delete m_item_definitions[name];
m_item_definitions.erase(name);
}
virtual void registerAlias(const std::string &name,
const std::string &convert_to)
{
if (m_item_definitions.find(name) == m_item_definitions.end()) {
TRACESTREAM(<< "ItemDefManager: setting alias " << name
<< " -> " << convert_to << std::endl);
m_aliases[name] = convert_to;
}
}
void serialize(std::ostream &os, u16 protocol_version)
{
writeU8(os, 0); // version
u16 count = m_item_definitions.size();
writeU16(os, count);
for (const auto &it : m_item_definitions) {
ItemDefinition *def = it.second;
// Serialize ItemDefinition and write wrapped in a string
std::ostringstream tmp_os(std::ios::binary);
def->serialize(tmp_os, protocol_version);
os << serializeString16(tmp_os.str());
}
writeU16(os, m_aliases.size());
for (const auto &it : m_aliases) {
os << serializeString16(it.first);
os << serializeString16(it.second);
}
}
void deSerialize(std::istream &is, u16 protocol_version)
{
// Clear everything
clear();
if(readU8(is) != 0)
throw SerializationError("unsupported ItemDefManager version");
u16 count = readU16(is);
for(u16 i=0; i<count; i++)
{
// Deserialize a string and grab an ItemDefinition from it
std::istringstream tmp_is(deSerializeString16(is), std::ios::binary);
ItemDefinition def;
def.deSerialize(tmp_is, protocol_version);
// Register
registerItem(def);
}
u16 num_aliases = readU16(is);
for(u16 i=0; i<num_aliases; i++)
{
std::string name = deSerializeString16(is);
std::string convert_to = deSerializeString16(is);
registerAlias(name, convert_to);
}
}
private:
// Key is name
std::map<std::string, ItemDefinition*> m_item_definitions;
// Aliases
StringMap m_aliases;
#ifndef SERVER
// The id of the thread that is allowed to use irrlicht directly
std::thread::id m_main_thread;
// Cached textures and meshes
mutable std::unordered_map<std::string, std::unique_ptr<ClientCached>> m_clientcached;
#endif
};
IWritableItemDefManager* createItemDefManager()
{
return new CItemDefManager();
}