Hardware coloring for itemstacks

Adds the possibility to colorize item stacks based on their metadata.

In the item/node definition you can specify palette (an image file)
and color (fallback color if the item has no palette or metadata).
Then you can add palette_index to the metadata.

Dropped itemstacks with different colors do not merge.
This commit is contained in:
Dániel Juhász 2017-03-10 18:25:58 +01:00 committed by Auke Kok
parent d4e9dd4643
commit 58d83a7bb2
23 changed files with 308 additions and 139 deletions

@ -53,6 +53,8 @@ core.register_entity(":__builtin:item", {
if itemtable then if itemtable then
itemname = stack:to_table().name itemname = stack:to_table().name
end end
-- Backwards compatibility: old clients use the texture
-- to get the type of the item
local item_texture = nil local item_texture = nil
local item_type = "" local item_type = ""
if core.registered_items[itemname] then if core.registered_items[itemname] then
@ -66,6 +68,7 @@ core.register_entity(":__builtin:item", {
visual_size = {x = s, y = s}, visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c}, collisionbox = {-c, -c, -c, c, c, c},
automatic_rotate = math.pi * 0.5, automatic_rotate = math.pi * 0.5,
wield_item = itemstring,
} }
self.object:set_properties(prop) self.object:set_properties(prop)
end, end,
@ -101,31 +104,39 @@ core.register_entity(":__builtin:item", {
self:set_item(self.itemstring) self:set_item(self.itemstring)
end, end,
-- moves items from this stack to an other stack
try_merge_with = function(self, own_stack, object, obj) try_merge_with = function(self, own_stack, object, obj)
-- other item's stack
local stack = ItemStack(obj.itemstring) local stack = ItemStack(obj.itemstring)
if own_stack:get_name() == stack:get_name() and stack:get_free_space() > 0 then -- only merge if items are the same
if own_stack:get_name() == stack:get_name() and
own_stack:get_meta() == stack:get_meta() and
own_stack:get_wear() == stack:get_wear() and
stack:get_free_space() > 0 then
local overflow = false local overflow = false
local count = stack:get_count() + own_stack:get_count() local count = stack:get_count() + own_stack:get_count()
local max_count = stack:get_stack_max() local max_count = stack:get_stack_max()
if count > max_count then if count > max_count then
overflow = true overflow = true
stack:set_count(max_count)
count = count - max_count count = count - max_count
own_stack:set_count(count)
else else
self.itemstring = '' self.itemstring = ''
stack:set_count(count)
end end
local pos = object:getpos() local pos = object:getpos()
pos.y = pos.y + (count - stack:get_count()) / max_count * 0.15 pos.y = pos.y + (count - stack:get_count()) / max_count * 0.15
object:moveto(pos, false) object:moveto(pos, false)
local s, c local s, c
local max_count = stack:get_stack_max()
local name = stack:get_name()
if not overflow then if not overflow then
obj.itemstring = name .. " " .. count obj.itemstring = stack:to_string()
s = 0.2 + 0.1 * (count / max_count) s = 0.2 + 0.1 * (count / max_count)
c = s c = s
object:set_properties({ object:set_properties({
visual_size = {x = s, y = s}, visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c} collisionbox = {-c, -c, -c, c, c, c},
wield_item = obj.itemstring
}) })
self.object:remove() self.object:remove()
-- merging succeeded -- merging succeeded
@ -133,18 +144,20 @@ core.register_entity(":__builtin:item", {
else else
s = 0.4 s = 0.4
c = 0.3 c = 0.3
obj.itemstring = stack:to_string()
object:set_properties({ object:set_properties({
visual_size = {x = s, y = s}, visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c} collisionbox = {-c, -c, -c, c, c, c},
wield_item = obj.itemstring
}) })
obj.itemstring = name .. " " .. max_count
s = 0.2 + 0.1 * (count / max_count) s = 0.2 + 0.1 * (count / max_count)
c = s c = s
self.itemstring = own_stack:to_string()
self.object:set_properties({ self.object:set_properties({
visual_size = {x = s, y = s}, visual_size = {x = s, y = s},
collisionbox = {-c, -c, -c, c, c, c} collisionbox = {-c, -c, -c, c, c, c},
wield_item = self.itemstring
}) })
self.itemstring = name .. " " .. count
end end
end end
-- merging didn't succeed -- merging didn't succeed

@ -1480,6 +1480,9 @@ Item metadata only contains a key-value store.
Some of the values in the key-value store are handled specially: Some of the values in the key-value store are handled specially:
* `description`: Set the itemstack's description. Defaults to idef.description * `description`: Set the itemstack's description. Defaults to idef.description
* `color`: A `ColorString`, which sets the stack's color.
* `palette_index`: If the item has a palette, this is used to get the
current color from the palette.
Example stuff: Example stuff:
@ -2855,6 +2858,8 @@ See `StorageRef`, `NodeMetaRef` and `ItemStackMetaRef`.
* Any non-table value will clear the metadata * Any non-table value will clear the metadata
* See "Node Metadata" for an example * See "Node Metadata" for an example
* returns `true` on success * returns `true` on success
* `equals(other)`
* returns `true` if this metadata has the same key-value pairs as `other`
### `NodeMetaRef` ### `NodeMetaRef`
Node metadata: reference extra data and functionality stored in a node. Node metadata: reference extra data and functionality stored in a node.
@ -3735,6 +3740,19 @@ Definition tables
{hard = 1, metal = 1, spikes = 1} {hard = 1, metal = 1, spikes = 1}
inventory_image = "default_tool_steelaxe.png", inventory_image = "default_tool_steelaxe.png",
wield_image = "", wield_image = "",
palette = "",
--[[
^ An image file containing the palette of a node.
^ You can set the currently used color as the
^ "palette_index" field of the item stack metadata.
^ The palette is always stretched to fit indices
^ between 0 and 255, to ensure compatibility with
^ "colorfacedir" and "colorwallmounted" nodes.
]]
color = "0xFFFFFFFF",
--[[
^ The color of the item. The palette overrides this.
]]
wield_scale = {x = 1, y = 1, z = 1}, wield_scale = {x = 1, y = 1, z = 1},
stack_max = 99, stack_max = 99,
range = 4.0, range = 4.0,

@ -501,7 +501,8 @@ void Camera::setDigging(s32 button)
void Camera::wield(const ItemStack &item) void Camera::wield(const ItemStack &item)
{ {
if (item.name != m_wield_item_next.name) { if (item.name != m_wield_item_next.name ||
item.metadata != m_wield_item_next.metadata) {
m_wield_item_next = item; m_wield_item_next = item;
if (m_wield_change_timer > 0) if (m_wield_change_timer > 0)
m_wield_change_timer = -m_wield_change_timer; m_wield_change_timer = -m_wield_change_timer;

@ -341,6 +341,8 @@ public:
*/ */
video::ITexture* getTextureForMesh(const std::string &name, u32 *id); video::ITexture* getTextureForMesh(const std::string &name, u32 *id);
virtual Palette* getPalette(const std::string &name);
// Returns a pointer to the irrlicht device // Returns a pointer to the irrlicht device
virtual IrrlichtDevice* getDevice() virtual IrrlichtDevice* getDevice()
{ {
@ -377,8 +379,6 @@ public:
video::ITexture* generateTextureFromMesh( video::ITexture* generateTextureFromMesh(
const TextureFromMeshParams &params); const TextureFromMeshParams &params);
video::IImage* generateImage(const std::string &name);
video::ITexture* getNormalTexture(const std::string &name); video::ITexture* getNormalTexture(const std::string &name);
video::SColor getTextureAverageColor(const std::string &name); video::SColor getTextureAverageColor(const std::string &name);
video::ITexture *getShaderFlagsTexture(bool normamap_present); video::ITexture *getShaderFlagsTexture(bool normamap_present);
@ -401,6 +401,13 @@ private:
// if baseimg is NULL, it is created. Otherwise stuff is made on it. // if baseimg is NULL, it is created. Otherwise stuff is made on it.
bool generateImagePart(std::string part_of_name, video::IImage *& baseimg); bool generateImagePart(std::string part_of_name, video::IImage *& baseimg);
/*! Generates an image from a full string like
* "stone.png^mineral_coal.png^[crack:1:0".
* Shall be called from the main thread.
* The returned Image should be dropped.
*/
video::IImage* generateImage(const std::string &name);
// Thread-safe cache of what source images are known (true = known) // Thread-safe cache of what source images are known (true = known)
MutexedMap<std::string, bool> m_source_image_existence; MutexedMap<std::string, bool> m_source_image_existence;
@ -419,6 +426,9 @@ private:
// but can't be deleted because the ITexture* might still be used // but can't be deleted because the ITexture* might still be used
std::vector<video::ITexture*> m_texture_trash; std::vector<video::ITexture*> m_texture_trash;
// Maps image file names to loaded palettes.
UNORDERED_MAP<std::string, Palette> m_palettes;
// Cached settings needed for making textures from meshes // Cached settings needed for making textures from meshes
bool m_setting_trilinear_filter; bool m_setting_trilinear_filter;
bool m_setting_bilinear_filter; bool m_setting_bilinear_filter;
@ -682,6 +692,61 @@ video::ITexture* TextureSource::getTextureForMesh(const std::string &name, u32 *
return getTexture(name + "^[applyfiltersformesh", id); return getTexture(name + "^[applyfiltersformesh", id);
} }
Palette* TextureSource::getPalette(const std::string &name)
{
// Only the main thread may load images
sanity_check(thr_is_current_thread(m_main_thread));
if (name == "")
return NULL;
UNORDERED_MAP<std::string, Palette>::iterator it = m_palettes.find(name);
if (it == m_palettes.end()) {
// Create palette
video::IImage *img = generateImage(name);
if (!img) {
warningstream << "TextureSource::getPalette(): palette \"" << name
<< "\" could not be loaded." << std::endl;
return NULL;
}
Palette new_palette;
u32 w = img->getDimension().Width;
u32 h = img->getDimension().Height;
// Real area of the image
u32 area = h * w;
if (area == 0)
return NULL;
if (area > 256) {
warningstream << "TextureSource::getPalette(): the specified"
<< " palette image \"" << name << "\" is larger than 256"
<< " pixels, using the first 256." << std::endl;
area = 256;
} else if (256 % area != 0)
warningstream << "TextureSource::getPalette(): the "
<< "specified palette image \"" << name << "\" does not "
<< "contain power of two pixels." << std::endl;
// We stretch the palette so it will fit 256 values
// This many param2 values will have the same color
u32 step = 256 / area;
// For each pixel in the image
for (u32 i = 0; i < area; i++) {
video::SColor c = img->getPixel(i % w, i / w);
// Fill in palette with 'step' colors
for (u32 j = 0; j < step; j++)
new_palette.push_back(c);
}
img->drop();
// Fill in remaining elements
while (new_palette.size() < 256)
new_palette.push_back(video::SColor(0xFFFFFFFF));
m_palettes[name] = new_palette;
it = m_palettes.find(name);
}
if (it != m_palettes.end())
return &((*it).second);
return NULL;
}
void TextureSource::processQueue() void TextureSource::processQueue()
{ {
/* /*

@ -33,6 +33,8 @@ class IGameDef;
struct TileSpec; struct TileSpec;
struct TileDef; struct TileDef;
typedef std::vector<video::SColor> Palette;
/* /*
tile.{h,cpp}: Texture handling stuff. tile.{h,cpp}: Texture handling stuff.
*/ */
@ -106,14 +108,15 @@ public:
const std::string &name, u32 *id = NULL)=0; const std::string &name, u32 *id = NULL)=0;
virtual video::ITexture* getTextureForMesh( virtual video::ITexture* getTextureForMesh(
const std::string &name, u32 *id = NULL) = 0; const std::string &name, u32 *id = NULL) = 0;
/*!
* Returns a palette from the given texture name.
* The pointer is valid until the texture source is
* destructed.
* Should be called from the main thread.
*/
virtual Palette* getPalette(const std::string &name) = 0;
virtual IrrlichtDevice* getDevice()=0; virtual IrrlichtDevice* getDevice()=0;
virtual bool isKnownSourceImage(const std::string &name)=0; virtual bool isKnownSourceImage(const std::string &name)=0;
/*! Generates an image from a full string like
* "stone.png^mineral_coal.png^[crack:1:0".
* Shall be called from the main thread.
* The returned Image should be dropped.
*/
virtual video::IImage* generateImage(const std::string &name)=0;
virtual video::ITexture* generateTextureFromMesh( virtual video::ITexture* generateTextureFromMesh(
const TextureFromMeshParams &params)=0; const TextureFromMeshParams &params)=0;
virtual video::ITexture* getNormalTexture(const std::string &name)=0; virtual video::ITexture* getNormalTexture(const std::string &name)=0;

@ -933,23 +933,30 @@ void GenericCAO::addToScene(scene::ISceneManager *smgr,
errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl; errorstream<<"GenericCAO::addToScene(): Could not load mesh "<<m_prop.mesh<<std::endl;
} }
else if(m_prop.visual == "wielditem") { else if(m_prop.visual == "wielditem") {
infostream<<"GenericCAO::addToScene(): wielditem"<<std::endl; ItemStack item;
infostream<<"textures: "<<m_prop.textures.size()<<std::endl; infostream << "GenericCAO::addToScene(): wielditem" << std::endl;
if(m_prop.textures.size() >= 1){ if (m_prop.wield_item == "") {
infostream<<"textures[0]: "<<m_prop.textures[0]<<std::endl; // Old format, only textures are specified.
IItemDefManager *idef = m_client->idef(); infostream << "textures: " << m_prop.textures.size() << std::endl;
ItemStack item(m_prop.textures[0], 1, 0, idef); if (m_prop.textures.size() >= 1) {
infostream << "textures[0]: " << m_prop.textures[0]
m_wield_meshnode = new WieldMeshSceneNode( << std::endl;
smgr->getRootSceneNode(), smgr, -1); IItemDefManager *idef = m_client->idef();
m_wield_meshnode->setItem(item, m_client); item = ItemStack(m_prop.textures[0], 1, 0, idef);
}
m_wield_meshnode->setScale(v3f(m_prop.visual_size.X/2, } else {
m_prop.visual_size.Y/2, infostream << "serialized form: " << m_prop.wield_item << std::endl;
m_prop.visual_size.X/2)); item.deSerialize(m_prop.wield_item, m_client->idef());
u8 li = m_last_light;
m_wield_meshnode->setColor(video::SColor(255,li,li,li));
} }
m_wield_meshnode = new WieldMeshSceneNode(smgr->getRootSceneNode(),
smgr, -1);
m_wield_meshnode->setItem(item, m_client);
m_wield_meshnode->setScale(
v3f(m_prop.visual_size.X / 2, m_prop.visual_size.Y / 2,
m_prop.visual_size.X / 2));
u8 li = m_last_light;
m_wield_meshnode->setColor(video::SColor(255, li, li, li));
} else { } else {
infostream<<"GenericCAO::addToScene(): \""<<m_prop.visual infostream<<"GenericCAO::addToScene(): \""<<m_prop.visual
<<"\" not supported"<<std::endl; <<"\" not supported"<<std::endl;

@ -32,6 +32,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "fontengine.h" #include "fontengine.h"
#include "guiscalingfilter.h" #include "guiscalingfilter.h"
#include "mesh.h" #include "mesh.h"
#include "wieldmesh.h"
#include <IGUIStaticText.h> #include <IGUIStaticText.h>
#ifdef HAVE_TOUCHSCREENGUI #ifdef HAVE_TOUCHSCREENGUI
@ -642,9 +643,10 @@ void drawItemStack(video::IVideoDriver *driver,
} }
const ItemDefinition &def = item.getDefinition(client->idef()); const ItemDefinition &def = item.getDefinition(client->idef());
scene::IMesh* mesh = client->idef()->getWieldMesh(def.name, client); ItemMesh *imesh = client->idef()->getWieldMesh(def.name, client);
if (mesh) { if (imesh && imesh->mesh) {
scene::IMesh *mesh = imesh->mesh;
driver->clearZBuffer(); driver->clearZBuffer();
s32 delta = 0; s32 delta = 0;
if (rotation_kind < IT_ROT_NONE) { if (rotation_kind < IT_ROT_NONE) {
@ -667,16 +669,28 @@ void drawItemStack(video::IVideoDriver *driver,
matrix.makeIdentity(); matrix.makeIdentity();
if (enable_animations) { if (enable_animations) {
float timer_f = (float)delta / 5000.0; float timer_f = (float) delta / 5000.0;
matrix.setRotationDegrees(core::vector3df(0, 360 * timer_f, 0)); matrix.setRotationDegrees(core::vector3df(0, 360 * timer_f, 0));
} }
driver->setTransform(video::ETS_WORLD, matrix); driver->setTransform(video::ETS_WORLD, matrix);
driver->setViewPort(rect); driver->setViewPort(rect);
video::SColor basecolor =
client->idef()->getItemstackColor(item, client);
u32 mc = mesh->getMeshBufferCount(); u32 mc = mesh->getMeshBufferCount();
for (u32 j = 0; j < mc; ++j) { for (u32 j = 0; j < mc; ++j) {
scene::IMeshBuffer *buf = mesh->getMeshBuffer(j); scene::IMeshBuffer *buf = mesh->getMeshBuffer(j);
// we can modify vertices relatively fast,
// because these meshes are not buffered.
assert(buf->getHardwareMappingHint_Vertex() == scene::EHM_NEVER);
video::SColor c = basecolor;
if (imesh->buffer_colors.size() > j) {
std::pair<bool, video::SColor> p = imesh->buffer_colors[j];
c = p.first ? p.second : basecolor;
}
colorizeMeshBuffer(buf, &c);
video::SMaterial &material = buf->getMaterial(); video::SMaterial &material = buf->getMaterial();
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF; material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL_REF;
material.Lighting = false; material.Lighting = false;

@ -82,6 +82,8 @@ ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
sound_place = def.sound_place; sound_place = def.sound_place;
sound_place_failed = def.sound_place_failed; sound_place_failed = def.sound_place_failed;
range = def.range; range = def.range;
palette_image = def.palette_image;
color = def.color;
return *this; return *this;
} }
@ -104,6 +106,8 @@ void ItemDefinition::reset()
description = ""; description = "";
inventory_image = ""; inventory_image = "";
wield_image = ""; wield_image = "";
palette_image = "";
color = video::SColor(0xFFFFFFFF);
wield_scale = v3f(1.0, 1.0, 1.0); wield_scale = v3f(1.0, 1.0, 1.0);
stack_max = 99; stack_max = 99;
usable = false; usable = false;
@ -153,6 +157,8 @@ void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
writeF1000(os, range); writeF1000(os, range);
os << serializeString(sound_place_failed.name); os << serializeString(sound_place_failed.name);
writeF1000(os, sound_place_failed.gain); writeF1000(os, sound_place_failed.gain);
os << serializeString(palette_image);
writeU32(os, color.color);
} }
void ItemDefinition::deSerialize(std::istream &is) void ItemDefinition::deSerialize(std::istream &is)
@ -209,6 +215,8 @@ void ItemDefinition::deSerialize(std::istream &is)
try { try {
sound_place_failed.name = deSerializeString(is); sound_place_failed.name = deSerializeString(is);
sound_place_failed.gain = readF1000(is); sound_place_failed.gain = readF1000(is);
palette_image = deSerializeString(is);
color.set(readU32(is));
} catch(SerializationError &e) {}; } catch(SerializationError &e) {};
} }
@ -224,11 +232,13 @@ class CItemDefManager: public IWritableItemDefManager
struct ClientCached struct ClientCached
{ {
video::ITexture *inventory_texture; video::ITexture *inventory_texture;
scene::IMesh *wield_mesh; ItemMesh wield_mesh;
Palette *palette;
ClientCached(): ClientCached():
inventory_texture(NULL), inventory_texture(NULL),
wield_mesh(NULL) wield_mesh(),
palette(NULL)
{} {}
}; };
#endif #endif
@ -250,8 +260,8 @@ public:
i = values.begin(); i != values.end(); ++i) i = values.begin(); i != values.end(); ++i)
{ {
ClientCached *cc = *i; ClientCached *cc = *i;
if (cc->wield_mesh) if (cc->wield_mesh.mesh)
cc->wield_mesh->drop(); cc->wield_mesh.mesh->drop();
delete cc; delete cc;
} }
@ -335,8 +345,9 @@ public:
ItemStack item = ItemStack(); ItemStack item = ItemStack();
item.name = def.name; item.name = def.name;
scene::IMesh *mesh = getItemMesh(client, item); getItemMesh(client, item, &(cc->wield_mesh));
cc->wield_mesh = mesh;
cc->palette = tsrc->getPalette(def.palette_image);
// Put in cache // Put in cache
m_clientcached.set(name, cc); m_clientcached.set(name, cc);
@ -390,13 +401,41 @@ public:
return cc->inventory_texture; return cc->inventory_texture;
} }
// Get item wield mesh // Get item wield mesh
virtual scene::IMesh* getWieldMesh(const std::string &name, virtual ItemMesh* getWieldMesh(const std::string &name,
Client *client) const Client *client) const
{ {
ClientCached *cc = getClientCached(name, client); ClientCached *cc = getClientCached(name, client);
if(!cc) if(!cc)
return NULL; return NULL;
return cc->wield_mesh; return &(cc->wield_mesh);
}
// Get item palette
virtual Palette* getPalette(const std::string &name,
Client *client) const
{
ClientCached *cc = getClientCached(name, client);
if(!cc)
return NULL;
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 != "")
&& parseColorString(colorstring, directcolor, true))
return directcolor;
// See if there is a palette
Palette *palette = getPalette(stack.name, client);
const std::string &index = stack.metadata.getString("palette_index", 0);
if ((palette != NULL) && (index != ""))
return (*palette)[mystoi(index, 0, 255)];
// Fallback color
return get(stack.name).color;
} }
#endif #endif
void clear() void clear()

@ -30,6 +30,11 @@ with this program; if not, write to the Free Software Foundation, Inc.,
class IGameDef; class IGameDef;
class Client; class Client;
struct ToolCapabilities; struct ToolCapabilities;
#ifndef SERVER
#include "client/tile.h"
struct ItemMesh;
struct ItemStack;
#endif
/* /*
Base item definition Base item definition
@ -57,6 +62,8 @@ struct ItemDefinition
*/ */
std::string inventory_image; // Optional for nodes, mandatory for tools/craftitems std::string inventory_image; // Optional for nodes, mandatory for tools/craftitems
std::string wield_image; // If empty, inventory_image or mesh (only nodes) is used std::string wield_image; // If empty, inventory_image or mesh (only nodes) is used
std::string palette_image; // If specified, the item will be colorized based on this
video::SColor color; // The fallback color of the node.
v3f wield_scale; v3f wield_scale;
/* /*
@ -110,8 +117,15 @@ public:
virtual video::ITexture* getInventoryTexture(const std::string &name, virtual video::ITexture* getInventoryTexture(const std::string &name,
Client *client) const=0; Client *client) const=0;
// Get item wield mesh // Get item wield mesh
virtual scene::IMesh* getWieldMesh(const std::string &name, virtual ItemMesh* getWieldMesh(const std::string &name,
Client *client) const=0; Client *client) const=0;
// Get item palette
virtual Palette* getPalette(const std::string &name,
Client *client) const = 0;
// Returns the base color of an item stack: the color of all
// tiles that do not define their own color.
virtual video::SColor getItemstackColor(const ItemStack &stack,
Client *client) const = 0;
#endif #endif
virtual void serialize(std::ostream &os, u16 protocol_version)=0; virtual void serialize(std::ostream &os, u16 protocol_version)=0;
@ -136,7 +150,7 @@ public:
virtual video::ITexture* getInventoryTexture(const std::string &name, virtual video::ITexture* getInventoryTexture(const std::string &name,
Client *client) const=0; Client *client) const=0;
// Get item wield mesh // Get item wield mesh
virtual scene::IMesh* getWieldMesh(const std::string &name, virtual ItemMesh* getWieldMesh(const std::string &name,
Client *client) const=0; Client *client) const=0;
#endif #endif

@ -786,6 +786,8 @@ void ContentFeatures::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc
tiledef_special[j].backface_culling, material_type); tiledef_special[j].backface_culling, material_type);
} }
palette = tsrc->getPalette(palette_name);
if ((drawtype == NDT_MESH) && (mesh != "")) { if ((drawtype == NDT_MESH) && (mesh != "")) {
// Meshnode drawtype // Meshnode drawtype
// Read the mesh and apply scale // Read the mesh and apply scale
@ -859,9 +861,6 @@ public:
virtual void removeNode(const std::string &name); virtual void removeNode(const std::string &name);
virtual void updateAliases(IItemDefManager *idef); virtual void updateAliases(IItemDefManager *idef);
virtual void applyTextureOverrides(const std::string &override_filepath); virtual void applyTextureOverrides(const std::string &override_filepath);
//! Returns a palette or NULL if not found. Only on client.
std::vector<video::SColor> *getPalette(const ContentFeatures &f,
const IGameDef *gamedef);
virtual void updateTextures(IGameDef *gamedef, virtual void updateTextures(IGameDef *gamedef,
void (*progress_cbk)(void *progress_args, u32 progress, u32 max_progress), void (*progress_cbk)(void *progress_args, u32 progress, u32 max_progress),
void *progress_cbk_args); void *progress_cbk_args);
@ -910,9 +909,6 @@ private:
// Next possibly free id // Next possibly free id
content_t m_next_id; content_t m_next_id;
// Maps image file names to loaded palettes.
UNORDERED_MAP<std::string, std::vector<video::SColor> > m_palettes;
// NodeResolvers to callback once node registration has ended // NodeResolvers to callback once node registration has ended
std::vector<NodeResolver *> m_pending_resolve_callbacks; std::vector<NodeResolver *> m_pending_resolve_callbacks;
@ -1401,78 +1397,6 @@ void CNodeDefManager::applyTextureOverrides(const std::string &override_filepath
} }
} }
std::vector<video::SColor> *CNodeDefManager::getPalette(
const ContentFeatures &f, const IGameDef *gamedef)
{
#ifndef SERVER
// This works because colors always use the most significant bits
// of param2. If you add a new colored type which uses param2
// in a more advanced way, you should change this code, too.
u32 palette_pixels = 0;
switch (f.param_type_2) {
case CPT2_COLOR:
palette_pixels = 256;
break;
case CPT2_COLORED_FACEDIR:
palette_pixels = 8;
break;
case CPT2_COLORED_WALLMOUNTED:
palette_pixels = 32;
break;
default:
return NULL;
}
// This many param2 values will have the same color
u32 step = 256 / palette_pixels;
const std::string &name = f.palette_name;
if (name == "")
return NULL;
Client *client = (Client *) gamedef;
ITextureSource *tsrc = client->tsrc();
UNORDERED_MAP<std::string, std::vector<video::SColor> >::iterator it =
m_palettes.find(name);
if (it == m_palettes.end()) {
// Create palette
if (!tsrc->isKnownSourceImage(name)) {
warningstream << "CNodeDefManager::getPalette(): palette \"" << name
<< "\" could not be loaded." << std::endl;
return NULL;
}
video::IImage *img = tsrc->generateImage(name);
std::vector<video::SColor> new_palette;
u32 w = img->getDimension().Width;
u32 h = img->getDimension().Height;
// Real area of the image
u32 area = h * w;
if (area != palette_pixels)
warningstream << "CNodeDefManager::getPalette(): the "
<< "specified palette image \"" << name << "\" does not "
<< "contain exactly " << palette_pixels
<< " pixels." << std::endl;
if (area > palette_pixels)
area = palette_pixels;
// For each pixel in the image
for (u32 i = 0; i < area; i++) {
video::SColor c = img->getPixel(i % w, i / w);
// Fill in palette with 'step' colors
for (u32 j = 0; j < step; j++)
new_palette.push_back(c);
}
img->drop();
// Fill in remaining elements
while (new_palette.size() < 256)
new_palette.push_back(video::SColor(0xFFFFFFFF));
m_palettes[name] = new_palette;
it = m_palettes.find(name);
}
if (it != m_palettes.end())
return &((*it).second);
#endif
return NULL;
}
void CNodeDefManager::updateTextures(IGameDef *gamedef, void CNodeDefManager::updateTextures(IGameDef *gamedef,
void (*progress_callback)(void *progress_args, u32 progress, u32 max_progress), void (*progress_callback)(void *progress_args, u32 progress, u32 max_progress),
void *progress_callback_args) void *progress_callback_args)
@ -1489,12 +1413,10 @@ void CNodeDefManager::updateTextures(IGameDef *gamedef,
TextureSettings tsettings; TextureSettings tsettings;
tsettings.readSettings(); tsettings.readSettings();
m_palettes.clear();
u32 size = m_content_features.size(); u32 size = m_content_features.size();
for (u32 i = 0; i < size; i++) { for (u32 i = 0; i < size; i++) {
ContentFeatures *f = &(m_content_features[i]); ContentFeatures *f = &(m_content_features[i]);
f->palette = getPalette(*f, gamedef);
f->updateTextures(tsrc, shdsrc, meshmanip, client, tsettings); f->updateTextures(tsrc, shdsrc, meshmanip, client, tsettings);
progress_callback(progress_callback_args, i, size); progress_callback(progress_callback_args, i, size);
} }

@ -117,6 +117,7 @@ void ObjectProperties::serialize(std::ostream &os) const
writeARGB8(os, nametag_color); writeARGB8(os, nametag_color);
writeF1000(os, automatic_face_movement_max_rotation_per_sec); writeF1000(os, automatic_face_movement_max_rotation_per_sec);
os << serializeString(infotext); os << serializeString(infotext);
os << serializeString(wield_item);
// Add stuff only at the bottom. // Add stuff only at the bottom.
// Never remove anything, because we don't want new versions of this // Never remove anything, because we don't want new versions of this
@ -159,6 +160,7 @@ void ObjectProperties::deSerialize(std::istream &is)
nametag_color = readARGB8(is); nametag_color = readARGB8(is);
automatic_face_movement_max_rotation_per_sec = readF1000(is); automatic_face_movement_max_rotation_per_sec = readF1000(is);
infotext = deSerializeString(is); infotext = deSerializeString(is);
wield_item = deSerializeString(is);
}catch(SerializationError &e){} }catch(SerializationError &e){}
} }
else else

@ -52,6 +52,8 @@ struct ObjectProperties
video::SColor nametag_color; video::SColor nametag_color;
f32 automatic_face_movement_max_rotation_per_sec; f32 automatic_face_movement_max_rotation_per_sec;
std::string infotext; std::string infotext;
//! For dropped items, this contains item information.
std::string wield_item;
ObjectProperties(); ObjectProperties();
std::string dump(); std::string dump();

@ -58,6 +58,12 @@ ItemDefinition read_item_definition(lua_State* L,int index,
getstringfield(L, index, "description", def.description); getstringfield(L, index, "description", def.description);
getstringfield(L, index, "inventory_image", def.inventory_image); getstringfield(L, index, "inventory_image", def.inventory_image);
getstringfield(L, index, "wield_image", def.wield_image); getstringfield(L, index, "wield_image", def.wield_image);
getstringfield(L, index, "palette", def.palette_image);
// Read item color.
lua_getfield(L, index, "color");
read_color(L, -1, &def.color);
lua_pop(L, 1);
lua_getfield(L, index, "wield_scale"); lua_getfield(L, index, "wield_scale");
if(lua_istable(L, -1)){ if(lua_istable(L, -1)){
@ -118,7 +124,7 @@ ItemDefinition read_item_definition(lua_State* L,int index,
/******************************************************************************/ /******************************************************************************/
void read_object_properties(lua_State *L, int index, void read_object_properties(lua_State *L, int index,
ObjectProperties *prop) ObjectProperties *prop, IItemDefManager *idef)
{ {
if(index < 0) if(index < 0)
index = lua_gettop(L) + 1 + index; index = lua_gettop(L) + 1 + index;
@ -216,6 +222,10 @@ void read_object_properties(lua_State *L, int index,
} }
lua_pop(L, 1); lua_pop(L, 1);
getstringfield(L, -1, "infotext", prop->infotext); getstringfield(L, -1, "infotext", prop->infotext);
lua_getfield(L, -1, "wield_item");
if (!lua_isnil(L, -1))
prop->wield_item = read_item(L, -1, idef).getItemString();
lua_pop(L, 1);
} }
/******************************************************************************/ /******************************************************************************/
@ -284,6 +294,8 @@ void push_object_properties(lua_State *L, ObjectProperties *prop)
lua_setfield(L, -2, "automatic_face_movement_max_rotation_per_sec"); lua_setfield(L, -2, "automatic_face_movement_max_rotation_per_sec");
lua_pushlstring(L, prop->infotext.c_str(), prop->infotext.size()); lua_pushlstring(L, prop->infotext.c_str(), prop->infotext.size());
lua_setfield(L, -2, "infotext"); lua_setfield(L, -2, "infotext");
lua_pushlstring(L, prop->wield_item.c_str(), prop->wield_item.size());
lua_setfield(L, -2, "wield_item");
} }
/******************************************************************************/ /******************************************************************************/

