Skip to content

Commit 01ce57a

Browse files
committedOct 3, 2014
Clean up nodedef.cpp
1 parent 5baf379 commit 01ce57a

File tree

1 file changed

+562
-540
lines changed

1 file changed

+562
-540
lines changed
 

‎src/nodedef.cpp

+562-540
Original file line numberDiff line numberDiff line change
@@ -370,592 +370,612 @@ void ContentFeatures::deSerialize(std::istream &is)
370370
CNodeDefManager
371371
*/
372372

373-
class CNodeDefManager: public IWritableNodeDefManager
374-
{
373+
class CNodeDefManager: public IWritableNodeDefManager {
375374
public:
376-
void clear()
377-
{
378-
m_content_features.clear();
379-
m_name_id_mapping.clear();
380-
m_name_id_mapping_with_aliases.clear();
381-
m_group_to_items.clear();
382-
m_next_id = 0;
383-
384-
u32 initial_length = 0;
385-
initial_length = MYMAX(initial_length, CONTENT_UNKNOWN + 1);
386-
initial_length = MYMAX(initial_length, CONTENT_AIR + 1);
387-
initial_length = MYMAX(initial_length, CONTENT_IGNORE + 1);
388-
m_content_features.resize(initial_length);
389-
390-
// Set CONTENT_UNKNOWN
391-
{
392-
ContentFeatures f;
393-
f.name = "unknown";
394-
// Insert directly into containers
395-
content_t c = CONTENT_UNKNOWN;
396-
m_content_features[c] = f;
397-
addNameIdMapping(c, f.name);
398-
}
375+
CNodeDefManager();
376+
virtual ~CNodeDefManager();
377+
void clear();
378+
virtual IWritableNodeDefManager *clone();
379+
virtual const ContentFeatures& get(content_t c) const;
380+
virtual const ContentFeatures& get(const MapNode &n) const;
381+
virtual bool getId(const std::string &name, content_t &result) const;
382+
virtual content_t getId(const std::string &name) const;
383+
virtual void getIds(const std::string &name, std::set<content_t> &result) const;
384+
virtual const ContentFeatures& get(const std::string &name) const;
385+
content_t allocateId();
386+
virtual content_t set(const std::string &name, const ContentFeatures &def);
387+
virtual content_t allocateDummy(const std::string &name);
388+
virtual void updateAliases(IItemDefManager *idef);
389+
virtual void updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc);
390+
void serialize(std::ostream &os, u16 protocol_version);
391+
void deSerialize(std::istream &is);
399392

400-
// Set CONTENT_AIR
401-
{
402-
ContentFeatures f;
403-
f.name = "air";
404-
f.drawtype = NDT_AIRLIKE;
405-
f.param_type = CPT_LIGHT;
406-
f.light_propagates = true;
407-
f.sunlight_propagates = true;
408-
f.walkable = false;
409-
f.pointable = false;
410-
f.diggable = false;
411-
f.buildable_to = true;
412-
f.is_ground_content = true;
413-
// Insert directly into containers
414-
content_t c = CONTENT_AIR;
415-
m_content_features[c] = f;
416-
addNameIdMapping(c, f.name);
417-
}
393+
private:
394+
void addNameIdMapping(content_t i, std::string name);
395+
#ifndef SERVER
396+
void fillTileAttribs(ITextureSource *tsrc, TileSpec *tile, TileDef *tiledef,
397+
u32 shader_id, bool use_normal_texture, u8 alpha, u8 material_type);
398+
#endif
418399

419-
// Set CONTENT_IGNORE
420-
{
421-
ContentFeatures f;
422-
f.name = "ignore";
423-
f.drawtype = NDT_AIRLIKE;
424-
f.param_type = CPT_NONE;
425-
f.light_propagates = false;
426-
f.sunlight_propagates = false;
427-
f.walkable = false;
428-
f.pointable = false;
429-
f.diggable = false;
430-
f.buildable_to = true; // A way to remove accidental CONTENT_IGNOREs
431-
f.is_ground_content = true;
432-
// Insert directly into containers
433-
content_t c = CONTENT_IGNORE;
434-
m_content_features[c] = f;
435-
addNameIdMapping(c, f.name);
436-
}
437-
}
438-
CNodeDefManager()
439-
{
440-
clear();
441-
}
442-
virtual ~CNodeDefManager()
443-
{
444-
}
445-
virtual IWritableNodeDefManager* clone()
446-
{
447-
CNodeDefManager *mgr = new CNodeDefManager();
448-
*mgr = *this;
449-
return mgr;
450-
}
451-
virtual const ContentFeatures& get(content_t c) const
400+
// Features indexed by id
401+
std::vector<ContentFeatures> m_content_features;
402+
403+
// A mapping for fast converting back and forth between names and ids
404+
NameIdMapping m_name_id_mapping;
405+
406+
// Like m_name_id_mapping, but only from names to ids, and includes
407+
// item aliases too. Updated by updateAliases()
408+
// Note: Not serialized.
409+
410+
std::map<std::string, content_t> m_name_id_mapping_with_aliases;
411+
412+
// A mapping from groups to a list of content_ts (and their levels)
413+
// that belong to it. Necessary for a direct lookup in getIds().
414+
// Note: Not serialized.
415+
std::map<std::string, GroupItems> m_group_to_items;
416+
417+
// Next possibly free id
418+
content_t m_next_id;
419+
};
420+
421+
422+
CNodeDefManager::CNodeDefManager()
423+
{
424+
clear();
425+
}
426+
427+
428+
CNodeDefManager::~CNodeDefManager()
429+
{
430+
}
431+
432+
433+
void CNodeDefManager::clear()
434+
{
435+
m_content_features.clear();
436+
m_name_id_mapping.clear();
437+
m_name_id_mapping_with_aliases.clear();
438+
m_group_to_items.clear();
439+
m_next_id = 0;
440+
441+
u32 initial_length = 0;
442+
initial_length = MYMAX(initial_length, CONTENT_UNKNOWN + 1);
443+
initial_length = MYMAX(initial_length, CONTENT_AIR + 1);
444+
initial_length = MYMAX(initial_length, CONTENT_IGNORE + 1);
445+
m_content_features.resize(initial_length);
446+
447+
// Set CONTENT_UNKNOWN
452448
{
453-
if(c < m_content_features.size())
454-
return m_content_features[c];
455-
else
456-
return m_content_features[CONTENT_UNKNOWN];
449+
ContentFeatures f;
450+
f.name = "unknown";
451+
// Insert directly into containers
452+
content_t c = CONTENT_UNKNOWN;
453+
m_content_features[c] = f;
454+
addNameIdMapping(c, f.name);
457455
}
458-
virtual const ContentFeatures& get(const MapNode &n) const
456+
457+
// Set CONTENT_AIR
459458
{
460-
return get(n.getContent());
459+
ContentFeatures f;
460+
f.name = "air";
461+
f.drawtype = NDT_AIRLIKE;
462+
f.param_type = CPT_LIGHT;
463+
f.light_propagates = true;
464+
f.sunlight_propagates = true;
465+
f.walkable = false;
466+
f.pointable = false;
467+
f.diggable = false;
468+
f.buildable_to = true;
469+
f.is_ground_content = true;
470+
// Insert directly into containers
471+
content_t c = CONTENT_AIR;
472+
m_content_features[c] = f;
473+
addNameIdMapping(c, f.name);
461474
}
462-
virtual bool getId(const std::string &name, content_t &result) const
475+
476+
// Set CONTENT_IGNORE
463477
{
464-
std::map<std::string, content_t>::const_iterator
465-
i = m_name_id_mapping_with_aliases.find(name);
466-
if(i == m_name_id_mapping_with_aliases.end())
467-
return false;
468-
result = i->second;
469-
return true;
478+
ContentFeatures f;
479+
f.name = "ignore";
480+
f.drawtype = NDT_AIRLIKE;
481+
f.param_type = CPT_NONE;
482+
f.light_propagates = false;
483+
f.sunlight_propagates = false;
484+
f.walkable = false;
485+
f.pointable = false;
486+
f.diggable = false;
487+
f.buildable_to = true; // A way to remove accidental CONTENT_IGNOREs
488+
f.is_ground_content = true;
489+
// Insert directly into containers
490+
content_t c = CONTENT_IGNORE;
491+
m_content_features[c] = f;
492+
addNameIdMapping(c, f.name);
470493
}
471-
virtual content_t getId(const std::string &name) const
472-
{
494+
}
495+
496+
497+
IWritableNodeDefManager *CNodeDefManager::clone()
498+
{
499+
CNodeDefManager *mgr = new CNodeDefManager();
500+
*mgr = *this;
501+
return mgr;
502+
}
503+
504+
505+
const ContentFeatures& CNodeDefManager::get(content_t c) const
506+
{
507+
if (c < m_content_features.size())
508+
return m_content_features[c];
509+
else
510+
return m_content_features[CONTENT_UNKNOWN];
511+
}
512+
513+
514+
const ContentFeatures& CNodeDefManager::get(const MapNode &n) const
515+
{
516+
return get(n.getContent());
517+
}
518+
519+
520+
bool CNodeDefManager::getId(const std::string &name, content_t &result) const
521+
{
522+
std::map<std::string, content_t>::const_iterator
523+
i = m_name_id_mapping_with_aliases.find(name);
524+
if(i == m_name_id_mapping_with_aliases.end())
525+
return false;
526+
result = i->second;
527+
return true;
528+
}
529+
530+
531+
content_t CNodeDefManager::getId(const std::string &name) const
532+
{
533+
content_t id = CONTENT_IGNORE;
534+
getId(name, id);
535+
return id;
536+
}
537+
538+
539+
void CNodeDefManager::getIds(const std::string &name, std::set<content_t> &result)
540+
const
541+
{
542+
//TimeTaker t("getIds", NULL, PRECISION_MICRO);
543+
if (name.substr(0,6) != "group:") {
473544
content_t id = CONTENT_IGNORE;
474-
getId(name, id);
475-
return id;
545+
if(getId(name, id))
546+
result.insert(id);
547+
return;
476548
}
477-
virtual void getIds(const std::string &name, std::set<content_t> &result)
478-
const
479-
{
480-
//TimeTaker t("getIds", NULL, PRECISION_MICRO);
481-
if(name.substr(0,6) != "group:"){
482-
content_t id = CONTENT_IGNORE;
483-
if(getId(name, id))
484-
result.insert(id);
485-
return;
549+
std::string group = name.substr(6);
550+
551+
std::map<std::string, GroupItems>::const_iterator
552+
i = m_group_to_items.find(group);
553+
if (i == m_group_to_items.end())
554+
return;
555+
556+
const GroupItems &items = i->second;
557+
for (GroupItems::const_iterator j = items.begin();
558+
j != items.end(); ++j) {
559+
if ((*j).second != 0)
560+
result.insert((*j).first);
561+
}
562+
//printf("getIds: %dus\n", t.stop());
563+
}
564+
565+
566+
const ContentFeatures& CNodeDefManager::get(const std::string &name) const
567+
{
568+
content_t id = CONTENT_UNKNOWN;
569+
getId(name, id);
570+
return get(id);
571+
}
572+
573+
574+
// returns CONTENT_IGNORE if no free ID found
575+
content_t CNodeDefManager::allocateId()
576+
{
577+
for (content_t id = m_next_id;
578+
id >= m_next_id; // overflow?
579+
++id) {
580+
while (id >= m_content_features.size()) {
581+
m_content_features.push_back(ContentFeatures());
486582
}
487-
std::string group = name.substr(6);
488-
489-
std::map<std::string, GroupItems>::const_iterator
490-
i = m_group_to_items.find(group);
491-
if (i == m_group_to_items.end())
492-
return;
493-
494-
const GroupItems &items = i->second;
495-
for (GroupItems::const_iterator j = items.begin();
496-
j != items.end(); ++j) {
497-
if ((*j).second != 0)
498-
result.insert((*j).first);
583+
const ContentFeatures &f = m_content_features[id];
584+
if (f.name == "") {
585+
m_next_id = id + 1;
586+
return id;
499587
}
500-
//printf("getIds: %dus\n", t.stop());
501-
}
502-
virtual const ContentFeatures& get(const std::string &name) const
503-
{
504-
content_t id = CONTENT_UNKNOWN;
505-
getId(name, id);
506-
return get(id);
507588
}
508-
// returns CONTENT_IGNORE if no free ID found
509-
content_t allocateId()
510-
{
511-
for(content_t id = m_next_id;
512-
id >= m_next_id; // overflow?
513-
++id){
514-
while(id >= m_content_features.size()){
515-
m_content_features.push_back(ContentFeatures());
516-
}
517-
const ContentFeatures &f = m_content_features[id];
518-
if(f.name == ""){
519-
m_next_id = id + 1;
520-
return id;
521-
}
522-
}
523-
// If we arrive here, an overflow occurred in id.
524-
// That means no ID was found
589+
// If we arrive here, an overflow occurred in id.
590+
// That means no ID was found
591+
return CONTENT_IGNORE;
592+
}
593+
594+
595+
// IWritableNodeDefManager
596+
content_t CNodeDefManager::set(const std::string &name, const ContentFeatures &def)
597+
{
598+
assert(name != "");
599+
assert(name == def.name);
600+
601+
// Don't allow redefining ignore (but allow air and unknown)
602+
if (name == "ignore") {
603+
infostream << "NodeDefManager: WARNING: Ignoring "
604+
"CONTENT_IGNORE redefinition"<<std::endl;
525605
return CONTENT_IGNORE;
526606
}
527-
// IWritableNodeDefManager
528-
virtual content_t set(const std::string &name,
529-
const ContentFeatures &def)
530-
{
531-
assert(name != "");
532-
assert(name == def.name);
533607

534-
// Don't allow redefining ignore (but allow air and unknown)
535-
if(name == "ignore"){
536-
infostream<<"NodeDefManager: WARNING: Ignoring "
537-
<<"CONTENT_IGNORE redefinition"<<std::endl;
608+
content_t id = CONTENT_IGNORE;
609+
if (!m_name_id_mapping.getId(name, id)) { // ignore aliases
610+
// Get new id
611+
id = allocateId();
612+
if (id == CONTENT_IGNORE) {
613+
infostream << "NodeDefManager: WARNING: Absolute "
614+
"limit reached" << std::endl;
538615
return CONTENT_IGNORE;
539616
}
617+
assert(id != CONTENT_IGNORE);
618+
addNameIdMapping(id, name);
619+
}
620+
m_content_features[id] = def;
621+
verbosestream << "NodeDefManager: registering content id \"" << id
622+
<< "\": name=\"" << def.name << "\""<<std::endl;
540623

541-
content_t id = CONTENT_IGNORE;
542-
bool found = m_name_id_mapping.getId(name, id); // ignore aliases
543-
if(!found){
544-
// Get new id
545-
id = allocateId();
546-
if(id == CONTENT_IGNORE){
547-
infostream<<"NodeDefManager: WARNING: Absolute "
548-
<<"limit reached"<<std::endl;
549-
return CONTENT_IGNORE;
550-
}
551-
assert(id != CONTENT_IGNORE);
552-
addNameIdMapping(id, name);
553-
}
554-
m_content_features[id] = def;
555-
verbosestream<<"NodeDefManager: registering content id \""<<id
556-
<<"\": name=\""<<def.name<<"\""<<std::endl;
557-
558-
// Add this content to the list of all groups it belongs to
559-
// FIXME: This should remove a node from groups it no longer
560-
// belongs to when a node is re-registered
561-
for (ItemGroupList::const_iterator i = def.groups.begin();
562-
i != def.groups.end(); ++i) {
563-
std::string group_name = i->first;
564-
565-
std::map<std::string, GroupItems>::iterator
566-
j = m_group_to_items.find(group_name);
567-
if (j == m_group_to_items.end()) {
568-
m_group_to_items[group_name].push_back(
569-
std::make_pair(id, i->second));
570-
} else {
571-
GroupItems &items = j->second;
572-
items.push_back(std::make_pair(id, i->second));
573-
}
624+
// Add this content to the list of all groups it belongs to
625+
// FIXME: This should remove a node from groups it no longer
626+
// belongs to when a node is re-registered
627+
for (ItemGroupList::const_iterator i = def.groups.begin();
628+
i != def.groups.end(); ++i) {
629+
std::string group_name = i->first;
630+
631+
std::map<std::string, GroupItems>::iterator
632+
j = m_group_to_items.find(group_name);
633+
if (j == m_group_to_items.end()) {
634+
m_group_to_items[group_name].push_back(
635+
std::make_pair(id, i->second));
636+
} else {
637+
GroupItems &items = j->second;
638+
items.push_back(std::make_pair(id, i->second));
574639
}
575-
return id;
576640
}
577-
virtual content_t allocateDummy(const std::string &name)
578-
{
579-
assert(name != "");
580-
ContentFeatures f;
581-
f.name = name;
582-
return set(name, f);
583-
}
584-
virtual void updateAliases(IItemDefManager *idef)
585-
{
586-
std::set<std::string> all = idef->getAll();
587-
m_name_id_mapping_with_aliases.clear();
588-
for(std::set<std::string>::iterator
589-
i = all.begin(); i != all.end(); i++)
590-
{
591-
std::string name = *i;
592-
std::string convert_to = idef->getAlias(name);
593-
content_t id;
594-
if(m_name_id_mapping.getId(convert_to, id))
595-
{
596-
m_name_id_mapping_with_aliases.insert(
597-
std::make_pair(name, id));
598-
}
641+
return id;
642+
}
643+
644+
645+
content_t CNodeDefManager::allocateDummy(const std::string &name)
646+
{
647+
assert(name != "");
648+
ContentFeatures f;
649+
f.name = name;
650+
return set(name, f);
651+
}
652+
653+
654+
void CNodeDefManager::updateAliases(IItemDefManager *idef)
655+
{
656+
std::set<std::string> all = idef->getAll();
657+
m_name_id_mapping_with_aliases.clear();
658+
for (std::set<std::string>::iterator
659+
i = all.begin(); i != all.end(); i++) {
660+
std::string name = *i;
661+
std::string convert_to = idef->getAlias(name);
662+
content_t id;
663+
if (m_name_id_mapping.getId(convert_to, id)) {
664+
m_name_id_mapping_with_aliases.insert(
665+
std::make_pair(name, id));
599666
}
600667
}
601-
virtual void updateTextures(ITextureSource *tsrc,
602-
IShaderSource *shdsrc)
603-
{
668+
}
669+
670+
671+
void CNodeDefManager::updateTextures(ITextureSource *tsrc, IShaderSource *shdsrc)
672+
{
604673
#ifndef SERVER
605-
infostream<<"CNodeDefManager::updateTextures(): Updating "
606-
<<"textures in node definitions"<<std::endl;
607-
608-
bool new_style_water = g_settings->getBool("new_style_water");
609-
bool new_style_leaves = g_settings->getBool("new_style_leaves");
610-
bool connected_glass = g_settings->getBool("connected_glass");
611-
bool opaque_water = g_settings->getBool("opaque_water");
612-
bool enable_shaders = g_settings->getBool("enable_shaders");
613-
bool enable_bumpmapping = g_settings->getBool("enable_bumpmapping");
614-
bool enable_parallax_occlusion = g_settings->getBool("enable_parallax_occlusion");
615-
616-
for(u32 i=0; i<m_content_features.size(); i++)
617-
{
618-
ContentFeatures *f = &m_content_features[i];
619-
620-
// Figure out the actual tiles to use
621-
TileDef tiledef[6];
622-
for(u32 j = 0; j < 6; j++)
623-
{
624-
tiledef[j] = f->tiledef[j];
625-
if(tiledef[j].name == "")
626-
tiledef[j].name = "unknown_node.png";
627-
}
674+
infostream << "CNodeDefManager::updateTextures(): Updating "
675+
"textures in node definitions" << std::endl;
628676

629-
bool is_liquid = false;
630-
bool is_water_surface = false;
677+
bool new_style_water = g_settings->getBool("new_style_water");
678+
bool new_style_leaves = g_settings->getBool("new_style_leaves");
679+
bool connected_glass = g_settings->getBool("connected_glass");
680+
bool opaque_water = g_settings->getBool("opaque_water");
681+
bool enable_shaders = g_settings->getBool("enable_shaders");
682+
bool enable_bumpmapping = g_settings->getBool("enable_bumpmapping");
683+
bool enable_parallax_occlusion = g_settings->getBool("enable_parallax_occlusion");
631684

632-
u8 material_type;
633-
material_type = (f->alpha == 255) ? TILE_MATERIAL_BASIC : TILE_MATERIAL_ALPHA;
685+
bool use_normal_texture = enable_shaders &&
686+
(enable_bumpmapping || enable_parallax_occlusion);
634687

635-
switch(f->drawtype){
636-
default:
637-
case NDT_NORMAL:
638-
f->solidness = 2;
639-
break;
640-
case NDT_AIRLIKE:
641-
f->solidness = 0;
642-
break;
643-
case NDT_LIQUID:
644-
assert(f->liquid_type == LIQUID_SOURCE);
645-
if(opaque_water)
646-
f->alpha = 255;
647-
if(new_style_water){
648-
f->solidness = 0;
649-
} else {
650-
f->solidness = 1;
651-
f->backface_culling = false;
652-
}
653-
is_liquid = true;
654-
break;
655-
case NDT_FLOWINGLIQUID:
656-
assert(f->liquid_type == LIQUID_FLOWING);
657-
f->solidness = 0;
658-
if(opaque_water)
659-
f->alpha = 255;
660-
is_liquid = true;
661-
break;
662-
case NDT_GLASSLIKE:
663-
f->solidness = 0;
664-
f->visual_solidness = 1;
665-
break;
666-
case NDT_GLASSLIKE_FRAMED:
667-
f->solidness = 0;
668-
f->visual_solidness = 1;
669-
break;
670-
case NDT_GLASSLIKE_FRAMED_OPTIONAL:
671-
f->solidness = 0;
672-
f->visual_solidness = 1;
673-
if (connected_glass) {
674-
f->drawtype = NDT_GLASSLIKE_FRAMED;
675-
} else {
676-
f->drawtype = NDT_GLASSLIKE;
677-
}
678-
break;
679-
case NDT_ALLFACES:
680-
f->solidness = 0;
681-
f->visual_solidness = 1;
682-
break;
683-
case NDT_ALLFACES_OPTIONAL:
684-
if(new_style_leaves){
685-
f->drawtype = NDT_ALLFACES;
686-
f->solidness = 0;
687-
f->visual_solidness = 1;
688-
} else {
689-
f->drawtype = NDT_NORMAL;
690-
f->solidness = 2;
691-
for(u32 i=0; i<6; i++){
692-
tiledef[i].name += std::string("^[noalpha");
693-
}
694-
}
695-
if (f->waving == 1)
696-
material_type = TILE_MATERIAL_WAVING_LEAVES;
697-
break;
698-
case NDT_PLANTLIKE:
688+
for (u32 i = 0; i < m_content_features.size(); i++) {
689+
ContentFeatures *f = &m_content_features[i];
690+
691+
// Figure out the actual tiles to use
692+
TileDef tiledef[6];
693+
for (u32 j = 0; j < 6; j++) {
694+
tiledef[j] = f->tiledef[j];
695+
if (tiledef[j].name == "")
696+
tiledef[j].name = "unknown_node.png";
697+
}
698+
699+
bool is_liquid = false;
700+
bool is_water_surface = false;
701+
702+
u8 material_type = (f->alpha == 255) ?
703+
TILE_MATERIAL_BASIC : TILE_MATERIAL_ALPHA;
704+
705+
switch (f->drawtype) {
706+
default:
707+
case NDT_NORMAL:
708+
f->solidness = 2;
709+
break;
710+
case NDT_AIRLIKE:
711+
f->solidness = 0;
712+
break;
713+
case NDT_LIQUID:
714+
assert(f->liquid_type == LIQUID_SOURCE);
715+
if (opaque_water)
716+
f->alpha = 255;
717+
if (new_style_water){
699718
f->solidness = 0;
719+
} else {
720+
f->solidness = 1;
700721
f->backface_culling = false;
701-
if (f->waving == 1)
702-
material_type = TILE_MATERIAL_WAVING_PLANTS;
703-
break;
704-
case NDT_FIRELIKE:
705-
f->backface_culling = false;
706-
case NDT_TORCHLIKE:
707-
case NDT_SIGNLIKE:
708-
case NDT_FENCELIKE:
709-
case NDT_RAILLIKE:
710-
case NDT_NODEBOX:
722+
}
723+
is_liquid = true;
724+
break;
725+
case NDT_FLOWINGLIQUID:
726+
assert(f->liquid_type == LIQUID_FLOWING);
727+
f->solidness = 0;
728+
if (opaque_water)
729+
f->alpha = 255;
730+
is_liquid = true;
731+
break;
732+
case NDT_GLASSLIKE:
733+
f->solidness = 0;
734+
f->visual_solidness = 1;
735+
break;
736+
case NDT_GLASSLIKE_FRAMED:
737+
f->solidness = 0;
738+
f->visual_solidness = 1;
739+
break;
740+
case NDT_GLASSLIKE_FRAMED_OPTIONAL:
741+
f->solidness = 0;
742+
f->visual_solidness = 1;
743+
f->drawtype = connected_glass ? NDT_GLASSLIKE_FRAMED : NDT_GLASSLIKE;
744+
break;
745+
case NDT_ALLFACES:
746+
f->solidness = 0;
747+
f->visual_solidness = 1;
748+
break;
749+
case NDT_ALLFACES_OPTIONAL:
750+
if (new_style_leaves) {
751+
f->drawtype = NDT_ALLFACES;
711752
f->solidness = 0;
712-
break;
753+
f->visual_solidness = 1;
754+
} else {
755+
f->drawtype = NDT_NORMAL;
756+
f->solidness = 2;
757+
for (u32 i = 0; i < 6; i++)
758+
tiledef[i].name += std::string("^[noalpha");
713759
}
760+
if (f->waving == 1)
761+
material_type = TILE_MATERIAL_WAVING_LEAVES;
762+
break;
763+
case NDT_PLANTLIKE:
764+
f->solidness = 0;
765+
f->backface_culling = false;
766+
if (f->waving == 1)
767+
material_type = TILE_MATERIAL_WAVING_PLANTS;
768+
break;
769+
case NDT_FIRELIKE:
770+
f->backface_culling = false;
771+
case NDT_TORCHLIKE:
772+
case NDT_SIGNLIKE:
773+
case NDT_FENCELIKE:
774+
case NDT_RAILLIKE:
775+
case NDT_NODEBOX:
776+
f->solidness = 0;
777+
break;
778+
}
714779

715-
if (is_liquid){
716-
material_type = (f->alpha == 255) ? TILE_MATERIAL_LIQUID_OPAQUE : TILE_MATERIAL_LIQUID_TRANSPARENT;
717-
if (f->name == "default:water_source")
718-
is_water_surface = true;
719-
}
720-
u32 tile_shader[6];
721-
for(u16 j=0; j<6; j++)
722-
tile_shader[j] = shdsrc->getShader("nodes_shader",material_type, f->drawtype);
723-
724-
if (is_water_surface)
725-
tile_shader[0] = shdsrc->getShader("water_surface_shader",material_type, f->drawtype);
726-
727-
// Tiles (fill in f->tiles[])
728-
for(u16 j = 0; j < 6; j++){
729-
// Shader
730-
f->tiles[j].shader_id = tile_shader[j];
731-
// Texture
732-
f->tiles[j].texture = tsrc->getTexture(
733-
tiledef[j].name,
734-
&f->tiles[j].texture_id);
735-
// Normal texture
736-
if (enable_shaders && (enable_bumpmapping || enable_parallax_occlusion))
737-
f->tiles[j].normal_texture = tsrc->getNormalTexture(tiledef[j].name);
738-
// Alpha
739-
f->tiles[j].alpha = f->alpha;
740-
// Material type
741-
f->tiles[j].material_type = material_type;
742-
// Material flags
743-
f->tiles[j].material_flags = 0;
744-
if(f->backface_culling)
745-
f->tiles[j].material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
746-
if(tiledef[j].animation.type == TAT_VERTICAL_FRAMES)
747-
f->tiles[j].material_flags |= MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES;
748-
// Animation parameters
749-
int frame_count = 1;
750-
if(f->tiles[j].material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES) {
751-
// Get texture size to determine frame count by
752-
// aspect ratio
753-
v2u32 size = f->tiles[j].texture->getOriginalSize();
754-
int frame_height = (float)size.X /
755-
(float)tiledef[j].animation.aspect_w *
756-
(float)tiledef[j].animation.aspect_h;
757-
frame_count = size.Y / frame_height;
758-
int frame_length_ms = 1000.0 *
759-
tiledef[j].animation.length / frame_count;
760-
f->tiles[j].animation_frame_count = frame_count;
761-
f->tiles[j].animation_frame_length_ms = frame_length_ms;
762-
}
763-
if(frame_count == 1) {
764-
f->tiles[j].material_flags &= ~MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES;
765-
} else {
766-
std::ostringstream os(std::ios::binary);
767-
for (int i = 0; i < frame_count; i++) {
768-
FrameSpec frame;
769-
os.str("");
770-
os<<tiledef[j].name<<"^[verticalframe:"<<frame_count<<":"<<i;
771-
frame.texture = tsrc->getTexture(os.str(), &frame.texture_id);
772-
if (f->tiles[j].normal_texture)
773-
frame.normal_texture = tsrc->getNormalTexture(os.str());
774-
f->tiles[j].frames[i]=frame;
775-
}
776-
}
777-
}
778-
// Special tiles (fill in f->special_tiles[])
779-
for(u16 j=0; j<CF_SPECIAL_COUNT; j++){
780-
// Shader
781-
f->special_tiles[j].shader_id = tile_shader[j];
782-
// Texture
783-
f->special_tiles[j].texture = tsrc->getTexture(
784-
f->tiledef_special[j].name,
785-
&f->special_tiles[j].texture_id);
786-
// Normal texture
787-
if (enable_shaders && (enable_bumpmapping || enable_parallax_occlusion))
788-
f->special_tiles[j].normal_texture = tsrc->getNormalTexture(f->tiledef_special[j].name);
789-
// Alpha
790-
f->special_tiles[j].alpha = f->alpha;
791-
// Material type
792-
f->special_tiles[j].material_type = material_type;
793-
// Material flags
794-
f->special_tiles[j].material_flags = 0;
795-
if(f->tiledef_special[j].backface_culling)
796-
f->special_tiles[j].material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
797-
if(f->tiledef_special[j].animation.type == TAT_VERTICAL_FRAMES)
798-
f->special_tiles[j].material_flags |= MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES;
799-
// Animation parameters
800-
int frame_count = 1;
801-
if(f->special_tiles[j].material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES) {
802-
// Get texture size to determine frame count by
803-
// aspect ratio
804-
v2u32 size = f->special_tiles[j].texture->getOriginalSize();
805-
int frame_height = (float)size.X /
806-
(float)f->tiledef_special[j].animation.aspect_w *
807-
(float)f->tiledef_special[j].animation.aspect_h;
808-
frame_count = size.Y / frame_height;
809-
int frame_length_ms = 1000.0 *
810-
f->tiledef_special[j].animation.length / frame_count;
811-
f->special_tiles[j].animation_frame_count = frame_count;
812-
f->special_tiles[j].animation_frame_length_ms = frame_length_ms;
813-
}
814-
if(frame_count == 1) {
815-
f->special_tiles[j].material_flags &= ~MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES;
816-
} else {
817-
std::ostringstream os(std::ios::binary);
818-
for (int i = 0; i < frame_count; i++) {
819-
FrameSpec frame;
820-
os.str("");
821-
os<<f->tiledef_special[j].name<<"^[verticalframe:"<<frame_count<<":"<<i;
822-
frame.texture = tsrc->getTexture(os.str(), &frame.texture_id);
823-
if (f->special_tiles[j].normal_texture)
824-
frame.normal_texture = tsrc->getNormalTexture(os.str());
825-
f->special_tiles[j].frames[i]=frame;
826-
}
827-
}
828-
}
780+
if (is_liquid) {
781+
material_type = (f->alpha == 255) ?
782+
TILE_MATERIAL_LIQUID_OPAQUE : TILE_MATERIAL_LIQUID_TRANSPARENT;
783+
if (f->name == "default:water_source")
784+
is_water_surface = true;
785+
}
786+
787+
u32 tile_shader[6];
788+
for (u16 j = 0; j < 6; j++) {
789+
tile_shader[j] = shdsrc->getShader("nodes_shader",
790+
material_type, f->drawtype);
791+
}
792+
793+
if (is_water_surface) {
794+
tile_shader[0] = shdsrc->getShader("water_surface_shader",
795+
material_type, f->drawtype);
796+
}
797+
798+
// Tiles (fill in f->tiles[])
799+
for (u16 j = 0; j < 6; j++) {
800+
fillTileAttribs(tsrc, &f->tiles[j], &tiledef[j], tile_shader[j],
801+
use_normal_texture, f->alpha, material_type);
802+
}
803+
804+
// Special tiles (fill in f->special_tiles[])
805+
for (u16 j = 0; j < CF_SPECIAL_COUNT; j++) {
806+
fillTileAttribs(tsrc, &f->special_tiles[j], &f->tiledef_special[j],
807+
tile_shader[j], use_normal_texture, f->alpha, material_type);
829808
}
809+
}
830810
#endif
811+
}
812+
813+
814+
#ifndef SERVER
815+
void CNodeDefManager::fillTileAttribs(ITextureSource *tsrc, TileSpec *tile,
816+
TileDef *tiledef, u32 shader_id, bool use_normal_texture,
817+
u8 alpha, u8 material_type)
818+
{
819+
tile->shader_id = shader_id;
820+
tile->texture = tsrc->getTexture(tiledef->name, &tile->texture_id);
821+
tile->alpha = alpha;
822+
tile->material_type = material_type;
823+
824+
// Normal texture
825+
if (use_normal_texture)
826+
tile->normal_texture = tsrc->getNormalTexture(tiledef->name);
827+
828+
// Material flags
829+
tile->material_flags = 0;
830+
if (tiledef->backface_culling)
831+
tile->material_flags |= MATERIAL_FLAG_BACKFACE_CULLING;
832+
if (tiledef->animation.type == TAT_VERTICAL_FRAMES)
833+
tile->material_flags |= MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES;
834+
835+
// Animation parameters
836+
int frame_count = 1;
837+
if (tile->material_flags & MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES) {
838+
// Get texture size to determine frame count by aspect ratio
839+
v2u32 size = tile->texture->getOriginalSize();
840+
int frame_height = (float)size.X /
841+
(float)tiledef->animation.aspect_w *
842+
(float)tiledef->animation.aspect_h;
843+
frame_count = size.Y / frame_height;
844+
int frame_length_ms = 1000.0 * tiledef->animation.length / frame_count;
845+
tile->animation_frame_count = frame_count;
846+
tile->animation_frame_length_ms = frame_length_ms;
831847
}
832-
void serialize(std::ostream &os, u16 protocol_version)
833-
{
834-
writeU8(os, 1); // version
835-
u16 count = 0;
836-
std::ostringstream os2(std::ios::binary);
837-
for(u32 i = 0; i < m_content_features.size(); i++)
838-
{
839-
if(i == CONTENT_IGNORE || i == CONTENT_AIR
840-
|| i == CONTENT_UNKNOWN)
841-
continue;
842-
ContentFeatures *f = &m_content_features[i];
843-
if(f->name == "")
844-
continue;
845-
writeU16(os2, i);
846-
// Wrap it in a string to allow different lengths without
847-
// strict version incompatibilities
848-
std::ostringstream wrapper_os(std::ios::binary);
849-
f->serialize(wrapper_os, protocol_version);
850-
os2<<serializeString(wrapper_os.str());
851-
852-
assert(count + 1 > count); // must not overflow
853-
count++;
848+
849+
if (frame_count == 1) {
850+
tile->material_flags &= ~MATERIAL_FLAG_ANIMATION_VERTICAL_FRAMES;
851+
} else {
852+
std::ostringstream os(std::ios::binary);
853+
for (int i = 0; i < frame_count; i++) {
854+
FrameSpec frame;
855+
856+
os.str("");
857+
os << tiledef->name << "^[verticalframe:"
858+
<< frame_count << ":" << i;
859+
860+
frame.texture = tsrc->getTexture(os.str(), &frame.texture_id);
861+
if (tile->normal_texture)
862+
frame.normal_texture = tsrc->getNormalTexture(os.str());
863+
tile->frames[i] = frame;
854864
}
855-
writeU16(os, count);
856-
os<<serializeLongString(os2.str());
857865
}
858-
void deSerialize(std::istream &is)
859-
{
860-
clear();
861-
int version = readU8(is);
862-
if(version != 1)
863-
throw SerializationError("unsupported NodeDefinitionManager version");
864-
u16 count = readU16(is);
865-
std::istringstream is2(deSerializeLongString(is), std::ios::binary);
866-
ContentFeatures f;
867-
for(u16 n = 0; n < count; n++){
868-
u16 i = readU16(is2);
869-
870-
// Read it from the string wrapper
871-
std::string wrapper = deSerializeString(is2);
872-
std::istringstream wrapper_is(wrapper, std::ios::binary);
873-
f.deSerialize(wrapper_is);
874-
875-
// Check error conditions
876-
if(i == CONTENT_IGNORE || i == CONTENT_AIR
877-
|| i == CONTENT_UNKNOWN){
878-
infostream<<"NodeDefManager::deSerialize(): WARNING: "
879-
<<"not changing builtin node "<<i
880-
<<std::endl;
881-
continue;
882-
}
883-
if(f.name == ""){
884-
infostream<<"NodeDefManager::deSerialize(): WARNING: "
885-
<<"received empty name"<<std::endl;
886-
continue;
887-
}
888-
u16 existing_id;
889-
bool found = m_name_id_mapping.getId(f.name, existing_id); // ignore aliases
890-
if(found && i != existing_id){
891-
infostream<<"NodeDefManager::deSerialize(): WARNING: "
892-
<<"already defined with different ID: "
893-
<<f.name<<std::endl;
894-
continue;
895-
}
866+
}
867+
#endif
896868

897-
// All is ok, add node definition with the requested ID
898-
if(i >= m_content_features.size())
899-
m_content_features.resize((u32)(i) + 1);
900-
m_content_features[i] = f;
901-
addNameIdMapping(i, f.name);
902-
verbosestream<<"deserialized "<<f.name<<std::endl;
903-
}
869+
870+
void CNodeDefManager::serialize(std::ostream &os, u16 protocol_version)
871+
{
872+
writeU8(os, 1); // version
873+
u16 count = 0;
874+
std::ostringstream os2(std::ios::binary);
875+
for (u32 i = 0; i < m_content_features.size(); i++) {
876+
if (i == CONTENT_IGNORE || i == CONTENT_AIR
877+
|| i == CONTENT_UNKNOWN)
878+
continue;
879+
ContentFeatures *f = &m_content_features[i];
880+
if (f->name == "")
881+
continue;
882+
writeU16(os2, i);
883+
// Wrap it in a string to allow different lengths without
884+
// strict version incompatibilities
885+
std::ostringstream wrapper_os(std::ios::binary);
886+
f->serialize(wrapper_os, protocol_version);
887+
os2<<serializeString(wrapper_os.str());
888+
889+
assert(count + 1 > count); // must not overflow
890+
count++;
904891
}
905-
private:
906-
void addNameIdMapping(content_t i, std::string name)
907-
{
908-
m_name_id_mapping.set(i, name);
909-
m_name_id_mapping_with_aliases.insert(std::make_pair(name, i));
892+
writeU16(os, count);
893+
os << serializeLongString(os2.str());
894+
}
895+
896+
897+
void CNodeDefManager::deSerialize(std::istream &is)
898+
{
899+
clear();
900+
int version = readU8(is);
901+
if (version != 1)
902+
throw SerializationError("unsupported NodeDefinitionManager version");
903+
u16 count = readU16(is);
904+
std::istringstream is2(deSerializeLongString(is), std::ios::binary);
905+
ContentFeatures f;
906+
for (u16 n = 0; n < count; n++) {
907+
u16 i = readU16(is2);
908+
909+
// Read it from the string wrapper
910+
std::string wrapper = deSerializeString(is2);
911+
std::istringstream wrapper_is(wrapper, std::ios::binary);
912+
f.deSerialize(wrapper_is);
913+
914+
// Check error conditions
915+
if (i == CONTENT_IGNORE || i == CONTENT_AIR || i == CONTENT_UNKNOWN) {
916+
infostream << "NodeDefManager::deSerialize(): WARNING: "
917+
"not changing builtin node " << i << std::endl;
918+
continue;
919+
}
920+
if (f.name == "") {
921+
infostream << "NodeDefManager::deSerialize(): WARNING: "
922+
"received empty name" << std::endl;
923+
continue;
924+
}
925+
926+
// Ignore aliases
927+
u16 existing_id;
928+
if (m_name_id_mapping.getId(f.name, existing_id) && i != existing_id) {
929+
infostream << "NodeDefManager::deSerialize(): WARNING: "
930+
"already defined with different ID: " << f.name << std::endl;
931+
continue;
932+
}
933+
934+
// All is ok, add node definition with the requested ID
935+
if (i >= m_content_features.size())
936+
m_content_features.resize((u32)(i) + 1);
937+
m_content_features[i] = f;
938+
addNameIdMapping(i, f.name);
939+
verbosestream << "deserialized " << f.name << std::endl;
910940
}
911-
private:
912-
// Features indexed by id
913-
std::vector<ContentFeatures> m_content_features;
914-
// A mapping for fast converting back and forth between names and ids
915-
NameIdMapping m_name_id_mapping;
916-
// Like m_name_id_mapping, but only from names to ids, and includes
917-
// item aliases too. Updated by updateAliases()
918-
// Note: Not serialized.
919-
std::map<std::string, content_t> m_name_id_mapping_with_aliases;
920-
// A mapping from groups to a list of content_ts (and their levels)
921-
// that belong to it. Necessary for a direct lookup in getIds().
922-
// Note: Not serialized.
923-
std::map<std::string, GroupItems> m_group_to_items;
924-
// Next possibly free id
925-
content_t m_next_id;
926-
};
941+
}
942+
927943

928-
IWritableNodeDefManager* createNodeDefManager()
944+
void CNodeDefManager::addNameIdMapping(content_t i, std::string name)
945+
{
946+
m_name_id_mapping.set(i, name);
947+
m_name_id_mapping_with_aliases.insert(std::make_pair(name, i));
948+
}
949+
950+
951+
IWritableNodeDefManager *createNodeDefManager()
929952
{
930953
return new CNodeDefManager();
931954
}
932955

933-
/*
934-
Serialization of old ContentFeatures formats
935-
*/
936956

957+
//// Serialization of old ContentFeatures formats
937958
void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version)
938959
{
939-
if(protocol_version == 13)
960+
if (protocol_version == 13)
940961
{
941962
writeU8(os, 5); // version
942963
os<<serializeString(name);
943964
writeU16(os, groups.size());
944-
for(ItemGroupList::const_iterator
945-
i = groups.begin(); i != groups.end(); i++){
965+
for (ItemGroupList::const_iterator
966+
i = groups.begin(); i != groups.end(); i++) {
946967
os<<serializeString(i->first);
947968
writeS16(os, i->second);
948969
}
949970
writeU8(os, drawtype);
950971
writeF1000(os, visual_scale);
951972
writeU8(os, 6);
952-
for(u32 i = 0; i < 6; i++)
973+
for (u32 i = 0; i < 6; i++)
953974
tiledef[i].serialize(os, protocol_version);
954975
//CF_SPECIAL_COUNT = 2 before cf ver. 7 and protocol ver. 24
955976
writeU8(os, 2);
956-
for(u32 i = 0; i < 2; i++){
977+
for (u32 i = 0; i < 2; i++)
957978
tiledef_special[i].serialize(os, protocol_version);
958-
}
959979
writeU8(os, alpha);
960980
writeU8(os, post_effect_color.getAlpha());
961981
writeU8(os, post_effect_color.getRed());
@@ -990,21 +1010,20 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version)
9901010
writeU8(os, 6); // version
9911011
os<<serializeString(name);
9921012
writeU16(os, groups.size());
993-
for(ItemGroupList::const_iterator
994-
i = groups.begin(); i != groups.end(); i++){
1013+
for (ItemGroupList::const_iterator
1014+
i = groups.begin(); i != groups.end(); i++) {
9951015
os<<serializeString(i->first);
9961016
writeS16(os, i->second);
9971017
}
9981018
writeU8(os, drawtype);
9991019
writeF1000(os, visual_scale);
10001020
writeU8(os, 6);
1001-
for(u32 i = 0; i < 6; i++)
1021+
for (u32 i = 0; i < 6; i++)
10021022
tiledef[i].serialize(os, protocol_version);
10031023
//CF_SPECIAL_COUNT = 2 before cf ver. 7 and protocol ver. 24
10041024
writeU8(os, 2);
1005-
for(u32 i = 0; i < 2; i++){
1025+
for (u32 i = 0; i < 2; i++)
10061026
tiledef_special[i].serialize(os, protocol_version);
1007-
}
10081027
writeU8(os, alpha);
10091028
writeU8(os, post_effect_color.getAlpha());
10101029
writeU8(os, post_effect_color.getRed());
@@ -1040,12 +1059,14 @@ void ContentFeatures::serializeOld(std::ostream &os, u16 protocol_version)
10401059
writeU8(os, leveled);
10411060
writeU8(os, liquid_range);
10421061
} else
1043-
throw SerializationError("ContentFeatures::serialize(): Unsupported version requested");
1062+
throw SerializationError("ContentFeatures::serialize(): "
1063+
"Unsupported version requested");
10441064
}
10451065

1066+
10461067
void ContentFeatures::deSerializeOld(std::istream &is, int version)
10471068
{
1048-
if(version == 5) // In PROTOCOL_VERSION 13
1069+
if (version == 5) // In PROTOCOL_VERSION 13
10491070
{
10501071
name = deSerializeString(is);
10511072
groups.clear();
@@ -1057,13 +1078,13 @@ void ContentFeatures::deSerializeOld(std::istream &is, int version)
10571078
}
10581079
drawtype = (enum NodeDrawType)readU8(is);
10591080
visual_scale = readF1000(is);
1060-
if(readU8(is) != 6)
1081+
if (readU8(is) != 6)
10611082
throw SerializationError("unsupported tile count");
1062-
for(u32 i=0; i<6; i++)
1083+
for (u32 i = 0; i < 6; i++)
10631084
tiledef[i].deSerialize(is);
1064-
if(readU8(is) != CF_SPECIAL_COUNT)
1085+
if (readU8(is) != CF_SPECIAL_COUNT)
10651086
throw SerializationError("unsupported CF_SPECIAL_COUNT");
1066-
for(u32 i=0; i<CF_SPECIAL_COUNT; i++)
1087+
for (u32 i = 0; i < CF_SPECIAL_COUNT; i++)
10671088
tiledef_special[i].deSerialize(is);
10681089
alpha = readU8(is);
10691090
post_effect_color.setAlpha(readU8(is));
@@ -1098,21 +1119,21 @@ void ContentFeatures::deSerializeOld(std::istream &is, int version)
10981119
name = deSerializeString(is);
10991120
groups.clear();
11001121
u32 groups_size = readU16(is);
1101-
for(u32 i=0; i<groups_size; i++){
1122+
for (u32 i = 0; i < groups_size; i++) {
11021123
std::string name = deSerializeString(is);
11031124
int value = readS16(is);
11041125
groups[name] = value;
11051126
}
11061127
drawtype = (enum NodeDrawType)readU8(is);
11071128
visual_scale = readF1000(is);
1108-
if(readU8(is) != 6)
1129+
if (readU8(is) != 6)
11091130
throw SerializationError("unsupported tile count");
1110-
for(u32 i=0; i<6; i++)
1131+
for (u32 i = 0; i < 6; i++)
11111132
tiledef[i].deSerialize(is);
11121133
// CF_SPECIAL_COUNT in version 6 = 2
1113-
if(readU8(is) != 2)
1134+
if (readU8(is) != 2)
11141135
throw SerializationError("unsupported CF_SPECIAL_COUNT");
1115-
for(u32 i=0; i<2; i++)
1136+
for (u32 i = 0; i < 2; i++)
11161137
tiledef_special[i].deSerialize(is);
11171138
alpha = readU8(is);
11181139
post_effect_color.setAlpha(readU8(is));
@@ -1148,6 +1169,7 @@ void ContentFeatures::deSerializeOld(std::istream &is, int version)
11481169
drowning = readU8(is);
11491170
leveled = readU8(is);
11501171
liquid_range = readU8(is);
1151-
} else
1172+
} else {
11521173
throw SerializationError("unsupported ContentFeatures version");
1174+
}
11531175
}

0 commit comments

Comments
 (0)
Please sign in to comment.