diff --git a/AI/VCAI/MapObjectsEvaluator.cpp b/AI/VCAI/MapObjectsEvaluator.cpp index e528d4d37..516e72b2a 100644 --- a/AI/VCAI/MapObjectsEvaluator.cpp +++ b/AI/VCAI/MapObjectsEvaluator.cpp @@ -32,10 +32,6 @@ MapObjectsEvaluator::MapObjectsEvaluator() { objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = handler->getAiValue().get(); } - else if(VLC->objtypeh->getObjGroupAiValue(primaryID) != boost::none) //if value is not initialized - fallback to default value for this object family if it exists - { - objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = VLC->objtypeh->getObjGroupAiValue(primaryID).get(); - } else //some default handling when aiValue not found, objects that require advanced properties (unavailable from handler) get their value calculated in getObjectValue { objectDatabase[CompoundMapObjectID(primaryID, secondaryID)] = 0; diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index f8371c27a..ae0620877 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -1371,7 +1371,7 @@ void CMapHandler::getTerrainDescr(const int3 & pos, std::string & out, bool isRM if(t.hasFavorableWinds()) { - out = CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS); + out = CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS, 0); return; } const TerrainTile2 & tt = ttiles[pos.z][pos.x][pos.y]; diff --git a/config/objects/creatureBanks.json b/config/objects/creatureBanks.json index 2a841cd6d..1712333b7 100644 --- a/config/objects/creatureBanks.json +++ b/config/objects/creatureBanks.json @@ -2,6 +2,7 @@ "creatureBank" : { "index" :16, "handler": "bank", + "lastReservedIndex" : 6, "base" : { "sounds" : { "visit" : ["ROGUE"] diff --git a/config/objects/dwellings.json b/config/objects/dwellings.json index 39bf11ac3..43e1fc944 100644 --- a/config/objects/dwellings.json +++ b/config/objects/dwellings.json @@ -2,6 +2,7 @@ "creatureGeneratorCommon" : { "index" :17, "handler": "dwelling", + "lastReservedIndex" : 79, "base" : { "base" : { "visitableFrom" : [ "---", "+++", "+++" ], @@ -528,6 +529,12 @@ "creatureGeneratorSpecial" : { "index" :20, "handler": "dwelling", + "lastReservedIndex" : 1, + "base" : { + "base" : { + "visitableFrom" : [ "---", "+++", "+++" ] + } + }, "types" : { "elementalConflux" : { "index" : 0, diff --git a/config/objects/generic.json b/config/objects/generic.json index 99c7838b0..859da47e9 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -238,8 +238,8 @@ }, "types" : { "object" : { - "index" : 0 }, - "objectWoG" : { "index" : 1 } // WoG object? Present on VCMI Test 2011b + "index" : 0 + } } }, @@ -341,9 +341,6 @@ "value" : 3000, "rarity" : 100 } - }, - "object1":{ //WoG? - "index" :1 } } }, @@ -573,9 +570,6 @@ "value" : 1500, "rarity" : 80 } - }, - "object1" : { //WoG? - "index" : 1 } } }, @@ -1042,10 +1036,5 @@ "grassHills" : { "index" :208, "handler": "static", "types" : { "object" : { "index" : 0} } }, "roughHills" : { "index" :209, "handler": "static", "types" : { "object" : { "index" : 0} } }, "subterraneanRocks" : { "index" :210, "handler": "static", "types" : { "object" : { "index" : 0} } }, - "swampFoliage" : { "index" :211, "handler": "static", "types" : { "object" : { "index" : 0} } }, - - //These are WoG objects? They are not available in H3 - "frozenLakeDUPLICATE" : { "index" :172, "handler": "static", "types" : { "object" : { "index" : 0} } }, - "oakTreesDUPLICATE" : { "index" :186, "handler": "static", "types" : { "object" : { "index" : 0} } }, - "plant" : { "index" :189, "handler": "static", "types" : { "object" : { "index" : 0} } } + "swampFoliage" : { "index" :211, "handler": "static", "types" : { "object" : { "index" : 0} } } } diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 8d47f61d0..d7f39908b 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -23,8 +23,8 @@ "hero" : { "index" :34, "handler": "hero", - "defaultAiValue" : 7500, "base" : { + "aiValue" : 7500, "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VVV", "VAV"] @@ -54,6 +54,7 @@ "resource" : { "index" :79, "handler": "resource", + "lastReservedIndex" : 6, "base" : { "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], @@ -67,8 +68,7 @@ "sulfur" : { "index" : 3, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } }, "crystal" : { "index" : 4, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } }, "gems" : { "index" : 5, "aiValue" : 2000, "rmg" : { "value" : 2000, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgems0.def" } } }, - "gold" : { "index" : 6, "aiValue" : 750, "rmg" : { "value" : 750, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgold0.def" } } }, - "mithril" : { "index" : 7, "aiValue" : 3500 } // TODO: move to WoG? + "gold" : { "index" : 6, "aiValue" : 750, "rmg" : { "value" : 750, "rarity" : 300 }, "templates" : { "res" : { "animation" : "AVTgold0.def" } } } } }, @@ -76,8 +76,8 @@ "town" : { "index" :98, "handler": "town", - "defaultAiValue" : 20000, "base" : { + "aiValue" : 20000, "filters" : { // village image - fort not present "village" : [ "noneOf", [ "fort" ] ], @@ -106,8 +106,9 @@ "boat" : { "index" :8, "handler": "boat", - "defaultAiValue" : 0, + "lastReservedIndex" : 2, "base" : { + "aiValue" : 0, "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VVV", "VAV" ] @@ -124,8 +125,9 @@ "borderGuard" : { "index" :9, "handler": "borderGuard", - "defaultAiValue" : 0, + "lastReservedIndex" : 7, "base" : { + "aiValue" : 0, "sounds" : { "visit" : ["CAVEHEAD"], "removal" : [ "PICKUP01", "PICKUP02", "PICKUP03", "PICKUP04", "PICKUP05", "PICKUP06", "PICKUP07" ] @@ -145,8 +147,9 @@ "borderGate" : { "index" :212, "handler": "borderGate", - "defaultAiValue" : 0, + "lastReservedIndex" : 7, "base" : { + "aiValue" : 0, "sounds" : { "visit" : ["CAVEHEAD"] } @@ -165,8 +168,9 @@ "keymasterTent" : { "index" :10, "handler": "keymaster", - "defaultAiValue" : 10000, + "lastReservedIndex" : 7, "base" : { + "aiValue" : 10000, "sounds" : { "visit" : ["CAVEHEAD"] } @@ -186,8 +190,9 @@ "seerHut" : { "index" :83, "handler": "seerHut", - "defaultAiValue" : 10000, + "lastReservedIndex" : 2, "base" : { + "aiValue" : 10000, "base" : { "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VVV", "VVV", "VAV" ] @@ -207,6 +212,7 @@ "cartographer" : { "index" :13, "handler": "cartographer", + "lastReservedIndex" : 2, "base" : { "sounds" : { "visit" : ["LIGHTHOUSE"] @@ -223,6 +229,7 @@ "mine" : { "index" :53, "handler": "mine", + "lastReservedIndex" : 7, "base" : { "sounds" : { "visit" : ["FLAGMINE"] @@ -237,8 +244,7 @@ }, "sounds" : { "ambient" : ["LOOPLUMB"] - }, - "battleground": "subterranean" + } }, "alchemistLab" : { "index" : 1, @@ -248,8 +254,7 @@ }, "sounds" : { "ambient" : ["LOOPSTAR"] - }, - "battleground": "subterranean" + } }, "orePit" : { "index" : 2, @@ -259,8 +264,7 @@ }, "sounds" : { "ambient" : ["LOOPSULF"] - }, - "battleground": "subterranean" + } }, "sulfurDune" : { "index" : 3, @@ -270,8 +274,7 @@ }, "sounds" : { "ambient" : ["LOOPSULF"] - }, - "battleground": "subterranean" + } }, "crystalCavern" : { "index" : 4, @@ -292,8 +295,7 @@ }, "sounds" : { "ambient" : ["LOOPGEMP"] - }, - "battleground": "subterranean" + } }, "goldMine" : { "index" : 6, @@ -320,8 +322,9 @@ "abandonedMine" : { "index" :220, "handler": "mine", - "defaultAiValue" : 3500, + "lastReservedIndex" : 7, "base" : { + "aiValue" : 3500, "sounds" : { "ambient" : ["LOOPCAVE"] } @@ -334,8 +337,9 @@ "garrisonHorizontal": { "index" :33, "handler": "garrison", - "defaultAiValue" : 0, + "lastReservedIndex" : 1, "base" : { + "aiValue" : 0, "sounds" : { "visit" : ["MILITARY"] } @@ -358,8 +362,9 @@ "garrisonVertical" : { "index" :219, "handler": "garrison", - "defaultAiValue" : 0, + "lastReservedIndex" : 1, "base" : { + "aiValue" : 0, "sounds" : { "visit" : ["MILITARY"] } @@ -384,6 +389,7 @@ "monolithOneWayEntrance" : { "index" :43, "handler": "monolith", + "lastReservedIndex" : 7, "base" : { "sounds" : { "ambient" : ["LOOPMON1"], @@ -404,6 +410,7 @@ "monolithOneWayExit" : { "index" :44, "handler": "monolith", + "lastReservedIndex" : 7, "base" : { "sounds" : { "ambient" : ["LOOPMON1"] } }, @@ -421,6 +428,7 @@ "monolithTwoWay" : { "index" :45, "handler": "monolith", + "lastReservedIndex" : 7, "base" : { "sounds" : { "ambient" : ["LOOPMON2"], @@ -441,7 +449,9 @@ // subtype: level "randomDwellingLvl" : { - "index" :217, "handler": "randomDwelling", + "index" :217, + "handler": "randomDwelling", + "lastReservedIndex" : 6, "types" : { "objectLvl1" : { "index" : 0}, "objectLvl2" : { "index" : 1}, @@ -457,6 +467,7 @@ "randomDwellingFaction" : { "index" :218, "handler": "randomDwelling", + "lastReservedIndex" : 8, "types" : { "objectCastle" : { "index" : 0}, "objectRampart" : { "index" : 1}, diff --git a/config/schemas/object.json b/config/schemas/object.json index 7c1daba0e..ad357ab7c 100644 --- a/config/schemas/object.json +++ b/config/schemas/object.json @@ -10,49 +10,18 @@ "index": { "type":"number" }, - "name": { - "type":"string" - }, - "defaultAiValue": { + "lastReservedIndex" : { "type":"number" }, - "handler": { "type":"string" }, - - "sounds": { - "type":"object", - "additionalProperties" : false, - "description": "Sounds used by this object", - "properties" : { - "ambient": { - "type":"array", - "description": "Background sound of an object", - "items": { - "type": "string" - } - }, - "visit": { - "type":"array", - "description": "Sound that played on object visit", - "items": { - "type": "string" - } - }, - "removal": { - "type":"array", - "description": "Sound that played on object removal", - "items": { - "type": "string" - } - } - } - }, - "base": { "type" : "object" }, + "name": { + "type":"string" + }, "types": { "type":"object", "additionalProperties": { diff --git a/config/schemas/objectType.json b/config/schemas/objectType.json index b5e80c935..49cc6eebd 100644 --- a/config/schemas/objectType.json +++ b/config/schemas/objectType.json @@ -10,13 +10,9 @@ "index": { "type":"number" }, - "name": { - "type":"string" - }, "aiValue": { "type":"number" }, - "sounds": { "type":"object", "additionalProperties" : false, diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 35e70ea73..0d218b2ad 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -390,7 +390,7 @@ CArtifact * CArtHandler::loadFromJson(const std::string & scope, const JsonNode JsonNode conf; conf.setMeta(scope); - VLC->objtypeh->loadSubObject(art->getJsonKey(), conf, Obj::ARTIFACT, art->getIndex()); + VLC->objtypeh->loadSubObject(art->identifier, conf, Obj::ARTIFACT, art->getIndex()); if(!art->advMapDef.empty()) { diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 53f1dd32a..f3b181161 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -129,7 +129,7 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst } else if(type == OBJ_NAMES) { - dst = VLC->objtypeh->getObjectName(ser); + dst = VLC->objtypeh->getObjectName(ser, 0); } else if(type == SEC_SKILL_NAME) { @@ -149,10 +149,10 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst dst = VLC->generaltexth->translate("core.arraytxt", ser); break; case CREGENS: - dst = VLC->generaltexth->translate("core.crgen1", ser); + dst = VLC->objtypeh->getObjectName(Obj::CREATURE_GENERATOR1, ser); break; case CREGENS4: - dst = VLC->generaltexth->translate("core.crgen4", ser); + dst = VLC->objtypeh->getObjectName(Obj::CREATURE_GENERATOR4, ser); break; case ADVOB_TXT: dst = VLC->generaltexth->translate("core.advevent", ser); diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index e6823d6ce..b29fa168e 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -440,8 +440,6 @@ CGeneralTextHandler::CGeneralTextHandler(): readToVector("core.advevent", "DATA/ADVEVENT.TXT" ); readToVector("core.restypes", "DATA/RESTYPES.TXT" ); readToVector("core.randsign", "DATA/RANDSIGN.TXT" ); - readToVector("core.crgen1", "DATA/CRGEN1.TXT" ); - readToVector("core.crgen4", "DATA/CRGEN4.TXT" ); readToVector("core.overview", "DATA/OVERVIEW.TXT" ); readToVector("core.arraytxt", "DATA/ARRAYTXT.TXT" ); readToVector("core.priskill", "DATA/PRISKILL.TXT" ); diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index 65ce1692a..82f90f2f1 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -160,6 +160,7 @@ class DLL_LINKAGE CGeneralTextHandler /// Attempts to detect encoding & language of H3 files void detectInstallParameters() const; public: + /// Loads translation from provided json /// Any entries loaded by this will have priority over texts registered normally void loadTranslationOverrides(JsonNode const & file); diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index bcb8bd1a4..117703ea9 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -92,7 +92,7 @@ CObjectClassesHandler::CObjectClassesHandler() CObjectClassesHandler::~CObjectClassesHandler() { for(auto p : objects) - delete p.second; + delete p; } std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) @@ -112,6 +112,8 @@ std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) legacyTemplates.insert(std::make_pair(key, std::shared_ptr(tmpl))); } + objects.resize(256); + std::vector ret(dataSize);// create storage for 256 objects assert(dataSize == 256); @@ -122,171 +124,170 @@ std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) namesParser.endLine(); } + JsonNode cregen1; + JsonNode cregen4; + CLegacyConfigParser cregen1Parser("data/crgen1"); do - customNames[Obj::CREATURE_GENERATOR1].push_back(cregen1Parser.readString()); + { + JsonNode subObject; + subObject["name"].String() = cregen1Parser.readString(); + cregen1.Vector().push_back(subObject); + } while(cregen1Parser.endLine()); CLegacyConfigParser cregen4Parser("data/crgen4"); do - customNames[Obj::CREATURE_GENERATOR4].push_back(cregen4Parser.readString()); + { + JsonNode subObject; + subObject["name"].String() = cregen4Parser.readString(); + cregen4.Vector().push_back(subObject); + } while(cregen4Parser.endLine()); + ret[Obj::CREATURE_GENERATOR1]["subObjects"] = cregen1; + ret[Obj::CREATURE_GENERATOR4]["subObjects"] = cregen4; + + ret[Obj::REFUGEE_CAMP]["subObjects"].Vector().push_back(ret[Obj::REFUGEE_CAMP]); + ret[Obj::WAR_MACHINE_FACTORY]["subObjects"].Vector().push_back(ret[Obj::WAR_MACHINE_FACTORY]); + return ret; } -/// selects preferred ID (or subID) for new object -template -si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 fixedObjectsBound) +void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj) { - assert(fixedObjectsBound > 0); - if(fixedID.isNull()) - { - auto lastID = map.empty() ? 0 : map.rbegin()->first; - return lastID < fixedObjectsBound ? fixedObjectsBound : lastID + 1; - } - auto id = static_cast(fixedID.Float()); - if(id >= fixedObjectsBound) - logGlobal->error("Getting next ID overflowed: %d >= %d", id, fixedObjectsBound); + auto object = loadSubObjectFromJson(scope, identifier, entry, obj, obj->objects.size()); - return id; + assert(object); + obj->objects.push_back(object); + + registerObject(scope, obj->getJsonKey(), object->getSubTypeName(), object->subtype); } -void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, const JsonNode & entry, ObjectContainter * obj, bool isSubobject) +void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index) { - static const si32 fixedObjectsBound = 1000; // legacy value for backward compatibilitty - static const si32 fixedSubobjectsBound = 10000000; // large enough arbitrary value to avoid ID-collisions - si32 usedBound = fixedObjectsBound; + auto object = loadSubObjectFromJson(scope, identifier, entry, obj, index); + + assert(object); + assert(obj->objects[index] == nullptr); // ensure that this id was not loaded before + obj->objects[index] = object; + + registerObject(scope, obj->getJsonKey(), object->getSubTypeName(), object->subtype); +} + +TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index) +{ + assert(identifier.find(':') == std::string::npos); + assert(!scope.empty()); if(!handlerConstructors.count(obj->handlerName)) { logGlobal->error("Handler with name %s was not found!", obj->handlerName); - return; - } - const auto convertedId = VLC->modh->normalizeIdentifier(entry.meta, CModHandler::scopeBuiltin(), identifier); - const auto & entryIndex = entry["index"]; - bool useSelectNextID = !isSubobject || entryIndex.isNull(); - - if(useSelectNextID && isSubobject) - { - usedBound = fixedSubobjectsBound; - logGlobal->error("Subobject index is Null. convertedId = '%s' obj->id = %d", convertedId, obj->id); - } - si32 id = useSelectNextID ? selectNextID(entryIndex, obj->subObjects, usedBound) - : (si32)entryIndex.Float(); - - auto handler = handlerConstructors.at(obj->handlerName)(); - handler->setType(obj->id, id); - handler->setTypeName(obj->identifier, convertedId); - - if (customNames.count(obj->id) && customNames.at(obj->id).size() > id) - handler->init(entry, customNames.at(obj->id).at(id)); - else - handler->init(entry); - - //if (handler->getTemplates().empty()) - { - auto range = legacyTemplates.equal_range(std::make_pair(obj->id, id)); - for (auto & templ : boost::make_iterator_range(range.first, range.second)) - { - handler->addTemplate(templ.second); - } - legacyTemplates.erase(range.first, range.second); + return nullptr; } - logGlobal->debug("Loaded object %s(%d)::%s(%d)", obj->identifier, obj->id, convertedId, id); + auto createdObject = handlerConstructors.at(obj->handlerName)(); - //some mods redefine content handlers in the decoration.json in such way: - //"core:sign" : { "types" : { "forgeSign" : { ... - static const std::vector breakersRMG - { - "hota.hota decorations:hotaPandoraBox" - , "hota.hota decorations:hotaSubterreanGate" - }; - const bool isExistingKey = obj->subObjects.count(id) > 0; - const bool isBreaker = std::any_of(breakersRMG.begin(), breakersRMG.end(), - [&handler](const std::string & str) - { - return str.compare(handler->subTypeName) == 0; - }); - const bool passedHandler = !isExistingKey && !isBreaker; + createdObject->modScope = scope; + createdObject->typeName = obj->identifier;; + createdObject->subTypeName = identifier; - if(passedHandler) + createdObject->type = obj->id; + createdObject->subtype = index; + createdObject->init(entry); + + auto range = legacyTemplates.equal_range(std::make_pair(obj->id, index)); + for (auto & templ : boost::make_iterator_range(range.first, range.second)) { - obj->subObjects[id] = handler; - obj->subIds[convertedId] = id; - } - else if(isExistingKey) //It's supposed that fan mods handlers are not overridden by default handlers - { - logGlobal->trace("Handler '%s' has not been overridden with handler '%s' in object %s(%d)::%s(%d)", - obj->subObjects[id]->subTypeName, obj->handlerName, obj->identifier, obj->id, convertedId, id); - } - else - { - logGlobal->warn("Handler '%s' for object %s(%d)::%s(%d) has not been activated as RMG breaker", - obj->handlerName, obj->identifier, obj->id, convertedId, id); + createdObject->addTemplate(templ.second); } + legacyTemplates.erase(range.first, range.second); + + logGlobal->debug("Loaded object %s(%d)::%s(%d)", obj->getJsonKey(), obj->id, identifier, index); + + return createdObject; } -CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name) +std::string ObjectClass::getJsonKey() const { - auto obj = new ObjectContainter(); - static const si32 fixedObjectsBound = 256; //Legacy value for backward compatibility + return modScope + ':' + identifier; +} +std::string ObjectClass::getNameTextID() const +{ + return TextIdentifier("object", modScope, identifier, "name").get(); +} + +std::string ObjectClass::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + +ObjectClass * CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index) +{ + auto obj = new ObjectClass(); + + obj->modScope = scope; obj->identifier = name; - obj->name = json["name"].String(); obj->handlerName = json["handler"].String(); obj->base = json["base"]; - obj->id = selectNextID(json["index"], objects, fixedObjectsBound); + obj->id = index; - if(json["defaultAiValue"].isNull()) - obj->groupDefaultAiValue = boost::none; - else - obj->groupDefaultAiValue = static_cast>(json["defaultAiValue"].Integer()); + VLC->generaltexth->registerString(obj->getNameTextID(), json["name"].String()); - for (auto entry : json["types"].Struct()) - loadObjectEntry(entry.first, entry.second, obj); + obj->objects.resize(json["lastReservedIndex"].Float() + 1); + for (auto subData : json["types"].Struct()) + { + if (!subData.second["index"].isNull()) + { + std::string const & subMeta = subData.second["index"].meta; + + if ( subMeta != "core") + logMod->warn("Object %s:%s.%s - attempt to load object with preset index! This option is reserved for built-in mod", subMeta, name, subData.first ); + size_t subIndex = subData.second["index"].Integer(); + loadSubObject(subData.second.meta, subData.first, subData.second, obj, subIndex); + } + else + loadSubObject(subData.second.meta, subData.first, subData.second, obj); + } return obj; } void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(scope, data, VLC->modh->normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name)); - objects[object->id] = object; + auto object = loadFromJson(scope, data, name, objects.size()); + objects.push_back(object); VLC->modh->identifiers.registerObject(scope, "object", name, object->id); } void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto object = loadFromJson(scope, data, VLC->modh->normalizeIdentifier(scope, CModHandler::scopeBuiltin(), name)); + auto object = loadFromJson(scope, data, name, index); assert(objects[(si32)index] == nullptr); // ensure that this id was not loaded before objects[(si32)index] = object; VLC->modh->identifiers.registerObject(scope, "object", name, object->id); } -void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, si32 ID, boost::optional subID) +void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, si32 ID, si32 subID) { - static const bool isSubObject = true; - config.setType(JsonNode::JsonType::DATA_STRUCT); // ensure that input is not NULL - assert(objects.count(ID)); - if (subID) - { - assert(objects.at(ID)->subObjects.count(subID.get()) == 0); - assert(config["index"].isNull()); - config["index"].Float() = subID.get(); - config["index"].setMeta(config.meta); - } + assert(ID < objects.size()); + assert(objects[ID]); + + if ( subID >= objects[ID]->objects.size()) + objects[ID]->objects.resize(subID+1); + JsonUtils::inherit(config, objects.at(ID)->base); - loadObjectEntry(identifier, config, objects[ID], isSubObject); + loadSubObject(config.meta, identifier, config, objects[ID], subID); } void CObjectClassesHandler::removeSubObject(si32 ID, si32 subID) { - assert(objects.count(ID)); - assert(objects.at(ID)->subObjects.count(subID)); - objects.at(ID)->subObjects.erase(subID); //TODO: cleanup string id map + assert(ID < objects.size()); + assert(objects[ID]); + assert(subID < objects[ID]->objects.size()); + objects[ID]->objects[subID] = nullptr; } std::vector CObjectClassesHandler::getDefaultAllowed() const @@ -296,29 +297,25 @@ std::vector CObjectClassesHandler::getDefaultAllowed() const TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) const { - if (objects.count(type)) - { - if (objects.at(type)->subObjects.count(subtype)) - return objects.at(type)->subObjects.at(subtype); - } - std::string errorString = "Failed to find object of type " + std::to_string(type) + "::" + std::to_string(subtype); - logGlobal->error(errorString); - throw std::runtime_error(errorString); + assert(type < objects.size()); + assert(objects[type]); + assert(subtype < objects[type]->objects.size()); + + return objects.at(type)->objects.at(subtype); } TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string scope, std::string type, std::string subtype) const { - boost::optional id = VLC->modh->identifiers.getIdentifier(scope, "object", type, false); + boost::optional id = VLC->modh->identifiers.getIdentifier(scope, "object", type); if(id) { - auto object = objects.at(id.get()); - if(object->subIds.count(subtype)) - { - si32 subId = object->subIds.at(subtype); + auto object = objects[id.get()]; + boost::optional subID = VLC->modh->identifiers.getIdentifier(scope, object->getJsonKey(), subtype); - return object->subObjects.at(subId); - } + if (subID) + return object->objects[subID.get()]; } + std::string errorString = "Failed to find object of type " + type + "::" + subtype; logGlobal->error(errorString); throw std::runtime_error(errorString); @@ -334,20 +331,23 @@ std::set CObjectClassesHandler::knownObjects() const std::set ret; for (auto entry : objects) - ret.insert(entry.first); + if (entry) + ret.insert(entry->id); return ret; } std::set CObjectClassesHandler::knownSubObjects(si32 primaryID) const { + assert(primaryID < objects.size()); + assert(objects[primaryID]); + std::set ret; - if (objects.count(primaryID)) - { - for (auto entry : objects.at(primaryID)->subObjects) - ret.insert(entry.first); - } + for (auto entry : objects.at(primaryID)->objects) + if (entry) + ret.insert(entry->subtype); + return ret; } @@ -355,68 +355,75 @@ void CObjectClassesHandler::beforeValidate(JsonNode & object) { for (auto & entry : object["types"].Struct()) { + if (object.Struct().count("subObjects")) + { + auto const & vector = object["subObjects"].Vector(); + + if (entry.second.Struct().count("index")) + { + size_t index = entry.second["index"].Integer(); + + if (index < vector.size()) + JsonUtils::inherit(entry.second, vector[index]); + } + } + JsonUtils::inherit(entry.second, object["base"]); for (auto & templ : entry.second["templates"].Struct()) JsonUtils::inherit(templ.second, entry.second["base"]); } + object.Struct().erase("subObjects"); } void CObjectClassesHandler::afterLoadFinalization() { for(auto entry : objects) { - for(auto obj : entry.second->subObjects) + if (!entry) + continue; + + for(auto obj : entry->objects) { - obj.second->afterLoadFinalization(); - if(obj.second->getTemplates().empty()) - logGlobal->warn("No templates found for %d:%d", entry.first, obj.first); + if (!obj) + continue; + + obj->afterLoadFinalization(); + if(obj->getTemplates().empty()) + logGlobal->warn("No templates found for %s:%s", entry->getJsonKey(), obj->getJsonKey()); } } //duplicate existing two-way portals to make reserve for RMG - auto& portalVec = objects[Obj::MONOLITH_TWO_WAY]->subObjects; + auto& portalVec = objects[Obj::MONOLITH_TWO_WAY]->objects; size_t portalCount = portalVec.size(); - size_t currentIndex = portalCount; - while(portalVec.size() < 100) - { - portalVec[(si32)currentIndex] = portalVec[static_cast(currentIndex % portalCount)]; - currentIndex++; - } -} -std::string CObjectClassesHandler::getObjectName(si32 type) const -{ - if (objects.count(type)) - return objects.at(type)->name; - logGlobal->error("Access to non existing object of type %d", type); - return ""; + for (size_t i = portalCount; i < 100; ++i) + portalVec.push_back(portalVec[static_cast(i % portalCount)]); } std::string CObjectClassesHandler::getObjectName(si32 type, si32 subtype) const { - if (knownSubObjects(type).count(subtype)) - { - auto name = getHandlerFor(type, subtype)->getCustomName(); - if (name) - return name.get(); - } - return getObjectName(type); -} - -SObjectSounds CObjectClassesHandler::getObjectSounds(si32 type) const -{ - if(objects.count(type)) - return objects.at(type)->sounds; - logGlobal->error("Access to non existing object of type %d", type); - return SObjectSounds(); + auto const handler = getHandlerFor(type, subtype); + if (handler->hasNameTextID()) + return handler->getNameTranslated(); + else + return objects[type]->getNameTranslated(); } SObjectSounds CObjectClassesHandler::getObjectSounds(si32 type, si32 subtype) const { - if(knownSubObjects(type).count(subtype)) - return getHandlerFor(type, subtype)->getSounds(); - else - return getObjectSounds(type); + // TODO: these objects may have subID's that does not have associated handler: + // Prison: uses hero type as subID + // Hero: uses hero type as subID, but registers hero classes as subtypes + // Spell scroll: uses spell ID as subID + if(type == Obj::PRISON || type == Obj::HERO || type == Obj::SPELL_SCROLL) + subtype = 0; + + assert(type < objects.size()); + assert(objects[type]); + assert(subtype < objects[type]->objects.size()); + + return getHandlerFor(type, subtype)->getSounds(); } std::string CObjectClassesHandler::getObjectHandlerName(si32 type) const @@ -424,11 +431,6 @@ std::string CObjectClassesHandler::getObjectHandlerName(si32 type) const return objects.at(type)->handlerName; } -boost::optional CObjectClassesHandler::getObjGroupAiValue(si32 primaryID) const -{ - return objects.at(primaryID)->groupDefaultAiValue; -} - AObjectTypeHandler::AObjectTypeHandler(): type(-1), subtype(-1) { @@ -439,16 +441,29 @@ AObjectTypeHandler::~AObjectTypeHandler() { } -void AObjectTypeHandler::setType(si32 type, si32 subtype) +std::string AObjectTypeHandler::getJsonKey() const { - this->type = type; - this->subtype = subtype; + return modScope + ':' + subTypeName; } -void AObjectTypeHandler::setTypeName(std::string type, std::string subtype) +si32 AObjectTypeHandler::getIndex() const { - this->typeName = type; - this->subTypeName = subtype; + return type; +} + +si32 AObjectTypeHandler::getSubIndex() const +{ + return subtype; +} + +std::string AObjectTypeHandler::getTypeName() const +{ + return typeName; +} + +std::string AObjectTypeHandler::getSubTypeName() const +{ + return subTypeName; } static ui32 loadJsonOrMax(const JsonNode & input) @@ -459,7 +474,7 @@ static ui32 loadJsonOrMax(const JsonNode & input) return static_cast(input.Float()); } -void AObjectTypeHandler::init(const JsonNode & input, boost::optional name) +void AObjectTypeHandler::init(const JsonNode & input) { base = input["base"]; @@ -491,11 +506,6 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optional AObjectTypeHandler::getCustomName() const +bool AObjectTypeHandler::hasNameTextID() const { - return objectName; + return false; +} + +std::string AObjectTypeHandler::getNameTextID() const +{ + return TextIdentifier("mapObject", modScope, typeName, subTypeName, "name").get(); +} + +std::string AObjectTypeHandler::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); } SObjectSounds AObjectTypeHandler::getSounds() const diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index a4a2eb910..a1c1e80e4 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -136,12 +136,12 @@ public: class CGObjectInstance; +/// Class responsible for creation of objects of specific type & subtype class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable { - RandomMapInfo rmgInfo; + friend class CObjectClassesHandler; - /// Human-readable name of this object, used for objects like banks and dwellings, if set - boost::optional objectName; + RandomMapInfo rmgInfo; JsonNode base; /// describes base template @@ -150,9 +150,15 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable SObjectSounds sounds; boost::optional aiValue; - boost::optional battlefield; + std::string modScope; + std::string typeName; + std::string subTypeName; + + si32 type; + si32 subtype; + protected: void preInitObject(CGObjectInstance * obj) const; virtual bool objectFilter(const CGObjectInstance *, std::shared_ptr) const; @@ -160,22 +166,23 @@ protected: /// initialization for classes that inherit this one virtual void initTypeData(const JsonNode & input); public: - std::string typeName; - std::string subTypeName; - si32 type; - si32 subtype; AObjectTypeHandler(); virtual ~AObjectTypeHandler(); - void setType(si32 type, si32 subtype); - void setTypeName(std::string type, std::string subtype); + si32 getIndex() const; + si32 getSubIndex() const; + + std::string getTypeName() const; + std::string getSubTypeName() const; /// loads generic data from Json structure and passes it towards type-specific constructors - void init(const JsonNode & input, boost::optional name = boost::optional()); + void init(const JsonNode & input); + + /// returns full form of identifier of this object in form of modName:objectName + std::string getJsonKey() const; /// Returns object-specific name, if set - boost::optional getCustomName() const; SObjectSounds getSounds() const; void addTemplate(std::shared_ptr templ); @@ -191,13 +198,19 @@ public: BattleField getBattlefield() const; - /// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle) - /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server) - const RandomMapInfo & getRMGInfo(); boost::optional getAiValue() const; + /// returns true if this class provides custom text ID's instead of generic per-object name + virtual bool hasNameTextID() const; + + /// returns object's name in form of translatable text ID + virtual std::string getNameTextID() const; + + /// returns object's name in form of human-readable text + std::string getNameTranslated() const; + virtual bool isStaticObject(); virtual void afterLoadFinalization(); @@ -219,7 +232,7 @@ public: h & subtype; h & templates; h & rmgInfo; - h & objectName; + h & modScope; h & typeName; h & subTypeName; h & sounds; @@ -230,40 +243,40 @@ public: typedef std::shared_ptr TObjectTypeHandler; +/// Class responsible for creation of adventure map objects of specific type +class DLL_LINKAGE ObjectClass +{ +public: + std::string modScope; + std::string identifier; + + si32 id; + std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map + + JsonNode base; + std::vector objects; + + ObjectClass() = default; + + std::string getJsonKey() const; + std::string getNameTextID() const; + std::string getNameTranslated() const; + + template void serialize(Handler &h, const int version) + { + h & handlerName; + h & base; + h & objects; + h & identifier; + h & modScope; + } +}; + +/// Main class responsible for creation of all adventure map objects class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase { - /// Small internal structure that contains information on specific group of objects - /// (creating separate entity is overcomplicating at least at this point) - struct ObjectContainter - { - si32 id; - std::string identifier; - std::string name; // human-readable name - std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map - - JsonNode base; - std::map subObjects; - std::map subIds;//full id from core scope -> subtype - - SObjectSounds sounds; - - boost::optional groupDefaultAiValue; - - template void serialize(Handler &h, const int version) - { - h & name; - h & handlerName; - h & base; - h & subObjects; - h & identifier; - h & subIds; - h & sounds; - h & groupDefaultAiValue; - } - }; - /// list of object handlers, each of them handles only one type - std::map objects; + std::vector objects; /// map that is filled during contruction with all known handlers. Not serializeable due to usage of std::function std::map > handlerConstructors; @@ -272,12 +285,12 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase typedef std::multimap, std::shared_ptr> TTemplatesContainer; TTemplatesContainer legacyTemplates; - /// contains list of custom names for H3 objects (e.g. Dwellings), used to load H3 data - /// format: customNames[primaryID][secondaryID] -> name - std::map> customNames; + TObjectTypeHandler loadSubObjectFromJson(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index); - void loadObjectEntry(const std::string & identifier, const JsonNode & entry, ObjectContainter * obj, bool isSubobject = false); - ObjectContainter * loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name); + void loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj); + void loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index); + + ObjectClass * loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index); public: CObjectClassesHandler(); @@ -288,7 +301,7 @@ public: void loadObject(std::string scope, std::string name, const JsonNode & data) override; void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override; - void loadSubObject(const std::string & identifier, JsonNode config, si32 ID, boost::optional subID = boost::optional()); + void loadSubObject(const std::string & identifier, JsonNode config, si32 ID, si32 subID); void removeSubObject(si32 ID, si32 subID); void beforeValidate(JsonNode & object) override; @@ -305,17 +318,13 @@ public: TObjectTypeHandler getHandlerFor(std::string scope, std::string type, std::string subtype) const; TObjectTypeHandler getHandlerFor(CompoundMapObjectID compoundIdentifier) const; - std::string getObjectName(si32 type) const; std::string getObjectName(si32 type, si32 subtype) const; - SObjectSounds getObjectSounds(si32 type) const; SObjectSounds getObjectSounds(si32 type, si32 subtype) const; /// Returns handler string describing the handler (for use in client) std::string getObjectHandlerName(si32 type) const; - boost::optional getObjGroupAiValue(si32 primaryID) const; //default AI value of objects belonging to particular primaryID - template void serialize(Handler &h, const int version) { h & objects; diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 5974c1149..160b7701c 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -1171,7 +1171,7 @@ void CGBorderGuard::getVisitText (MetaString &text, std::vector &comp void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const { if (!onHover) - text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER); + text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER, subID); } bool CGBorderGuard::checkQuest(const CGHeroInstance * h) const diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index c0bf427b9..f9ec58364 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -16,6 +16,7 @@ #include "../TerrainHandler.h" #include "../mapping/CMap.h" #include "../CHeroHandler.h" +#include "../CGeneralTextHandler.h" #include "../CCreatureHandler.h" #include "JsonRandom.h" #include "../CModHandler.h" @@ -150,8 +151,18 @@ CDwellingInstanceConstructor::CDwellingInstanceConstructor() } +bool CDwellingInstanceConstructor::hasNameTextID() const +{ + return true; +} + void CDwellingInstanceConstructor::initTypeData(const JsonNode & input) { + if (input.Struct().count("name") == 0) + logMod->warn("Dwelling %s missing name!", getJsonKey()); + + VLC->generaltexth->registerString(getNameTextID(), input["name"].String()); + const JsonVector & levels = input["creatures"].Vector(); const auto totalLevels = levels.size(); @@ -272,9 +283,18 @@ CBankInstanceConstructor::CBankInstanceConstructor() } +bool CBankInstanceConstructor::hasNameTextID() const +{ + return true; +} + void CBankInstanceConstructor::initTypeData(const JsonNode & input) { - //TODO: name = input["name"].String(); + if (input.Struct().count("name") == 0) + logMod->warn("Bank %s missing name!", getJsonKey()); + + VLC->generaltexth->registerString(getNameTextID(), input["name"].String()); + levels = input["levels"].Vector(); bankResetDuration = static_cast(input["resetDuration"].Float()); } diff --git a/lib/mapObjects/CommonConstructors.h b/lib/mapObjects/CommonConstructors.h index 1051e9b05..07ffcfc55 100644 --- a/lib/mapObjects/CommonConstructors.h +++ b/lib/mapObjects/CommonConstructors.h @@ -127,6 +127,7 @@ protected: void initTypeData(const JsonNode & input) override; public: + bool hasNameTextID() const override; CDwellingInstanceConstructor(); CGObjectInstance * create(std::shared_ptr tmpl = nullptr) const override; @@ -218,6 +219,8 @@ public: CGObjectInstance * create(std::shared_ptr tmpl = nullptr) const override; void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override; + bool hasNameTextID() const override; + std::unique_ptr getObjectInfo(std::shared_ptr tmpl) const override; template void serialize(Handler &h, const int version) diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 59727bbec..01d05c38c 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -559,6 +559,9 @@ void ObjectTemplate::recalculate() calculateBlockedOffsets(); calculateBlockMapOffset(); calculateVisitableOffset(); + + if (visitable && visitDir == 0) + logMod->warn("Template for %s is visitable but has no visitable directions!", animationFile); } VCMI_LIB_NAMESPACE_END diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 528eb1811..c482e2936 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -172,7 +172,7 @@ namespace TriggeredEventsDetail { si32 subtype = *subtypes.begin(); auto handler = VLC->objtypeh->getHandlerFor(type, subtype); - identifier = handler->typeName; + identifier = handler->getTypeName(); } } break; @@ -1141,7 +1141,7 @@ void CMapLoaderJson::MapObjectLoader::construct() pos.z = static_cast(configuration["l"].Float()); //special case for grail - if(typeName == "grail") + if(typeName == "grail") { owner->map->grailPos = pos; @@ -1159,8 +1159,8 @@ void CMapLoaderJson::MapObjectLoader::construct() auto appearance = new ObjectTemplate; - appearance->id = Obj(handler->type); - appearance->subid = handler->subtype; + appearance->id = Obj(handler->getIndex()); + appearance->subid = handler->getSubIndex(); appearance->readJson(configuration["template"], false); // Will be destroyed soon and replaced with shared template diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 06bb1fe2d..5403c3b1c 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -468,6 +468,9 @@ void MainWindow::addGroupIntoCatalog(const std::string & groupName, bool useCust itemGroup = itms.front(); } + if (VLC->objtypeh->knownObjects().count(ID) == 0) + return; + auto knownSubObjects = VLC->objtypeh->knownSubObjects(ID); for(auto secondaryID : knownSubObjects) { @@ -477,10 +480,7 @@ void MainWindow::addGroupIntoCatalog(const std::string & groupName, bool useCust if(staticOnly && !factory->isStaticObject()) continue; - auto subGroupName = QString::fromStdString(factory->subTypeName); - auto customName = factory->getCustomName(); - if(customName) - subGroupName = tr(customName->c_str()); + auto subGroupName = QString::fromStdString(factory->getNameTranslated()); auto * itemType = new QStandardItem(subGroupName); for(int templateId = 0; templateId < templates.size(); ++templateId) diff --git a/mapeditor/mapcontroller.cpp b/mapeditor/mapcontroller.cpp index 85127ee8b..e992e40c3 100644 --- a/mapeditor/mapcontroller.cpp +++ b/mapeditor/mapcontroller.cpp @@ -106,8 +106,8 @@ void MapController::repairMap() if(obj->ID != Obj::HERO && obj->ID != Obj::PRISON && (obj->typeName.empty() || obj->subTypeName.empty())) { auto handler = VLC->objtypeh->getHandlerFor(obj->ID, obj->subID); - obj->typeName = handler->typeName; - obj->subTypeName = handler->subTypeName; + obj->typeName = handler->getTypeName(); + obj->subTypeName = handler->getSubTypeName(); } //fix flags if(obj->getOwner() == PlayerColor::UNFLAGGABLE)