@ -89,7 +89,8 @@ void push_tool_capabilities (lua_State *L,
ItemDefinition read_item_definition (lua_State *L, int index, ItemDefinition read_item_definition (lua_State *L, int index,
ItemDefinition default_def); ItemDefinition default_def);
void read_object_properties (lua_State *L, int index, void read_object_properties (lua_State *L, int index,
ObjectProperties *prop); ObjectProperties *prop,
IItemDefManager *idef);
void push_object_properties (lua_State *L, void push_object_properties (lua_State *L,
ObjectProperties *prop); ObjectProperties *prop);

@ -23,6 +23,7 @@ with this program; if not, write to the Free Software Foundation, Inc.,
#include "object_properties.h" #include "object_properties.h"
#include "common/c_converter.h" #include "common/c_converter.h"
#include "common/c_content.h" #include "common/c_content.h"
#include "server.h"
bool ScriptApiEntity::luaentity_Add(u16 id, const char *name) bool ScriptApiEntity::luaentity_Add(u16 id, const char *name)
{ {
@ -187,11 +188,11 @@ void ScriptApiEntity::luaentity_GetProperties(u16 id,
getstringfield(L, -1, "mesh", prop->mesh); getstringfield(L, -1, "mesh", prop->mesh);
// Deprecated: read object properties directly // Deprecated: read object properties directly
read_object_properties(L, -1, prop); read_object_properties(L, -1, prop, getServer()->idef());
// Read initial_properties // Read initial_properties
lua_getfield(L, -1, "initial_properties"); lua_getfield(L, -1, "initial_properties");
read_object_properties(L, -1, prop); read_object_properties(L, -1, prop, getServer()->idef());
lua_pop(L, 1); lua_pop(L, 1);
} }

@ -92,6 +92,10 @@ void ItemStackMetaRef::Register(lua_State *L)
lua_pushcfunction(L, gc_object); lua_pushcfunction(L, gc_object);
lua_settable(L, metatable); lua_settable(L, metatable);
lua_pushliteral(L, "__eq");
lua_pushcfunction(L, l_equals);
lua_settable(L, metatable);
lua_pop(L, 1); // drop metatable lua_pop(L, 1); // drop metatable
luaL_openlib(L, 0, methods, 0); // fill methodtable luaL_openlib(L, 0, methods, 0); // fill methodtable
@ -111,5 +115,6 @@ const luaL_Reg ItemStackMetaRef::methods[] = {
luamethod(MetaDataRef, set_float), luamethod(MetaDataRef, set_float),
luamethod(MetaDataRef, to_table), luamethod(MetaDataRef, to_table),
luamethod(MetaDataRef, from_table), luamethod(MetaDataRef, from_table),
luamethod(MetaDataRef, equals),
{0,0} {0,0}
}; };

@ -250,3 +250,17 @@ bool MetaDataRef::handleFromTable(lua_State *L, int table, Metadata *meta)
return true; return true;
} }
// equals(self, other)
int MetaDataRef::l_equals(lua_State *L)
{
MetaDataRef *ref1 = checkobject(L, 1);
Metadata *data1 = ref1->getmeta(false);
MetaDataRef *ref2 = checkobject(L, 2);
Metadata *data2 = ref2->getmeta(false);
if (data1 == NULL || data2 == NULL)
lua_pushboolean(L, data1 == data2);
else
lua_pushboolean(L, *data1 == *data2);
return 1;
}

