]> git.lizzy.rs Git - minetest.git/blob - src/itemdef.cpp
New damage system, add damageGroups to ToolCapabilities, bump protocol version
[minetest.git] / src / itemdef.cpp
1 /*
2 Minetest
3 Copyright (C) 2010-2013 celeron55, Perttu Ahola <celeron55@gmail.com>
4 Copyright (C) 2013 Kahrl <kahrl@gmx.net>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU Lesser General Public License as published by
8 the Free Software Foundation; either version 2.1 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 GNU Lesser General Public License for more details.
15
16 You should have received a copy of the GNU Lesser General Public License along
17 with this program; if not, write to the Free Software Foundation, Inc.,
18 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19 */
20
21 #include "itemdef.h"
22
23 #include "gamedef.h"
24 #include "nodedef.h"
25 #include "tool.h"
26 #include "inventory.h"
27 #ifndef SERVER
28 #include "mapblock_mesh.h"
29 #include "mesh.h"
30 #include "tile.h"
31 #endif
32 #include "log.h"
33 #include "main.h" // g_settings
34 #include "settings.h"
35 #include "util/serialize.h"
36 #include "util/container.h"
37 #include "util/thread.h"
38 #include <map>
39 #include <set>
40
41 /*
42         ItemDefinition
43 */
44 ItemDefinition::ItemDefinition()
45 {
46         resetInitial();
47 }
48
49 ItemDefinition::ItemDefinition(const ItemDefinition &def)
50 {
51         resetInitial();
52         *this = def;
53 }
54
55 ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
56 {
57         if(this == &def)
58                 return *this;
59
60         reset();
61
62         type = def.type;
63         name = def.name;
64         description = def.description;
65         inventory_image = def.inventory_image;
66         wield_image = def.wield_image;
67         wield_scale = def.wield_scale;
68         stack_max = def.stack_max;
69         usable = def.usable;
70         liquids_pointable = def.liquids_pointable;
71         if(def.tool_capabilities)
72         {
73                 tool_capabilities = new ToolCapabilities(
74                                 *def.tool_capabilities);
75         }
76         groups = def.groups;
77         node_placement_prediction = def.node_placement_prediction;
78         return *this;
79 }
80
81 ItemDefinition::~ItemDefinition()
82 {
83         reset();
84 }
85
86 void ItemDefinition::resetInitial()
87 {
88         // Initialize pointers to NULL so reset() does not delete undefined pointers
89         tool_capabilities = NULL;
90         reset();
91 }
92
93 void ItemDefinition::reset()
94 {
95         type = ITEM_NONE;
96         name = "";
97         description = "";
98         inventory_image = "";
99         wield_image = "";
100         wield_scale = v3f(1.0, 1.0, 1.0);
101         stack_max = 99;
102         usable = false;
103         liquids_pointable = false;
104         if(tool_capabilities)
105         {
106                 delete tool_capabilities;
107                 tool_capabilities = NULL;
108         }
109         groups.clear();
110
111         node_placement_prediction = "";
112 }
113
114 void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
115 {
116         writeU8(os, 1); // version
117         writeU8(os, type);
118         os<<serializeString(name);
119         os<<serializeString(description);
120         os<<serializeString(inventory_image);
121         os<<serializeString(wield_image);
122         writeV3F1000(os, wield_scale);
123         writeS16(os, stack_max);
124         writeU8(os, usable);
125         writeU8(os, liquids_pointable);
126         std::string tool_capabilities_s = "";
127         if(tool_capabilities){
128                 std::ostringstream tmp_os(std::ios::binary);
129                 tool_capabilities->serialize(tmp_os, protocol_version);
130                 tool_capabilities_s = tmp_os.str();
131         }
132         os<<serializeString(tool_capabilities_s);
133         writeU16(os, groups.size());
134         for(std::map<std::string, int>::const_iterator
135                         i = groups.begin(); i != groups.end(); i++){
136                 os<<serializeString(i->first);
137                 writeS16(os, i->second);
138         }
139         os<<serializeString(node_placement_prediction);
140 }
141
142 void ItemDefinition::deSerialize(std::istream &is)
143 {
144         // Reset everything
145         reset();
146
147         // Deserialize
148         int version = readU8(is);
149         if(version != 1)
150                 throw SerializationError("unsupported ItemDefinition version");
151         type = (enum ItemType)readU8(is);
152         name = deSerializeString(is);
153         description = deSerializeString(is);
154         inventory_image = deSerializeString(is);
155         wield_image = deSerializeString(is);
156         wield_scale = readV3F1000(is);
157         stack_max = readS16(is);
158         usable = readU8(is);
159         liquids_pointable = readU8(is);
160         std::string tool_capabilities_s = deSerializeString(is);
161         if(!tool_capabilities_s.empty())
162         {
163                 std::istringstream tmp_is(tool_capabilities_s, std::ios::binary);
164                 tool_capabilities = new ToolCapabilities;
165                 tool_capabilities->deSerialize(tmp_is);
166         }
167         groups.clear();
168         u32 groups_size = readU16(is);
169         for(u32 i=0; i<groups_size; i++){
170                 std::string name = deSerializeString(is);
171                 int value = readS16(is);
172                 groups[name] = value;
173         }
174         // If you add anything here, insert it primarily inside the try-catch
175         // block to not need to increase the version.
176         try{
177                 node_placement_prediction = deSerializeString(is);
178         }catch(SerializationError &e) {};
179 }
180
181 /*
182         CItemDefManager
183 */
184
185 // SUGG: Support chains of aliases?
186
187 class CItemDefManager: public IWritableItemDefManager
188 {
189 #ifndef SERVER
190         struct ClientCached
191         {
192                 video::ITexture *inventory_texture;
193                 scene::IMesh *wield_mesh;
194
195                 ClientCached():
196                         inventory_texture(NULL),
197                         wield_mesh(NULL)
198                 {}
199         };
200 #endif
201
202 public:
203         CItemDefManager()
204         {
205 #ifndef SERVER
206                 m_main_thread = get_current_thread_id();
207 #endif
208         
209                 clear();
210         }
211         virtual ~CItemDefManager()
212         {
213 #ifndef SERVER
214                 const std::list<ClientCached*> &values = m_clientcached.getValues();
215                 for(std::list<ClientCached*>::const_iterator
216                                 i = values.begin(); i != values.end(); ++i)
217                 {
218                         ClientCached *cc = *i;
219                         cc->wield_mesh->drop();
220                 }
221 #endif
222         }
223         virtual const ItemDefinition& get(const std::string &name_) const
224         {
225                 // Convert name according to possible alias
226                 std::string name = getAlias(name_);
227                 // Get the definition
228                 std::map<std::string, ItemDefinition*>::const_iterator i;
229                 i = m_item_definitions.find(name);
230                 if(i == m_item_definitions.end())
231                         i = m_item_definitions.find("unknown");
232                 assert(i != m_item_definitions.end());
233                 return *(i->second);
234         }
235         virtual std::string getAlias(const std::string &name) const
236         {
237                 std::map<std::string, std::string>::const_iterator i;
238                 i = m_aliases.find(name);
239                 if(i != m_aliases.end())
240                         return i->second;
241                 return name;
242         }
243         virtual std::set<std::string> getAll() const
244         {
245                 std::set<std::string> result;
246                 for(std::map<std::string, ItemDefinition*>::const_iterator
247                                 i = m_item_definitions.begin();
248                                 i != m_item_definitions.end(); i++)
249                 {
250                         result.insert(i->first);
251                 }
252                 for(std::map<std::string, std::string>::const_iterator
253                                 i = m_aliases.begin();
254                                 i != m_aliases.end(); i++)
255                 {
256                         result.insert(i->first);
257                 }
258                 return result;
259         }
260         virtual bool isKnown(const std::string &name_) const
261         {
262                 // Convert name according to possible alias
263                 std::string name = getAlias(name_);
264                 // Get the definition
265                 std::map<std::string, ItemDefinition*>::const_iterator i;
266                 return m_item_definitions.find(name) != m_item_definitions.end();
267         }
268 #ifndef SERVER
269         ClientCached* createClientCachedDirect(const std::string &name,
270                         IGameDef *gamedef) const
271         {
272                 infostream<<"Lazily creating item texture and mesh for \""
273                                 <<name<<"\""<<std::endl;
274
275                 // This is not thread-safe
276                 assert(get_current_thread_id() == m_main_thread);
277
278                 // Skip if already in cache
279                 ClientCached *cc = NULL;
280                 m_clientcached.get(name, &cc);
281                 if(cc)
282                         return cc;
283
284                 ITextureSource *tsrc = gamedef->getTextureSource();
285                 INodeDefManager *nodedef = gamedef->getNodeDefManager();
286                 IrrlichtDevice *device = tsrc->getDevice();
287                 video::IVideoDriver *driver = device->getVideoDriver();
288                 const ItemDefinition *def = &get(name);
289
290                 // Create new ClientCached
291                 cc = new ClientCached();
292
293                 bool need_node_mesh = false;
294
295                 // Create an inventory texture
296                 cc->inventory_texture = NULL;
297                 if(def->inventory_image != "")
298                 {
299                         cc->inventory_texture = tsrc->getTextureRaw(def->inventory_image);
300                 }
301                 else if(def->type == ITEM_NODE)
302                 {
303                         need_node_mesh = true;
304                 }
305
306                 // Create a wield mesh
307                 if(cc->wield_mesh != NULL)
308                 {
309                         cc->wield_mesh->drop();
310                         cc->wield_mesh = NULL;
311                 }
312                 if(def->type == ITEM_NODE && def->wield_image == "")
313                 {
314                         need_node_mesh = true;
315                 }
316                 else if(def->wield_image != "" || def->inventory_image != "")
317                 {
318                         // Extrude the wield image into a mesh
319
320                         std::string imagename;
321                         if(def->wield_image != "")
322                                 imagename = def->wield_image;
323                         else
324                                 imagename = def->inventory_image;
325
326                         cc->wield_mesh = createExtrudedMesh(
327                                         tsrc->getTextureRaw(imagename),
328                                         driver,
329                                         def->wield_scale * v3f(40.0, 40.0, 4.0));
330                         if(cc->wield_mesh == NULL)
331                         {
332                                 infostream<<"ItemDefManager: WARNING: "
333                                         <<"updateTexturesAndMeshes(): "
334                                         <<"Unable to create extruded mesh for item "
335                                         <<def->name<<std::endl;
336                         }
337                 }
338
339                 if(need_node_mesh)
340                 {
341                         /*
342                                 Get node properties
343                         */
344                         content_t id = nodedef->getId(def->name);
345                         const ContentFeatures &f = nodedef->get(id);
346
347                         u8 param1 = 0;
348                         if(f.param_type == CPT_LIGHT)
349                                 param1 = 0xee;
350
351                         /*
352                                 Make a mesh from the node
353                         */
354                         MeshMakeData mesh_make_data(gamedef);
355                         MapNode mesh_make_node(id, param1, 0);
356                         mesh_make_data.fillSingleNode(&mesh_make_node);
357                         MapBlockMesh mapblock_mesh(&mesh_make_data);
358
359                         scene::IMesh *node_mesh = mapblock_mesh.getMesh();
360                         assert(node_mesh);
361                         video::SColor c(255, 255, 255, 255);
362                         if(g_settings->getS32("enable_shaders") != 0)
363                                 c = MapBlock_LightColor(255, 0xffff, decode_light(f.light_source));
364                         setMeshColor(node_mesh, c);
365
366                         /*
367                                 Scale and translate the mesh so it's a unit cube
368                                 centered on the origin
369                         */
370                         scaleMesh(node_mesh, v3f(1.0/BS, 1.0/BS, 1.0/BS));
371                         translateMesh(node_mesh, v3f(-1.0, -1.0, -1.0));
372
373                         /*
374                                 Draw node mesh into a render target texture
375                         */
376                         if(cc->inventory_texture == NULL)
377                         {
378                                 core::dimension2d<u32> dim(64,64);
379                                 std::string rtt_texture_name = "INVENTORY_"
380                                         + def->name + "_RTT";
381                                 v3f camera_position(0, 1.0, -1.5);
382                                 camera_position.rotateXZBy(45);
383                                 v3f camera_lookat(0, 0, 0);
384                                 core::CMatrix4<f32> camera_projection_matrix;
385                                 // Set orthogonal projection
386                                 camera_projection_matrix.buildProjectionMatrixOrthoLH(
387                                                 1.65, 1.65, 0, 100);
388
389                                 video::SColorf ambient_light(0.2,0.2,0.2);
390                                 v3f light_position(10, 100, -50);
391                                 video::SColorf light_color(0.5,0.5,0.5);
392                                 f32 light_radius = 1000;
393
394                                 cc->inventory_texture = generateTextureFromMesh(
395                                         node_mesh, device, dim, rtt_texture_name,
396                                         camera_position,
397                                         camera_lookat,
398                                         camera_projection_matrix,
399                                         ambient_light,
400                                         light_position,
401                                         light_color,
402                                         light_radius);
403
404                                 // render-to-target didn't work
405                                 if(cc->inventory_texture == NULL)
406                                 {
407                                         cc->inventory_texture =
408                                                 tsrc->getTextureRaw(f.tiledef[0].name);
409                                 }
410                         }
411
412                         /*
413                                 Use the node mesh as the wield mesh
414                         */
415                         if(cc->wield_mesh == NULL)
416                         {
417                                 // Scale to proper wield mesh proportions
418                                 scaleMesh(node_mesh, v3f(30.0, 30.0, 30.0)
419                                                 * def->wield_scale);
420                                 cc->wield_mesh = node_mesh;
421                                 cc->wield_mesh->grab();
422                         }
423
424                         // falling outside of here deletes node_mesh
425                 }
426
427                 // Put in cache
428                 m_clientcached.set(name, cc);
429
430                 return cc;
431         }
432         ClientCached* getClientCached(const std::string &name,
433                         IGameDef *gamedef) const
434         {
435                 ClientCached *cc = NULL;
436                 m_clientcached.get(name, &cc);
437                 if(cc)
438                         return cc;
439
440                 if(get_current_thread_id() == m_main_thread)
441                 {
442                         return createClientCachedDirect(name, gamedef);
443                 }
444                 else
445                 {
446                         // We're gonna ask the result to be put into here
447                         ResultQueue<std::string, ClientCached*, u8, u8> result_queue;
448                         // Throw a request in
449                         m_get_clientcached_queue.add(name, 0, 0, &result_queue);
450                         try{
451                                 // Wait result for a second
452                                 GetResult<std::string, ClientCached*, u8, u8>
453                                                 result = result_queue.pop_front(1000);
454                                 // Check that at least something worked OK
455                                 assert(result.key == name);
456                                 // Return it
457                                 return result.item;
458                         }
459                         catch(ItemNotFoundException &e)
460                         {
461                                 errorstream<<"Waiting for clientcached timed out."<<std::endl;
462                                 return &m_dummy_clientcached;
463                         }
464                 }
465         }
466         // Get item inventory texture
467         virtual video::ITexture* getInventoryTexture(const std::string &name,
468                         IGameDef *gamedef) const
469         {
470                 ClientCached *cc = getClientCached(name, gamedef);
471                 if(!cc)
472                         return NULL;
473                 return cc->inventory_texture;
474         }
475         // Get item wield mesh
476         virtual scene::IMesh* getWieldMesh(const std::string &name,
477                         IGameDef *gamedef) const
478         {
479                 ClientCached *cc = getClientCached(name, gamedef);
480                 if(!cc)
481                         return NULL;
482                 return cc->wield_mesh;
483         }
484 #endif
485         void clear()
486         {
487                 for(std::map<std::string, ItemDefinition*>::const_iterator
488                                 i = m_item_definitions.begin();
489                                 i != m_item_definitions.end(); i++)
490                 {
491                         delete i->second;
492                 }
493                 m_item_definitions.clear();
494                 m_aliases.clear();
495
496                 // Add the four builtin items:
497                 //   "" is the hand
498                 //   "unknown" is returned whenever an undefined item is accessed
499                 //   "air" is the air node
500                 //   "ignore" is the ignore node
501
502                 ItemDefinition* hand_def = new ItemDefinition;
503                 hand_def->name = "";
504                 hand_def->wield_image = "wieldhand.png";
505                 hand_def->tool_capabilities = new ToolCapabilities;
506                 m_item_definitions.insert(std::make_pair("", hand_def));
507
508                 ItemDefinition* unknown_def = new ItemDefinition;
509                 unknown_def->name = "unknown";
510                 m_item_definitions.insert(std::make_pair("unknown", unknown_def));
511
512                 ItemDefinition* air_def = new ItemDefinition;
513                 air_def->type = ITEM_NODE;
514                 air_def->name = "air";
515                 m_item_definitions.insert(std::make_pair("air", air_def));
516
517                 ItemDefinition* ignore_def = new ItemDefinition;
518                 ignore_def->type = ITEM_NODE;
519                 ignore_def->name = "ignore";
520                 m_item_definitions.insert(std::make_pair("ignore", ignore_def));
521         }
522         virtual void registerItem(const ItemDefinition &def)
523         {
524                 verbosestream<<"ItemDefManager: registering \""<<def.name<<"\""<<std::endl;
525                 // Ensure that the "" item (the hand) always has ToolCapabilities
526                 if(def.name == "")
527                         assert(def.tool_capabilities != NULL);
528                 
529                 if(m_item_definitions.count(def.name) == 0)
530                         m_item_definitions[def.name] = new ItemDefinition(def);
531                 else
532                         *(m_item_definitions[def.name]) = def;
533
534                 // Remove conflicting alias if it exists
535                 bool alias_removed = (m_aliases.erase(def.name) != 0);
536                 if(alias_removed)
537                         infostream<<"ItemDefManager: erased alias "<<def.name
538                                         <<" because item was defined"<<std::endl;
539         }
540         virtual void registerAlias(const std::string &name,
541                         const std::string &convert_to)
542         {
543                 if(m_item_definitions.find(name) == m_item_definitions.end())
544                 {
545                         verbosestream<<"ItemDefManager: setting alias "<<name
546                                 <<" -> "<<convert_to<<std::endl;
547                         m_aliases[name] = convert_to;
548                 }
549         }
550         void serialize(std::ostream &os, u16 protocol_version)
551         {
552                 writeU8(os, 0); // version
553                 u16 count = m_item_definitions.size();
554                 writeU16(os, count);
555                 for(std::map<std::string, ItemDefinition*>::const_iterator
556                                 i = m_item_definitions.begin();
557                                 i != m_item_definitions.end(); i++)
558                 {
559                         ItemDefinition *def = i->second;
560                         // Serialize ItemDefinition and write wrapped in a string
561                         std::ostringstream tmp_os(std::ios::binary);
562                         def->serialize(tmp_os, protocol_version);
563                         os<<serializeString(tmp_os.str());
564                 }
565                 writeU16(os, m_aliases.size());
566                 for(std::map<std::string, std::string>::const_iterator
567                         i = m_aliases.begin(); i != m_aliases.end(); i++)
568                 {
569                         os<<serializeString(i->first);
570                         os<<serializeString(i->second);
571                 }
572         }
573         void deSerialize(std::istream &is)
574         {
575                 // Clear everything
576                 clear();
577                 // Deserialize
578                 int version = readU8(is);
579                 if(version != 0)
580                         throw SerializationError("unsupported ItemDefManager version");
581                 u16 count = readU16(is);
582                 for(u16 i=0; i<count; i++)
583                 {
584                         // Deserialize a string and grab an ItemDefinition from it
585                         std::istringstream tmp_is(deSerializeString(is), std::ios::binary);
586                         ItemDefinition def;
587                         def.deSerialize(tmp_is);
588                         // Register
589                         registerItem(def);
590                 }
591                 u16 num_aliases = readU16(is);
592                 for(u16 i=0; i<num_aliases; i++)
593                 {
594                         std::string name = deSerializeString(is);
595                         std::string convert_to = deSerializeString(is);
596                         registerAlias(name, convert_to);
597                 }
598         }
599         void processQueue(IGameDef *gamedef)
600         {
601 #ifndef SERVER
602                 while(!m_get_clientcached_queue.empty())
603                 {
604                         GetRequest<std::string, ClientCached*, u8, u8>
605                                         request = m_get_clientcached_queue.pop();
606                         GetResult<std::string, ClientCached*, u8, u8>
607                                         result;
608                         result.key = request.key;
609                         result.callers = request.callers;
610                         result.item = createClientCachedDirect(request.key, gamedef);
611                         request.dest->push_back(result);
612                 }
613 #endif
614         }
615 private:
616         // Key is name
617         std::map<std::string, ItemDefinition*> m_item_definitions;
618         // Aliases
619         std::map<std::string, std::string> m_aliases;
620 #ifndef SERVER
621         // The id of the thread that is allowed to use irrlicht directly
622         threadid_t m_main_thread;
623         // A reference to this can be returned when nothing is found, to avoid NULLs
624         mutable ClientCached m_dummy_clientcached;
625         // Cached textures and meshes
626         mutable MutexedMap<std::string, ClientCached*> m_clientcached;
627         // Queued clientcached fetches (to be processed by the main thread)
628         mutable RequestQueue<std::string, ClientCached*, u8, u8> m_get_clientcached_queue;
629 #endif
630 };
631
632 IWritableItemDefManager* createItemDefManager()
633 {
634         return new CItemDefManager();
635 }
636