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