@ -67,6 +67,9 @@ protected:
// from_table(self, table) // from_table(self, table)
static int l_from_table(lua_State *L); static int l_from_table(lua_State *L);
// equals(self, other)
static int l_equals(lua_State *L);
}; };
#endif /* L_NODEMETA_H_ */ #endif /* L_NODEMETA_H_ */

@ -204,6 +204,10 @@ void NodeMetaRef::RegisterCommon(lua_State *L)
lua_pushcfunction(L, gc_object); lua_pushcfunction(L, gc_object);
lua_settable(L, metatable); lua_settable(L, metatable);
lua_pushliteral(L, "__eq");
lua_pushcfunction(L, l_equals);
lua_settable(L, metatable);
lua_pop(L, 1); // drop metatable lua_pop(L, 1); // drop metatable
} }
@ -225,6 +229,7 @@ const luaL_Reg NodeMetaRef::methodsServer[] = {
luamethod(MetaDataRef, to_table), luamethod(MetaDataRef, to_table),
luamethod(MetaDataRef, from_table), luamethod(MetaDataRef, from_table),
luamethod(NodeMetaRef, get_inventory), luamethod(NodeMetaRef, get_inventory),
luamethod(MetaDataRef, equals),
{0,0} {0,0}
}; };

@ -737,7 +737,7 @@ int ObjectRef::l_set_properties(lua_State *L)
ObjectProperties *prop = co->accessObjectProperties(); ObjectProperties *prop = co->accessObjectProperties();
if (!prop) if (!prop)
return 0; return 0;
read_object_properties(L, 2, prop); read_object_properties(L, 2, prop, getServer(L)->idef());
co->notifyObjectPropertiesModified(); co->notifyObjectPropertiesModified();
return 0; return 0;
} }

