]> git.lizzy.rs Git - minetest.git/blob - src/itemdef.cpp
Refactoring and code style fixes in preparation of adding mesh typed items
[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 "wieldmesh.h"
31 #endif
32 #include "log.h"
33 #include "settings.h"
34 #include "util/serialize.h"
35
36 #ifdef __ANDROID__
37 #include <GLES/gl.h>
38 #endif
39
40 /*
41         ItemDefinition
42 */
43 ItemDefinition::ItemDefinition()
44 {
45         resetInitial();
46 }
47
48 ItemDefinition::ItemDefinition(const ItemDefinition &def)
49 {
50         resetInitial();
51         *this = def;
52 }
53
54 ItemDefinition& ItemDefinition::operator=(const ItemDefinition &def)
55 {
56         if(this == &def)
57                 return *this;
58
59         reset();
60
61         type = def.type;
62         name = def.name;
63         description = def.description;
64         inventory_image = def.inventory_image;
65         wield_image = def.wield_image;
66         wield_scale = def.wield_scale;
67         stack_max = def.stack_max;
68         usable = def.usable;
69         liquids_pointable = def.liquids_pointable;
70         if(def.tool_capabilities)
71         {
72                 tool_capabilities = new ToolCapabilities(
73                                 *def.tool_capabilities);
74         }
75         groups = def.groups;
76         node_placement_prediction = def.node_placement_prediction;
77         sound_place = def.sound_place;
78         sound_place_failed = def.sound_place_failed;
79         range = def.range;
80         return *this;
81 }
82
83 ItemDefinition::~ItemDefinition()
84 {
85         reset();
86 }
87
88 void ItemDefinition::resetInitial()
89 {
90         // Initialize pointers to NULL so reset() does not delete undefined pointers
91         tool_capabilities = NULL;
92         reset();
93 }
94
95 void ItemDefinition::reset()
96 {
97         type = ITEM_NONE;
98         name = "";
99         description = "";
100         inventory_image = "";
101         wield_image = "";
102         wield_scale = v3f(1.0, 1.0, 1.0);
103         stack_max = 99;
104         usable = false;
105         liquids_pointable = false;
106         if(tool_capabilities)
107         {
108                 delete tool_capabilities;
109                 tool_capabilities = NULL;
110         }
111         groups.clear();
112         sound_place = SimpleSoundSpec();
113         sound_place_failed = SimpleSoundSpec();
114         range = -1;
115
116         node_placement_prediction = "";
117 }
118
119 void ItemDefinition::serialize(std::ostream &os, u16 protocol_version) const
120 {
121         if(protocol_version <= 17)
122                 writeU8(os, 1); // version
123         else if(protocol_version <= 20)
124                 writeU8(os, 2); // version
125         else
126                 writeU8(os, 3); // version
127         writeU8(os, type);
128         os<<serializeString(name);
129         os<<serializeString(description);
130         os<<serializeString(inventory_image);
131         os<<serializeString(wield_image);
132         writeV3F1000(os, wield_scale);
133         writeS16(os, stack_max);
134         writeU8(os, usable);
135         writeU8(os, liquids_pointable);
136         std::string tool_capabilities_s = "";
137         if(tool_capabilities){
138                 std::ostringstream tmp_os(std::ios::binary);
139                 tool_capabilities->serialize(tmp_os, protocol_version);
140                 tool_capabilities_s = tmp_os.str();
141         }
142         os<<serializeString(tool_capabilities_s);
143         writeU16(os, groups.size());
144         for(std::map<std::string, int>::const_iterator
145                         i = groups.begin(); i != groups.end(); ++i){
146                 os<<serializeString(i->first);
147                 writeS16(os, i->second);
148         }
149         os<<serializeString(node_placement_prediction);
150         if(protocol_version > 17){
151                 //serializeSimpleSoundSpec(sound_place, os);
152                 os<<serializeString(sound_place.name);
153                 writeF1000(os, sound_place.gain);
154         }
155         if (protocol_version > 20) {
156                 writeF1000(os, range);
157                 os << serializeString(sound_place_failed.name);
158                 writeF1000(os, sound_place_failed.gain);
159         }
160 }
161
162 void ItemDefinition::deSerialize(std::istream &is)
163 {
164         // Reset everything
165         reset();
166
167         // Deserialize
168         int version = readU8(is);
169         if(version < 1 || version > 3)
170                 throw SerializationError("unsupported ItemDefinition version");
171         type = (enum ItemType)readU8(is);
172         name = deSerializeString(is);
173         description = deSerializeString(is);
174         inventory_image = deSerializeString(is);
175         wield_image = deSerializeString(is);
176         wield_scale = readV3F1000(is);
177         stack_max = readS16(is);
178         usable = readU8(is);
179         liquids_pointable = readU8(is);
180         std::string tool_capabilities_s = deSerializeString(is);
181         if(!tool_capabilities_s.empty())
182         {
183                 std::istringstream tmp_is(tool_capabilities_s, std::ios::binary);
184                 tool_capabilities = new ToolCapabilities;
185                 tool_capabilities->deSerialize(tmp_is);
186         }
187         groups.clear();
188         u32 groups_size = readU16(is);
189         for(u32 i=0; i<groups_size; i++){
190                 std::string name = deSerializeString(is);
191                 int value = readS16(is);
192                 groups[name] = value;
193         }
194         if(version == 1){
195                 // We cant be sure that node_placement_prediction is send in version 1
196                 try{
197                         node_placement_prediction = deSerializeString(is);
198                 }catch(SerializationError &e) {};
199                 // Set the old default sound
200                 sound_place.name = "default_place_node";
201                 sound_place.gain = 0.5;
202         } else if(version >= 2) {
203                 node_placement_prediction = deSerializeString(is);
204                 //deserializeSimpleSoundSpec(sound_place, is);
205                 sound_place.name = deSerializeString(is);
206                 sound_place.gain = readF1000(is);
207         }
208         if(version == 3) {
209                 range = readF1000(is);
210         }
211         // If you add anything here, insert it primarily inside the try-catch
212         // block to not need to increase the version.
213         try {
214                 sound_place_failed.name = deSerializeString(is);
215                 sound_place_failed.gain = readF1000(is);
216         } catch(SerializationError &e) {};
217 }
218
219 /*
220         CItemDefManager
221 */
222
223 // SUGG: Support chains of aliases?
224
225 #ifndef SERVER
226 CItemDefManager::ClientCached::ClientCached() :
227                 inventory_texture(NULL),
228                 wield_mesh(NULL)
229 {}
230 #endif
231
232 CItemDefManager::CItemDefManager()
233 {
234 #ifndef SERVER
235         m_main_thread = thr_get_current_thread_id();
236 #endif
237         clear();
238 }
239
240 /******************************************************************************/
241 CItemDefManager::~CItemDefManager()
242 {
243 #ifndef SERVER
244         const std::vector<ClientCached*> &values = m_clientcached.getValues();
245         for(std::vector<ClientCached*>::const_iterator
246                         i = values.begin(); i != values.end(); ++i) {
247
248                 ClientCached *cc = *i;
249                 if (cc->wield_mesh)
250                         cc->wield_mesh->drop();
251                 delete cc;
252         }
253
254 #endif
255         for (std::map<std::string, ItemDefinition*>::iterator iter =
256                         m_item_definitions.begin(); iter != m_item_definitions.end();
257                         ++iter) {
258
259                 delete iter->second;
260         }
261         m_item_definitions.clear();
262 }
263
264 /******************************************************************************/
265 const ItemDefinition& CItemDefManager::get(const std::string &name_) const
266 {
267         // Convert name according to possible alias
268         std::string name = getAlias(name_);
269         // Get the definition
270         std::map<std::string, ItemDefinition*>::const_iterator i;
271         i = m_item_definitions.find(name);
272         if(i == m_item_definitions.end())
273                 i = m_item_definitions.find("unknown");
274         assert(i != m_item_definitions.end());
275         return *(i->second);
276 }
277
278 /******************************************************************************/
279 std::string CItemDefManager::getAlias(const std::string &name) const
280 {
281         StringMap::const_iterator it = m_aliases.find(name);
282         if (it != m_aliases.end())
283                 return it->second;
284
285         return name;
286 }
287
288 /******************************************************************************/
289 std::set<std::string> CItemDefManager::getAll() const
290 {
291         std::set<std::string> result;
292         for(std::map<std::string, ItemDefinition *>::const_iterator
293                         it = m_item_definitions.begin();
294                         it != m_item_definitions.end(); ++it) {
295
296                 result.insert(it->first);
297         }
298         for (StringMap::const_iterator
299                         it = m_aliases.begin();
300                         it != m_aliases.end(); ++it) {
301
302                 result.insert(it->first);
303         }
304         return result;
305 }
306
307 /******************************************************************************/
308 bool CItemDefManager::isKnown(const std::string &name_) const
309 {
310         // Convert name according to possible alias
311         std::string name = getAlias(name_);
312         // Get the definition
313         std::map<std::string, ItemDefinition*>::const_iterator i;
314
315         return m_item_definitions.find(name) != m_item_definitions.end();
316 }
317 #ifndef SERVER
318
319 /******************************************************************************/
320 CItemDefManager::ClientCached* CItemDefManager::createClientCachedDirect(const std::string &name,
321                 IGameDef *gamedef) const
322 {
323         infostream<<"Lazily creating item texture and mesh for \""
324                         <<name<<"\""<<std::endl;
325
326         // This is not thread-safe
327         sanity_check(thr_is_current_thread(m_main_thread));
328
329         // Skip if already in cache
330         ClientCached *cc = NULL;
331         m_clientcached.get(name, &cc);
332         if (cc)
333                 return cc;
334
335         ITextureSource *tsrc = gamedef->getTextureSource();
336         INodeDefManager *nodedef = gamedef->getNodeDefManager();
337         const ItemDefinition &def = get(name);
338
339         // Create new ClientCached
340         cc = new ClientCached();
341
342         // Create an inventory texture
343         cc->inventory_texture = NULL;
344         if (def.inventory_image != "")
345                 cc->inventory_texture = tsrc->getTexture(def.inventory_image);
346
347         // Additional processing for nodes:
348         // - Create a wield mesh if WieldMeshSceneNode can't render
349         //   the node on its own.
350         // - If inventory_texture isn't set yet, create one using
351         //   render-to-texture.
352         if (def.type == ITEM_NODE) {
353                 createNodeItemTexture(name, def, nodedef, cc, gamedef, tsrc);
354         }
355
356         // Put in cache
357         m_clientcached.set(name, cc);
358
359         return cc;
360 }
361
362 /******************************************************************************/
363 CItemDefManager::ClientCached* CItemDefManager::getClientCached(const std::string &name,
364                 IGameDef *gamedef) const
365 {
366         ClientCached *cc = NULL;
367         m_clientcached.get(name, &cc);
368         if (cc)
369                 return cc;
370
371         if (thr_is_current_thread(m_main_thread)) {
372
373                 return createClientCachedDirect(name, gamedef);
374         }
375         else
376         {
377                 // We're gonna ask the result to be put into here
378                 static ResultQueue<std::string, ClientCached*, u8, u8> result_queue;
379
380                 // Throw a request in
381                 m_get_clientcached_queue.add(name, 0, 0, &result_queue);
382                 try{
383                         while (true) {
384                                 // Wait result for a second
385                                 GetResult<std::string, ClientCached*, u8, u8>
386                                         result = result_queue.pop_front(1000);
387
388                                 if (result.key == name) {
389                                         return result.item;
390                                 }
391                         }
392                 }
393                 catch(ItemNotFoundException &e)
394                 {
395                         errorstream<<"Waiting for clientcached " << name << " timed out."<<std::endl;
396                         return &m_dummy_clientcached;
397                 }
398         }
399 }
400
401 /******************************************************************************/
402 // Get item inventory texture
403 video::ITexture* CItemDefManager::getInventoryTexture(const std::string &name,
404                 IGameDef *gamedef) const
405 {
406         ClientCached *cc = getClientCached(name, gamedef);
407         if (!cc)
408                 return NULL;
409
410         return cc->inventory_texture;
411 }
412
413 /******************************************************************************/
414 // Get item wield mesh
415 scene::IMesh* CItemDefManager::getWieldMesh(const std::string &name,
416                 IGameDef *gamedef) const
417 {
418         ClientCached *cc = getClientCached(name, gamedef);
419         if (!cc)
420                 return NULL;
421
422         return cc->wield_mesh;
423 }
424 #endif
425
426 /******************************************************************************/
427 void CItemDefManager::clear()
428 {
429         for (std::map<std::string, ItemDefinition*>::const_iterator
430                         i = m_item_definitions.begin();
431                         i != m_item_definitions.end(); ++i) {
432
433                 delete i->second;
434         }
435         m_item_definitions.clear();
436         m_aliases.clear();
437
438         // Add the four builtin items:
439         //   "" is the hand
440         //   "unknown" is returned whenever an undefined item
441         //     is accessed (is also the unknown node)
442         //   "air" is the air node
443         //   "ignore" is the ignore node
444
445         ItemDefinition* hand_def = new ItemDefinition;
446         hand_def->name = "";
447         hand_def->wield_image = "wieldhand.png";
448         hand_def->tool_capabilities = new ToolCapabilities;
449         m_item_definitions.insert(std::make_pair("", hand_def));
450
451         ItemDefinition* unknown_def = new ItemDefinition;
452         unknown_def->type = ITEM_NODE;
453         unknown_def->name = "unknown";
454         m_item_definitions.insert(std::make_pair("unknown", unknown_def));
455
456         ItemDefinition* air_def = new ItemDefinition;
457         air_def->type = ITEM_NODE;
458         air_def->name = "air";
459         m_item_definitions.insert(std::make_pair("air", air_def));
460
461         ItemDefinition* ignore_def = new ItemDefinition;
462         ignore_def->type = ITEM_NODE;
463         ignore_def->name = "ignore";
464         m_item_definitions.insert(std::make_pair("ignore", ignore_def));
465 }
466
467 /******************************************************************************/
468 void CItemDefManager::registerItem(const ItemDefinition &def)
469 {
470         verbosestream<<"ItemDefManager: registering \""<<def.name<<"\""<<std::endl;
471         // Ensure that the "" item (the hand) always has ToolCapabilities
472         if (def.name == "")
473                 FATAL_ERROR_IF(!def.tool_capabilities, "Hand does not have ToolCapabilities");
474
475         if (m_item_definitions.count(def.name) == 0)
476                 m_item_definitions[def.name] = new ItemDefinition(def);
477         else
478                 *(m_item_definitions[def.name]) = def;
479
480         // Remove conflicting alias if it exists
481         bool alias_removed = (m_aliases.erase(def.name) != 0);
482         if (alias_removed)
483                 infostream<<"ItemDefManager: erased alias "<<def.name
484                                 <<" because item was defined"<<std::endl;
485 }
486
487 /******************************************************************************/
488 void CItemDefManager::registerAlias(const std::string &name,
489                 const std::string &convert_to)
490 {
491         if (m_item_definitions.find(name) == m_item_definitions.end()) {
492
493                 verbosestream<<"ItemDefManager: setting alias "<<name
494                         <<" -> "<<convert_to<<std::endl;
495                 m_aliases[name] = convert_to;
496         }
497 }
498
499 /******************************************************************************/
500 void CItemDefManager::serialize(std::ostream &os, u16 protocol_version)
501 {
502         writeU8(os, 0); // version
503         u16 count = m_item_definitions.size();
504         writeU16(os, count);
505
506         for (std::map<std::string, ItemDefinition *>::const_iterator
507                         it = m_item_definitions.begin();
508                         it != m_item_definitions.end(); ++it) {
509                 ItemDefinition *def = it->second;
510                 // Serialize ItemDefinition and write wrapped in a string
511                 std::ostringstream tmp_os(std::ios::binary);
512                 def->serialize(tmp_os, protocol_version);
513                 os << serializeString(tmp_os.str());
514         }
515
516         writeU16(os, m_aliases.size());
517
518         for (StringMap::const_iterator
519                         it = m_aliases.begin();
520                         it != m_aliases.end(); ++it) {
521                 os << serializeString(it->first);
522                 os << serializeString(it->second);
523         }
524 }
525
526 /******************************************************************************/
527 void CItemDefManager::deSerialize(std::istream &is)
528 {
529         // Clear everything
530         clear();
531         // Deserialize
532         int version = readU8(is);
533         if(version != 0)
534                 throw SerializationError("unsupported ItemDefManager version");
535         u16 count = readU16(is);
536
537         for (u16 i=0; i<count; i++) {
538
539                 // Deserialize a string and grab an ItemDefinition from it
540                 std::istringstream tmp_is(deSerializeString(is), std::ios::binary);
541                 ItemDefinition def;
542                 def.deSerialize(tmp_is);
543                 // Register
544                 registerItem(def);
545         }
546
547         u16 num_aliases = readU16(is);
548         for (u16 i=0; i<num_aliases; i++) {
549
550                 std::string name = deSerializeString(is);
551                 std::string convert_to = deSerializeString(is);
552                 registerAlias(name, convert_to);
553         }
554 }
555
556 /******************************************************************************/
557 void CItemDefManager::processQueue(IGameDef *gamedef)
558 {
559 #ifndef SERVER
560         //NOTE this is only thread safe for ONE consumer thread!
561         while (!m_get_clientcached_queue.empty()) {
562
563                 GetRequest<std::string, ClientCached*, u8, u8>
564                                 request = m_get_clientcached_queue.pop();
565
566                 m_get_clientcached_queue.pushResult(request,
567                                 createClientCachedDirect(request.key, gamedef));
568         }
569 #endif
570 }
571
572 #ifndef SERVER
573 /******************************************************************************/
574 void CItemDefManager::createNodeItemTexture(const std::string& name,
575                 const ItemDefinition& def, INodeDefManager* nodedef,
576                 ClientCached* cc, IGameDef* gamedef, ITextureSource* tsrc) const
577 {
578         // Get node properties
579         content_t id = nodedef->getId(name);
580         const ContentFeatures& f = nodedef->get(id);
581         bool need_rtt_mesh = cc->inventory_texture == NULL;
582         // Keep this in sync with WieldMeshSceneNode::setItem()
583         bool need_wield_mesh = !(f.mesh_ptr[0] || f.drawtype == NDT_NORMAL
584                         || f.drawtype == NDT_ALLFACES || f.drawtype == NDT_AIRLIKE);
585         scene::IMesh* node_mesh = NULL;
586
587         if (need_rtt_mesh || need_wield_mesh) {
588
589                 u8 param1 = 0;
590                 if (f.param_type == CPT_LIGHT)
591                         param1 = 0xee;
592
593                 /*
594                  Make a mesh from the node
595                  */
596                 MeshMakeData mesh_make_data(gamedef, false);
597                 u8 param2 = 0;
598                 if (f.param_type_2 == CPT2_WALLMOUNTED)
599                         param2 = 1;
600
601                 MapNode mesh_make_node(id, param1, param2);
602                 mesh_make_data.fillSingleNode(&mesh_make_node);
603                 MapBlockMesh mapblock_mesh(&mesh_make_data, v3s16(0, 0, 0));
604                 node_mesh = mapblock_mesh.getMesh();
605                 node_mesh->grab();
606                 video::SColor c(255, 255, 255, 255);
607                 setMeshColor(node_mesh, c);
608                 // scale and translate the mesh so it's a
609                 // unit cube centered on the origin
610                 scaleMesh(node_mesh, v3f(1.0 / BS, 1.0 / BS, 1.0 / BS));
611                 translateMesh(node_mesh, v3f(-1.0, -1.0, -1.0));
612         }
613         /*
614          Draw node mesh into a render target texture
615          */
616         if (need_rtt_mesh) {
617
618                 TextureFromMeshParams params;
619                 params.mesh = node_mesh;
620                 params.dim.set(64, 64);
621                 params.rtt_texture_name = "INVENTORY_" + def.name + "_RTT";
622                 params.delete_texture_on_shutdown = true;
623                 params.camera_position.set(0, 1.0, -1.5);
624                 params.camera_position.rotateXZBy(45);
625                 params.camera_lookat.set(0, 0, 0);
626                 // Set orthogonal projection
627                 params.camera_projection_matrix.buildProjectionMatrixOrthoLH(1.65,
628                                 1.65, 0, 100);
629                 params.ambient_light.set(1.0, 0.2, 0.2, 0.2);
630                 params.light_position.set(10, 100, -50);
631                 params.light_color.set(1.0, 0.5, 0.5, 0.5);
632                 params.light_radius = 1000;
633                 cc->inventory_texture = tsrc->generateTextureFromMesh(params);
634                 // render-to-target didn't work
635                 if (cc->inventory_texture == NULL) {
636
637                         cc->inventory_texture = tsrc->getTexture(f.tiledef[0].name);
638                 }
639         }
640         /*
641          Use the node mesh as the wield mesh
642          */
643         if (need_wield_mesh) {
644
645                 cc->wield_mesh = node_mesh;
646                 cc->wield_mesh->grab();
647                 // no way reference count can be smaller than 2 in this place!
648                 assert(cc->wield_mesh->getReferenceCount() >= 2);
649         }
650         if (node_mesh)
651                 node_mesh->drop();
652 }
653 #endif
654
655 /******************************************************************************/
656
657 IWritableItemDefManager* createItemDefManager()
658 {
659         return new CItemDefManager();
660 }