From c5f72ccdc76b8645337882a0d28ffc2a19bdf106 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 30 Dec 2022 19:18:31 +0200 Subject: [PATCH 01/14] Names for object supertypes are passed through translator --- lib/CGeneralTextHandler.h | 1 + lib/mapObjects/CObjectClassesHandler.cpp | 31 ++++++++++++++++++------ lib/mapObjects/CObjectClassesHandler.h | 20 ++++++++++++--- 3 files changed, 40 insertions(+), 12 deletions(-) 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..7997d4ee3 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -177,7 +177,7 @@ void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, cons auto handler = handlerConstructors.at(obj->handlerName)(); handler->setType(obj->id, id); - handler->setTypeName(obj->identifier, convertedId); + handler->setTypeName(obj->getIdentifier(), convertedId); if (customNames.count(obj->id) && customNames.at(obj->id).size() > id) handler->init(entry, customNames.at(obj->id).at(id)); @@ -194,7 +194,7 @@ void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, cons legacyTemplates.erase(range.first, range.second); } - logGlobal->debug("Loaded object %s(%d)::%s(%d)", obj->identifier, obj->id, convertedId, id); + logGlobal->debug("Loaded object %s(%d)::%s(%d)", obj->getIdentifier(), obj->id, convertedId, id); //some mods redefine content handlers in the decoration.json in such way: //"core:sign" : { "types" : { "forgeSign" : { ... @@ -219,22 +219,37 @@ void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, cons 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); + obj->subObjects[id]->subTypeName, obj->handlerName, obj->getIdentifier(), 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); + obj->handlerName, obj->getIdentifier(), obj->id, convertedId, id); } } +std::string CObjectClassesHandler::ObjectContainter::getIdentifier() const +{ + return modScope + ":" + identifier; +} + +std::string CObjectClassesHandler::ObjectContainter::getNameTextID() const +{ + return TextIdentifier ("object", modScope, identifier, "name").get(); +} + +std::string CObjectClassesHandler::ObjectContainter::getNameTranslated() const +{ + return VLC->generaltexth->translate(getNameTextID()); +} + CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name) { - auto obj = new ObjectContainter(); + auto obj = new ObjectContainter(scope, name); static const si32 fixedObjectsBound = 256; //Legacy value for backward compatibility - obj->identifier = name; - obj->name = json["name"].String(); + VLC->generaltexth->registerString( obj->getNameTextID(), json["name"].String()); + obj->handlerName = json["handler"].String(); obj->base = json["base"]; obj->id = selectNextID(json["index"], objects, fixedObjectsBound); @@ -387,7 +402,7 @@ void CObjectClassesHandler::afterLoadFinalization() std::string CObjectClassesHandler::getObjectName(si32 type) const { if (objects.count(type)) - return objects.at(type)->name; + return objects.at(type)->getNameTranslated(); logGlobal->error("Access to non existing object of type %d", type); return ""; } diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index a4a2eb910..011d658d5 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -234,11 +234,19 @@ 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 + class DLL_LINKAGE ObjectContainter { - si32 id; std::string identifier; - std::string name; // human-readable name + std::string modScope; + + public: + ObjectContainter() = default; + ObjectContainter(const std::string & modScope, const std::string & identifier): + identifier(identifier), + modScope(modScope) + {} + + si32 id; std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map JsonNode base; @@ -249,13 +257,17 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase boost::optional groupDefaultAiValue; + std::string getIdentifier() const; + std::string getNameTextID() const; // {scope, "objects", name, "name"} + std::string getNameTranslated() const; + template void serialize(Handler &h, const int version) { - h & name; h & handlerName; h & base; h & subObjects; h & identifier; + h & modScope; h & subIds; h & sounds; h & groupDefaultAiValue; From 6c472339cece8dd88ba93030b8865f16a6b20981 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 31 Dec 2022 15:01:19 +0200 Subject: [PATCH 02/14] Refactoring of ObjectClassesHandler --- AI/VCAI/MapObjectsEvaluator.cpp | 4 - client/mapHandler.cpp | 2 +- config/objects/creatureBanks.json | 1 + config/objects/dwellings.json | 2 + config/objects/generic.json | 2 + config/objects/moddables.json | 38 ++- config/schemas/object.json | 37 --- lib/CGameState.cpp | 2 +- lib/mapObjects/CObjectClassesHandler.cpp | 328 ++++++++++------------- lib/mapObjects/CObjectClassesHandler.h | 120 ++++----- lib/mapObjects/CQuest.cpp | 2 +- lib/mapping/MapFormatJson.cpp | 4 +- mapeditor/mainwindow.cpp | 5 +- mapeditor/mapcontroller.cpp | 4 +- 14 files changed, 237 insertions(+), 314 deletions(-) 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..cedce2c19 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,7 @@ "creatureGeneratorSpecial" : { "index" :20, "handler": "dwelling", + "lastReservedIndex" : 1, "types" : { "elementalConflux" : { "index" : 0, diff --git a/config/objects/generic.json b/config/objects/generic.json index 99c7838b0..715407900 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -230,6 +230,7 @@ "subterraneanGate" : { "index" :103, "handler" : "subterraneanGate", + "lastReservedIndex" : 1, "base" : { "sounds" : { "ambient" : ["LOOPGATE"], @@ -559,6 +560,7 @@ "witchHut" : { "index" :113, "handler" : "witch", + "lastReservedIndex" : 1, "base" : { "sounds" : { "visit" : ["GAZEBO"] diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 8d47f61d0..25880cc7d 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" : 7, "base" : { "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], @@ -76,8 +77,8 @@ "town" : { "index" :98, "handler": "town", - "defaultAiValue" : 20000, "base" : { + "aiValue" : 20000, "filters" : { // village image - fort not present "village" : [ "noneOf", [ "fort" ] ], @@ -106,8 +107,9 @@ "boat" : { "index" :8, "handler": "boat", - "defaultAiValue" : 0, + "lastReservedIndex" : 2, "base" : { + "aiValue" : 0, "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VVV", "VAV" ] @@ -124,8 +126,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 +148,9 @@ "borderGate" : { "index" :212, "handler": "borderGate", - "defaultAiValue" : 0, + "lastReservedIndex" : 7, "base" : { + "aiValue" : 0, "sounds" : { "visit" : ["CAVEHEAD"] } @@ -165,8 +169,9 @@ "keymasterTent" : { "index" :10, "handler": "keymaster", - "defaultAiValue" : 10000, + "lastReservedIndex" : 7, "base" : { + "aiValue" : 10000, "sounds" : { "visit" : ["CAVEHEAD"] } @@ -186,8 +191,9 @@ "seerHut" : { "index" :83, "handler": "seerHut", - "defaultAiValue" : 10000, + "lastReservedIndex" : 2, "base" : { + "aiValue" : 10000, "base" : { "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ "VVV", "VVV", "VAV" ] @@ -207,6 +213,7 @@ "cartographer" : { "index" :13, "handler": "cartographer", + "lastReservedIndex" : 2, "base" : { "sounds" : { "visit" : ["LIGHTHOUSE"] @@ -223,6 +230,7 @@ "mine" : { "index" :53, "handler": "mine", + "lastReservedIndex" : 7, "base" : { "sounds" : { "visit" : ["FLAGMINE"] @@ -320,8 +328,8 @@ "abandonedMine" : { "index" :220, "handler": "mine", - "defaultAiValue" : 3500, "base" : { + "aiValue" : 3500, "sounds" : { "ambient" : ["LOOPCAVE"] } @@ -334,8 +342,9 @@ "garrisonHorizontal": { "index" :33, "handler": "garrison", - "defaultAiValue" : 0, + "lastReservedIndex" : 1, "base" : { + "aiValue" : 0, "sounds" : { "visit" : ["MILITARY"] } @@ -358,8 +367,9 @@ "garrisonVertical" : { "index" :219, "handler": "garrison", - "defaultAiValue" : 0, + "lastReservedIndex" : 1, "base" : { + "aiValue" : 0, "sounds" : { "visit" : ["MILITARY"] } @@ -384,6 +394,7 @@ "monolithOneWayEntrance" : { "index" :43, "handler": "monolith", + "lastReservedIndex" : 7, "base" : { "sounds" : { "ambient" : ["LOOPMON1"], @@ -404,6 +415,7 @@ "monolithOneWayExit" : { "index" :44, "handler": "monolith", + "lastReservedIndex" : 7, "base" : { "sounds" : { "ambient" : ["LOOPMON1"] } }, @@ -421,6 +433,7 @@ "monolithTwoWay" : { "index" :45, "handler": "monolith", + "lastReservedIndex" : 7, "base" : { "sounds" : { "ambient" : ["LOOPMON2"], @@ -441,7 +454,9 @@ // subtype: level "randomDwellingLvl" : { - "index" :217, "handler": "randomDwelling", + "index" :217, + "handler": "randomDwelling", + "lastReservedIndex" : 6, "types" : { "objectLvl1" : { "index" : 0}, "objectLvl2" : { "index" : 1}, @@ -457,6 +472,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..6df7696f6 100644 --- a/config/schemas/object.json +++ b/config/schemas/object.json @@ -10,46 +10,9 @@ "index": { "type":"number" }, - "name": { - "type":"string" - }, - "defaultAiValue": { - "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" }, diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 53f1dd32a..9f93d7ddb 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) { diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 7997d4ee3..88c53fe13 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) @@ -118,190 +118,149 @@ std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) CLegacyConfigParser namesParser("Data/ObjNames.txt"); for (size_t i=0; i<256; i++) { - ret[i]["name"].String() = namesParser.readString(); + ret[i]["base"]["name"].String() = namesParser.readString(); 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; + 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, VLC->modh->normalizeIdentifier(scope, CModHandler::scopeBuiltin(), identifier), entry, obj, obj->objects.size()); - return id; + assert(object); + obj->objects.push_back(object); + + registerObject(scope, obj->getIdentifier(), identifier, 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; + //TODO: load name for subobjects + auto object = loadSubObjectFromJson(scope, VLC->modh->normalizeIdentifier(scope, CModHandler::scopeBuiltin(), 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->getIdentifier(), identifier, object->subtype); +} + +TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index) +{ 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->getIdentifier(), 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->getIdentifier(), obj->id, convertedId, id); + auto createdObject = handlerConstructors.at(obj->handlerName)(); + createdObject->setType(obj->id, index); + createdObject->setTypeName(obj->getIdentifier(), identifier); + createdObject->init(entry); - //some mods redefine content handlers in the decoration.json in such way: - //"core:sign" : { "types" : { "forgeSign" : { ... - static const std::vector breakersRMG + auto range = legacyTemplates.equal_range(std::make_pair(obj->id, index)); + for (auto & templ : boost::make_iterator_range(range.first, range.second)) { - "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->addTemplate(templ.second); + } + legacyTemplates.erase(range.first, range.second); - if(passedHandler) - { - 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->getIdentifier(), 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->getIdentifier(), obj->id, convertedId, id); - } + logGlobal->debug("Loaded object %s(%d)::%s(%d)", obj->getIdentifier(), obj->id, identifier, index); + + return createdObject; } -std::string CObjectClassesHandler::ObjectContainter::getIdentifier() const +std::string ObjectClass::getIdentifier() const { - return modScope + ":" + identifier; + return identifier + "Object"; } -std::string CObjectClassesHandler::ObjectContainter::getNameTextID() const +ObjectClass * CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index) { - return TextIdentifier ("object", modScope, identifier, "name").get(); -} - -std::string CObjectClassesHandler::ObjectContainter::getNameTranslated() const -{ - return VLC->generaltexth->translate(getNameTextID()); -} - -CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name) -{ - auto obj = new ObjectContainter(scope, name); - static const si32 fixedObjectsBound = 256; //Legacy value for backward compatibility - - VLC->generaltexth->registerString( obj->getNameTextID(), json["name"].String()); + auto obj = new ObjectClass(scope, name); 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()); - - for (auto entry : json["types"].Struct()) - loadObjectEntry(entry.first, entry.second, obj); + obj->objects.resize(json["lastReservedIndex"].Float() + 1); + auto originalData = json["subObjects"].Vector(); + for (auto subData : json["types"].Struct()) + { + if (!subData.second["index"].isNull()) + { + size_t subIndex = subData.second["index"].Integer(); + if (subIndex < originalData.size()) + { + JsonUtils::merge(originalData[subIndex], subData.second); + std::swap(originalData[subIndex], subData.second); + } + loadSubObject(scope, subData.first, subData.second, obj, subIndex); + } + else + loadSubObject(scope, 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, VLC->modh->normalizeIdentifier(scope, CModHandler::scopeBuiltin(), 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, VLC->modh->normalizeIdentifier(scope, CModHandler::scopeBuiltin(), 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]); + 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 @@ -311,14 +270,11 @@ 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 @@ -326,14 +282,13 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string scope, std:: boost::optional id = VLC->modh->identifiers.getIdentifier(scope, "object", type, false); 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->getIdentifier(), subtype, false); - 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); @@ -349,20 +304,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; } @@ -380,16 +338,19 @@ 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); + obj->afterLoadFinalization(); + if(obj->getTemplates().empty()) + logGlobal->warn("No templates found for %s:%s", entry->getIdentifier(), obj->getIdentifier()); } } //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) @@ -399,39 +360,18 @@ void CObjectClassesHandler::afterLoadFinalization() } } -std::string CObjectClassesHandler::getObjectName(si32 type) const -{ - if (objects.count(type)) - return objects.at(type)->getNameTranslated(); - logGlobal->error("Access to non existing object of type %d", type); - return ""; -} - 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(); + return getHandlerFor(type, subtype)->getNameTranslated(); } SObjectSounds CObjectClassesHandler::getObjectSounds(si32 type, si32 subtype) const { - if(knownSubObjects(type).count(subtype)) - return getHandlerFor(type, subtype)->getSounds(); - else - return getObjectSounds(type); + 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 @@ -439,11 +379,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) { @@ -466,6 +401,21 @@ void AObjectTypeHandler::setTypeName(std::string type, std::string subtype) this->subTypeName = subtype; } +std::string AObjectTypeHandler::getIdentifier() const +{ + return modName + ":" + subTypeName; +} + +std::string AObjectTypeHandler::getTypeName() +{ + return typeName; +} + +std::string AObjectTypeHandler::getSubTypeName() +{ + return subTypeName; +} + static ui32 loadJsonOrMax(const JsonNode & input) { if (input.isNull()) @@ -474,7 +424,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"]; @@ -506,10 +456,7 @@ void AObjectTypeHandler::init(const JsonNode & input, boost::optionalgeneraltexth->registerString(getNameTextID(), input["name"].String()); for(const JsonNode & node : input["sounds"]["ambient"].Vector()) sounds.ambient.push_back(node.String()); @@ -551,9 +498,14 @@ void AObjectTypeHandler::initTypeData(const JsonNode & input) // empty implementation for overrides } -boost::optional AObjectTypeHandler::getCustomName() const +std::string AObjectTypeHandler::getNameTextID() const { - return objectName; + return TextIdentifier( typeName, modName, 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 011d658d5..38d03f77c 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -136,12 +136,13 @@ public: class CGObjectInstance; +/// Class responsible for creation of objects of specific type & subtype class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable { RandomMapInfo rmgInfo; - /// Human-readable name of this object, used for objects like banks and dwellings, if set - boost::optional objectName; + /// Text ID for human-readable name of this object, used for objects like banks and dwellings, if set + boost::optional nameTextID; JsonNode base; /// describes base template @@ -150,7 +151,6 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable SObjectSounds sounds; boost::optional aiValue; - boost::optional battlefield; protected: @@ -159,9 +159,10 @@ protected: /// initialization for classes that inherit this one virtual void initTypeData(const JsonNode & input); -public: + std::string modName; std::string typeName; std::string subTypeName; +public: si32 type; si32 subtype; @@ -171,11 +172,20 @@ public: void setType(si32 type, si32 subtype); void setTypeName(std::string type, std::string subtype); + std::string getTypeName(); + std::string getSubTypeName(); + /// 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 getIdentifier() const; + + /// returns objet's name in form of translatable text ID + std::string getNameTextID() const; + std::string getNameTranslated() const; /// Returns object-specific name, if set - boost::optional getCustomName() const; SObjectSounds getSounds() const; void addTemplate(std::shared_ptr templ); @@ -191,9 +201,6 @@ 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; @@ -219,7 +226,8 @@ public: h & subtype; h & templates; h & rmgInfo; - h & objectName; + h & nameTextID; + h & modName; h & typeName; h & subTypeName; h & sounds; @@ -230,52 +238,42 @@ public: typedef std::shared_ptr TObjectTypeHandler; +/// Class responsible for creation of adventure map objects of specific type +class DLL_LINKAGE ObjectClass +{ + std::string modScope; + std::string identifier; + +public: + ObjectClass() = default; + ObjectClass(const std::string & modScope, const std::string & identifier): + identifier(identifier), + modScope(modScope) + {} + + si32 id; + std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map + + JsonNode base; + std::vector objects; + + std::string getIdentifier() 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) - class DLL_LINKAGE ObjectContainter - { - std::string identifier; - std::string modScope; - - public: - ObjectContainter() = default; - ObjectContainter(const std::string & modScope, const std::string & identifier): - identifier(identifier), - modScope(modScope) - {} - - si32 id; - 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; - - std::string getIdentifier() const; - std::string getNameTextID() const; // {scope, "objects", name, "name"} - std::string getNameTranslated() const; - - template void serialize(Handler &h, const int version) - { - h & handlerName; - h & base; - h & subObjects; - h & identifier; - h & modScope; - 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; @@ -284,12 +282,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(); @@ -300,7 +298,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; @@ -317,17 +315,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/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 528eb1811..e172d7cd7 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; diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 06bb1fe2d..05b496c68 100644 --- a/mapeditor/mainwindow.cpp +++ b/mapeditor/mainwindow.cpp @@ -477,10 +477,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) From 6f324216dbcc1bbd1c3599fa4d592fc3a3711b48 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 1 Jan 2023 20:51:56 +0200 Subject: [PATCH 03/14] Bugfixing --- config/objects/generic.json | 20 ++++++-------------- config/schemas/object.json | 9 ++++++++- config/schemas/objectType.json | 3 +-- lib/mapObjects/CObjectClassesHandler.cpp | 20 ++++++++++++++------ 4 files changed, 29 insertions(+), 23 deletions(-) diff --git a/config/objects/generic.json b/config/objects/generic.json index 715407900..1ff3c73dc 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -230,7 +230,6 @@ "subterraneanGate" : { "index" :103, "handler" : "subterraneanGate", - "lastReservedIndex" : 1, "base" : { "sounds" : { "ambient" : ["LOOPGATE"], @@ -239,8 +238,8 @@ }, "types" : { "object" : { - "index" : 0 }, - "objectWoG" : { "index" : 1 } // WoG object? Present on VCMI Test 2011b + "index" : 0 + } } }, @@ -342,9 +341,6 @@ "value" : 3000, "rarity" : 100 } - }, - "object1":{ //WoG? - "index" :1 } } }, @@ -560,7 +556,6 @@ "witchHut" : { "index" :113, "handler" : "witch", - "lastReservedIndex" : 1, "base" : { "sounds" : { "visit" : ["GAZEBO"] @@ -575,9 +570,6 @@ "value" : 1500, "rarity" : 80 } - }, - "object1" : { //WoG? - "index" : 1 } } }, @@ -1044,10 +1036,10 @@ "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} } }, + "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} } } + //"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} } } } diff --git a/config/schemas/object.json b/config/schemas/object.json index 6df7696f6..b171928d3 100644 --- a/config/schemas/object.json +++ b/config/schemas/object.json @@ -3,13 +3,20 @@ "$schema": "http://json-schema.org/draft-04/schema", "title" : "VCMI map object format", "description" : "Description of map object class", - "required": [ "handler", "name" ], + "required": [ "handler" ], "additionalProperties" : false, "properties":{ "index": { "type":"number" }, + "lastReservedIndex" : { + "type":"number" + }, + "subObjects" : { + "type" : "array", + "additionalItems" : true + }, "handler": { "type":"string" }, diff --git a/config/schemas/objectType.json b/config/schemas/objectType.json index b5e80c935..95ba5f09e 100644 --- a/config/schemas/objectType.json +++ b/config/schemas/objectType.json @@ -3,7 +3,7 @@ "$schema": "http://json-schema.org/draft-04/schema", "title" : "VCMI map object type format", "description" : "Description of map object type, used only as sub-schema of object", - "required": [ ], + "required": [ "name" ], "additionalProperties" : true, // may have type-dependant properties "properties":{ @@ -16,7 +16,6 @@ "aiValue": { "type":"number" }, - "sounds": { "type":"object", "additionalProperties" : false, diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 88c53fe13..a34ac0c53 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -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); @@ -251,6 +253,9 @@ void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNo 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); loadSubObject(config.meta, identifier, config, objects[ID], subID); } @@ -343,6 +348,9 @@ void CObjectClassesHandler::afterLoadFinalization() for(auto obj : entry->objects) { + if (!obj) + continue; + obj->afterLoadFinalization(); if(obj->getTemplates().empty()) logGlobal->warn("No templates found for %s:%s", entry->getIdentifier(), obj->getIdentifier()); @@ -352,12 +360,9 @@ void CObjectClassesHandler::afterLoadFinalization() //duplicate existing two-way portals to make reserve for RMG 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++; - } + + 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 @@ -367,6 +372,9 @@ std::string CObjectClassesHandler::getObjectName(si32 type, si32 subtype) const SObjectSounds CObjectClassesHandler::getObjectSounds(si32 type, si32 subtype) const { + if(type == Obj::PRISON || type == Obj::HERO) + subtype = 0; + assert(type < objects.size()); assert(objects[type]); assert(subtype < objects[type]->objects.size()); From d0fe875cc6df66ff825c675ffcdef4e41315df32 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 1 Jan 2023 20:59:50 +0200 Subject: [PATCH 04/14] Removed wog object --- config/objects/moddables.json | 21 ++++++++------------- 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 25880cc7d..d7f39908b 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -54,7 +54,7 @@ "resource" : { "index" :79, "handler": "resource", - "lastReservedIndex" : 7, + "lastReservedIndex" : 6, "base" : { "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], @@ -68,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" } } } } }, @@ -245,8 +244,7 @@ }, "sounds" : { "ambient" : ["LOOPLUMB"] - }, - "battleground": "subterranean" + } }, "alchemistLab" : { "index" : 1, @@ -256,8 +254,7 @@ }, "sounds" : { "ambient" : ["LOOPSTAR"] - }, - "battleground": "subterranean" + } }, "orePit" : { "index" : 2, @@ -267,8 +264,7 @@ }, "sounds" : { "ambient" : ["LOOPSULF"] - }, - "battleground": "subterranean" + } }, "sulfurDune" : { "index" : 3, @@ -278,8 +274,7 @@ }, "sounds" : { "ambient" : ["LOOPSULF"] - }, - "battleground": "subterranean" + } }, "crystalCavern" : { "index" : 4, @@ -300,8 +295,7 @@ }, "sounds" : { "ambient" : ["LOOPGEMP"] - }, - "battleground": "subterranean" + } }, "goldMine" : { "index" : 6, @@ -328,6 +322,7 @@ "abandonedMine" : { "index" :220, "handler": "mine", + "lastReservedIndex" : 7, "base" : { "aiValue" : 3500, "sounds" : { From b13637cdd5227659da0d0863717f2128aad69a65 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 10 Jan 2023 01:30:01 +0200 Subject: [PATCH 05/14] Fix regressions in RMG & map editor --- lib/mapObjects/CObjectClassesHandler.cpp | 2 +- mapeditor/mainwindow.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index a34ac0c53..643319e88 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -372,7 +372,7 @@ std::string CObjectClassesHandler::getObjectName(si32 type, si32 subtype) const SObjectSounds CObjectClassesHandler::getObjectSounds(si32 type, si32 subtype) const { - if(type == Obj::PRISON || type == Obj::HERO) + if(type == Obj::PRISON || type == Obj::HERO || type == Obj::SPELL_SCROLL) subtype = 0; assert(type < objects.size()); diff --git a/mapeditor/mainwindow.cpp b/mapeditor/mainwindow.cpp index 05b496c68..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) { From f71cbc5d9ba24e7dd40c57a28e5eb0d851019107 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 10 Jan 2023 16:24:42 +0200 Subject: [PATCH 06/14] Fixed loading of object names --- config/schemas/object.json | 4 ---- lib/mapObjects/CObjectClassesHandler.cpp | 23 +++++++++++++++++------ lib/mapObjects/CObjectClassesHandler.h | 6 ++---- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/config/schemas/object.json b/config/schemas/object.json index b171928d3..f9c5c2e18 100644 --- a/config/schemas/object.json +++ b/config/schemas/object.json @@ -13,10 +13,6 @@ "lastReservedIndex" : { "type":"number" }, - "subObjects" : { - "type" : "array", - "additionalItems" : true - }, "handler": { "type":"string" }, diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 643319e88..3061eafec 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -182,6 +182,7 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin } auto createdObject = handlerConstructors.at(obj->handlerName)(); + createdObject->modName = scope; createdObject->setType(obj->id, index); createdObject->setTypeName(obj->getIdentifier(), identifier); createdObject->init(entry); @@ -212,18 +213,14 @@ ObjectClass * CObjectClassesHandler::loadFromJson(const std::string & scope, con obj->id = index; obj->objects.resize(json["lastReservedIndex"].Float() + 1); - auto originalData = json["subObjects"].Vector(); for (auto subData : json["types"].Struct()) { if (!subData.second["index"].isNull()) { + if (subData.second["index"].meta != "core") + logMod->warn("Object %s:%s from mod %s - attempt to load object with preset index!", name, subData.first, scope); size_t subIndex = subData.second["index"].Integer(); - if (subIndex < originalData.size()) - { - JsonUtils::merge(originalData[subIndex], subData.second); - std::swap(originalData[subIndex], subData.second); - } loadSubObject(scope, subData.first, subData.second, obj, subIndex); } else @@ -333,10 +330,24 @@ 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")) + continue; + + 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() diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 38d03f77c..f36b1c0fc 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -139,10 +139,9 @@ class CGObjectInstance; /// Class responsible for creation of objects of specific type & subtype class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable { - RandomMapInfo rmgInfo; + friend class CObjectClassesHandler; - /// Text ID for human-readable name of this object, used for objects like banks and dwellings, if set - boost::optional nameTextID; + RandomMapInfo rmgInfo; JsonNode base; /// describes base template @@ -226,7 +225,6 @@ public: h & subtype; h & templates; h & rmgInfo; - h & nameTextID; h & modName; h & typeName; h & subTypeName; From 50e8a62178139afdef702b693a6184a7370dbebb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 10 Jan 2023 16:25:25 +0200 Subject: [PATCH 07/14] Remove access to creature dwellings names via CGeneralTextHandler --- lib/CGameState.cpp | 4 ++-- lib/CGeneralTextHandler.cpp | 2 -- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 9f93d7ddb..f3b181161 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -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" ); From 49d0459412b2b4aea631c337f614d05c0316c525 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 10 Jan 2023 18:58:30 +0200 Subject: [PATCH 08/14] Remove unused code --- config/objects/generic.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/config/objects/generic.json b/config/objects/generic.json index 1ff3c73dc..859da47e9 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -1037,9 +1037,4 @@ "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} } } } From bc27faea57445a7c3934e94d40750db7d05997bd Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 11 Jan 2023 00:48:51 +0200 Subject: [PATCH 09/14] All objects will register single name per object type --- config/schemas/object.json | 5 +- config/schemas/objectType.json | 5 +- lib/mapObjects/CObjectClassesHandler.cpp | 72 +++++++++++++++++------- lib/mapObjects/CObjectClassesHandler.h | 39 +++++++------ 4 files changed, 80 insertions(+), 41 deletions(-) diff --git a/config/schemas/object.json b/config/schemas/object.json index f9c5c2e18..ad357ab7c 100644 --- a/config/schemas/object.json +++ b/config/schemas/object.json @@ -3,7 +3,7 @@ "$schema": "http://json-schema.org/draft-04/schema", "title" : "VCMI map object format", "description" : "Description of map object class", - "required": [ "handler" ], + "required": [ "handler", "name" ], "additionalProperties" : false, "properties":{ @@ -19,6 +19,9 @@ "base": { "type" : "object" }, + "name": { + "type":"string" + }, "types": { "type":"object", "additionalProperties": { diff --git a/config/schemas/objectType.json b/config/schemas/objectType.json index 95ba5f09e..49cc6eebd 100644 --- a/config/schemas/objectType.json +++ b/config/schemas/objectType.json @@ -3,16 +3,13 @@ "$schema": "http://json-schema.org/draft-04/schema", "title" : "VCMI map object type format", "description" : "Description of map object type, used only as sub-schema of object", - "required": [ "name" ], + "required": [ ], "additionalProperties" : true, // may have type-dependant properties "properties":{ "index": { "type":"number" }, - "name": { - "type":"string" - }, "aiValue": { "type":"number" }, diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 3061eafec..00faa14f1 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -120,7 +120,7 @@ std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) CLegacyConfigParser namesParser("Data/ObjNames.txt"); for (size_t i=0; i<256; i++) { - ret[i]["base"]["name"].String() = namesParser.readString(); + ret[i]["name"].String() = namesParser.readString(); namesParser.endLine(); } @@ -148,6 +148,9 @@ std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) 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; } @@ -158,7 +161,7 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std:: assert(object); obj->objects.push_back(object); - registerObject(scope, obj->getIdentifier(), identifier, object->subtype); + registerObject(scope, "mapObject", obj->getJsonKey() + "." + object->getSubTypeName(), object->subtype); } void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index) @@ -170,7 +173,7 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std:: assert(obj->objects[index] == nullptr); // ensure that this id was not loaded before obj->objects[index] = object; - registerObject(scope, obj->getIdentifier(), identifier, object->subtype); + registerObject(scope, "mapObject", 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) @@ -182,9 +185,13 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin } auto createdObject = handlerConstructors.at(obj->handlerName)(); - createdObject->modName = scope; + + if (identifier.find(':') == std::string::npos) + createdObject->setTypeName(obj->getJsonKey(), scope + ":" + identifier); + else + createdObject->setTypeName(obj->getJsonKey(), identifier); + createdObject->setType(obj->id, index); - createdObject->setTypeName(obj->getIdentifier(), identifier); createdObject->init(entry); auto range = legacyTemplates.equal_range(std::make_pair(obj->id, index)); @@ -194,14 +201,24 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin } legacyTemplates.erase(range.first, range.second); - logGlobal->debug("Loaded object %s(%d)::%s(%d)", obj->getIdentifier(), obj->id, identifier, index); + logGlobal->debug("Loaded object %s(%d)::%s(%d)", obj->getJsonKey(), obj->id, identifier, index); return createdObject; } -std::string ObjectClass::getIdentifier() const +std::string ObjectClass::getJsonKey() const { - return identifier + "Object"; + return identifier; +} + +std::string ObjectClass::getNameTextID() const +{ + return TextIdentifier("object", 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) @@ -212,14 +229,18 @@ ObjectClass * CObjectClassesHandler::loadFromJson(const std::string & scope, con obj->base = json["base"]; obj->id = index; + VLC->generaltexth->registerString(obj->getNameTextID(), json["name"].String()); + obj->objects.resize(json["lastReservedIndex"].Float() + 1); for (auto subData : json["types"].Struct()) { if (!subData.second["index"].isNull()) { - if (subData.second["index"].meta != "core") - logMod->warn("Object %s:%s from mod %s - attempt to load object with preset index!", name, subData.first, scope); + 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(scope, subData.first, subData.second, obj, subIndex); } @@ -285,7 +306,7 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string scope, std:: if(id) { auto object = objects[id.get()]; - boost::optional subID = VLC->modh->identifiers.getIdentifier(scope, object->getIdentifier(), subtype, false); + boost::optional subID = VLC->modh->identifiers.getIdentifier(scope, object->getJsonKey(), subtype, false); if (subID) return object->objects[subID.get()]; @@ -364,7 +385,7 @@ void CObjectClassesHandler::afterLoadFinalization() obj->afterLoadFinalization(); if(obj->getTemplates().empty()) - logGlobal->warn("No templates found for %s:%s", entry->getIdentifier(), obj->getIdentifier()); + logGlobal->warn("No templates found for %s:%s", entry->getJsonKey(), obj->getJsonKey()); } } @@ -378,11 +399,19 @@ void CObjectClassesHandler::afterLoadFinalization() std::string CObjectClassesHandler::getObjectName(si32 type, si32 subtype) const { - return getHandlerFor(type, subtype)->getNameTranslated(); + 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 { + // 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; @@ -420,17 +449,17 @@ void AObjectTypeHandler::setTypeName(std::string type, std::string subtype) this->subTypeName = subtype; } -std::string AObjectTypeHandler::getIdentifier() const +std::string AObjectTypeHandler::getJsonKey() const { - return modName + ":" + subTypeName; + return subTypeName; } -std::string AObjectTypeHandler::getTypeName() +std::string AObjectTypeHandler::getTypeName() const { return typeName; } -std::string AObjectTypeHandler::getSubTypeName() +std::string AObjectTypeHandler::getSubTypeName() const { return subTypeName; } @@ -475,8 +504,6 @@ void AObjectTypeHandler::init(const JsonNode & input) } } - VLC->generaltexth->registerString(getNameTextID(), input["name"].String()); - for(const JsonNode & node : input["sounds"]["ambient"].Vector()) sounds.ambient.push_back(node.String()); @@ -517,9 +544,14 @@ void AObjectTypeHandler::initTypeData(const JsonNode & input) // empty implementation for overrides } +bool AObjectTypeHandler::hasNameTextID() const +{ + return false; +} + std::string AObjectTypeHandler::getNameTextID() const { - return TextIdentifier( typeName, modName, subTypeName, "name" ).get(); + return TextIdentifier("mapObject", getTypeName(), getJsonKey(), "name").get(); } std::string AObjectTypeHandler::getNameTranslated() const diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index f36b1c0fc..274449818 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -158,7 +158,6 @@ protected: /// initialization for classes that inherit this one virtual void initTypeData(const JsonNode & input); - std::string modName; std::string typeName; std::string subTypeName; public: @@ -171,18 +170,14 @@ public: void setType(si32 type, si32 subtype); void setTypeName(std::string type, std::string subtype); - std::string getTypeName(); - std::string getSubTypeName(); + 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); /// returns full form of identifier of this object in form of modName:objectName - std::string getIdentifier() const; - - /// returns objet's name in form of translatable text ID - std::string getNameTextID() const; - std::string getNameTranslated() const; + std::string getJsonKey() const; /// Returns object-specific name, if set SObjectSounds getSounds() const; @@ -204,6 +199,15 @@ public: 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(); @@ -225,7 +229,6 @@ public: h & subtype; h & templates; h & rmgInfo; - h & modName; h & typeName; h & subTypeName; h & sounds; @@ -239,15 +242,18 @@ typedef std::shared_ptr TObjectTypeHandler; /// Class responsible for creation of adventure map objects of specific type class DLL_LINKAGE ObjectClass { - std::string modScope; std::string identifier; public: ObjectClass() = default; - ObjectClass(const std::string & modScope, const std::string & identifier): - identifier(identifier), - modScope(modScope) - {} + ObjectClass(const std::string & modScope, const std::string & identifier) + { + if (identifier.find(':') == std::string::npos) + this->identifier = modScope + ":" + identifier; + else + this->identifier = identifier; + + } si32 id; std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map @@ -255,7 +261,9 @@ public: JsonNode base; std::vector objects; - std::string getIdentifier() const; + std::string getJsonKey() const; + std::string getNameTextID() const; + std::string getNameTranslated() const; template void serialize(Handler &h, const int version) { @@ -263,7 +271,6 @@ public: h & base; h & objects; h & identifier; - h & modScope; } }; From 7b82387a49d38fbd9c6a82342e8919aad35cbe87 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 11 Jan 2023 00:49:31 +0200 Subject: [PATCH 10/14] Dwellings & Banks will register custom names for their objects --- lib/mapObjects/CommonConstructors.cpp | 22 +++++++++++++++++++++- lib/mapObjects/CommonConstructors.h | 3 +++ 2 files changed, 24 insertions(+), 1 deletion(-) 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) From eebc6fd62560d915064736d9dbd51bd86d891fbf Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 00:54:19 +0200 Subject: [PATCH 11/14] Map object use format mod:object (or mod:object.subobject) --- lib/mapObjects/CObjectClassesHandler.cpp | 52 ++++++++++++------------ lib/mapObjects/CObjectClassesHandler.h | 32 +++++++-------- lib/mapping/MapFormatJson.cpp | 4 +- 3 files changed, 44 insertions(+), 44 deletions(-) diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 00faa14f1..595eb6721 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -156,7 +156,7 @@ std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj) { - auto object = loadSubObjectFromJson(scope, VLC->modh->normalizeIdentifier(scope, CModHandler::scopeBuiltin(), identifier), entry, obj, obj->objects.size()); + auto object = loadSubObjectFromJson(scope, identifier, entry, obj, obj->objects.size()); assert(object); obj->objects.push_back(object); @@ -166,8 +166,7 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std:: void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index) { - //TODO: load name for subobjects - auto object = loadSubObjectFromJson(scope, VLC->modh->normalizeIdentifier(scope, CModHandler::scopeBuiltin(), identifier), entry, obj, index); + auto object = loadSubObjectFromJson(scope, identifier, entry, obj, index); assert(object); assert(obj->objects[index] == nullptr); // ensure that this id was not loaded before @@ -178,6 +177,9 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std:: 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); @@ -186,12 +188,12 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin auto createdObject = handlerConstructors.at(obj->handlerName)(); - if (identifier.find(':') == std::string::npos) - createdObject->setTypeName(obj->getJsonKey(), scope + ":" + identifier); - else - createdObject->setTypeName(obj->getJsonKey(), identifier); + createdObject->modScope = scope; + createdObject->typeName = obj->identifier;; + createdObject->subTypeName = identifier; - createdObject->setType(obj->id, index); + createdObject->type = obj->id; + createdObject->subtype = index; createdObject->init(entry); auto range = legacyTemplates.equal_range(std::make_pair(obj->id, index)); @@ -208,7 +210,7 @@ TObjectTypeHandler CObjectClassesHandler::loadSubObjectFromJson(const std::strin std::string ObjectClass::getJsonKey() const { - return identifier; + return modScope + ':' + identifier; } std::string ObjectClass::getNameTextID() const @@ -223,8 +225,10 @@ std::string ObjectClass::getNameTranslated() const ObjectClass * CObjectClassesHandler::loadFromJson(const std::string & scope, const JsonNode & json, const std::string & name, size_t index) { - auto obj = new ObjectClass(scope, name); + auto obj = new ObjectClass(); + obj->modScope = scope; + obj->identifier = name; obj->handlerName = json["handler"].String(); obj->base = json["base"]; obj->id = index; @@ -252,14 +256,14 @@ ObjectClass * CObjectClassesHandler::loadFromJson(const std::string & scope, con 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.size()); + 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), index); + 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); @@ -437,21 +441,19 @@ AObjectTypeHandler::~AObjectTypeHandler() { } -void AObjectTypeHandler::setType(si32 type, si32 subtype) -{ - this->type = type; - this->subtype = subtype; -} - -void AObjectTypeHandler::setTypeName(std::string type, std::string subtype) -{ - this->typeName = type; - this->subTypeName = subtype; -} - std::string AObjectTypeHandler::getJsonKey() const { - return subTypeName; + return modScope + ':' + subTypeName; +} + +si32 AObjectTypeHandler::getIndex() const +{ + return type; +} + +si32 AObjectTypeHandler::getSubIndex() const +{ + return subtype; } std::string AObjectTypeHandler::getTypeName() const diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 274449818..a1c1e80e4 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -152,23 +152,26 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable 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; /// initialization for classes that inherit this one virtual void initTypeData(const JsonNode & input); - std::string typeName; - std::string subTypeName; public: - 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; @@ -229,6 +232,7 @@ public: h & subtype; h & templates; h & rmgInfo; + h & modScope; h & typeName; h & subTypeName; h & sounds; @@ -242,18 +246,9 @@ typedef std::shared_ptr TObjectTypeHandler; /// Class responsible for creation of adventure map objects of specific type class DLL_LINKAGE ObjectClass { - std::string identifier; - public: - ObjectClass() = default; - ObjectClass(const std::string & modScope, const std::string & identifier) - { - if (identifier.find(':') == std::string::npos) - this->identifier = modScope + ":" + identifier; - else - this->identifier = identifier; - - } + std::string modScope; + std::string identifier; si32 id; std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map @@ -261,6 +256,8 @@ public: JsonNode base; std::vector objects; + ObjectClass() = default; + std::string getJsonKey() const; std::string getNameTextID() const; std::string getNameTranslated() const; @@ -271,6 +268,7 @@ public: h & base; h & objects; h & identifier; + h & modScope; } }; diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index e172d7cd7..c482e2936 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -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 From a3846f1b31755b60298d26f2ec40e5b68084d7c9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 19 Jan 2023 01:43:12 +0200 Subject: [PATCH 12/14] Fix incorrect identifiers format --- lib/CArtHandler.cpp | 2 +- lib/mapObjects/CObjectClassesHandler.cpp | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) 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/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 595eb6721..f68c3cfa0 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -215,7 +215,7 @@ std::string ObjectClass::getJsonKey() const std::string ObjectClass::getNameTextID() const { - return TextIdentifier("object", identifier, "name").get(); + return TextIdentifier("object", modScope, identifier, "name").get(); } std::string ObjectClass::getNameTranslated() const @@ -246,10 +246,10 @@ ObjectClass * CObjectClassesHandler::loadFromJson(const std::string & scope, con 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(scope, subData.first, subData.second, obj, subIndex); + loadSubObject(subData.second.meta, subData.first, subData.second, obj, subIndex); } else - loadSubObject(scope, subData.first, subData.second, obj); + loadSubObject(subData.second.meta, subData.first, subData.second, obj); } return obj; } @@ -553,7 +553,7 @@ bool AObjectTypeHandler::hasNameTextID() const std::string AObjectTypeHandler::getNameTextID() const { - return TextIdentifier("mapObject", getTypeName(), getJsonKey(), "name").get(); + return TextIdentifier("mapObject", modScope, typeName, subTypeName, "name").get(); } std::string AObjectTypeHandler::getNameTranslated() const From 8b2d3365622364614d8098c81c260a0cec377d24 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 20 Jan 2023 02:07:22 +0200 Subject: [PATCH 13/14] Fix initialization of visitDir in templates --- config/objects/dwellings.json | 5 +++++ lib/mapObjects/CObjectClassesHandler.cpp | 12 ++++++------ lib/mapObjects/ObjectTemplate.cpp | 3 +++ 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/config/objects/dwellings.json b/config/objects/dwellings.json index cedce2c19..43e1fc944 100644 --- a/config/objects/dwellings.json +++ b/config/objects/dwellings.json @@ -530,6 +530,11 @@ "index" :20, "handler": "dwelling", "lastReservedIndex" : 1, + "base" : { + "base" : { + "visitableFrom" : [ "---", "+++", "+++" ] + } + }, "types" : { "elementalConflux" : { "index" : 0, diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index f68c3cfa0..eda8b7ecc 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -359,13 +359,13 @@ void CObjectClassesHandler::beforeValidate(JsonNode & object) { auto const & vector = object["subObjects"].Vector(); - if (!entry.second.Struct().count("index")) - continue; + if (entry.second.Struct().count("index")) + { + size_t index = entry.second["index"].Integer(); - size_t index = entry.second["index"].Integer(); - - if (index < vector.size()) - JsonUtils::inherit(entry.second, vector[index]); + if (index < vector.size()) + JsonUtils::inherit(entry.second, vector[index]); + } } JsonUtils::inherit(entry.second, object["base"]); 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 From e87adf334d2381fff290a2bcaf9627ebd7bf3167 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 20 Jan 2023 13:45:27 +0200 Subject: [PATCH 14/14] Fix loading of vcmi json maps --- lib/mapObjects/CObjectClassesHandler.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index eda8b7ecc..117703ea9 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -161,7 +161,7 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std:: assert(object); obj->objects.push_back(object); - registerObject(scope, "mapObject", obj->getJsonKey() + "." + object->getSubTypeName(), object->subtype); + registerObject(scope, obj->getJsonKey(), object->getSubTypeName(), object->subtype); } void CObjectClassesHandler::loadSubObject(const std::string & scope, const std::string & identifier, const JsonNode & entry, ObjectClass * obj, size_t index) @@ -172,7 +172,7 @@ void CObjectClassesHandler::loadSubObject(const std::string & scope, const std:: assert(obj->objects[index] == nullptr); // ensure that this id was not loaded before obj->objects[index] = object; - registerObject(scope, "mapObject", obj->getJsonKey() + "." + object->getSubTypeName(), object->subtype); + 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) @@ -306,11 +306,11 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 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[id.get()]; - boost::optional subID = VLC->modh->identifiers.getIdentifier(scope, object->getJsonKey(), subtype, false); + boost::optional subID = VLC->modh->identifiers.getIdentifier(scope, object->getJsonKey(), subtype); if (subID) return object->objects[subID.get()];