@ -98,6 +98,10 @@ void StorageRef::Register(lua_State *L)
lua_pushcfunction(L, gc_object); lua_pushcfunction(L, gc_object);
lua_settable(L, metatable); lua_settable(L, metatable);
lua_pushliteral(L, "__eq");
lua_pushcfunction(L, l_equals);
lua_settable(L, metatable);
lua_pop(L, 1); // drop metatable lua_pop(L, 1); // drop metatable
luaL_openlib(L, 0, methods, 0); // fill methodtable luaL_openlib(L, 0, methods, 0); // fill methodtable
@ -138,5 +142,6 @@ const luaL_Reg StorageRef::methods[] = {
luamethod(MetaDataRef, set_float), luamethod(MetaDataRef, set_float),
luamethod(MetaDataRef, to_table), luamethod(MetaDataRef, to_table),
luamethod(MetaDataRef, from_table), luamethod(MetaDataRef, from_table),
luamethod(MetaDataRef, equals),
{0,0} {0,0}
}; };

@ -318,11 +318,15 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client)
u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL); u32 shader_id = shdrsrc->getShader("wielded_shader", TILE_MATERIAL_BASIC, NDT_NORMAL);
m_material_type = shdrsrc->getShaderInfo(shader_id).material; m_material_type = shdrsrc->getShaderInfo(shader_id).material;
} }
// Color-related
m_colors.clear(); m_colors.clear();
video::SColor basecolor = idef->getItemstackColor(item, client);
// If wield_image is defined, it overrides everything else // If wield_image is defined, it overrides everything else
if (def.wield_image != "") { if (def.wield_image != "") {
setExtruded(def.wield_image, def.wield_scale, tsrc, 1); setExtruded(def.wield_image, def.wield_scale, tsrc, 1);
m_colors.push_back(basecolor);
return; return;
} }
// Handle nodes // Handle nodes
@ -371,7 +375,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client)
} else { } else {
material.setTexture(0, tile->texture); material.setTexture(0, tile->texture);
} }
m_colors.push_back(tile->color); m_colors.push_back(tile->has_color ? tile->color : basecolor);
material.MaterialType = m_material_type; material.MaterialType = m_material_type;
if (m_enable_shaders) { if (m_enable_shaders) {
if (tile->normal_texture) { if (tile->normal_texture) {
@ -389,6 +393,7 @@ void WieldMeshSceneNode::setItem(const ItemStack &item, Client *client)
} }
else if (def.inventory_image != "") { else if (def.inventory_image != "") {
setExtruded(def.inventory_image, def.wield_scale, tsrc, 1); setExtruded(def.inventory_image, def.wield_scale, tsrc, 1);
m_colors.push_back(basecolor);
return; return;
} }
@ -455,7 +460,7 @@ void WieldMeshSceneNode::changeToMesh(scene::IMesh *mesh)
m_meshnode->setVisible(true); m_meshnode->setVisible(true);
} }
scene::IMesh *getItemMesh(Client *client, const ItemStack &item) void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result)
{ {
ITextureSource *tsrc = client->getTextureSource(); ITextureSource *tsrc = client->getTextureSource();
IItemDefManager *idef = client->getItemDefManager(); IItemDefManager *idef = client->getItemDefManager();
@ -475,12 +480,13 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item)
// If inventory_image is defined, it overrides everything else // If inventory_image is defined, it overrides everything else
if (def.inventory_image != "") { if (def.inventory_image != "") {
mesh = getExtrudedMesh(tsrc, def.inventory_image); mesh = getExtrudedMesh(tsrc, def.inventory_image);
return mesh; result->mesh = mesh;
result->buffer_colors.push_back(
std::pair<bool, video::SColor>(false, video::SColor(0xFFFFFFFF)));
} else if (def.type == ITEM_NODE) { } else if (def.type == ITEM_NODE) {
if (f.mesh_ptr[0]) { if (f.mesh_ptr[0]) {
mesh = cloneMesh(f.mesh_ptr[0]); mesh = cloneMesh(f.mesh_ptr[0]);
scaleMesh(mesh, v3f(0.12, 0.12, 0.12)); scaleMesh(mesh, v3f(0.12, 0.12, 0.12));
setMeshColor(mesh, video::SColor (255, 255, 255, 255));
} else if (f.drawtype == NDT_PLANTLIKE) { } else if (f.drawtype == NDT_PLANTLIKE) {
mesh = getExtrudedMesh(tsrc, mesh = getExtrudedMesh(tsrc,
tsrc->getTextureName(f.tiles[0].texture_id)); tsrc->getTextureName(f.tiles[0].texture_id));
@ -515,6 +521,8 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item)
for (u32 i = 0; i < mc; ++i) { for (u32 i = 0; i < mc; ++i) {
const TileSpec *tile = &(f.tiles[i]); const TileSpec *tile = &(f.tiles[i]);
scene::IMeshBuffer *buf = mesh->getMeshBuffer(i); scene::IMeshBuffer *buf = mesh->getMeshBuffer(i);
result->buffer_colors.push_back(
std::pair<bool, video::SColor>(tile->has_color, tile->color));
colorizeMeshBuffer(buf, &tile->color); colorizeMeshBuffer(buf, &tile->color);
video::SMaterial &material = buf->getMaterial(); video::SMaterial &material = buf->getMaterial();
material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL; material.MaterialType = video::EMT_TRANSPARENT_ALPHA_CHANNEL;
@ -532,9 +540,8 @@ scene::IMesh *getItemMesh(Client *client, const ItemStack &item)
rotateMeshXZby(mesh, -45); rotateMeshXZby(mesh, -45);
rotateMeshYZby(mesh, -30); rotateMeshYZby(mesh, -30);
return mesh; result->mesh = mesh;
} }
return NULL;
} }
scene::IMesh * getExtrudedMesh(ITextureSource *tsrc, scene::IMesh * getExtrudedMesh(ITextureSource *tsrc,

@ -28,6 +28,22 @@ class Client;
class ITextureSource; class ITextureSource;
struct TileSpec; struct TileSpec;
struct ItemMesh
{
scene::IMesh* mesh;
/*!
* Stores the color of each mesh buffer.
* If the boolean is true, the color is fixed, else
* palettes can modify it.
*/
std::vector<std::pair<bool, video::SColor> > buffer_colors;
ItemMesh():
mesh(NULL),
buffer_colors()
{}
};
/* /*
Wield item scene node, renders the wield mesh of some item Wield item scene node, renders the wield mesh of some item
*/ */
@ -79,7 +95,7 @@ private:
aabb3f m_bounding_box; aabb3f m_bounding_box;
}; };
scene::IMesh *getItemMesh(Client *client, const ItemStack &item); void getItemMesh(Client *client, const ItemStack &item, ItemMesh *result);
scene::IMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename); scene::IMesh *getExtrudedMesh(ITextureSource *tsrc, const std::string &imagename);
#endif #endif