mirror of
https://github.com/luanti-org/luanti.git
synced 2025-06-27 16:36:03 +00:00
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).
582 lines
16 KiB
C++
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();
|
|
}
|