diff --git a/CMakeLists.txt b/CMakeLists.txt index abacac259..b9123cb75 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ set(VCMI_VERSION_PATCH 0) option(ENABLE_ERM "Enable compilation of ERM scripting module" OFF) option(ENABLE_EDITOR "Enable compilation of map editor" OFF) option(ENABLE_LAUNCHER "Enable compilation of launcher" ON) -option(ENABLE_TEST "Enable compilation of unit tests" OFF) +option(ENABLE_TEST "Enable compilation of unit tests" ON) option(ENABLE_PCH "Enable compilation using precompiled headers" ON) ############################################ diff --git a/Mods/vcmi/Sprites/ScSelC.json b/Mods/vcmi/Sprites/ScSelC.json new file mode 100644 index 000000000..e07bbfc7d --- /dev/null +++ b/Mods/vcmi/Sprites/ScSelC.json @@ -0,0 +1,7 @@ +{ + "basepath" : "mapFormatIcons/", + "images" : + [ + { "group" : 1, "frame" : 0, "file" : "vcmi1.png"} + ] +} \ No newline at end of file diff --git a/Mods/vcmi/Sprites/mapFormatIcons/vcmi1.png b/Mods/vcmi/Sprites/mapFormatIcons/vcmi1.png new file mode 100644 index 000000000..b575cf133 Binary files /dev/null and b/Mods/vcmi/Sprites/mapFormatIcons/vcmi1.png differ diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index ff00530bc..d694b6b66 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -1117,8 +1117,9 @@ void SelectionTab::parseMaps(const std::unordered_set &files) CMapInfo mapInfo; mapInfo.mapInit(file.getName()); - // ignore unsupported map versions (e.g. WoG maps without WoG - if (mapInfo.mapHeader->version <= CGI->modh->settings.data["textData"]["mapVersion"].Float()) + // ignore unsupported map versions (e.g. WoG maps without WoG) + // but accept VCMI maps + if((mapInfo.mapHeader->version >= EMapFormat::VCMI) || (mapInfo.mapHeader->version <= CGI->modh->settings.data["textData"]["mapVersion"].Float())) allItems.push_back(std::move(mapInfo)); } catch(std::exception & e) @@ -1283,7 +1284,9 @@ SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::functionaddUsedEvents(WHEEL); - format = CDefHandler::giveDef("SCSELC.DEF"); + + formatIcons = new CAnimation("SCSELC.DEF"); + formatIcons->load(); sortingBy = _format; ascending = true; @@ -1312,7 +1315,7 @@ SelectionTab::SelectionTab(CMenuScreen::EState Type, const std::functionmapHeader->version) { case EMapFormat::ROE: - temp=0; + frame = 0; break; case EMapFormat::AB: - temp=1; + frame = 1; break; case EMapFormat::SOD: - temp=2; + frame = 2; break; case EMapFormat::WOG: - temp=3; + frame = 3; + break; + case EMapFormat::VCMI: + frame = 0; + group = 1; break; default: // Unknown version. Be safe and ignore that map logGlobal->warnStream() << "Warning: " << currentItem->fileURI << " has wrong version!"; continue; } - blitAtLoc(format->ourImages[temp].bitmap, 88, 117 + line * 25, to); + IImage * icon = formatIcons->getImage(frame,group); + if(icon) + icon->draw(to, pos.x + 88, pos.y + 117 + line * 25); + //victory conditions blitAtLoc(CGP->victory->ourImages[currentItem->mapHeader->victoryIconIndex].bitmap, 306, 117 + line * 25, to); diff --git a/client/CPreGame.h b/client/CPreGame.h index 59ac607b6..05ac2648f 100644 --- a/client/CPreGame.h +++ b/client/CPreGame.h @@ -149,7 +149,7 @@ public: class SelectionTab : public CIntObject { private: - CDefHandler *format; //map size + CAnimation * formatIcons; void parseMaps(const std::unordered_set &files); void parseGames(const std::unordered_set &files, bool multi); diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 464f8d923..293b47f32 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -19,9 +19,11 @@ #include "mapObjects/MapObjects.h" #include "NetPacksBase.h" #include "GameConstants.h" +#include "StringConstants.h" #include "CRandomGenerator.h" #include "mapObjects/CObjectClassesHandler.h" +#include "mapping/CMap.h" // Note: list must match entries in ArtTraits.txt #define ART_POS_LIST \ @@ -201,28 +203,71 @@ std::vector CArtHandler::loadLegacyData(size_t dataSize) void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->id = ArtifactID(artifacts.size()); object->iconIndex = object->id + 5; artifacts.push_back(object); - VLC->modh->identifiers.registerObject(scope, "artifact", name, object->id); + VLC->modh->identifiers.requestIdentifier(scope, "object", "artifact", [=](si32 index) + { + JsonNode conf; + conf.setMeta(scope); + + VLC->objtypeh->loadSubObject(object->identifier, conf, Obj::ARTIFACT, object->id.num); + + if (!object->advMapDef.empty()) + { + JsonNode templ; + templ.setMeta(scope); + templ["animation"].String() = object->advMapDef; + + // add new template. + // Necessary for objects added via mods that don't have any templates in H3 + VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, object->id)->addTemplate(templ); + } + // object does not have any templates - this is not usable object (e.g. pseudo-art like lock) + if (VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, object->id)->getTemplates().empty()) + VLC->objtypeh->removeSubObject(Obj::ARTIFACT, object->id); + }); + + registerObject(scope, "artifact", name, object->id); } void CArtHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->id = ArtifactID(index); object->iconIndex = object->id; assert(artifacts[index] == nullptr); // ensure that this id was not loaded before artifacts[index] = object; - VLC->modh->identifiers.registerObject(scope, "artifact", name, object->id); + VLC->modh->identifiers.requestIdentifier(scope, "object", "artifact", [=](si32 index) + { + JsonNode conf; + conf.setMeta(scope); + + VLC->objtypeh->loadSubObject(object->identifier, conf, Obj::ARTIFACT, object->id.num); + + if (!object->advMapDef.empty()) + { + JsonNode templ; + templ.setMeta(scope); + templ["animation"].String() = object->advMapDef; + + // add new template. + // Necessary for objects added via mods that don't have any templates in H3 + VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, object->id)->addTemplate(templ); + } + // object does not have any templates - this is not usable object (e.g. pseudo-art like lock) + if (VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, object->id)->getTemplates().empty()) + VLC->objtypeh->removeSubObject(Obj::ARTIFACT, object->id); + }); + registerObject(scope, "artifact", name, object->id); } -CArtifact * CArtHandler::loadFromJson(const JsonNode & node) +CArtifact * CArtHandler::loadFromJson(const JsonNode & node, const std::string & identifier) { CArtifact * art; @@ -234,7 +279,7 @@ CArtifact * CArtHandler::loadFromJson(const JsonNode & node) loadGrowingArt(growing, node); art = growing; } - + art->identifier = identifier; const JsonNode & text = node["text"]; art->name = text["name"].String(); art->description = text["description"].String(); @@ -629,7 +674,7 @@ std::vector CArtHandler::getDefaultAllowed() const std::vector allowedArtifacts; allowedArtifacts.resize(127, true); allowedArtifacts.resize(141, false); - allowedArtifacts.resize(GameConstants::ARTIFACTS_QUANTITY, true); + allowedArtifacts.resize(artifacts.size(), true); return allowedArtifacts; } @@ -695,24 +740,20 @@ void CArtHandler::afterLoadFinalization() } } CBonusSystemNode::treeHasChanged(); +} - for (CArtifact * art : artifacts) - { - VLC->objtypeh->loadSubObject(art->Name(), JsonNode(), Obj::ARTIFACT, art->id.num); +si32 CArtHandler::decodeArfifact(const std::string& identifier) +{ + auto rawId = VLC->modh->identifiers.getIdentifier("core", "artifact", identifier); + if(rawId) + return rawId.get(); + else + return -1; +} - if (!art->advMapDef.empty()) - { - JsonNode templ; - templ["animation"].String() = art->advMapDef; - - // add new template. - // Necessary for objects added via mods that don't have any templates in H3 - VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->id)->addTemplate(templ); - } - // object does not have any templates - this is not usable object (e.g. pseudo-art like lock) - if (VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->id)->getTemplates().empty()) - VLC->objtypeh->removeSubObject(Obj::ARTIFACT, art->id); - } +std::string CArtHandler::encodeArtifact(const si32 index) +{ + return VLC->arth->artifacts[index]->identifier; } CArtifactInstance::CArtifactInstance() @@ -937,6 +978,40 @@ CArtifactInstance * CArtifactInstance::createNewArtifactInstance(int aid) return createNewArtifactInstance(VLC->arth->artifacts[aid]); } +CArtifactInstance * CArtifactInstance::createArtifact(CMap * map, int aid, int spellID) +{ + CArtifactInstance * a = nullptr; + if(aid >= 0) + { + if(spellID < 0) + { + a = CArtifactInstance::createNewArtifactInstance(aid); + } + else + { + a = CArtifactInstance::createScroll(SpellID(spellID).toSpell()); + } + } + else //FIXME: create combined artifact instance for random combined artifacts, just in case + { + a = new CArtifactInstance(); //random, empty + } + + map->addNewArtifactInstance(a); + + //TODO make it nicer + if(a->artType && (!!a->artType->constituents)) + { + CCombinedArtifactInstance * comb = dynamic_cast(a); + for(CCombinedArtifactInstance::ConstituentInfo & ci : comb->constituentsInfo) + { + map->addNewArtifactInstance(ci.art); + } + } + return a; +} + + void CArtifactInstance::deserializationFix() { setType(artType); @@ -1313,3 +1388,13 @@ void CArtifactSet::artDeserializationFix(CBonusSystemNode *node) if(elem.second.artifact && !elem.second.locked) node->attachTo(elem.second.artifact); } + +void CArtifactSet::writeJson(JsonNode& json) const +{ + +} + +void CArtifactSet::readJson(const JsonNode& json) +{ + +} diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index a19539c12..823756fdf 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -25,6 +25,7 @@ struct ArtifactLocation; class CArtifactSet; class CArtifactInstance; class CRandomGenerator; +class CMap; #define ART_BEARER_LIST \ ART_BEARER(HERO)\ @@ -49,6 +50,7 @@ protected: public: enum EartClass {ART_SPECIAL=1, ART_TREASURE=2, ART_MINOR=4, ART_MAJOR=8, ART_RELIC=16}; //artifact classes + std::string identifier; std::string image; std::string large; // big image for cutom artifacts, used in drag & drop std::string advMapDef; //used for adventure map object @@ -79,6 +81,10 @@ public: h & static_cast(*this); h & name & description & eventText & image & large & advMapDef & iconIndex & price & possibleSlots & constituents & constituentOf & aClass & id; + if(version>=759) + { + h & identifier; + } } CArtifact(); @@ -147,6 +153,15 @@ public: static CArtifactInstance *createScroll(SpellID sid); static CArtifactInstance *createNewArtifactInstance(CArtifact *Art); static CArtifactInstance *createNewArtifactInstance(int aid); + + /** + * Creates an artifact instance. + * + * @param aid the id of the artifact + * @param spellID optional. the id of a spell if a spell scroll object should be created + * @return the created artifact instance + */ + static CArtifactInstance * createArtifact(CMap * map, int aid, int spellID = -1); }; class DLL_LINKAGE CCombinedArtifactInstance : public CArtifactInstance @@ -240,6 +255,12 @@ public: std::vector getDefaultAllowed() const override; + ///json serialization helper + static si32 decodeArfifact(const std::string & identifier); + + ///json serialization helper + static std::string encodeArtifact(const si32 index); + template void serialize(Handler &h, const int version) { h & artifacts & allowedArtifacts & treasures & minors & majors & relics @@ -248,7 +269,7 @@ public: } private: - CArtifact * loadFromJson(const JsonNode & node); + CArtifact * loadFromJson(const JsonNode & node, const std::string & identifier); void addSlot(CArtifact * art, const std::string & slotID); void loadSlots(CArtifact * art, const JsonNode & node); @@ -313,6 +334,10 @@ public: void artDeserializationFix(CBonusSystemNode *node); +protected: + void writeJson(JsonNode & json) const; + void readJson(const JsonNode & json); + protected: std::pair searchForConstituent(int aid) const; }; diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index cbde78fe7..2194a4fdd 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -167,12 +167,12 @@ static void AddAbility(CCreature *cre, const JsonVector &ability_vec) } nsf->type = it->second; - + JsonUtils::parseTypedBonusShort(ability_vec,nsf); nsf->source = Bonus::CREATURE_ABILITY; nsf->sid = cre->idNumber; - + cre->addNewBonus(nsf); } @@ -188,6 +188,16 @@ CCreatureHandler::CCreatureHandler() loadCommanders(); } +const CCreature * CCreatureHandler::getCreature(const std::string & scope, const std::string & identifier) const +{ + boost::optional index = VLC->modh->identifiers.getIdentifier(scope, "creature", identifier); + + if(!index) + throw std::runtime_error("Creature not found "+identifier); + + return creatures[*index]; +} + void CCreatureHandler::loadCommanders() { JsonNode data(ResourceID("config/commanders.json")); @@ -358,23 +368,41 @@ std::vector CCreatureHandler::loadLegacyData(size_t dataSize) void CCreatureHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->setId(CreatureID(creatures.size())); object->iconIndex = object->idNumber + 2; creatures.push_back(object); - VLC->modh->identifiers.registerObject(scope, "creature", name, object->idNumber); + VLC->modh->identifiers.requestIdentifier(scope, "object", "monster", [=](si32 index) + { + JsonNode conf; + conf.setMeta(scope); + + VLC->objtypeh->loadSubObject(object->identifier, conf, Obj::MONSTER, object->idNumber.num); + if (!object->advMapDef.empty()) + { + JsonNode templ; + templ["animation"].String() = object->advMapDef; + VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->addTemplate(templ); + } + + // object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower) + if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->getTemplates().empty()) + VLC->objtypeh->removeSubObject(Obj::MONSTER, object->idNumber.num); + }); + + registerObject(scope, "creature", name, object->idNumber); for(auto node : data["extraNames"].Vector()) { - VLC->modh->identifiers.registerObject(scope, "creature", node.String(), object->idNumber); + registerObject(scope, "creature", node.String(), object->idNumber); } } void CCreatureHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->setId(CreatureID(index)); object->iconIndex = object->idNumber + 2; @@ -386,10 +414,28 @@ void CCreatureHandler::loadObject(std::string scope, std::string name, const Jso assert(creatures[index] == nullptr); // ensure that this id was not loaded before creatures[index] = object; - VLC->modh->identifiers.registerObject(scope, "creature", name, object->idNumber); + VLC->modh->identifiers.requestIdentifier(scope, "object", "monster", [=](si32 index) + { + JsonNode conf; + conf.setMeta(scope); + + VLC->objtypeh->loadSubObject(object->identifier, conf, Obj::MONSTER, object->idNumber.num); + if (!object->advMapDef.empty()) + { + JsonNode templ; + templ["animation"].String() = object->advMapDef; + VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->addTemplate(templ); + } + + // object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower) + if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, object->idNumber.num)->getTemplates().empty()) + VLC->objtypeh->removeSubObject(Obj::MONSTER, object->idNumber.num); + }); + + registerObject(scope, "creature", name, object->idNumber); for(auto & node : data["extraNames"].Vector()) { - VLC->modh->identifiers.registerObject(scope, "creature", node.String(), object->idNumber); + registerObject(scope, "creature", node.String(), object->idNumber); } } @@ -568,11 +614,12 @@ void CCreatureHandler::loadUnitAnimInfo(JsonNode & graphics, CLegacyConfigParser graphics.Struct().erase("missile"); } -CCreature * CCreatureHandler::loadFromJson(const JsonNode & node) +CCreature * CCreatureHandler::loadFromJson(const JsonNode & node, const std::string & identifier) { auto cre = new CCreature(); const JsonNode & name = node["name"]; + cre->identifier = identifier; cre->nameSing = name["singular"].String(); cre->namePl = name["plural"].String(); @@ -1130,20 +1177,7 @@ void CCreatureHandler::buildBonusTreeForTiers() void CCreatureHandler::afterLoadFinalization() { - for (CCreature * crea : creatures) - { - VLC->objtypeh->loadSubObject(crea->nameSing, JsonNode(), Obj::MONSTER, crea->idNumber.num); - if (!crea->advMapDef.empty()) - { - JsonNode templ; - templ["animation"].String() = crea->advMapDef; - VLC->objtypeh->getHandlerFor(Obj::MONSTER, crea->idNumber)->addTemplate(templ); - } - // object does not have any templates - this is not usable object (e.g. pseudo-creature like Arrow Tower) - if (VLC->objtypeh->getHandlerFor(Obj::MONSTER, crea->idNumber.num)->getTemplates().empty()) - VLC->objtypeh->removeSubObject(Obj::MONSTER, crea->idNumber.num); - } } void CCreatureHandler::deserializationFix() diff --git a/lib/CCreatureHandler.h b/lib/CCreatureHandler.h index 12ba7e146..acec549fe 100644 --- a/lib/CCreatureHandler.h +++ b/lib/CCreatureHandler.h @@ -26,6 +26,8 @@ class CCreature; class DLL_LINKAGE CCreature : public CBonusSystemNode { public: + std::string identifier; + std::string nameRef; // reference name, stringID std::string nameSing;// singular name, e.g. Centaur std::string namePl; // plural name, e.g. Centaurs @@ -136,6 +138,10 @@ public: h & idNumber & faction & sounds & animation; h & doubleWide & special; + if(version>=759) + { + h & identifier; + } } CCreature(); @@ -148,7 +154,7 @@ private: CBonusSystemNode creaturesOfLevel[GameConstants::CREATURES_PER_TOWN + 1];//index 0 is used for creatures of unknown tier or outside <1-7> range /// load one creature from json config - CCreature * loadFromJson(const JsonNode & node); + CCreature * loadFromJson(const JsonNode & node, const std::string & identifier); void loadJsonAnimation(CCreature * creature, const JsonNode & graphics); void loadStackExperience(CCreature * creature, const JsonNode &input); @@ -186,6 +192,8 @@ public: std::vector< std::vector > skillLevels; //how much of a bonus will be given to commander with every level. SPELL_POWER also gives CASTS and RESISTANCE std::vector > > skillRequirements; // first - Bonus, second - which two skills are needed to use it + const CCreature * getCreature(const std::string & scope, const std::string & identifier) const; + void deserializationFix(); CreatureID pickRandomMonster(CRandomGenerator & rand, int tier = -1) const; //tier <1 - CREATURES_PER_TOWN> or -1 for any void addBonusForTier(int tier, Bonus *b); //tier must be <1-7> diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 2a0d890f1..23c6c58b8 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -11,6 +11,7 @@ #include "spells/CSpellHandler.h" #include "CHeroHandler.h" #include "IBonusTypeHandler.h" +#include "serializer/JsonSerializeFormat.h" /* * CCreatureSet.cpp, part of VCMI engine @@ -479,6 +480,38 @@ CCreatureSet & CCreatureSet::operator=(const CCreatureSet&cs) void CCreatureSet::armyChanged() { + +} + +void CCreatureSet::serializeJson(JsonSerializeFormat & handler, const std::string & fieldName) +{ + if(handler.saving && stacks.empty()) + return; + JsonNode & json = handler.getCurrent()[fieldName]; + + if(handler.saving) + { + for(const auto & p : stacks) + { + JsonNode stack_node; + p.second->writeJson(stack_node); + json.Vector()[p.first.getNum()] = stack_node; + } + } + else + { + for(size_t idx = 0; idx < json.Vector().size(); idx++) + { + if(json.Vector()[idx]["amount"].Float() > 0) + { + CStackInstance * new_stack = new CStackInstance(); + + new_stack->readJson(json.Vector()[idx]); + + putStack(SlotID(idx), new_stack); + } + } + } } CStackInstance::CStackInstance() @@ -605,7 +638,7 @@ std::string CStackInstance::bonusToString(const Bonus *bonus, bool description) { return VLC->getBth()->bonusToString(bonus, this, description); } - + } std::string CStackInstance::bonusToGraphics(const Bonus *bonus) const @@ -700,6 +733,25 @@ ArtBearer::ArtBearer CStackInstance::bearerType() const return ArtBearer::CREATURE; } +void CStackInstance::writeJson(JsonNode& json) const +{ + if(idRand > -1) + { + json["level"].Float() = (int)idRand / 2; + json["upgraded"].Bool() = (idRand % 2) > 0; + } + CStackBasicDescriptor::writeJson(json); +} + +void CStackInstance::readJson(const JsonNode& json) +{ + if(json["type"].String() == "") + { + idRand = json["level"].Float() * 2 + (int)json["upgraded"].Bool(); + } + CStackBasicDescriptor::readJson(json); +} + CCommanderInstance::CCommanderInstance() { init(); @@ -792,6 +844,22 @@ CStackBasicDescriptor::CStackBasicDescriptor(const CCreature *c, TQuantity Count { } +void CStackBasicDescriptor::writeJson(JsonNode& json) const +{ + json.setType(JsonNode::DATA_STRUCT); + if(type) + json["type"].String() = type->identifier; + json["amount"].Float() = count; +} + +void CStackBasicDescriptor::readJson(const JsonNode& json) +{ + auto typeName = json["type"].String(); + if(typeName != "") + type = VLC->creh->getCreature("core", json["type"].String()); + count = json["amount"].Float(); +} + DLL_LINKAGE std::ostream & operator<<(std::ostream & str, const CStackInstance & sth) { if(!sth.valid(true)) diff --git a/lib/CCreatureSet.h b/lib/CCreatureSet.h index 08037f1dd..27075e8be 100644 --- a/lib/CCreatureSet.h +++ b/lib/CCreatureSet.h @@ -14,11 +14,12 @@ * Full text of license available in license.txt file, in main folder * */ - +class JsonNode; class CCreature; class CGHeroInstance; class CArmedInstance; class CCreatureArtifactSet; +class JsonSerializeFormat; class DLL_LINKAGE CStackBasicDescriptor { @@ -34,6 +35,10 @@ public: { h & type & count; } + + void writeJson(JsonNode & json) const; + + void readJson(const JsonNode & json); }; class DLL_LINKAGE CStackInstance : public CBonusSystemNode, public CStackBasicDescriptor, public CArtifactSet @@ -59,6 +64,10 @@ public: BONUS_TREE_DESERIALIZATION_FIX } + void writeJson(JsonNode & json) const; + + void readJson(const JsonNode & json); + //overrides CBonusSystemNode std::string bonusToString(const Bonus *bonus, bool description) const override; // how would bonus description look for this particular type of node std::string bonusToGraphics(const Bonus *bonus) const; //file name of graphics from StackSkills , in future possibly others @@ -110,7 +119,7 @@ public: bool gainsLevel() const; //true if commander has lower level than should upon his experience ui64 getPower() const override {return 0;}; int getExpRank() const override; - int getLevel() const override; + int getLevel() const override; ArtBearer::ArtBearer bearerType() const override; //from CArtifactSet template void serialize(Handler &h, const int version) @@ -211,6 +220,9 @@ public: { h & stacks & formation; } + + void serializeJson(JsonSerializeFormat & handler, const std::string & fieldName); + operator bool() const { return !stacks.empty(); diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 9b6618544..087783d89 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -736,7 +736,7 @@ BattleInfo * CGameState::setupBattle(int3 tile, const CArmedInstance *armies[2], { const TerrainTile &t = map->getTile(tile); ETerrainType terrain = t.terType; - if(t.isCoastal() && !t.isWater()) + if(map->isCoastalTile(tile)) //coastal tile is always ground terrain = ETerrainType::SAND; BFieldType terType = battleGetBattlefieldType(tile); @@ -1852,7 +1852,7 @@ void CGameState::initMapObjects() { if(obj) { - //logGlobal->traceStream() << boost::format ("Calling Init for object %d, %d") % obj->ID % obj->subID; + logGlobal->traceStream() << boost::format ("Calling Init for object %d, %s, %s") % obj->id.getNum() % obj->typeName % obj->subTypeName; obj->initObj(); } } @@ -1956,7 +1956,7 @@ BFieldType CGameState::battleGetBattlefieldType(int3 tile) } } - if(!t.isWater() && t.isCoastal()) + if(map->isCoastalTile(tile)) //coastal tile is always ground return BFieldType::SAND_SHORE; switch(t.terType) diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 44f772375..e21b7a05f 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -96,12 +96,12 @@ bool CObstacleInfo::isAppropriate(ETerrainType terrainType, int specialBattlefie return vstd::contains(allowedTerrains, terrainType); } -CHeroClass *CHeroClassHandler::loadFromJson(const JsonNode & node) +CHeroClass * CHeroClassHandler::loadFromJson(const JsonNode & node, const std::string & identifier) { std::string affinityStr[2] = { "might", "magic" }; auto heroClass = new CHeroClass(); - + heroClass->identifier = identifier; heroClass->imageBattleFemale = node["animation"]["battle"]["female"].String(); heroClass->imageBattleMale = node["animation"]["battle"]["male"].String(); //MODS COMPATIBILITY FOR 0.96 @@ -192,7 +192,7 @@ std::vector CHeroClassHandler::loadLegacyData(size_t dataSize) void CHeroClassHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->id = heroClasses.size(); heroClasses.push_back(object); @@ -210,7 +210,7 @@ void CHeroClassHandler::loadObject(std::string scope, std::string name, const Js void CHeroClassHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->id = index; assert(heroClasses[index] == nullptr); // ensure that this id was not loaded before @@ -292,10 +292,10 @@ CHeroHandler::CHeroHandler() loadExperience(); } -CHero * CHeroHandler::loadFromJson(const JsonNode & node) +CHero * CHeroHandler::loadFromJson(const JsonNode & node, const std::string & identifier) { auto hero = new CHero; - + hero->identifier = identifier; hero->sex = node["female"].Bool(); hero->special = node["special"].Bool(); @@ -540,7 +540,7 @@ std::vector CHeroHandler::loadLegacyData(size_t dataSize) void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->ID = HeroTypeID(heroes.size()); object->imageIndex = heroes.size() + 30; // 2 special frames + some extra portraits @@ -551,7 +551,7 @@ void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNod void CHeroHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->ID = HeroTypeID(index); object->imageIndex = index; @@ -611,3 +611,31 @@ std::vector CHeroHandler::getDefaultAllowedAbilities() const allowedAbilities.resize(GameConstants::SKILL_QUANTITY, true); return allowedAbilities; } + +si32 CHeroHandler::decodeHero(const std::string & identifier) +{ + auto rawId = VLC->modh->identifiers.getIdentifier("core", "hero", identifier); + if(rawId) + return rawId.get(); + else + return -1; +} + +std::string CHeroHandler::encodeHero(const si32 index) +{ + return VLC->heroh->heroes.at(index)->identifier; +} + +si32 CHeroHandler::decodeSkill(const std::string & identifier) +{ + auto rawId = VLC->modh->identifiers.getIdentifier("core", "skill", identifier); + if(rawId) + return rawId.get(); + else + return -1; +} + +std::string CHeroHandler::encodeSkill(const si32 index) +{ + return NSecondarySkill::names[index]; +} diff --git a/lib/CHeroHandler.h b/lib/CHeroHandler.h index 6c87666d6..709ff1255 100644 --- a/lib/CHeroHandler.h +++ b/lib/CHeroHandler.h @@ -59,7 +59,7 @@ public: h & minAmount & maxAmount & creature; } }; - + std::string identifier; HeroTypeID ID; si32 imageIndex; @@ -92,6 +92,10 @@ public: h & ID & imageIndex & initialArmy & heroClass & secSkillsInit & spec & specialty & spells & haveSpellBook & sex & special; h & name & biography & specName & specDescr & specTooltip; h & iconSpecSmall & iconSpecLarge & portraitSmall & portraitLarge; + if(version>=759) + { + h & identifier; + } } }; @@ -169,7 +173,7 @@ struct DLL_LINKAGE CObstacleInfo class DLL_LINKAGE CHeroClassHandler : public IHandlerBase { - CHeroClass *loadFromJson(const JsonNode & node); + CHeroClass *loadFromJson(const JsonNode & node, const std::string & identifier); public: std::vector< ConstTransitivePtr > heroClasses; @@ -207,7 +211,7 @@ class DLL_LINKAGE CHeroHandler : public IHandlerBase void loadObstacles(); /// Load single hero from json - CHero * loadFromJson(const JsonNode & node); + CHero * loadFromJson(const JsonNode & node, const std::string & identifier); public: CHeroClassHandler classes; @@ -253,6 +257,18 @@ public: */ std::vector getDefaultAllowedAbilities() const; + ///json serialization helper + static si32 decodeHero(const std::string & identifier); + + ///json serialization helper + static std::string encodeHero(const si32 index); + + ///json serialization helper + static si32 decodeSkill(const std::string & identifier); + + ///json serialization helper + static std::string encodeSkill(const si32 index); + template void serialize(Handler &h, const int version) { h & classes & heroes & expPerLevel & ballistics & terrCosts; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 28fafdaee..be534cff8 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -11,14 +11,17 @@ set(lib_SRCS filesystem/CCompressedStream.cpp filesystem/CFilesystemLoader.cpp filesystem/CArchiveLoader.cpp + filesystem/CMemoryBuffer.cpp filesystem/CMemoryStream.cpp filesystem/CBinaryReader.cpp filesystem/CFileInputStream.cpp filesystem/CZipLoader.cpp + filesystem/CZipSaver.cpp filesystem/FileInfo.cpp filesystem/Filesystem.cpp filesystem/FileStream.cpp filesystem/ResourceID.cpp + filesystem/MinizipExtensions.cpp mapObjects/CArmedInstance.cpp mapObjects/CBank.cpp @@ -101,6 +104,10 @@ set(lib_SRCS Connection.cpp NetPacksLib.cpp + serializer/JsonSerializer.cpp + serializer/JsonDeserializer.cpp + serializer/JsonSerializeFormat.cpp + registerTypes/RegisterTypes.cpp registerTypes/TypesClientPacks1.cpp registerTypes/TypesClientPacks2.cpp diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 85fcdb924..081983bc7 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -205,10 +205,21 @@ std::vector CIdentifierStorage::getPossibleIdent else { //...unless destination mod was specified explicitly - auto myDeps = VLC->modh->getModData(request.localScope).dependencies; - if (request.remoteScope == "core" || // allow only available to all core mod - myDeps.count(request.remoteScope)) // or dependencies + //note: getModData does not work for "core" by design + + //for map format support core mod has access to any mod + //TODO: better solution for access from map? + if(request.localScope == "core" || request.localScope == "") + { allowedScopes.insert(request.remoteScope); + } + else + { + // allow only available to all core mod or dependencies + auto myDeps = VLC->modh->getModData(request.localScope).dependencies; + if (request.remoteScope == "core" || myDeps.count(request.remoteScope)) + allowedScopes.insert(request.remoteScope); + } } std::string fullID = request.type + '.' + request.name; @@ -844,9 +855,16 @@ void CModHandler::loadModFilesystems() CModInfo & CModHandler::getModData(TModID modId) { - CModInfo & mod = allMods.at(modId); - assert(vstd::contains(activeMods, modId)); // not really necessary but won't hurt - return mod; + auto it = allMods.find(modId); + + if(it == allMods.end()) + { + throw std::runtime_error("Mod not found '" + modId+"'"); + } + else + { + return it->second; + } } void CModHandler::initializeConfig() @@ -905,3 +923,16 @@ void CModHandler::afterLoad() FileStream file(*CResourceHandler::get()->getResourceName(ResourceID("config/modSettings.json")), std::ofstream::out | std::ofstream::trunc); file << modSettings; } + +std::string CModHandler::normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) const +{ + auto p = splitString(identifier, ':'); + + if(p.first.empty()) + p.first = scope; + + if(p.first == remoteScope) + p.first.clear(); + + return p.first.empty() ? p.second : p.first +":"+p.second; +} diff --git a/lib/CModHandler.h b/lib/CModHandler.h index b74918ca7..02acf33ec 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -292,6 +292,8 @@ public: CModHandler(); + std::string normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) const; + template void serialize(Handler &h, const int version) { h & allMods & activeMods & settings & modules & identifiers; diff --git a/lib/CPathfinder.cpp b/lib/CPathfinder.cpp index 1ff5b1f15..d35ba3043 100644 --- a/lib/CPathfinder.cpp +++ b/lib/CPathfinder.cpp @@ -133,6 +133,7 @@ void CPathfinder::calculatePaths() if(!hlp->isLayerAvailable(i)) continue; + /// Check transition without tile accessability rules if(cp->layer != i && !isLayerTransitionPossible(i)) continue; @@ -143,6 +144,7 @@ void CPathfinder::calculatePaths() if(dp->accessible == CGPathNode::NOT_SET) continue; + /// Check transition using tile accessability rules if(cp->layer != i && !isLayerTransitionPossible()) continue; @@ -302,16 +304,23 @@ bool CPathfinder::isLayerTransitionPossible(const ELayer destLayer) const switch(cp->layer) { case ELayer::LAND: - if(destLayer != ELayer::AIR) - return true; - - if(!options.lightweightFlyingMode || isSourceInitialPosition()) + if(destLayer == ELayer::AIR) + { + if(!options.lightweightFlyingMode || isSourceInitialPosition()) + return true; + } + else if(destLayer == ELayer::SAIL) + { + if(dt->isWater()) + return true; + } + else return true; break; case ELayer::SAIL: - if(destLayer == ELayer::LAND && dt->isCoastal()) + if(destLayer == ELayer::LAND && !dt->isWater()) return true; break; diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 4e1906c1b..9462bf9b6 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -526,7 +526,7 @@ void CTownHandler::loadClientData(CTown &town, const JsonNode & source) info.tavernVideo = source["tavernVideo"].String(); else info.tavernVideo = "TAVERN.BIK"; - //end of legacy assignment + //end of legacy assignment loadTownHall(town, source["hallSlots"]); loadStructures(town, source["structures"]); @@ -646,7 +646,7 @@ void CTownHandler::loadPuzzle(CFaction &faction, const JsonNode &source) assert(faction.puzzleMap.size() == GameConstants::PUZZLE_MAP_PIECES); } -CFaction * CTownHandler::loadFromJson(const JsonNode &source, std::string identifier) +CFaction * CTownHandler::loadFromJson(const JsonNode &source, const std::string & identifier) { auto faction = new CFaction(); @@ -682,7 +682,7 @@ CFaction * CTownHandler::loadFromJson(const JsonNode &source, std::string identi void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(data, name); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->index = factions.size(); factions.push_back(object); @@ -699,7 +699,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod { // register town once objects are loaded JsonNode config = data["town"]["mapObject"]; - config["faction"].String() = object->identifier; + config["faction"].String() = name; config["faction"].meta = scope; if (config.meta.empty())// MODS COMPATIBILITY FOR 0.96 config.meta = scope; @@ -722,7 +722,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod void CTownHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { - auto object = loadFromJson(data, name); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->index = index; assert(factions[index] == nullptr); // ensure that this id was not loaded before factions[index] = object; @@ -739,7 +739,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod { // register town once objects are loaded JsonNode config = data["town"]["mapObject"]; - config["faction"].String() = object->identifier; + config["faction"].String() = name; config["faction"].meta = scope; VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index); }); @@ -795,3 +795,17 @@ std::set CTownHandler::getAllowedFactions(bool withTown /*=true*/) con return allowedFactions; } + +si32 CTownHandler::decodeFaction(const std::string & identifier) +{ + auto rawId = VLC->modh->identifiers.getIdentifier("core", "faction", identifier); + if(rawId) + return rawId.get(); + else + return -1; +} + +std::string CTownHandler::encodeFaction(const si32 index) +{ + return VLC->townh->factions[index]->identifier; +} diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 39b99291f..7df2d78a6 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -122,8 +122,8 @@ public: std::string creatureBg120; std::string creatureBg130; - - + + std::vector puzzleMap; @@ -142,7 +142,7 @@ public: static std::vector defaultMoatHexes(); CFaction * faction; - + std::vector names; //names of the town instances /// level -> list of creatures on this tier @@ -264,7 +264,7 @@ class DLL_LINKAGE CTownHandler : public IHandlerBase void loadPuzzle(CFaction & faction, const JsonNode & source); - CFaction * loadFromJson(const JsonNode & data, std::string identifier); + CFaction * loadFromJson(const JsonNode & data, const std::string & identifier); public: std::vector > factions; @@ -282,6 +282,12 @@ public: std::vector getDefaultAllowed() const override; std::set getAllowedFactions(bool withTown = true) const; + //json serialization helper + static si32 decodeFaction(const std::string & identifier); + + //json serialization helper + static std::string encodeFaction(const si32 index); + template void serialize(Handler &h, const int version) { h & factions; diff --git a/lib/Connection.h b/lib/Connection.h index 169a1a1e5..ca084e8ba 100644 --- a/lib/Connection.h +++ b/lib/Connection.h @@ -27,7 +27,7 @@ #include "mapping/CCampaignHandler.h" //for CCampaignState #include "rmg/CMapGenerator.h" // for CMapGenOptions -const ui32 version = 758; +const ui32 version = 759; const ui32 minSupportedVersion = 753; class CISer; diff --git a/lib/IHandlerBase.cpp b/lib/IHandlerBase.cpp index bddbae3ba..e4bfe44e7 100644 --- a/lib/IHandlerBase.cpp +++ b/lib/IHandlerBase.cpp @@ -17,3 +17,8 @@ void IHandlerBase::registerObject(std::string scope, std::string type_name, std: { return VLC->modh->identifiers.registerObject(scope, type_name, name, index); } + +std::string IHandlerBase::normalizeIdentifier(const std::string& scope, const std::string& remoteScope, const std::string& identifier) const +{ + return VLC->modh->normalizeIdentifier(scope, remoteScope, identifier); +} diff --git a/lib/IHandlerBase.h b/lib/IHandlerBase.h index c26b6f944..c11a91be8 100644 --- a/lib/IHandlerBase.h +++ b/lib/IHandlerBase.h @@ -25,6 +25,7 @@ class DLL_LINKAGE IHandlerBase protected: /// Calls modhandler. Mostly needed to avoid large number of includes in headers void registerObject(std::string scope, std::string type_name, std::string name, si32 index); + std::string normalizeIdentifier(const std::string & scope, const std::string & remoteScope, const std::string & identifier) const; public: /// loads all original game data in vector of json nodes @@ -65,7 +66,7 @@ public: void loadObject(std::string scope, std::string name, const JsonNode & data) override { auto type_name = getTypeName(); - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->id = _ObjectID(objects.size()); objects.push_back(object); @@ -75,7 +76,7 @@ public: void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override { auto type_name = getTypeName(); - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); object->id = _ObjectID(index); @@ -99,7 +100,7 @@ public: return objects[raw_id]; } protected: - virtual _Object * loadFromJson(const JsonNode & json) = 0; + virtual _Object * loadFromJson(const JsonNode & json, const std::string & identifier) = 0; virtual const std::string getTypeName() const = 0; public: //todo: make private std::vector> objects; diff --git a/lib/JsonDetail.cpp b/lib/JsonDetail.cpp index fb70d8b93..6b01614d2 100644 --- a/lib/JsonDetail.cpp +++ b/lib/JsonDetail.cpp @@ -61,19 +61,32 @@ void JsonWriter::writeEntry(JsonVector::const_iterator entry) void JsonWriter::writeString(const std::string &string) { static const std::string escaped = "\"\\\b\f\n\r\t"; - + + static const std::array escaped_code = {'\"', '\\', 'b', 'f', 'n', 'r', 't'}; + out <<'\"'; size_t pos=0, start=0; for (; pos class Writer : public boost::static_visitor { diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 1283d7350..54254be11 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -386,6 +386,7 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs ) //If hero on Boat is removed, the Boat disappears if(h->boat) { + gs->map->instanceNames.erase(h->boat->instanceName); gs->map->objects[h->boat->id.getNum()].dellNull(); h->boat = nullptr; } @@ -430,7 +431,7 @@ DLL_LINKAGE void RemoveObject::applyGs( CGameState *gs ) }; event.trigger = event.trigger.morph(patcher); } - + gs->map->instanceNames.erase(obj->instanceName); gs->map->objects[id.getNum()].dellNull(); gs->map->calculateGuardingGreaturePositions(); } diff --git a/lib/StringConstants.h b/lib/StringConstants.h index 3004d3afa..78fa1a8d8 100644 --- a/lib/StringConstants.h +++ b/lib/StringConstants.h @@ -21,7 +21,7 @@ namespace GameConstants const std::string TERRAIN_NAMES [TERRAIN_TYPES] = { "dirt", "sand", "grass", "snow", "swamp", "rough", "subterra", "lava", "water", "rock" }; - + const std::string RESOURCE_NAMES [RESOURCE_QUANTITY] = { "wood", "mercury", "ore", "sulfur", "crystal", "gems", "gold", "mithril" }; @@ -84,3 +84,17 @@ namespace ETownType "stronghold", "fortress", "conflux" }; } + +namespace NArtifactPosition +{ + const std::string names [19] = + { + "head", "shoulders", "neck", "rightHand", "leftHand", "torso", //5 + "rightRing", "leftRing", "feet", //8 + "misc1", "misc2", "misc3", "misc4", //12 + "mach1", "mach2", "mach3", "mach4", //16 + "spellbook", "misc5" //18 + }; + + const std::string backpack = "backpack"; +} diff --git a/lib/VCMI_lib.cbp b/lib/VCMI_lib.cbp index e49996ada..946c88d88 100644 --- a/lib/VCMI_lib.cbp +++ b/lib/VCMI_lib.cbp @@ -152,6 +152,7 @@ + @@ -217,11 +218,18 @@ + + + + + + + @@ -229,6 +237,8 @@ + + @@ -306,6 +316,12 @@ + + + + + + diff --git a/lib/VCMI_lib.vcxproj b/lib/VCMI_lib.vcxproj index 2a1796676..310a6fa3a 100644 --- a/lib/VCMI_lib.vcxproj +++ b/lib/VCMI_lib.vcxproj @@ -188,8 +188,14 @@ + + + + + + @@ -308,13 +314,19 @@ + + + + + + @@ -370,6 +382,9 @@ + + + diff --git a/lib/VCMI_lib.vcxproj.filters b/lib/VCMI_lib.vcxproj.filters index bf53bd06e..f84db0b5d 100644 --- a/lib/VCMI_lib.vcxproj.filters +++ b/lib/VCMI_lib.vcxproj.filters @@ -26,6 +26,9 @@ {bda963b1-00e1-412a-9b44-f5cd3f8e9e33} + + {2f582170-d8a6-42f3-8da3-8255bac28f5a} + @@ -234,6 +237,24 @@ filesystem + + serializer + + + serializer + + + serializer + + + filesystem + + + filesystem + + + filesystem + @@ -581,5 +602,32 @@ Header Files + + serializer + + + serializer + + + serializer + + + filesystem + + + filesystem + + + filesystem + + + filesystem + + + filesystem + + + filesystem + \ No newline at end of file diff --git a/lib/filesystem/CFileInputStream.cpp b/lib/filesystem/CFileInputStream.cpp index 3d514d9cb..6de595dd3 100644 --- a/lib/filesystem/CFileInputStream.cpp +++ b/lib/filesystem/CFileInputStream.cpp @@ -18,6 +18,7 @@ CFileInputStream::CFileInputStream(const boost::filesystem::path & file, si64 st fileStream.seekg(dataStart, std::ios::beg); } + si64 CFileInputStream::read(ui8 * data, si64 size) { si64 origin = tell(); diff --git a/lib/filesystem/CInputOutputStream.h b/lib/filesystem/CInputOutputStream.h new file mode 100644 index 000000000..55c88b360 --- /dev/null +++ b/lib/filesystem/CInputOutputStream.h @@ -0,0 +1,9 @@ +#pragma once + +#include "CInputStream.h" +#include "COutputStream.h" + +class CInputOutputStream: public CInputStream, public COutputStream +{ + +}; diff --git a/lib/filesystem/CInputStream.h b/lib/filesystem/CInputStream.h index d20a197bd..eab845f75 100644 --- a/lib/filesystem/CInputStream.h +++ b/lib/filesystem/CInputStream.h @@ -1,5 +1,7 @@ #pragma once +#include "CStream.h" + /* * CInputStream.h, part of VCMI engine * @@ -13,7 +15,7 @@ /** * Abstract class which provides method definitions for reading from a stream. */ -class DLL_LINKAGE CInputStream : private boost::noncopyable +class DLL_LINKAGE CInputStream : public virtual CStream { public: /** @@ -30,36 +32,6 @@ public: */ virtual si64 read(ui8 * data, si64 size) = 0; - /** - * Seeks the internal read pointer to the specified position. - * - * @param position The read position from the beginning. - * @return the position actually moved to, -1 on error. - */ - virtual si64 seek(si64 position) = 0; - - /** - * Gets the current read position in the stream. - * - * @return the read position. - */ - virtual si64 tell() = 0; - - /** - * Skips delta numbers of bytes. - * - * @param delta The count of bytes to skip. - * @return the count of bytes skipped actually. - */ - virtual si64 skip(si64 delta) = 0; - - /** - * Gets the length in bytes of the stream. - * - * @return the length in bytes of the stream. - */ - virtual si64 getSize() = 0; - /** * @brief for convenience, reads whole stream at once * diff --git a/lib/filesystem/CMemoryBuffer.cpp b/lib/filesystem/CMemoryBuffer.cpp new file mode 100644 index 000000000..849502e7a --- /dev/null +++ b/lib/filesystem/CMemoryBuffer.cpp @@ -0,0 +1,73 @@ +/* + * CMemoryBuffer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CMemoryBuffer.h" + +///CMemoryBuffer +CMemoryBuffer::CMemoryBuffer(): + position(0) +{ + buffer.reserve(4096); +} + +si64 CMemoryBuffer::write(const ui8 * data, si64 size) +{ + //do not shrink + const si64 newSize = tell()+size; + if(newSize>getSize()) + buffer.resize(newSize); + + std::copy(data, data + size, buffer.data() + position); + position += size; + + return size; +} + +si64 CMemoryBuffer::read(ui8 * data, si64 size) +{ + si64 toRead = std::min(getSize() - tell(), size); + + if(toRead > 0) + { + std::copy(buffer.data() + position, buffer.data() + position + toRead, data); + position += toRead; + } + + + return toRead; +} + +si64 CMemoryBuffer::seek(si64 position) +{ + this->position = position; + if (this->position >getSize()) + this->position = getSize(); + return this->position; +} + +si64 CMemoryBuffer::tell() +{ + return position; +} + +si64 CMemoryBuffer::skip(si64 delta) +{ + auto old_position = tell(); + + return seek(old_position + delta) - old_position; +} + +si64 CMemoryBuffer::getSize() +{ + return buffer.size(); +} + + diff --git a/lib/filesystem/CMemoryBuffer.h b/lib/filesystem/CMemoryBuffer.h new file mode 100644 index 000000000..1ceeaff69 --- /dev/null +++ b/lib/filesystem/CMemoryBuffer.h @@ -0,0 +1,88 @@ +#pragma once + +/* + * CMemoryBuffer.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + + +#include "CInputOutputStream.h" + +/** + * A class which provides IO memory buffer. + */ + +class DLL_LINKAGE CMemoryBuffer : public CInputOutputStream +{ +public: + typedef std::vector TBuffer; + + /** + * C-tor. + * + */ + CMemoryBuffer(); + + /** + * Write n bytes from the stream into the data buffer. + * + * @param data A pointer to the destination data array. + * @param size The number of bytes to write. + * @return the number of bytes written actually. + */ + si64 write(const ui8 * data, si64 size) override; + + /** + * Reads n bytes from the stream into the data buffer. + * + * @param data A pointer to the destination data array. + * @param size The number of bytes to read. + * @return the number of bytes read actually. + */ + si64 read(ui8 * data, si64 size) override; + + /** + * Seeks the internal read pointer to the specified position. + * + * @param position The read position from the beginning. + * @return the position actually moved to, -1 on error. + */ + si64 seek(si64 position) override; + + /** + * Gets the current read position in the stream. + * + * @return the read position. + */ + si64 tell() override; + + /** + * Skips delta numbers of bytes. + * + * @param delta The count of bytes to skip. + * @return the count of bytes skipped actually. + */ + si64 skip(si64 delta) override; + + /** + * Gets the length in bytes of the stream. + * + * @return the length in bytes of the stream. + */ + si64 getSize() override; + + const TBuffer & getBuffer(){return buffer;} + +private: + /** Actual data. */ + TBuffer buffer; + + /** Current reading position of the stream. */ + si64 position; +}; + diff --git a/lib/filesystem/CMemoryStream.h b/lib/filesystem/CMemoryStream.h index 55d1447ee..5b039db12 100644 --- a/lib/filesystem/CMemoryStream.h +++ b/lib/filesystem/CMemoryStream.h @@ -14,7 +14,8 @@ /** * A class which provides method definitions for reading from memory. - */ + * @deprecated use CMemoryBuffer + */ class DLL_LINKAGE CMemoryStream : public CInputStream { public: diff --git a/lib/filesystem/COutputStream.h b/lib/filesystem/COutputStream.h new file mode 100644 index 000000000..86181a20e --- /dev/null +++ b/lib/filesystem/COutputStream.h @@ -0,0 +1,34 @@ +#pragma once + +#include "CStream.h" + +/* + * COutputStream.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +/** + * Abstract class which provides method definitions for writing into a stream. + */ +class DLL_LINKAGE COutputStream : public virtual CStream +{ +public: + /** + * D-tor. + */ + virtual ~COutputStream() {} + + /** + * Write n bytes from the stream into the data buffer. + * + * @param data A pointer to the destination data array. + * @param size The number of bytes to write. + * @return the number of bytes written actually. + */ + virtual si64 write(const ui8 * data, si64 size) = 0; +}; diff --git a/lib/filesystem/CStream.h b/lib/filesystem/CStream.h new file mode 100644 index 000000000..21026b72b --- /dev/null +++ b/lib/filesystem/CStream.h @@ -0,0 +1,50 @@ +#pragma once + +/* + * CStream.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +class DLL_LINKAGE CStream : private boost::noncopyable +{ +public: + /** + * D-tor. + */ + virtual ~CStream() {} + + /** + * Seeks to the specified position. + * + * @param position The position from the beginning. + * @return the position actually moved to, -1 on error. + */ + virtual si64 seek(si64 position) = 0; + + /** + * Gets the current position in the stream. + * + * @return the position. + */ + virtual si64 tell() = 0; + + /** + * Relative seeks to the specified position. + * + * @param delta The count of bytes to seek from current position. + * @return the count of bytes skipped actually. + */ + virtual si64 skip(si64 delta) = 0; + + /** + * Gets the length of the stream. + * + * @return the length in bytes + */ + virtual si64 getSize() = 0; +}; diff --git a/lib/filesystem/CZipLoader.cpp b/lib/filesystem/CZipLoader.cpp index efb213b3b..86e6cacdb 100644 --- a/lib/filesystem/CZipLoader.cpp +++ b/lib/filesystem/CZipLoader.cpp @@ -1,5 +1,4 @@ #include "StdInc.h" -//#include "../../Global.h" #include "CZipLoader.h" #include "FileStream.h" @@ -15,9 +14,13 @@ * */ -CZipStream::CZipStream(const boost::filesystem::path & archive, unz64_file_pos filepos) +CZipStream::CZipStream(std::shared_ptr api, const boost::filesystem::path & archive, unz64_file_pos filepos) { - file = unzOpen2_64(archive.c_str(), FileStream::GetMinizipFilefunc()); + zlib_filefunc64_def zlibApi; + + zlibApi = api->getApiStructure(); + + file = unzOpen2_64(archive.c_str(), &zlibApi); unzGoToFilePos64(file, &filepos); unzOpenCurrentFile(file); } @@ -47,7 +50,10 @@ ui32 CZipStream::calculateCRC32() return info.crc; } -CZipLoader::CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive): +///CZipLoader +CZipLoader::CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive, std::shared_ptr api): + ioApi(api), + zlibApi(ioApi->getApiStructure()), archiveName(archive), mountPoint(mountPoint), files(listFiles(mountPoint, archive)) @@ -59,7 +65,10 @@ std::unordered_map CZipLoader::listFiles(const std:: { std::unordered_map ret; - unzFile file = unzOpen2_64(archive.c_str(), FileStream::GetMinizipFilefunc()); + unzFile file = unzOpen2_64(archive.c_str(), &zlibApi); + + if(file == nullptr) + logGlobal->errorStream() << archive << " failed to open"; if (unzGoToFirstFile(file) == UNZ_OK) { @@ -86,7 +95,7 @@ std::unordered_map CZipLoader::listFiles(const std:: std::unique_ptr CZipLoader::load(const ResourceID & resourceName) const { - return make_unique(archiveName, files.at(resourceName)); + return std::unique_ptr(new CZipStream(ioApi, archiveName, files.at(resourceName))); } bool CZipLoader::existsResource(const ResourceID & resourceName) const diff --git a/lib/filesystem/CZipLoader.h b/lib/filesystem/CZipLoader.h index cf2d749c8..a81745a47 100644 --- a/lib/filesystem/CZipLoader.h +++ b/lib/filesystem/CZipLoader.h @@ -15,12 +15,7 @@ #include "ResourceID.h" #include "CCompressedStream.h" -// Necessary here in order to get all types -#ifdef USE_SYSTEM_MINIZIP -#include -#else -#include "../minizip/unzip.h" -#endif +#include "MinizipExtensions.h" class DLL_LINKAGE CZipStream : public CBufferedStream { @@ -29,10 +24,11 @@ class DLL_LINKAGE CZipStream : public CBufferedStream public: /** * @brief constructs zip stream from already opened file + * @param api virtual filesystem interface * @param archive path to archive to open * @param filepos position of file to open */ - CZipStream(const boost::filesystem::path & archive, unz64_file_pos filepos); + CZipStream(std::shared_ptr api, const boost::filesystem::path & archive, unz64_file_pos filepos); ~CZipStream(); si64 getSize() override; @@ -44,6 +40,8 @@ protected: class DLL_LINKAGE CZipLoader : public ISimpleResourceLoader { + std::shared_ptr ioApi; + zlib_filefunc64_def zlibApi; boost::filesystem::path archiveName; std::string mountPoint; @@ -51,7 +49,7 @@ class DLL_LINKAGE CZipLoader : public ISimpleResourceLoader std::unordered_map listFiles(const std::string & mountPoint, const boost::filesystem::path &archive); public: - CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive); + CZipLoader(const std::string & mountPoint, const boost::filesystem::path & archive, std::shared_ptr api = std::shared_ptr(new CDefaultIOApi())); /// Interface implementation /// @see ISimpleResourceLoader @@ -61,7 +59,6 @@ public: std::unordered_set getFilteredFiles(std::function filter) const override; }; - namespace ZipArchive { /// List all files present in archive diff --git a/lib/filesystem/CZipSaver.cpp b/lib/filesystem/CZipSaver.cpp new file mode 100644 index 000000000..3475f9b7f --- /dev/null +++ b/lib/filesystem/CZipSaver.cpp @@ -0,0 +1,120 @@ +/* + * CZipSaver.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "StdInc.h" +#include "CZipSaver.h" + +///CZipOutputStream +CZipOutputStream::CZipOutputStream(CZipSaver * owner_, zipFile archive, const std::string & archiveFilename): + handle(archive), + owner(owner_) +{ + zip_fileinfo fileInfo; + + std::time_t t = time(nullptr); + fileInfo.dosDate = 0; + + struct tm * localTime = std::localtime(&t); + fileInfo.tmz_date.tm_hour = localTime->tm_hour; + fileInfo.tmz_date.tm_mday = localTime->tm_mday; + fileInfo.tmz_date.tm_min = localTime->tm_min; + fileInfo.tmz_date.tm_mon = localTime->tm_mon; + fileInfo.tmz_date.tm_sec = localTime->tm_sec; + fileInfo.tmz_date.tm_year = localTime->tm_year; + + fileInfo.external_fa = 0; //??? + fileInfo.internal_fa = 0; + + int status = zipOpenNewFileInZip4_64( + handle, + archiveFilename.c_str(), + &fileInfo, + nullptr,//extrafield_local + 0, + nullptr,//extrafield_global + 0, + nullptr,//comment + Z_DEFLATED, + Z_DEFAULT_COMPRESSION, + 0,//raw + -15,//windowBits + 9,//memLevel + Z_DEFAULT_STRATEGY,//strategy + nullptr,//password + 0,//crcForCrypting + 20,//versionMadeBy + 0,//flagBase + 0//zip64 + ); + + if(status != ZIP_OK) + throw new std::runtime_error("CZipOutputStream: zipOpenNewFileInZip failed"); + + owner->activeStream = this; +} + +CZipOutputStream::~CZipOutputStream() +{ + int status = zipCloseFileInZip(handle); + if (status != ZIP_OK) + logGlobal->errorStream() << "CZipOutputStream: stream finalize failed: "<activeStream = nullptr; +} + +si64 CZipOutputStream::write(const ui8 * data, si64 size) +{ + int ret = zipWriteInFileInZip(handle, (const void*)data, (unsigned)size); + + if (ret == ZIP_OK) + return size; + else + return 0; +} + +///CZipSaver +CZipSaver::CZipSaver(std::shared_ptr api, const std::string & path): + ioApi(api), + zipApi(ioApi->getApiStructure()), + handle(nullptr), + activeStream(nullptr) +{ + handle = zipOpen2_64(path.c_str(), APPEND_STATUS_CREATE, nullptr, &zipApi); + + if (handle == nullptr) + throw new std::runtime_error("CZipSaver: Failed to create archive"); +} + +CZipSaver::~CZipSaver() +{ + if(activeStream != nullptr) + { + logGlobal->error("CZipSaver::~CZipSaver: active stream found"); + zipCloseFileInZip(handle); + } + + + if(handle != nullptr) + { + int status = zipClose(handle, nullptr); + if (status != ZIP_OK) + logGlobal->errorStream() << "CZipSaver: archive finalize failed: "< CZipSaver::addFile(const std::string & archiveFilename) +{ + if(activeStream != nullptr) + throw new std::runtime_error("CZipSaver::addFile: stream already opened"); + + std::unique_ptr stream(new CZipOutputStream(this, handle, archiveFilename)); + return std::move(stream); +} + diff --git a/lib/filesystem/CZipSaver.h b/lib/filesystem/CZipSaver.h new file mode 100644 index 000000000..06a4c1717 --- /dev/null +++ b/lib/filesystem/CZipSaver.h @@ -0,0 +1,57 @@ +#pragma once + +/* + * CZipSaver.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#include "COutputStream.h" + +#include "MinizipExtensions.h" + +class CZipSaver; + +class DLL_LINKAGE CZipOutputStream: public COutputStream +{ +public: + /** + * @brief constructs zip stream from already opened file + * @param archive archive handle, must be opened + * @param archiveFilename name of file to write + */ + explicit CZipOutputStream(CZipSaver * owner_, zipFile archive, const std::string & archiveFilename); + ~CZipOutputStream(); + + si64 write(const ui8 * data, si64 size) override; + + si64 seek(si64 position) override {return -1;}; + si64 tell() override {return 0;}; + si64 skip(si64 delta) override {return 0;}; + si64 getSize() override {return 0;}; +private: + zipFile handle; + CZipSaver * owner; +}; + +class DLL_LINKAGE CZipSaver +{ +public: + explicit CZipSaver(std::shared_ptr api, const std::string & path); + virtual ~CZipSaver(); + + std::unique_ptr addFile(const std::string & archiveFilename); +private: + std::shared_ptr ioApi; + zlib_filefunc64_def zipApi; + + zipFile handle; + + ///due to minizip design only one file stream may opened at a time + COutputStream * activeStream; + friend class CZipOutputStream; +}; diff --git a/lib/filesystem/MinizipExtensions.cpp b/lib/filesystem/MinizipExtensions.cpp new file mode 100644 index 000000000..aa3b77a2d --- /dev/null +++ b/lib/filesystem/MinizipExtensions.cpp @@ -0,0 +1,254 @@ +/* + * MinizipExtensions.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" +#include "MinizipExtensions.h" + +#include "CMemoryBuffer.h" +#include "FileStream.h" + +template inline uLong streamRead(voidpf opaque, voidpf stream, void * buf, uLong size) +{ + assert(opaque != nullptr); + assert(stream != nullptr); + + _Stream * actualStream = static_cast<_Stream *>(stream); + + return actualStream->read((ui8 *)buf, size); +} + +template inline ZPOS64_T streamTell(voidpf opaque, voidpf stream) +{ + assert(opaque != nullptr); + assert(stream != nullptr); + + _Stream * actualStream = static_cast<_Stream *>(stream); + return actualStream->tell(); +} + +template inline long streamSeek(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) +{ + assert(opaque != nullptr); + assert(stream != nullptr); + + _Stream * actualStream = static_cast<_Stream *>(stream); + + long ret = 0; + switch (origin) + { + case ZLIB_FILEFUNC_SEEK_CUR : + if(actualStream->skip(offset) != offset) + ret = -1; + break; + case ZLIB_FILEFUNC_SEEK_END: + { + const si64 pos = actualStream->getSize() - offset; + if(actualStream->seek(pos) != pos) + ret = -1; + } + break; + case ZLIB_FILEFUNC_SEEK_SET : + if(actualStream->seek(offset) != offset) + ret = -1; + break; + default: ret = -1; + } + if(ret == -1) + logGlobal->error("Stream seek failed"); + return ret; +} + +template inline int streamProxyClose(voidpf opaque, voidpf stream) +{ + assert(opaque != nullptr); + assert(stream != nullptr); + + _Stream * actualStream = static_cast<_Stream *>(stream); + + logGlobal->traceStream() << "Proxy stream closed"; + + actualStream->seek(0); + + return 0; +} + +///CDefaultIOApi +CDefaultIOApi::CDefaultIOApi() +{ + +} + +CDefaultIOApi::~CDefaultIOApi() +{ + +} + +zlib_filefunc64_def CDefaultIOApi::getApiStructure() +{ + return * FileStream::GetMinizipFilefunc(); +} + +///CProxyIOApi +CProxyIOApi::CProxyIOApi(CInputOutputStream * buffer): + data(buffer) +{ + +} + +CProxyIOApi::~CProxyIOApi() +{ + +} + +zlib_filefunc64_def CProxyIOApi::getApiStructure() +{ + zlib_filefunc64_def api; + api.opaque = this; + api.zopen64_file = &openFileProxy; + api.zread_file = &readFileProxy; + api.zwrite_file = &writeFileProxy; + api.ztell64_file = &tellFileProxy; + api.zseek64_file = &seekFileProxy; + api.zclose_file = &closeFileProxy; + api.zerror_file = &errorFileProxy; + + return api; +} + +voidpf ZCALLBACK CProxyIOApi::openFileProxy(voidpf opaque, const void * filename, int mode) +{ + assert(opaque != nullptr); + + boost::filesystem::path path; + + if(filename != nullptr) + path = static_cast(filename); + + return ((CProxyIOApi *)opaque)->openFile(path, mode); +} + +uLong ZCALLBACK CProxyIOApi::readFileProxy(voidpf opaque, voidpf stream, void * buf, uLong size) +{ + return streamRead(opaque, stream, buf, size); +} + +uLong ZCALLBACK CProxyIOApi::writeFileProxy(voidpf opaque, voidpf stream, const void * buf, uLong size) +{ + assert(opaque != nullptr); + assert(stream != nullptr); + + CInputOutputStream * actualStream = static_cast(stream); + return (uLong)actualStream->write((const ui8 *)buf, size); +} + +ZPOS64_T ZCALLBACK CProxyIOApi::tellFileProxy(voidpf opaque, voidpf stream) +{ + return streamTell(opaque, stream); +} + +long ZCALLBACK CProxyIOApi::seekFileProxy(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) +{ + return streamSeek(opaque, stream, offset, origin); +} + +int ZCALLBACK CProxyIOApi::closeFileProxy(voidpf opaque, voidpf stream) +{ + return streamProxyClose(opaque, stream); +} + +int ZCALLBACK CProxyIOApi::errorFileProxy(voidpf opaque, voidpf stream) +{ + return 0; +} + +CInputOutputStream * CProxyIOApi::openFile(const boost::filesystem::path & filename, int mode) +{ + logGlobal->traceStream() << "CProxyIOApi: stream opened for " <seek(0); + return data; +} + +///CProxyROIOApi +CProxyROIOApi::CProxyROIOApi(CInputStream * buffer): + data(buffer) +{ + +} + +CProxyROIOApi::~CProxyROIOApi() +{ + +} + +zlib_filefunc64_def CProxyROIOApi::getApiStructure() +{ + zlib_filefunc64_def api; + api.opaque = this; + api.zopen64_file = &openFileProxy; + api.zread_file = &readFileProxy; + api.zwrite_file = &writeFileProxy; + api.ztell64_file = &tellFileProxy; + api.zseek64_file = &seekFileProxy; + api.zclose_file = &closeFileProxy; + api.zerror_file = &errorFileProxy; + + return api; +} + +CInputStream * CProxyROIOApi::openFile(const boost::filesystem::path& filename, int mode) +{ + logGlobal->traceStream() << "CProxyIOApi: stream opened for " <seek(0); + return data; +} + +voidpf ZCALLBACK CProxyROIOApi::openFileProxy(voidpf opaque, const void* filename, int mode) +{ + assert(opaque != nullptr); + + boost::filesystem::path path; + + if(filename != nullptr) + path = static_cast(filename); + + return ((CProxyROIOApi *)opaque)->openFile(path, mode); +} + +uLong ZCALLBACK CProxyROIOApi::readFileProxy(voidpf opaque, voidpf stream, void * buf, uLong size) +{ + return streamRead(opaque, stream, buf, size); +} + +uLong ZCALLBACK CProxyROIOApi::writeFileProxy(voidpf opaque, voidpf stream, const void* buf, uLong size) +{ + logGlobal->errorStream() << "Attempt to write to read-only stream"; + return 0; +} + +ZPOS64_T ZCALLBACK CProxyROIOApi::tellFileProxy(voidpf opaque, voidpf stream) +{ + return streamTell(opaque, stream); +} + +long ZCALLBACK CProxyROIOApi::seekFileProxy(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin) +{ + return streamSeek(opaque, stream, offset, origin); +} + +int ZCALLBACK CProxyROIOApi::closeFileProxy(voidpf opaque, voidpf stream) +{ + return streamProxyClose(opaque, stream); +} + +int ZCALLBACK CProxyROIOApi::errorFileProxy(voidpf opaque, voidpf stream) +{ + return 0; +} diff --git a/lib/filesystem/MinizipExtensions.h b/lib/filesystem/MinizipExtensions.h new file mode 100644 index 000000000..efd54a152 --- /dev/null +++ b/lib/filesystem/MinizipExtensions.h @@ -0,0 +1,88 @@ +#pragma once + +/* + * MinizipExtensions.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#ifdef USE_SYSTEM_MINIZIP +#include +#include +#include +#else +#include "../minizip/unzip.h" +#include "../minizip/zip.h" +#include "../minizip/ioapi.h" +#endif +class CInputStream; +class CInputOutputStream; +class CMemoryBuffer; + +class DLL_LINKAGE CIOApi +{ +public: + virtual ~CIOApi(){}; + + virtual zlib_filefunc64_def getApiStructure() = 0; +}; + +///redirects back to minizip ioapi +//todo: replace with Virtual FileSystem interface +class DLL_LINKAGE CDefaultIOApi: public CIOApi +{ +public: + CDefaultIOApi(); + ~CDefaultIOApi(); + + zlib_filefunc64_def getApiStructure() override; +}; + +///redirects all file IO to single stream +class DLL_LINKAGE CProxyIOApi: public CIOApi +{ +public: + CProxyIOApi(CInputOutputStream * buffer); + ~CProxyIOApi(); + + zlib_filefunc64_def getApiStructure() override; +private: + CInputOutputStream * openFile(const boost::filesystem::path & filename, int mode); + + CInputOutputStream * data; + + static voidpf ZCALLBACK openFileProxy(voidpf opaque, const void * filename, int mode); + static uLong ZCALLBACK readFileProxy(voidpf opaque, voidpf stream, void * buf, uLong size); + static uLong ZCALLBACK writeFileProxy(voidpf opaque, voidpf stream, const void * buf, uLong size); + static ZPOS64_T ZCALLBACK tellFileProxy(voidpf opaque, voidpf stream); + static long ZCALLBACK seekFileProxy(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin); + static int ZCALLBACK closeFileProxy(voidpf opaque, voidpf stream); + static int ZCALLBACK errorFileProxy(voidpf opaque, voidpf stream); +}; + +///redirects all file IO to single stream read-only +class DLL_LINKAGE CProxyROIOApi: public CIOApi +{ +public: + CProxyROIOApi(CInputStream * buffer); + ~CProxyROIOApi(); + + zlib_filefunc64_def getApiStructure() override; +private: + CInputStream * openFile(const boost::filesystem::path & filename, int mode); + + CInputStream * data; + + static voidpf ZCALLBACK openFileProxy(voidpf opaque, const void * filename, int mode); + static uLong ZCALLBACK readFileProxy(voidpf opaque, voidpf stream, void * buf, uLong size); + static uLong ZCALLBACK writeFileProxy(voidpf opaque, voidpf stream, const void * buf, uLong size); + static ZPOS64_T ZCALLBACK tellFileProxy(voidpf opaque, voidpf stream); + static long ZCALLBACK seekFileProxy(voidpf opaque, voidpf stream, ZPOS64_T offset, int origin); + static int ZCALLBACK closeFileProxy(voidpf opaque, voidpf stream); + static int ZCALLBACK errorFileProxy(voidpf opaque, voidpf stream); +}; + diff --git a/lib/filesystem/ResourceID.cpp b/lib/filesystem/ResourceID.cpp index 3b146a489..779bc7a1e 100644 --- a/lib/filesystem/ResourceID.cpp +++ b/lib/filesystem/ResourceID.cpp @@ -136,7 +136,8 @@ EResType::Type EResTypeHelper::getTypeFromExtension(std::string extension) {".VSGM1", EResType::SERVER_SAVEGAME}, {".ERM", EResType::ERM}, {".ERT", EResType::ERT}, - {".ERS", EResType::ERS} + {".ERS", EResType::ERS}, + {".VMAP", EResType::MAP} }; auto iter = stringToRes.find(extension); diff --git a/lib/mapObjects/CArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp index f35c1b322..ecb37b01c 100644 --- a/lib/mapObjects/CArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -108,7 +108,7 @@ void CArmedInstance::updateMoraleBonusFromArmy() undeadModifier = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116]); undeadModifier->description = undeadModifier->description.substr(0, undeadModifier->description.size()-2);//trim value addNewBonus(undeadModifier); - } + } } else if(undeadModifier) removeBonus(undeadModifier); diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index d6cd8657b..5ed39b9f0 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -25,6 +25,7 @@ #include "../CTownHandler.h" #include "../mapping/CMap.h" #include "CGTownInstance.h" +#include "../serializer/JsonSerializeFormat.h" ///helpers static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) @@ -1470,6 +1471,49 @@ bool CGHeroInstance::hasVisions(const CGObjectInstance * target, const int subty return (distance < visionsRange) && (target->pos.z == pos.z); } +void CGHeroInstance::serializeJsonOptions(JsonSerializeFormat& handler) +{ + serializeJsonOwner(handler); + + if(handler.saving) + { + if(type) + { + handler.serializeString("type", type->identifier); + } + else + { + auto temp = VLC->heroh->heroes[subID]->identifier; + handler.serializeString("type", temp); + } + } + else + { + if(ID == Obj::HERO || ID == Obj::PRISON) + { + std::string typeName; + handler.serializeString("type", typeName); + + auto rawId = VLC->modh->identifiers.getIdentifier("core", "hero", typeName); + + if(rawId) + subID = rawId.get(); + else + subID = 0; //fallback to Orrin, throw error instead? + } + } + CCreatureSet::serializeJson(handler, "army"); + + { + auto artifacts = handler.enterStruct("artifacts"); + if(handler.saving) + CArtifactSet::writeJson(handler.getCurrent()); + else + CArtifactSet::readJson(handler.getCurrent()); + } + +} + bool CGHeroInstance::isMissionCritical() const { for(const TriggeredEvent & event : IObjectInterface::cb->getMapHeader()->triggeredEvents) diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index dfaf979fb..3fd439854 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -251,6 +251,7 @@ public: std::string getObjectName() const override; protected: void setPropertyDer(ui8 what, ui32 val) override;//synchr + void serializeJsonOptions(JsonSerializeFormat & handler) override; private: void levelUpAutomatically(); diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index e661f0af6..733349c6d 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -11,6 +11,7 @@ #include "StdInc.h" #include "CGTownInstance.h" #include "CObjectClassesHandler.h" +#include "../spells/CSpellHandler.h" #include "../NetPacks.h" #include "../CGeneralTextHandler.h" @@ -19,6 +20,7 @@ #include "../CGameState.h" #include "../mapping/CMapDefines.h" #include "../CPlayerState.h" +#include "../serializer/JsonSerializeFormat.h" std::vector CGTownInstance::merchantArtifacts; std::vector CGTownInstance::universitySkills; @@ -314,6 +316,13 @@ void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) } } +void CGDwelling::serializeJsonOptions(JsonSerializeFormat & handler) +{ + //todo: CGDwelling::serializeJsonOptions + if(ID != Obj::WAR_MACHINE_FACTORY && ID != Obj::REFUGEE_CAMP) + serializeJsonOwner(handler); +} + int CGTownInstance::getSightRadius() const //returns sight distance { if (subID == ETownType::TOWER) @@ -1181,6 +1190,63 @@ void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResu } } +void CGTownInstance::serializeJsonOptions(JsonSerializeFormat & handler) +{ + CGObjectInstance::serializeJsonOwner(handler); + CCreatureSet::serializeJson(handler, "army"); + handler.serializeBool("tightFormation", 1, 0, formation); + handler.serializeString("name", name); + + + + if(!handler.saving) + { + builtBuildings.insert(BuildingID::DEFAULT);//just in case + } + + //todo: serialize buildings +// { +// std::vector standard; +// standard.resize(44, true); +// +// +// JsonSerializeFormat::LIC buildingsLIC(, CTownHandler::decodeBuilding, CTownHandler::encodeBuilding); +// } + + { + JsonSerializeFormat::LIC spellsLIC(VLC->spellh->getDefaultAllowed(), CSpellHandler::decodeSpell, CSpellHandler::encodeSpell); + + for(SpellID id : possibleSpells) + spellsLIC.any[id.num] = true; + + for(SpellID id : obligatorySpells) + spellsLIC.all[id.num] = true; + + handler.serializeLIC("spells", spellsLIC); + + if(!handler.saving) + { + possibleSpells.clear(); + for(si32 idx = 0; idx < spellsLIC.any.size(); idx++) + { + if(spellsLIC.any[idx]) + { + possibleSpells.push_back(SpellID(idx)); + } + } + + obligatorySpells.clear(); + for(si32 idx = 0; idx < spellsLIC.all.size(); idx++) + { + if(spellsLIC.all[idx]) + { + obligatorySpells.push_back(SpellID(idx)); + } + } + } + } +} + COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN) { ID = index; diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 271f95894..34b781e84 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -52,6 +52,9 @@ public: CSpecObjInfo * info; //h3m info about dewlling TCreaturesSet creatures; //creatures[level] -> +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; + private: void initObj() override; void onHeroVisit(const CGHeroInstance * h) const override; @@ -254,4 +257,5 @@ public: std::string getObjectName() const override; protected: void setPropertyDer(ui8 what, ui32 val) override; + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 157ba846a..4141edfca 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -74,7 +74,6 @@ CObjectClassesHandler::CObjectClassesHandler() SET_HANDLER("pandora", CGPandoraBox); SET_HANDLER("pickable", CGPickable); SET_HANDLER("prison", CGHeroInstance); - SET_HANDLER("prison", CGHeroInstance); SET_HANDLER("questGuard", CGQuestGuard); SET_HANDLER("resource", CGResource); SET_HANDLER("scholar", CGScholar); @@ -148,17 +147,21 @@ si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 defaultID) return defaultID; // some H3M objects loaded, first modded found } -void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContainter * obj) +void CObjectClassesHandler::loadObjectEntry(const std::string & identifier, const JsonNode & entry, ObjectContainter * obj) { if (!handlerConstructors.count(obj->handlerName)) { logGlobal->errorStream() << "Handler with name " << obj->handlerName << " was not found!"; return; } - si32 id = selectNextID(entry["index"], obj->objects, 1000); + + std::string convertedId = VLC->modh->normalizeIdentifier(entry.meta, "core", identifier); + + si32 id = selectNextID(entry["index"], obj->subObjects, 1000); 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)); @@ -175,50 +178,49 @@ void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContai legacyTemplates.erase(range.first, range.second); } - logGlobal->debugStream() << "Loaded object " << obj->id << ":" << id; - assert(!obj->objects.count(id)); // DO NOT override - obj->objects[id] = handler; + logGlobal->debugStream() << "Loaded object " << obj->identifier << "(" << obj->id << ")" << ":" << convertedId << "(" << id << ")" ; + assert(!obj->subObjects.count(id)); // DO NOT override + obj->subObjects[id] = handler; + obj->subIds[convertedId] = id; } -CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json) +CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json, const std::string & name) { auto obj = new ObjectContainter(); + obj->identifier = name; obj->name = json["name"].String(); obj->handlerName = json["handler"].String(); obj->base = json["base"]; obj->id = selectNextID(json["index"], objects, 256); for (auto entry : json["types"].Struct()) { - loadObjectEntry(entry.second, obj); + loadObjectEntry(entry.first, entry.second, obj); } return obj; } void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { - auto object = loadFromJson(data); + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); objects[object->id] = 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(data); - + auto object = loadFromJson(data, normalizeIdentifier(scope, "core", name)); assert(objects[index] == nullptr); // ensure that this id was not loaded before objects[index] = object; - VLC->modh->identifiers.registerObject(scope, "object", name, object->id); } -void CObjectClassesHandler::loadSubObject(std::string name, JsonNode config, si32 ID, boost::optional subID) +void CObjectClassesHandler::loadSubObject(const std::string & identifier, JsonNode config, si32 ID, boost::optional subID) { config.setType(JsonNode::DATA_STRUCT); // ensure that input is not NULL assert(objects.count(ID)); if (subID) { - assert(objects.at(ID)->objects.count(subID.get()) == 0); + assert(objects.at(ID)->subObjects.count(subID.get()) == 0); assert(config["index"].isNull()); config["index"].Float() = subID.get(); } @@ -227,14 +229,14 @@ void CObjectClassesHandler::loadSubObject(std::string name, JsonNode config, si3 JsonUtils::inherit(config, objects.at(ID)->base); config.setMeta(oldMeta); - loadObjectEntry(config, objects[ID]); + loadObjectEntry(identifier, config, objects[ID]); } void CObjectClassesHandler::removeSubObject(si32 ID, si32 subID) { assert(objects.count(ID)); - assert(objects.at(ID)->objects.count(subID)); - objects.at(ID)->objects.erase(subID); + assert(objects.at(ID)->subObjects.count(subID)); + objects.at(ID)->subObjects.erase(subID); //TODO: cleanup string id map } std::vector CObjectClassesHandler::getDefaultAllowed() const @@ -246,14 +248,28 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) { if (objects.count(type)) { - if (objects.at(type)->objects.count(subtype)) - return objects.at(type)->objects.at(subtype); + if (objects.at(type)->subObjects.count(subtype)) + return objects.at(type)->subObjects.at(subtype); } logGlobal->errorStream() << "Failed to find object of type " << type << ":" << subtype; - assert(0); // FIXME: throw error? + throw std::runtime_error("Object type handler not found"); return nullptr; } +TObjectTypeHandler CObjectClassesHandler::getHandlerFor(std::string type, std::string subtype) const +{ + boost::optional id = VLC->modh->identifiers.getIdentifier("core", "object", type, false); + if(id) + { + si32 subId = objects.at(id.get())->subIds.at(subtype); + return objects.at(id.get())->subObjects.at(subId); + } + logGlobal->errorStream() << "Failed to find object of type " << type << ":" << subtype; + throw std::runtime_error("Object type handler not found"); + return nullptr; +} + + std::set CObjectClassesHandler::knownObjects() const { std::set ret; @@ -270,7 +286,7 @@ std::set CObjectClassesHandler::knownSubObjects(si32 primaryID) const if (objects.count(primaryID)) { - for (auto entry : objects.at(primaryID)->objects) + for (auto entry : objects.at(primaryID)->subObjects) ret.insert(entry.first); } return ret; @@ -292,7 +308,7 @@ void CObjectClassesHandler::afterLoadFinalization() { for (auto entry : objects) { - for (auto obj : entry.second->objects) + for (auto obj : entry.second->subObjects) { obj.second->afterLoadFinalization(); if (obj.second->getTemplates().empty()) @@ -301,7 +317,7 @@ void CObjectClassesHandler::afterLoadFinalization() } //duplicate existing two-way portals to make reserve for RMG - auto& portalVec = objects[Obj::MONOLITH_TWO_WAY]->objects; + auto& portalVec = objects[Obj::MONOLITH_TWO_WAY]->subObjects; size_t portalCount = portalVec.size(); size_t currentIndex = portalCount; while (portalVec.size() < 100) @@ -341,6 +357,12 @@ void AObjectTypeHandler::setType(si32 type, si32 subtype) this->subtype = subtype; } +void AObjectTypeHandler::setTypeName(std::string type, std::string subtype) +{ + this->typeName = type; + this->subTypeName = subtype; +} + static ui32 loadJsonOrMax(const JsonNode & input) { if (input.isNull()) @@ -387,6 +409,14 @@ bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemp return false; // by default there are no overrides } +void AObjectTypeHandler::preInitObject(CGObjectInstance * obj) const +{ + obj->ID = Obj(type); + obj->subID = subtype; + obj->typeName = typeName; + obj->subTypeName = subTypeName; +} + void AObjectTypeHandler::initTypeData(const JsonNode & input) { // empty implementation for overrides diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 526fd8ad1..2aed19be8 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -101,22 +101,26 @@ class DLL_LINKAGE AObjectTypeHandler : public boost::noncopyable /// Human-readable name of this object, used for objects like banks and dwellings, if set boost::optional objectName; - si32 type; - si32 subtype; - JsonNode base; /// describes base template std::vector templates; protected: - + void preInitObject(CGObjectInstance * obj) const; virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; /// initialization for classes that inherit this one virtual void initTypeData(const JsonNode & input); public: + std::string typeName; + std::string subTypeName; + + si32 type; + si32 subtype; + virtual ~AObjectTypeHandler(){} void setType(si32 type, si32 subtype); + void setTypeName(std::string type, std::string subtype); /// loads generic data from Json structure and passes it towards type-specific constructors void init(const JsonNode & input, boost::optional name = boost::optional()); @@ -149,12 +153,16 @@ public: /// This should set remaining properties, including randomized or depending on map virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0; - /// Returns object configuration, if available. Othervice returns NULL + /// Returns object configuration, if available. Otherwise returns NULL virtual std::unique_ptr getObjectInfo(ObjectTemplate tmpl) const = 0; template void serialize(Handler &h, const int version) { h & type & subtype & templates & rmgInfo & objectName; + if(version >= 759) + { + h & typeName & subTypeName; + } } }; @@ -167,16 +175,21 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase struct ObjectContainter { si32 id; - + std::string identifier; std::string name; // human-readable name - std::string handlerName; // ID of handler that controls this object, shoul be determined using hadlerConstructor map + std::string handlerName; // ID of handler that controls this object, should be determined using handlerConstructor map JsonNode base; - std::map objects; + std::map subObjects; + std::map subIds;//full id from core scope -> subtype template void serialize(Handler &h, const int version) { - h & name & handlerName & base & objects; + h & name & handlerName & base & subObjects; + if(version >= 759) + { + h & identifier & subIds; + } } }; @@ -194,8 +207,8 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase /// format: customNames[primaryID][secondaryID] -> name std::map> customNames; - void loadObjectEntry(const JsonNode & entry, ObjectContainter * obj); - ObjectContainter * loadFromJson(const JsonNode & json); + void loadObjectEntry(const std::string & identifier, const JsonNode & entry, ObjectContainter * obj); + ObjectContainter * loadFromJson(const JsonNode & json, const std::string & name); public: CObjectClassesHandler(); @@ -204,7 +217,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(std::string name, JsonNode config, si32 ID, boost::optional subID = boost::optional()); + void loadSubObject(const std::string & identifier, JsonNode config, si32 ID, boost::optional subID = boost::optional()); void removeSubObject(si32 ID, si32 subID); void beforeValidate(JsonNode & object) override; @@ -218,10 +231,11 @@ public: /// returns handler for specified object (ID-based). ObjectHandler keeps ownership TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const; + TObjectTypeHandler getHandlerFor(std::string type, std::string subtype) const; std::string getObjectName(si32 type) const; std::string getObjectName(si32 type, si32 subtype) const; - + /// Returns handler string describing the handler (for use in client) std::string getObjectHandlerName(si32 type) const; diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index e73a2ecbb..f43c10751 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -18,11 +18,14 @@ #include "../filesystem/ResourceID.h" #include "../IGameCallback.h" #include "../CGameState.h" +#include "../StringConstants.h" #include "../mapping/CMap.h" #include "CObjectClassesHandler.h" #include "CGTownInstance.h" +#include "../serializer/JsonSerializeFormat.h" + IGameCallback * IObjectInterface::cb = nullptr; ///helpers @@ -317,6 +320,66 @@ bool CGObjectInstance::passableFor(PlayerColor color) const return false; } +void CGObjectInstance::serializeJson(JsonSerializeFormat & handler) +{ + //only save here, loading is handled by map loader + if(handler.saving) + { + handler.serializeString("type", typeName); + handler.serializeString("subtype", subTypeName); + + handler.serializeNumeric("x", pos.x); + handler.serializeNumeric("y", pos.y); + handler.serializeNumeric("l", pos.z); + appearance.writeJson(handler.getCurrent()["template"], false); + } + + { + auto options = handler.enterStruct("options"); + serializeJsonOptions(handler); + } + + if(handler.saving && handler.getCurrent()["options"].Struct().empty()) + { + handler.getCurrent().Struct().erase("options"); + } +} + +void CGObjectInstance::serializeJsonOptions(JsonSerializeFormat & handler) +{ + +} + +void CGObjectInstance::serializeJsonOwner(JsonSerializeFormat & handler) +{ + std::string temp; + + //todo: use enum serialize + if(handler.saving) + { + if(tempOwner.isValidPlayer()) + { + temp = GameConstants::PLAYER_COLOR_NAMES[tempOwner.getNum()]; + handler.serializeString("owner", temp); + } + } + else + { + tempOwner = PlayerColor::NEUTRAL;//this method assumes that object is ownable + + handler.serializeString("owner", temp); + + if(temp != "") + { + auto rawOwner = vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, temp); + if(rawOwner >=0) + tempOwner = PlayerColor(rawOwner); + else + logGlobal->errorStream() << "Invalid owner :" << temp; + } + } +} + CGObjectInstanceBySubIdFinder::CGObjectInstanceBySubIdFinder(CGObjectInstance * obj) : obj(obj) { diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index 875980f9c..0c18f97d1 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -1,4 +1,4 @@ -#pragma once +#pragma once #include "ObjectTemplate.h" @@ -21,6 +21,7 @@ class IGameCallback; class CGObjectInstance; struct MetaString; struct BattleResult; +class JsonSerializeFormat; // This one teleport-specific, but has to be available everywhere in callbacks and netpacks // For now it's will be there till teleports code refactored and moved into own file @@ -39,7 +40,7 @@ public: virtual void newTurn() const; virtual void initObj(); //synchr virtual void setProperty(ui8 what, ui32 val);//synchr - + //Called when queries created DURING HERO VISIT are resolved //First parameter is always hero that visited object and triggered the query virtual void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const; @@ -117,6 +118,10 @@ public: /// If true hero can visit this object only from neighbouring tiles and can't stand on this object bool blockVisit; + std::string instanceName; + std::string typeName; + std::string subTypeName; + CGObjectInstance(); ~CGObjectInstance(); @@ -168,17 +173,32 @@ public: //friend class CGameHandler; + ///Entry point of binary (de-)serialization template void serialize(Handler &h, const int version) { + if(version >= 759) + { + h & instanceName & typeName & subTypeName; + } + h & pos & ID & subID & id & tempOwner & blockVisit & appearance; //definfo is handled by map serializer } + + ///Entry point of Json (de-)serialization + void serializeJson(JsonSerializeFormat & handler); + protected: /// virtual method that allows synchronously update object state on server and all clients virtual void setPropertyDer(ui8 what, ui32 val); /// Gives dummy bonus from this object to hero. Can be used to track visited state void giveDummyBonus(ObjectInstanceID heroID, ui8 duration = Bonus::ONE_DAY) const; + + ///Serialize object-type specific options + virtual void serializeJsonOptions(JsonSerializeFormat & handler); + + void serializeJsonOwner(JsonSerializeFormat & handler); }; /// function object which can be used to find an object with an specific sub ID diff --git a/lib/mapObjects/CRewardableConstructor.cpp b/lib/mapObjects/CRewardableConstructor.cpp index 3835733b7..c71384a3f 100644 --- a/lib/mapObjects/CRewardableConstructor.cpp +++ b/lib/mapObjects/CRewardableConstructor.cpp @@ -188,6 +188,7 @@ void CRewardableConstructor::initTypeData(const JsonNode & config) CGObjectInstance * CRewardableConstructor::create(ObjectTemplate tmpl) const { auto ret = new CRewardableObject(); + preInitObject(ret); ret->appearance = tmpl; return ret; } diff --git a/lib/mapObjects/CommonConstructors.h b/lib/mapObjects/CommonConstructors.h index e7cff5ac5..7c516fdd4 100644 --- a/lib/mapObjects/CommonConstructors.h +++ b/lib/mapObjects/CommonConstructors.h @@ -32,8 +32,7 @@ protected: ObjectType * createTyped(ObjectTemplate tmpl) const { auto obj = new ObjectType(); - obj->ID = tmpl.id; - obj->subID = tmpl.subid; + preInitObject(obj); obj->appearance = tmpl; return obj; } diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 0cada5240..ea7e345cc 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -11,17 +11,19 @@ #include "StdInc.h" #include "MiscObjects.h" +#include "../StringConstants.h" #include "../NetPacks.h" #include "../CGeneralTextHandler.h" #include "../CSoundBase.h" #include "../CModHandler.h" - +#include "../CHeroHandler.h" #include "CObjectClassesHandler.h" #include "../spells/CSpellHandler.h" #include "../IGameCallback.h" #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CPlayerState.h" +#include "../serializer/JsonSerializeFormat.h" std::map > CGMagi::eyelist; ui8 CGObelisk::obeliskCount = 0; //how many obelisks are on map @@ -592,6 +594,63 @@ void CGCreature::giveReward(const CGHeroInstance * h) const } } +static const std::vector CHARACTER_JSON = +{ + "compliant", "friendly", "aggressive", "hostile", "savage" +}; + +void CGCreature::serializeJsonOptions(JsonSerializeFormat & handler) +{ + handler.serializeNumericEnum("character", CHARACTER_JSON, (si8)0, character); + + if(handler.saving) + { + if(hasStackAtSlot(SlotID(0))) + { + si32 amount = getStack(SlotID(0)).count; + handler.serializeNumeric("amount", amount); + } + + if(resources.nonZero()) + { + for(size_t idx = 0; idx < resources.size(); idx++) + handler.getCurrent()["rewardResources"][GameConstants::RESOURCE_NAMES[idx]].Float() = resources[idx]; + } + + auto tmp = (gainedArtifact == ArtifactID(ArtifactID::NONE) ? "" : gainedArtifact.toArtifact()->identifier); + handler.serializeString("rewardArtifact", tmp); + } + else + { + si32 amount = 0; + handler.serializeNumeric("amount", amount); + auto hlp = new CStackInstance(); + hlp->count = amount; + //type will be set during initialization + putStack(SlotID(0), hlp); + { + TResources tmp(handler.getCurrent()["rewardResources"]); + std::swap(tmp,resources); + } + { + gainedArtifact = ArtifactID(ArtifactID::NONE); + std::string tmp; + handler.serializeString("rewardArtifact", tmp); + + if(tmp != "") + { + auto artid = VLC->modh->identifiers.getIdentifier("core", "artifact", tmp); + if(artid) + gainedArtifact = ArtifactID(artid.get()); + } + } + } + handler.serializeBool("noGrowing", notGrowingTeam); + handler.serializeBool("neverFlees", neverFlees); + handler.serializeString("rewardMessage", message); +} + +//CGMine void CGMine::onHeroVisit( const CGHeroInstance * h ) const { int relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); @@ -630,7 +689,7 @@ void CGMine::newTurn() const void CGMine::initObj() { - if(subID >= 7) //Abandoned Mine + if(isAbandoned()) { //set guardians int howManyTroglodytes = cb->gameState()->getRandomGenerator().nextInt(100, 199); @@ -657,6 +716,11 @@ void CGMine::initObj() producedQuantity = defaultResProduction(); } +bool CGMine::isAbandoned() const +{ + return (subID >= 7); +} + std::string CGMine::getObjectName() const { return VLC->generaltexth->mines.at(subID).first; @@ -714,7 +778,7 @@ void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &resu { if(result.winner == 0) //attacker won { - if(subID == 7) + if(isAbandoned()) { showInfoDialog(hero->tempOwner, 85, 0); } @@ -728,6 +792,63 @@ void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) con cb->startBattleI(hero, this); } +void CGMine::serializeJsonOptions(JsonSerializeFormat & handler) +{ + CCreatureSet::serializeJson(handler, "army"); + + if(isAbandoned()) + { + auto possibleResources = handler.enterStruct("possibleResources"); + + JsonNode & node = handler.getCurrent(); + + if(handler.saving) + { + for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) + if(tempOwner.getNum() & 1< possibleResources; + + if(node.Vector().size() == 0) + { + //assume all allowed + for(int i = (int)Res::WOOD; i < (int) Res::GOLD; i++) + possibleResources.insert(i); + } + else + { + auto names = node.convertTo>(); + + for(const std::string & s : names) + { + int raw_res = vstd::find_pos(GameConstants::RESOURCE_NAMES, s); + if(raw_res < 0) + logGlobal->errorStream() << "Invalid resource name: "+s; + else + possibleResources.insert(raw_res); + } + + int tmp = 0; + + for(int r : possibleResources) + tmp |= (1<generaltexth->restypes[subID]; @@ -812,6 +933,13 @@ void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) cb->startBattleI(hero, this); } +void CGResource::serializeJsonOptions(JsonSerializeFormat & handler) +{ + CCreatureSet::serializeJson(handler, "guards"); + handler.serializeNumeric("amount", amount); + handler.serializeString("guardMessage", message); +} + CGTeleport::CGTeleport() : type(UNKNOWN), channel(TeleportChannelID()) { @@ -1298,6 +1426,21 @@ void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) cb->startBattleI(hero, this); } +void CGArtifact::serializeJsonOptions(JsonSerializeFormat& handler) +{ + handler.serializeString("guardMessage", message); + CCreatureSet::serializeJson(handler, "guards"); + + if(handler.saving && ID == Obj::SPELL_SCROLL) + { + const Bonus * b = storedArtifact->getBonusLocalFirst(Selector::type(Bonus::SPELL)); + SpellID spellId(b->subtype); + + std::string spell = SpellID(b->subtype).toSpell()->identifier; + handler.serializeString("spell", spell); + } +} + void CGWitchHut::initObj() { if (allowedAbilities.empty()) //this can happen for RMG. regular maps load abilities from map file @@ -1355,6 +1498,33 @@ std::string CGWitchHut::getHoverText(const CGHeroInstance * hero) const return hoverName; } +void CGWitchHut::serializeJsonOptions(JsonSerializeFormat & handler) +{ + //TODO: unify allowed abilities with others - make them std::vector + + std::vector temp; + temp.resize(GameConstants::SKILL_QUANTITY, false); + + auto standard = VLC->heroh->getDefaultAllowedAbilities(); //todo: for WitchHut default is all except Leadership and Necromancy + + if(handler.saving) + { + for(si32 i = 0; i < GameConstants::SKILL_QUANTITY; ++i) + if(vstd::contains(allowedAbilities, i)) + temp[i] = true; + } + + handler.serializeLIC("allowedSkills", &CHeroHandler::decodeSkill, &CHeroHandler::encodeSkill, standard, temp); + + if(!handler.saving) + { + allowedAbilities.clear(); + for (si32 i=0; iremoveObject(this); } -//TODO: remove -//void CGScholar::giveAnyBonus( const CGHeroInstance * h ) const -//{ -// -//} +void CGSignBottle::serializeJsonOptions(JsonSerializeFormat& handler) +{ + handler.serializeString("text", message); +} void CGScholar::onHeroVisit( const CGHeroInstance * h ) const { @@ -1599,6 +1773,59 @@ void CGScholar::initObj() } } +void CGScholar::serializeJsonOptions(JsonSerializeFormat & handler) +{ + JsonNode& json = handler.getCurrent(); + if(handler.saving) + { + switch(bonusType) + { + case PRIM_SKILL: + json["rewardPrimSkill"].String() = PrimarySkill::names[bonusID]; + break; + case SECONDARY_SKILL: + json["rewardSkill"].String() = NSecondarySkill::names[bonusID]; + break; + case SPELL: + json["rewardSpell"].String() = VLC->spellh->objects.at(bonusID)->identifier; + break; + case RANDOM: + break; + } + } + else + { + bonusType = RANDOM; + if(json["rewardPrimSkill"].String() != "") + { + auto raw = VLC->modh->identifiers.getIdentifier("core", "primSkill", json["rewardPrimSkill"].String()); + if(raw) + { + bonusType = PRIM_SKILL; + bonusID = raw.get(); + } + } + else if(json["rewardSkill"].String() != "") + { + auto raw = VLC->modh->identifiers.getIdentifier("core", "skill", json["rewardSkill"].String()); + if(raw) + { + bonusType = SECONDARY_SKILL; + bonusID = raw.get(); + } + } + else if(json["rewardSpell"].String() != "") + { + auto raw = VLC->modh->identifiers.getIdentifier("core", "spell", json["rewardSpell"].String()); + if(raw) + { + bonusType = SPELL; + bonusID = raw.get(); + } + } + } +} + void CGGarrison::onHeroVisit (const CGHeroInstance *h) const { int ally = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); @@ -1635,6 +1862,13 @@ void CGGarrison::battleFinished(const CGHeroInstance *hero, const BattleResult & onHeroVisit(hero); } +void CGGarrison::serializeJsonOptions(JsonSerializeFormat& handler) +{ + handler.serializeBool("removableUnits", removableUnits); + serializeJsonOwner(handler); + CCreatureSet::serializeJson(handler, "army"); +} + void CGMagi::initObj() { if (ID == Obj::EYE_OF_MAGI) @@ -1772,6 +2006,11 @@ void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const } } +void CGShipyard::serializeJsonOptions(JsonSerializeFormat& handler) +{ + serializeJsonOwner(handler); +} + void CCartographer::onHeroVisit( const CGHeroInstance * h ) const { //if player has not bought map of this subtype yet and underground exist for stalagmite cartographer @@ -1953,3 +2192,8 @@ void CGLighthouse::giveBonusTo( PlayerColor player ) const gb.bonus.sid = id.getNum(); cb->sendAndApply(&gb); } + +void CGLighthouse::serializeJsonOptions(JsonSerializeFormat& handler) +{ + serializeJsonOwner(handler); +} diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 006fdb837..582d3db9e 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -14,6 +14,8 @@ * */ +class CMap; + class DLL_LINKAGE CPlayersVisited: public CGObjectInstance { public: @@ -84,8 +86,9 @@ public: } protected: void setPropertyDer(ui8 what, ui32 val) override; -private: + void serializeJsonOptions(JsonSerializeFormat & handler) override; +private: void fight(const CGHeroInstance *h) const; void flee( const CGHeroInstance * h ) const; void fleeDecision(const CGHeroInstance *h, ui32 pursue) const; @@ -96,7 +99,6 @@ private: }; - class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles { public: @@ -110,6 +112,8 @@ public: h & static_cast(*this); h & message; } +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; class DLL_LINKAGE CGWitchHut : public CPlayersVisited @@ -127,6 +131,8 @@ public: h & static_cast(*this); h & allowedAbilities & ability; } +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; class DLL_LINKAGE CGScholar : public CGObjectInstance @@ -136,7 +142,6 @@ public: EBonusType bonusType; ui16 bonusID; //ID of skill/spell -// void giveAnyBonus(const CGHeroInstance * h) const; //TODO: remove CGScholar() : bonusType(EBonusType::RANDOM){}; void onHeroVisit(const CGHeroInstance * h) const override; void initObj() override; @@ -145,6 +150,8 @@ public: h & static_cast(*this); h & bonusType & bonusID; } +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; class DLL_LINKAGE CGGarrison : public CArmedInstance @@ -161,6 +168,8 @@ public: h & static_cast(*this); h & removableUnits; } +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; class DLL_LINKAGE CGArtifact : public CArmedInstance @@ -185,6 +194,8 @@ public: h & static_cast(*this); h & message & storedArtifact; } +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; class DLL_LINKAGE CGResource : public CArmedInstance @@ -207,6 +218,8 @@ public: h & static_cast(*this); h & amount & message; } +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; class DLL_LINKAGE CGShrine : public CPlayersVisited @@ -223,6 +236,8 @@ public: h & static_cast(*this);; h & spell; } +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; class DLL_LINKAGE CGMine : public CArmedInstance @@ -243,6 +258,7 @@ private: std::string getObjectName() const override; std::string getHoverText(PlayerColor player) const override; + bool isAbandoned() const; public: template void serialize(Handler &h, const int version) { @@ -250,6 +266,8 @@ public: h & producedResource & producedQuantity; } ui32 defaultResProduction(); +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; struct DLL_LINKAGE TeleportChannel @@ -414,6 +432,8 @@ public: h & static_cast(*this); h & static_cast(*this); } +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; class DLL_LINKAGE CGMagi : public CGObjectInstance @@ -430,8 +450,6 @@ public: } }; - - class DLL_LINKAGE CCartographer : public CPlayersVisited { ///behaviour varies depending on surface and floor @@ -482,4 +500,6 @@ public: h & static_cast(*this); } void giveBonusTo( PlayerColor player ) const; +protected: + void serializeJsonOptions(JsonSerializeFormat & handler) override; }; diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 450c66249..389d4ccf0 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -183,7 +183,7 @@ void ObjectTemplate::readMap(CBinaryReader & reader) } } -void ObjectTemplate::readJson(const JsonNode &node) +void ObjectTemplate::readJson(const JsonNode &node, const bool withTerrain) { animationFile = node["animation"].String(); @@ -202,7 +202,7 @@ void ObjectTemplate::readJson(const JsonNode &node) else visitDir = 0x00; - if (!node["allowedTerrains"].isNull()) + if(withTerrain && !node["allowedTerrains"].isNull()) { for (auto & entry : node["allowedTerrains"].Vector()) allowedTerrains.insert(ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.String()))); @@ -211,11 +211,14 @@ void ObjectTemplate::readJson(const JsonNode &node) { for (size_t i=0; i< GameConstants::TERRAIN_TYPES; i++) allowedTerrains.insert(ETerrainType(i)); + + allowedTerrains.erase(ETerrainType::ROCK); } - if (allowedTerrains.empty()) + if(withTerrain && allowedTerrains.empty()) logGlobal->warnStream() << "Loaded template without allowed terrains!"; + auto charToTile = [&](const char & ch) -> ui8 { switch (ch) @@ -249,11 +252,97 @@ void ObjectTemplate::readJson(const JsonNode &node) usedTiles[mask.size() - 1 - i][line.size() - 1 - j] = charToTile(line[j]); } - const JsonNode zindex = node["zIndex"]; - if (!zindex.isNull()) - printPriority = node["zIndex"].Float(); - else - printPriority = 0; //default value + printPriority = node["zIndex"].Float(); +} + +void ObjectTemplate::writeJson(JsonNode & node, const bool withTerrain) const +{ + node["animation"].String() = animationFile; + + if(visitDir != 0x0 && isVisitable()) + { + JsonVector & visitDirs = node["visitableFrom"].Vector(); + visitDirs.resize(3); + + visitDirs[0].String().resize(3); + visitDirs[1].String().resize(3); + visitDirs[2].String().resize(3); + + visitDirs[0].String()[0] = (visitDir & 1) ? '+' : '-'; + visitDirs[0].String()[1] = (visitDir & 2) ? '+' : '-'; + visitDirs[0].String()[2] = (visitDir & 4) ? '+' : '-'; + visitDirs[1].String()[2] = (visitDir & 8) ? '+' : '-'; + visitDirs[2].String()[2] = (visitDir & 16) ? '+' : '-'; + visitDirs[2].String()[1] = (visitDir & 32) ? '+' : '-'; + visitDirs[2].String()[0] = (visitDir & 64) ? '+' : '-'; + visitDirs[1].String()[0] = (visitDir & 128) ? '+' : '-'; + + visitDirs[1].String()[1] = '-'; + } + + if(withTerrain) + { + //assumed that ROCK terrain not included + if(allowedTerrains.size() < (GameConstants::TERRAIN_TYPES - 1)) + { + JsonVector & data = node["allowedTerrains"].Vector(); + + for(auto type : allowedTerrains) + { + JsonNode value(JsonNode::DATA_STRING); + value.String() = GameConstants::TERRAIN_NAMES[type.num]; + data.push_back(value); + } + } + } + + auto tileToChar = [&](const ui8 & tile) -> char + { + if(tile & VISIBLE) + { + if(tile & BLOCKED) + { + if(tile & VISITABLE) + return 'A'; + else + return 'B'; + } + else + return 'V'; + } + else + { + if(tile & BLOCKED) + { + if(tile & VISITABLE) + return 'T'; + else + return 'H'; + } + else + return '0'; + } + }; + + size_t height = getHeight(); + size_t width = getWidth(); + + JsonVector & mask = node["mask"].Vector(); + + + for(size_t i=0; i < height; i++) + { + JsonNode lineNode(JsonNode::DATA_STRING); + + std::string & line = lineNode.String(); + line.resize(width); + for(size_t j=0; j < width; j++) + line[j] = tileToChar(usedTiles[height - 1 - i][width - 1 - j]); + mask.push_back(lineNode); + } + + if(printPriority != 0) + node["zIndex"].Float() = printPriority; } ui32 ObjectTemplate::getWidth() const diff --git a/lib/mapObjects/ObjectTemplate.h b/lib/mapObjects/ObjectTemplate.h index 8184efba1..bbb3522b9 100644 --- a/lib/mapObjects/ObjectTemplate.h +++ b/lib/mapObjects/ObjectTemplate.h @@ -73,7 +73,8 @@ public: void readTxt(CLegacyConfigParser & parser); void readMsk(); void readMap(CBinaryReader & reader); - void readJson(const JsonNode & node); + void readJson(const JsonNode & node, const bool withTerrain = true); + void writeJson(JsonNode & node, const bool withTerrain = true) const; bool operator==(const ObjectTemplate& ot) const { return (id == ot.id && subid == ot.subid); } diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 2c02321aa..2857822bb 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -19,7 +19,7 @@ SHeroName::SHeroName() : heroId(-1) PlayerInfo::PlayerInfo(): canHumanPlay(false), canComputerPlay(false), aiTactic(EAiTactic::RANDOM), isFactionRandom(false), mainCustomHeroPortrait(-1), mainCustomHeroId(-1), hasMainTown(false), - generateHeroAtMainTown(false), team(255), hasRandomHero(false), /* following are unused */ generateHero(false), p7(0), powerPlaceholders(-1) + generateHeroAtMainTown(false), team(TeamID::NO_TEAM), hasRandomHero(false), /* following are unused */ generateHero(false), p7(0), powerPlaceholders(-1) { allowedFactions = VLC->townh->getAllowedFactions(); } @@ -63,6 +63,7 @@ EventCondition::EventCondition(EWinLoseType condition): object(nullptr), value(-1), objectType(-1), + objectSubtype(-1), position(-1, -1, -1), condition(condition) { @@ -72,6 +73,7 @@ EventCondition::EventCondition(EWinLoseType condition, si32 value, si32 objectTy object(nullptr), value(value), objectType(objectType), + objectSubtype(-1), position(position), condition(condition) {} @@ -141,11 +143,6 @@ CGObjectInstance * TerrainTile::topVisitableObj(bool excludeTop) const return visitableObjects.back(); } -bool TerrainTile::isCoastal() const -{ - return extTileFlags & 64; -} - EDiggingStatus TerrainTile::getDiggingStatus(const bool excludeTop) const { if(terType == ETerrainType::WATER || terType == ETerrainType::ROCK) @@ -322,6 +319,35 @@ CGHeroInstance * CMap::getHero(int heroID) return nullptr; } +bool CMap::isCoastalTile(const int3 & pos) const +{ + //todo: refactoring: extract neighbor tile iterator and use it in GameState + static const int3 dirs[] = { int3(0,1,0),int3(0,-1,0),int3(-1,0,0),int3(+1,0,0), + int3(1,1,0),int3(-1,1,0),int3(1,-1,0),int3(-1,-1,0) }; + + if(!isInTheMap(pos)) + { + logGlobal->errorStream() << "Coastal check outside of map :"<= width || pos.y >= height @@ -349,7 +375,7 @@ const TerrainTile & CMap::getTile(const int3 & tile) const bool CMap::isWaterTile(const int3 &pos) const { - return isInTheMap(pos) && getTile(pos).terType == ETerrainType::WATER; + return isInTheMap(pos) && getTile(pos).isWater(); } bool CMap::checkForVisitableDir(const int3 & src, const TerrainTile *pom, const int3 & dst ) const @@ -534,6 +560,32 @@ void CMap::addQuest(CGObjectInstance * quest) quests.push_back(q->quest); } +void CMap::addNewObject(CGObjectInstance * obj) +{ + if(obj->id != ObjectInstanceID(objects.size())) + throw std::runtime_error("Invalid object instance id"); + + if(obj->instanceName == "") + throw std::runtime_error("Object instance name missing"); + + auto it = instanceNames.find(obj->instanceName); + if(it != instanceNames.end()) + throw std::runtime_error("Object instance name duplicated:"+obj->instanceName); + + objects.push_back(obj); + instanceNames[obj->instanceName] = obj; + addBlockVisTiles(obj); + + if(obj->ID == Obj::TOWN) + { + towns.push_back(static_cast(obj)); + } + if(obj->ID == Obj::HERO) + { + heroesOnMap.push_back(static_cast(obj)); + } +} + void CMap::initTerrain() { int level = twoLevel ? 2 : 1; diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index 72c2b9c46..87ba1b994 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -76,7 +76,7 @@ struct DLL_LINKAGE PlayerInfo bool hasMainTown; /// The default value is false. bool generateHeroAtMainTown; /// The default value is false. int3 posOfMainTown; - TeamID team; /// The default value is 255 representing that the player belongs to no team. + TeamID team; /// The default value NO_TEAM bool hasRandomHero; /// Player has a random hero bool generateHero; /// Unused. @@ -98,6 +98,7 @@ struct DLL_LINKAGE PlayerInfo struct DLL_LINKAGE EventCondition { enum EWinLoseType { + //internal use, deprecated HAVE_ARTIFACT, // type - required artifact HAVE_CREATURES, // type - creatures to collect, value - amount to collect HAVE_RESOURCES, // type - resource ID, value - amount to collect @@ -105,26 +106,44 @@ struct DLL_LINKAGE EventCondition CONTROL, // position - position of object, optional, type - type of object DESTROY, // position - position of object, optional, type - type of object TRANSPORT, // position - where artifact should be transported, type - type of artifact + + //map format version pre 1.0 DAYS_PASSED, // value - number of days from start of the game IS_HUMAN, // value - 0 = player is AI, 1 = player is human DAYS_WITHOUT_TOWN, // value - how long player can live without town, 0=instakill STANDARD_WIN, // normal defeat all enemies condition - CONST_VALUE // condition that always evaluates to "value" (0 = false, 1 = true) + CONST_VALUE, // condition that always evaluates to "value" (0 = false, 1 = true) + + //map format version 1.0+ + HAVE_0, + HAVE_BUILDING_0, + DESTROY_0 }; EventCondition(EWinLoseType condition = STANDARD_WIN); EventCondition(EWinLoseType condition, si32 value, si32 objectType, int3 position = int3(-1, -1, -1)); - const CGObjectInstance * object; // object that was at specified position on start + const CGObjectInstance * object; // object that was at specified position or with instance name on start si32 value; si32 objectType; + si32 objectSubtype; + std::string objectInstanceName; int3 position; EWinLoseType condition; template void serialize(Handler & h, const int version) { - h & object & value & objectType & position & condition; + h & object; + h & value; + h & objectType; + h & position; + h & condition; + if(version > 759) + { + h & objectSubtype; + h & objectInstanceName; + } } }; @@ -171,7 +190,10 @@ struct DLL_LINKAGE TriggeredEvent template void serialize(Handler & h, const int version) { - h & identifier & trigger & description & onFulfill & effect; + h & identifier; + h & trigger; + h & description; + h & onFulfill & effect; } }; @@ -207,7 +229,7 @@ struct DLL_LINKAGE DisposedHero namespace EMapFormat { -enum EMapFormat +enum EMapFormat: ui8 { INVALID = 0, // HEX DEC @@ -215,7 +237,8 @@ enum EMapFormat AB = 0x15, // 21 SOD = 0x1c, // 28 // HOTA = 0x1e ... 0x20 // 28 ... 30 - WOG = 0x33 // 51 + WOG = 0x33, // 51 + VCMI = 0xF0 }; } @@ -251,7 +274,7 @@ public: std::vector players; /// The default size of the vector is PlayerColor::PLAYER_LIMIT. ui8 howManyTeams; std::vector allowedHeroes; - std::vector placeholdedHeroes; + bool areAnyPlayers; /// Unused. True if there are any playable players on the map. /// "main quests" of the map that describe victory and loss conditions @@ -277,8 +300,10 @@ public: CMapEditManager * getEditManager(); TerrainTile & getTile(const int3 & tile); const TerrainTile & getTile(const int3 & tile) const; + bool isCoastalTile(const int3 & pos) const; bool isInTheMap(const int3 & pos) const; bool isWaterTile(const int3 & pos) const; + bool checkForVisitableDir( const int3 & src, const TerrainTile *pom, const int3 & dst ) const; int3 guardingCreaturePosition (int3 pos) const; @@ -289,6 +314,7 @@ public: void addNewArtifactInstance(CArtifactInstance * art); void eraseArtifactInstance(CArtifactInstance * art); void addQuest(CGObjectInstance * quest); + void addNewObject(CGObjectInstance * obj); /// Gets object of specified type on requested position const CGObjectInstance * getObjectiveObjectFrom(int3 pos, Obj::EObj type); @@ -326,6 +352,8 @@ public: int3 ***guardingCreaturePositions; + std::map > instanceNames; + private: /// a 3-dimensional array of terrain tiles, access is as follows: x, y, level. where level=1 is underground TerrainTile*** terrain; @@ -393,5 +421,10 @@ public: h & CGObelisk::obeliskCount & CGObelisk::visited; h & CGTownInstance::merchantArtifacts; h & CGTownInstance::universitySkills; + + if(formatVersion >= 759) + { + h & instanceNames; + } } }; diff --git a/lib/mapping/CMapDefines.h b/lib/mapping/CMapDefines.h index aff08b444..288892cdc 100644 --- a/lib/mapping/CMapDefines.h +++ b/lib/mapping/CMapDefines.h @@ -70,7 +70,6 @@ struct DLL_LINKAGE TerrainTile Obj topVisitableId(bool excludeTop = false) const; CGObjectInstance * topVisitableObj(bool excludeTop = false) const; bool isWater() const; - bool isCoastal() const; EDiggingStatus getDiggingStatus(const bool excludeTop = true) const; bool hasFavorableWinds() const; diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index ec629b883..87d15b922 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -478,7 +478,6 @@ void CDrawTerrainOperation::execute() updateTerrainTypes(); updateTerrainViews(); - //TODO add coastal bit to extTileFlags appropriately } void CDrawTerrainOperation::undo() @@ -1044,16 +1043,12 @@ void CInsertObjectOperation::execute() { obj->pos = pos; obj->id = ObjectInstanceID(map->objects.size()); - map->objects.push_back(obj); - if(obj->ID == Obj::TOWN) - { - map->towns.push_back(static_cast(obj)); - } - if(obj->ID == Obj::HERO) - { - map->heroesOnMap.push_back(static_cast(obj)); - } - map->addBlockVisTiles(obj); + + boost::format fmt("%s_%d"); + fmt % obj->typeName % obj->id.getNum(); + obj->instanceName = fmt.str(); + + map->addNewObject(obj); } void CInsertObjectOperation::undo() diff --git a/lib/mapping/CMapService.cpp b/lib/mapping/CMapService.cpp index fc4efb2e3..4b58c7ae6 100644 --- a/lib/mapping/CMapService.cpp +++ b/lib/mapping/CMapService.cpp @@ -5,6 +5,7 @@ #include "../filesystem/CBinaryReader.h" #include "../filesystem/CCompressedStream.h" #include "../filesystem/CMemoryStream.h" + #include "CMap.h" #include "MapFormatH3M.h" @@ -68,21 +69,31 @@ std::unique_ptr CMapService::getMapLoader(std::unique_ptrseek(0); - // Check which map format is used - // gzip header is 3 bytes only in size - switch(header & 0xffffff) + //check for ZIP magic. Zip files are VCMI maps + switch(header) { - // gzip header magic number, reversed for LE - case 0x00088B1F: - stream = std::unique_ptr(new CCompressedStream(std::move(stream), true)); - return std::unique_ptr(new CMapLoaderH3M(stream.get())); - case EMapFormat::WOG : - case EMapFormat::AB : - case EMapFormat::ROE : - case EMapFormat::SOD : - return std::unique_ptr(new CMapLoaderH3M(stream.get())); - default : - throw std::runtime_error("Unknown map format"); + case 0x06054b50: + case 0x04034b50: + case 0x02014b50: + return std::unique_ptr(new CMapLoaderJson(stream.get())); + break; + default: + // Check which map format is used + // gzip header is 3 bytes only in size + switch(header & 0xffffff) + { + // gzip header magic number, reversed for LE + case 0x00088B1F: + stream = std::unique_ptr(new CCompressedStream(std::move(stream), true)); + return std::unique_ptr(new CMapLoaderH3M(stream.get())); + case EMapFormat::WOG : + case EMapFormat::AB : + case EMapFormat::ROE : + case EMapFormat::SOD : + return std::unique_ptr(new CMapLoaderH3M(stream.get())); + default : + throw std::runtime_error("Unknown map format"); + } } } @@ -103,5 +114,5 @@ std::unique_ptr CMapService::getMapPatcher(std::string scenarioName boost::to_lower(scenarioName); logGlobal->debugStream() << "Request to patch map " << scenarioName; - return std::unique_ptr(new CMapLoaderJson(node[scenarioName])); + return std::unique_ptr(new CMapPatcher(node[scenarioName])); } diff --git a/lib/mapping/CMapService.h b/lib/mapping/CMapService.h index 82333e9ae..caf9d3f75 100644 --- a/lib/mapping/CMapService.h +++ b/lib/mapping/CMapService.h @@ -126,7 +126,7 @@ public: virtual std::unique_ptr loadMapHeader() = 0; }; -class DLL_LINKAGE IMapPatcher : public IMapLoader +class DLL_LINKAGE IMapPatcher { public: /** @@ -135,3 +135,16 @@ public: */ virtual void patchMapHeader(std::unique_ptr & header) = 0; }; + +/** + * Interface for saving a map. + */ +class DLL_LINKAGE IMapSaver +{ +public: + /** + * Saves the VCMI/H3 map file. + * + */ + virtual void saveMap(const std::unique_ptr & map) = 0; +}; diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index bdf07b9ae..03446f083 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -119,11 +119,6 @@ void CMapLoaderH3M::init() readEvents(); times.push_back(MapLoadingTime("events", sw.getDiff())); - // Calculate blocked / visitable positions - for(auto & elem : map->objects) - { - map->addBlockVisTiles(elem); - } times.push_back(MapLoadingTime("blocked/visitable tiles", sw.getDiff())); // Print profiling times @@ -615,10 +610,15 @@ void CMapLoaderH3M::readAllowedHeroes() if(mapHeader->version > EMapFormat::ROE) { int placeholdersQty = reader.readUInt32(); - for(int p = 0; p < placeholdersQty; ++p) - { - mapHeader->placeholdedHeroes.push_back(reader.readUInt8()); - } + + reader.skip(placeholdersQty * 1); + +// std::vector placeholdedHeroes; +// +// for(int p = 0; p < placeholdersQty; ++p) +// { +// placeholdedHeroes.push_back(reader.readUInt8()); +// } } } @@ -828,7 +828,7 @@ void CMapLoaderH3M::loadArtifactsOfHero(CGHeroInstance * hero) { // catapult by default assert(!hero->getArt(ArtifactPosition::MACH4)); - hero->putArtifact(ArtifactPosition::MACH4, createArtifact(ArtifactID::CATAPULT)); + hero->putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createArtifact(map, ArtifactID::CATAPULT)); } } @@ -885,7 +885,7 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot) } // this is needed, because some H3M maps (last scenario of ROE map) contain invalid data like misplaced artifacts - auto artifact = createArtifact(aid); + auto artifact = CArtifactInstance::createArtifact(map, aid); auto artifactPos = ArtifactPosition(slot); if (artifact->canBePutAt(ArtifactLocation(hero, artifactPos))) { @@ -900,40 +900,6 @@ bool CMapLoaderH3M::loadArtifactToSlot(CGHeroInstance * hero, int slot) return isArt; } -CArtifactInstance * CMapLoaderH3M::createArtifact(int aid, int spellID /*= -1*/) -{ - CArtifactInstance * a = nullptr; - if(aid >= 0) - { - if(spellID < 0) - { - a = CArtifactInstance::createNewArtifactInstance(aid); - } - else - { - a = CArtifactInstance::createScroll(SpellID(spellID).toSpell()); - } - } - else //FIXME: create combined artifact instance for random combined artifacts, just in case - { - a = new CArtifactInstance(); //random, empty - } - - map->addNewArtifactInstance(a); - - //TODO make it nicer - if(a->artType && (!!a->artType->constituents)) - { - CCombinedArtifactInstance * comb = dynamic_cast(a); - for(CCombinedArtifactInstance::ConstituentInfo & ci : comb->constituentsInfo) - { - map->addNewArtifactInstance(ci.art); - } - } - - return a; -} - void CMapLoaderH3M::readTerrain() { map->initTerrain(); @@ -1236,7 +1202,7 @@ void CMapLoaderH3M::readObjects() artID = objTempl.subid; } - art->storedArtifact = createArtifact(artID, spellID); + art->storedArtifact = CArtifactInstance::createArtifact(map, artID, spellID); break; } case Obj::RANDOM_RESOURCE: @@ -1488,16 +1454,16 @@ void CMapLoaderH3M::readObjects() } nobj->appearance = objTempl; assert(idToBeGiven == ObjectInstanceID(map->objects.size())); - map->objects.push_back(nobj); - if(nobj->ID == Obj::TOWN) + { - map->towns.push_back(static_cast(nobj)); - } - if(nobj->ID == Obj::HERO) - { - logGlobal->debugStream() << "Hero: " << VLC->heroh->heroes[nobj->subID]->name << " at " << objPos; - map->heroesOnMap.push_back(static_cast(nobj)); + //TODO: define valid typeName and subtypeName fro H3M maps + //boost::format fmt("%s_%d"); + //fmt % nobj->typeName % nobj->id.getNum(); + boost::format fmt("obj_%d"); + fmt % nobj->id.getNum(); + nobj->instanceName = fmt.str(); } + map->addNewObject(nobj); } std::sort(map->heroesOnMap.begin(), map->heroesOnMap.end(), [](const ConstTransitivePtr & a, const ConstTransitivePtr & b) @@ -1998,7 +1964,7 @@ CGTownInstance * CMapLoaderH3M::readTown(int castleID) ui8 c = reader.readUInt8(); for(int yy = 0; yy < 8; ++yy) { - int spellid = i * 8 + yy; + int spellid = i * 8 + yy; if(spellid < GameConstants::SPELLS_QUANTITY) { if(c != (c | static_cast(std::pow(2., yy))) && map->allowedSpell[spellid]) //add random spell only if it's allowed on entire map diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index 5d306bc06..de1c8724e 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -124,15 +124,6 @@ private: */ bool loadArtifactToSlot(CGHeroInstance * hero, int slot); - /** - * Creates an artifact instance. - * - * @param aid the id of the artifact - * @param spellID optional. the id of a spell if a spell scroll object should be created - * @return the created artifact instance - */ - CArtifactInstance * createArtifact(int aid, int spellID = -1); - /** * Read rumors. */ diff --git a/lib/mapping/MapFormatJson.cpp b/lib/mapping/MapFormatJson.cpp index 9e2189ef4..4df1ddd54 100644 --- a/lib/mapping/MapFormatJson.cpp +++ b/lib/mapping/MapFormatJson.cpp @@ -11,115 +11,374 @@ #include "StdInc.h" #include "MapFormatJson.h" +#include "../filesystem/CInputStream.h" +#include "../filesystem/COutputStream.h" #include "CMap.h" #include "../CModHandler.h" +#include "../CHeroHandler.h" +#include "../CTownHandler.h" #include "../VCMI_Lib.h" +#include "../mapObjects/ObjectTemplate.h" +#include "../mapObjects/CObjectHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/CGHeroInstance.h" +#include "../mapObjects/CGTownInstance.h" +#include "../spells/CSpellHandler.h" +#include "../StringConstants.h" +#include "../serializer/JsonDeserializer.h" +#include "../serializer/JsonSerializer.h" -static const std::string conditionNames[] = { -"haveArtifact", "haveCreatures", "haveResources", "haveBuilding", -"control", "destroy", "transport", "daysPassed", -"isHuman", "daysWithoutTown", "standardWin", "constValue" -}; - -static const std::string typeNames[] = { "victory", "defeat" }; - -CMapLoaderJson::CMapLoaderJson(JsonNode stream): - input(stream) +namespace HeaderDetail { + static const ui8 difficultyDefault = 1;//normal -} - -std::unique_ptr CMapLoaderJson::loadMap() -{ - map = new CMap(); - mapHeader.reset(map); - readMap(); - mapHeader.reset(); - return std::unique_ptr(map); -} - -std::unique_ptr CMapLoaderJson::loadMapHeader() -{ - mapHeader.reset(new CMapHeader); - readHeader(); - return std::move(mapHeader); -} - -/* - //This code can be used to write map header to console or file in its Json representation - - JsonNode out; - JsonNode data; - data["victoryString"].String() = mapHeader->victoryMessage; - data["defeatString"].String() = mapHeader->defeatMessage; - - data["victoryIconIndex"].Float() = mapHeader->victoryIconIndex; - data["defeatIconIndex"].Float() = mapHeader->defeatIconIndex; - - for (const TriggeredEvent & entry : mapHeader->triggeredEvents) + static const std::vector difficultyMap = { - JsonNode event; - event["message"].String() = entry.onFulfill; - event["effect"]["messageToSend"].String() = entry.effect.toOtherMessage; - event["effect"]["type"].String() = typeNames[entry.effect.type]; - event["condition"] = entry.trigger.toJson(eventToJson); - data["triggeredEvents"][entry.identifier] = event; + "EASY", + "NORMAL", + "HARD", + "EXPERT", + "IMPOSSIBLE" + }; +} + +namespace TriggeredEventsDetail +{ + static const std::array conditionNames = + { + "haveArtifact", "haveCreatures", "haveResources", "haveBuilding", + "control", "destroy", "transport", "daysPassed", + "isHuman", "daysWithoutTown", "standardWin", "constValue", + + "have_0", "haveBuilding_0", "destroy_0" + }; + + + static const std::array typeNames = { "victory", "defeat" }; + + static EventCondition JsonToCondition(const JsonNode & node) + { + //todo: support of new condition format + EventCondition event; + + const auto & conditionName = node.Vector()[0].String(); + + auto pos = vstd::find_pos(conditionNames, conditionName); + + event.condition = EventCondition::EWinLoseType(pos); + if (node.Vector().size() > 1) + { + const JsonNode & data = node.Vector()[1]; + if (data["type"].getType() == JsonNode::DATA_STRING) + { + auto identifier = VLC->modh->identifiers.getIdentifier(data["type"]); + if(identifier) + event.objectType = identifier.get(); + else + throw std::runtime_error("Identifier resolution failed in event condition"); + } + + if (data["type"].getType() == JsonNode::DATA_FLOAT) + event.objectType = data["type"].Float(); + + if (!data["value"].isNull()) + event.value = data["value"].Float(); + + if (!data["position"].isNull()) + { + auto & position = data["position"].Vector(); + event.position.x = position.at(0).Float(); + event.position.y = position.at(1).Float(); + event.position.z = position.at(2).Float(); + } + +// if(!data["subtype"].isNull()) +// { +// //todo +// } +// event.objectInstanceName = data["object"].String(); + } + return event; } - out[mapHeader->name] = data; - logGlobal->errorStream() << out; + static JsonNode ConditionToJson(const EventCondition& event) + { + JsonNode json; -JsonNode eventToJson(const EventCondition & cond) -{ - JsonNode ret; - ret.Vector().resize(2); - ret.Vector()[0].String() = conditionNames[size_t(cond.condition)]; - JsonNode & data = ret.Vector()[1]; - data["type"].Float() = cond.objectType; - data["value"].Float() = cond.value; - data["position"].Vector().resize(3); - data["position"].Vector()[0].Float() = cond.position.x; - data["position"].Vector()[1].Float() = cond.position.y; - data["position"].Vector()[2].Float() = cond.position.z; + JsonVector& asVector = json.Vector(); - return ret; -} -*/ -void CMapLoaderJson::patchMapHeader(std::unique_ptr & header) + JsonNode condition; + condition.String() = conditionNames.at(event.condition); + asVector.push_back(condition); + + JsonNode data; + + //todo: save identifier + + if(event.objectType != -1) + data["type"].Float() = event.objectType; + + if(event.value != -1) + data["value"].Float() = event.value; + + if(event.position != int3(-1,-1,-1)) + { + auto & position = data["position"].Vector(); + position.resize(3); + position[0].Float() = event.position.x; + position[1].Float() = event.position.y; + position[2].Float() = event.position.z; + } + + if(!data.isNull()) + asVector.push_back(data); + + return std::move(json); + } +}//namespace TriggeredEventsDetail + +namespace TerrainDetail { - header.swap(mapHeader); - if (!input.isNull()) - readPatchData(); - header.swap(mapHeader); + static const std::array terrainCodes = + { + "dt", "sa", "gr", "sn", "sw", "rg", "sb", "lv", "wt", "rc" + }; + static const std::array roadCodes = + { + "", "pd", "pg", "pc" + }; + + static const std::array riverCodes = + { + "", "rw", "ri", "rm", "rl" + }; + + static const std::array flipCodes = + { + '_', '-', '|', '+' + }; } -void CMapLoaderJson::readMap() +///CMapFormatJson +const int CMapFormatJson::VERSION_MAJOR = 1; +const int CMapFormatJson::VERSION_MINOR = 0; + +const std::string CMapFormatJson::HEADER_FILE_NAME = "header.json"; +const std::string CMapFormatJson::OBJECTS_FILE_NAME = "objects.json"; + +void CMapFormatJson::serializeAllowedFactions(JsonSerializeFormat & handler, std::set & value) { - readHeader(); - assert(0); // Not implemented, vcmi does not have its own map format right now + //TODO: unify allowed factions with others - make them std::vector + + std::vector temp; + temp.resize(VLC->townh->factions.size(), false); + auto standard = VLC->townh->getDefaultAllowed(); + + if(handler.saving) + { + for(auto faction : VLC->townh->factions) + if(faction->town && vstd::contains(value, faction->index)) + temp[std::size_t(faction->index)] = true; + } + + handler.serializeLIC("allowedFactions", &CTownHandler::decodeFaction, &CTownHandler::encodeFaction, standard, temp); + + if(!handler.saving) + { + value.clear(); + for (std::size_t i=0; iname); + handler.serializeString("description", mapHeader->description); + handler.serializeNumeric("heroLevelLimit", mapHeader->levelLimit); + + //todo: support arbitrary percentage + handler.serializeNumericEnum("difficulty", HeaderDetail::difficultyMap, HeaderDetail::difficultyDefault, mapHeader->difficulty); + + serializePlayerInfo(handler); + + handler.serializeLIC("allowedHeroes", &CHeroHandler::decodeHero, &CHeroHandler::encodeHero, VLC->heroh->getDefaultAllowed(), mapHeader->allowedHeroes); } -void CMapLoaderJson::readPatchData() +void CMapFormatJson::serializePlayerInfo(JsonSerializeFormat & handler) { - mapHeader->victoryMessage = input["victoryString"].String(); - mapHeader->victoryIconIndex = input["victoryIconIndex"].Float(); + auto playersData = handler.enterStruct("players"); - mapHeader->defeatMessage = input["defeatString"].String(); - mapHeader->defeatIconIndex = input["defeatIconIndex"].Float(); + for(int player = 0; player < PlayerColor::PLAYER_LIMIT_I; player++) + { + PlayerInfo & info = mapHeader->players[player]; - readTriggeredEvents(); + if(handler.saving) + { + if(!info.canAnyonePlay()) + continue; + } + + auto playerData = playersData.enterStruct(GameConstants::PLAYER_COLOR_NAMES[player]); + + if(!handler.saving) + { + if(playerData.get().isNull()) + { + info.canComputerPlay = false; + info.canHumanPlay = false; + continue; + } + info.canComputerPlay = true; + } + + serializeAllowedFactions(handler, info.allowedFactions); + + handler.serializeEnum("canPlay", "PlayerOrAI", "AIOnly", info.canHumanPlay); + + //saving whole structure only if position is valid + if(!handler.saving || info.posOfMainTown.valid()) + { + auto mainTown = handler.enterStruct("mainTown"); + handler.serializeBool("generateHero", info.generateHeroAtMainTown); + handler.serializeNumeric("x", info.posOfMainTown.x); + handler.serializeNumeric("y", info.posOfMainTown.y); + handler.serializeNumeric("l", info.posOfMainTown.z); + } + if(!handler.saving) + { + info.hasMainTown = info.posOfMainTown.valid(); + } + + //mainHero + + //mainHeroPortrait + + //mainCustomHeroName + + //heroes + { + //auto heroes = playerData.enterStruct("heroes"); + + } + + if(!handler.saving) + { + //isFactionRandom indicates that player may select faction, depends on towns & heroes + // true if main town is random and generateHeroAtMainTown==true + // or main hero is random + //TODO: recheck mechanics + info.isFactionRandom = info.allowedFactions.size() > 1; + } + } } -void CMapLoaderJson::readTriggeredEvents() +void CMapFormatJson::readTeams(JsonDeserializer & handler) { + auto teams = handler.enterStruct("teams"); + const JsonNode & src = teams.get(); + + if(src.getType() != JsonNode::DATA_VECTOR) + { + // No alliances + if(src.getType() != JsonNode::DATA_NULL) + logGlobal->errorStream() << "Invalid teams field type"; + + mapHeader->howManyTeams = 0; + for(int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) + { + if(mapHeader->players[i].canComputerPlay || mapHeader->players[i].canHumanPlay) + { + mapHeader->players[i].team = TeamID(mapHeader->howManyTeams++); + } + } + } + else + { + const JsonVector & srcVector = src.Vector(); + mapHeader->howManyTeams = srcVector.size(); + + for(int team = 0; team < mapHeader->howManyTeams; team++) + { + for(const JsonNode & playerData : srcVector[team].Vector()) + { + PlayerColor player = PlayerColor(vstd::find_pos(GameConstants::PLAYER_COLOR_NAMES, playerData.String())); + if(player.isValidPlayer()) + { + if(mapHeader->players[player.getNum()].canAnyonePlay()) + { + mapHeader->players[player.getNum()].team = TeamID(team); + } + } + } + } + + for(PlayerInfo & player : mapHeader->players) + { + if(player.canAnyonePlay() && player.team == TeamID::NO_TEAM) + player.team = TeamID(mapHeader->howManyTeams++); + } + + } +} + + +void CMapFormatJson::writeTeams(JsonSerializer & handler) +{ + auto teams = handler.enterStruct("teams"); + JsonNode & dest = teams.get(); + std::vector> teamsData; + + teamsData.resize(mapHeader->howManyTeams); + + //get raw data + for(int idx = 0; idx < mapHeader->players.size(); idx++) + { + const PlayerInfo & player = mapHeader->players.at(idx); + int team = player.team.getNum(); + if(vstd::iswithin(team, 0, mapHeader->howManyTeams-1) && player.canAnyonePlay()) + teamsData.at(team).insert(PlayerColor(idx)); + } + + //remove single-member teams + vstd::erase_if(teamsData, [](std::set & elem) -> bool + { + return elem.size() <= 1; + }); + + //construct output + dest.setType(JsonNode::DATA_VECTOR); + + for(const std::set & teamData : teamsData) + { + JsonNode team(JsonNode::DATA_VECTOR); + for(const PlayerColor & player : teamData) + { + JsonNode member(JsonNode::DATA_STRING); + member.String() = GameConstants::PLAYER_COLOR_NAMES[player.getNum()]; + team.Vector().push_back(std::move(member)); + } + dest.Vector().push_back(std::move(team)); + } +} + +void CMapFormatJson::serializeTriggeredEvents(JsonSerializeFormat & handler) +{ + handler.serializeString("victoryString", mapHeader->victoryMessage); + handler.serializeNumeric("victoryIconIndex", mapHeader->victoryIconIndex); + + handler.serializeString("defeatString", mapHeader->defeatMessage); + handler.serializeNumeric("defeatIconIndex", mapHeader->defeatIconIndex); +} + + +void CMapFormatJson::readTriggeredEvents(JsonDeserializer & handler) +{ + const JsonNode & input = handler.getCurrent(); + + serializeTriggeredEvents(handler); + mapHeader->triggeredEvents.clear(); for (auto & entry : input["triggeredEvents"].Struct()) @@ -131,33 +390,10 @@ void CMapLoaderJson::readTriggeredEvents() } } -static EventCondition JsonToCondition(const JsonNode & node) +void CMapFormatJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode & source) { - EventCondition event; - event.condition = EventCondition::EWinLoseType(vstd::find_pos(conditionNames, node.Vector()[0].String())); - if (node.Vector().size() > 1) - { - const JsonNode & data = node.Vector()[1]; - if (data["type"].getType() == JsonNode::DATA_STRING) - event.objectType = VLC->modh->identifiers.getIdentifier(data["type"]).get(); - if (data["type"].getType() == JsonNode::DATA_FLOAT) - event.objectType = data["type"].Float(); + using namespace TriggeredEventsDetail; - if (!data["value"].isNull()) - event.value = data["value"].Float(); - - if (!data["position"].isNull()) - { - event.position.x = data["position"].Vector()[0].Float(); - event.position.y = data["position"].Vector()[1].Float(); - event.position.z = data["position"].Vector()[2].Float(); - } - } - return event; -} - -void CMapLoaderJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode & source) -{ event.onFulfill = source["message"].String(); event.description = source["description"].String(); event.effect.type = vstd::find_pos(typeNames, source["effect"]["type"].String()); @@ -165,7 +401,613 @@ void CMapLoaderJson::readTriggeredEvent(TriggeredEvent & event, const JsonNode & event.trigger = EventExpression(source["condition"], JsonToCondition); // logical expression } -void CMapLoaderJson::readPlayerInfo() +void CMapFormatJson::writeTriggeredEvents(JsonSerializer & handler) { - assert(0); // Not implemented + JsonNode & output = handler.getCurrent(); + + serializeTriggeredEvents(handler); + + JsonMap & triggeredEvents = output["triggeredEvents"].Struct(); + + for(auto event : mapHeader->triggeredEvents) + writeTriggeredEvent(event, triggeredEvents[event.identifier]); } + +void CMapFormatJson::writeTriggeredEvent(const TriggeredEvent& event, JsonNode& dest) +{ + using namespace TriggeredEventsDetail; + + dest["message"].String() = event.onFulfill; + dest["description"].String() = event.description; + + dest["effect"]["type"].String() = typeNames.at(size_t(event.effect.type)); + dest["effect"]["messageToSend"].String() = event.effect.toOtherMessage; + + dest["condition"] = event.trigger.toJson(ConditionToJson); +} + +void CMapFormatJson::serializeOptions(JsonSerializeFormat & handler) +{ + //rumors + + //disposedHeroes + + //predefinedHeroes + + handler.serializeLIC("allowedAbilities", &CHeroHandler::decodeSkill, &CHeroHandler::encodeSkill, VLC->heroh->getDefaultAllowedAbilities(), map->allowedAbilities); + + handler.serializeLIC("allowedArtifacts", &CArtHandler::decodeArfifact, &CArtHandler::encodeArtifact, VLC->arth->getDefaultAllowed(), map->allowedArtifact); + + handler.serializeLIC("allowedSpells", &CSpellHandler::decodeSpell, &CSpellHandler::encodeSpell, VLC->spellh->getDefaultAllowed(), map->allowedSpell); + + //events +} + +void CMapFormatJson::readOptions(JsonDeserializer & handler) +{ + serializeOptions(handler); +} + +void CMapFormatJson::writeOptions(JsonSerializer & handler) +{ + serializeOptions(handler); +} + + +///CMapPatcher +CMapPatcher::CMapPatcher(JsonNode stream): + input(stream) +{ + //todo: update map patches and change this + fileVersionMajor = 0; + fileVersionMinor = 0; +} + +void CMapPatcher::patchMapHeader(std::unique_ptr & header) +{ + map = nullptr; + mapHeader = header.get(); + if (!input.isNull()) + readPatchData(); +} + +void CMapPatcher::readPatchData() +{ + JsonDeserializer handler(input); + readTriggeredEvents(handler); +} + + +///CMapLoaderJson +CMapLoaderJson::CMapLoaderJson(CInputStream * stream): + buffer(stream), + ioApi(new CProxyROIOApi(buffer)), + loader("", "_", ioApi) +{ + +} + +si32 CMapLoaderJson::getIdentifier(const std::string& type, const std::string& name) +{ + boost::optional res = VLC->modh->identifiers.getIdentifier("core", type, name, false); + + if(!res) + { + throw new std::runtime_error("Map load failed. Identifier not resolved."); + } + return res.get(); +} + +std::unique_ptr CMapLoaderJson::loadMap() +{ + LOG_TRACE(logGlobal); + std::unique_ptr result = std::unique_ptr(new CMap()); + map = result.get(); + mapHeader = map; + readMap(); + return std::move(result); +} + +std::unique_ptr CMapLoaderJson::loadMapHeader() +{ + LOG_TRACE(logGlobal); + map = nullptr; + std::unique_ptr result = std::unique_ptr(new CMapHeader()); + mapHeader = result.get(); + readHeader(false); + return std::move(result); +} + +JsonNode CMapLoaderJson::getFromArchive(const std::string & archiveFilename) +{ + ResourceID resource(archiveFilename, EResType::TEXT); + + if(!loader.existsResource(resource)) + throw new std::runtime_error(archiveFilename+" not found"); + + auto data = loader.load(resource)->readAll(); + + JsonNode res(reinterpret_cast(data.first.get()), data.second); + + return std::move(res); +} + +void CMapLoaderJson::readMap() +{ + LOG_TRACE(logGlobal); + readHeader(true); + map->initTerrain(); + readTerrain(); + readObjects(); + + map->calculateGuardingGreaturePositions(); +} + +void CMapLoaderJson::readHeader(const bool complete) +{ + //do not use map field here, use only mapHeader + JsonNode header = getFromArchive(HEADER_FILE_NAME); + + fileVersionMajor = header["versionMajor"].Float(); + + if(fileVersionMajor != VERSION_MAJOR) + { + logGlobal->errorStream() << "Unsupported map format version: " << fileVersionMajor; + throw std::runtime_error("Unsupported map format version"); + } + + fileVersionMinor = header["versionMinor"].Float(); + + if(fileVersionMinor > VERSION_MINOR) + { + logGlobal->traceStream() << "Too new map format revision: " << fileVersionMinor << ". This map should work but some of map features may be ignored."; + } + + JsonDeserializer handler(header); + + mapHeader->version = EMapFormat::VCMI;//todo: new version field + + //todo: multilevel map load support + { + auto levels = handler.enterStruct("mapLevels"); + + { + auto surface = levels.enterStruct("surface"); + mapHeader->height = surface.get()["height"].Float(); + mapHeader->width = surface.get()["width"].Float(); + } + { + auto underground = levels.enterStruct("underground"); + mapHeader->twoLevel = !underground.get().isNull(); + } + } + + serializeHeader(handler); + + readTriggeredEvents(handler); + + + readTeams(handler); + //TODO: readHeader + + if(complete) + readOptions(handler); +} + + +void CMapLoaderJson::readTerrainTile(const std::string& src, TerrainTile& tile) +{ + using namespace TerrainDetail; + {//terrain type + const std::string typeCode = src.substr(0,2); + + int rawType = vstd::find_pos(terrainCodes, typeCode); + + if(rawType < 0) + throw new std::runtime_error("Invalid terrain type code in "+src); + + tile.terType = ETerrainType(rawType); + } + int startPos = 2; //0+typeCode fixed length + {//terrain view + int pos = startPos; + while(isdigit(src.at(pos))) + pos++; + int len = pos - startPos; + if(len<=0) + throw new std::runtime_error("Invalid terrain view in "+src); + const std::string rawCode = src.substr(startPos,len); + tile.terView = atoi(rawCode.c_str()); + startPos+=len; + } + {//terrain flip + int terrainFlip = vstd::find_pos(flipCodes, src.at(startPos++)); + if(terrainFlip < 0) + throw new std::runtime_error("Invalid terrain flip in "+src); + else + tile.extTileFlags = terrainFlip; + } + if(startPos >= src.size()) + return; + bool hasRoad = true; + {//road type + const std::string typeCode = src.substr(startPos,2); + startPos+=2; + int rawType = vstd::find_pos(roadCodes, typeCode); + if(rawType < 0) + { + rawType = vstd::find_pos(riverCodes, typeCode); + if(rawType < 0) + throw new std::runtime_error("Invalid river type in "+src); + else + { + tile.riverType = ERiverType::ERiverType(rawType); + hasRoad = false; + } + } + else + tile.roadType = ERoadType::ERoadType(rawType); + } + if(hasRoad) + {//road dir + int pos = startPos; + while(isdigit(src.at(pos))) + pos++; + int len = pos - startPos; + if(len<=0) + throw new std::runtime_error("Invalid road dir in "+src); + const std::string rawCode = src.substr(startPos,len); + tile.roadDir = atoi(rawCode.c_str()); + startPos+=len; + } + if(hasRoad) + {//road flip + int flip = vstd::find_pos(flipCodes, src.at(startPos++)); + if(flip < 0) + throw new std::runtime_error("Invalid road flip in "+src); + else + tile.extTileFlags |= (flip<<4); + } + if(startPos >= src.size()) + return; + if(hasRoad) + {//river type + const std::string typeCode = src.substr(startPos,2); + startPos+=2; + int rawType = vstd::find_pos(riverCodes, typeCode); + if(rawType < 0) + throw new std::runtime_error("Invalid river type in "+src); + tile.riverType = ERiverType::ERiverType(rawType); + } + {//river dir + int pos = startPos; + while(isdigit(src.at(pos))) + pos++; + int len = pos - startPos; + if(len<=0) + throw new std::runtime_error("Invalid river dir in "+src); + const std::string rawCode = src.substr(startPos,len); + tile.riverDir = atoi(rawCode.c_str()); + startPos+=len; + } + {//river flip + int flip = vstd::find_pos(flipCodes, src.at(startPos++)); + if(flip < 0) + throw new std::runtime_error("Invalid road flip in "+src); + else + tile.extTileFlags |= (flip<<2); + } +} + +void CMapLoaderJson::readTerrainLevel(const JsonNode& src, const int index) +{ + int3 pos(0,0,index); + + const JsonVector & rows = src.Vector(); + + if(rows.size() != map->height) + throw new std::runtime_error("Invalid terrain data"); + + for(pos.y = 0; pos.y < map->height; pos.y++) + { + const JsonVector & tiles = rows[pos.y].Vector(); + + if(tiles.size() != map->width) + throw new std::runtime_error("Invalid terrain data"); + + for(pos.x = 0; pos.x < map->width; pos.x++) + readTerrainTile(tiles[pos.x].String(), map->getTile(pos)); + } +} + +void CMapLoaderJson::readTerrain() +{ + { + const JsonNode surface = getFromArchive("surface_terrain.json"); + readTerrainLevel(surface, 0); + } + if(map->twoLevel) + { + const JsonNode underground = getFromArchive("underground_terrain.json"); + readTerrainLevel(underground, 1); + } + +} + +CMapLoaderJson::MapObjectLoader::MapObjectLoader(CMapLoaderJson * _owner, JsonMap::value_type& json): + owner(_owner), instance(nullptr),id(-1), jsonKey(json.first), configuration(json.second) +{ + +} + +void CMapLoaderJson::MapObjectLoader::construct() +{ + logGlobal->debugStream() <<"Loading: " <errorStream() << "Object type missing"; + logGlobal->debugStream() << configuration; + return; + } + + int3 pos; + pos.x = configuration["x"].Float(); + pos.y = configuration["y"].Float(); + pos.z = configuration["l"].Float(); + + //special case for grail + if(typeName == "grail") + { + owner->map->grailPos = pos; + + owner->map->grailRadius = configuration["options"]["grailRadius"].Float(); + return; + } + else if(subtypeName.empty()) + { + logGlobal->errorStream() << "Object subtype missing"; + logGlobal->debugStream() << configuration; + return; + } + + auto handler = VLC->objtypeh->getHandlerFor(typeName, subtypeName); + + ObjectTemplate appearance; + + appearance.readJson(configuration["template"], false); + appearance.id = Obj(handler->type); + appearance.subid = handler->subtype; + + instance = handler->create(appearance); + + instance->id = ObjectInstanceID(owner->map->objects.size()); + instance->instanceName = jsonKey; + instance->pos = pos; + owner->map->addNewObject(instance); +} + +void CMapLoaderJson::MapObjectLoader::configure() +{ + if(nullptr == instance) + return; + + JsonDeserializer handler(configuration); + + instance->serializeJson(handler); + + if(auto art = dynamic_cast(instance)) + { + //todo: find better place for this code + + int artID = ArtifactID::NONE; + int spellID = -1; + + if(art->ID == Obj::SPELL_SCROLL) + { + auto spellIdentifier = configuration["options"]["spell"].String(); + auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", spellIdentifier); + if(rawId) + spellID = rawId.get(); + else + spellID = 0; + artID = ArtifactID::SPELL_SCROLL; + } + else if(art->ID == Obj::ARTIFACT) + { + //specific artifact + artID = art->subID; + } + + art->storedArtifact = CArtifactInstance::createArtifact(owner->map, artID, spellID); + } +} + +void CMapLoaderJson::readObjects() +{ + LOG_TRACE(logGlobal); + + std::vector> loaders;//todo: optimize MapObjectLoader memory layout + + JsonNode data = getFromArchive(OBJECTS_FILE_NAME); + + //get raw data + for(auto & p : data.Struct()) + loaders.push_back(vstd::make_unique(this, p)); + + for(auto & ptr : loaders) + ptr->construct(); + + //configure objects after all objects are constructed so we may resolve internal IDs even to actual pointers OTF + for(auto & ptr : loaders) + ptr->configure(); + + std::sort(map->heroesOnMap.begin(), map->heroesOnMap.end(), [](const ConstTransitivePtr & a, const ConstTransitivePtr & b) + { + return a->subID < b->subID; + }); +} + +///CMapSaverJson +CMapSaverJson::CMapSaverJson(CInputOutputStream * stream): + buffer(stream), + ioApi(new CProxyIOApi(buffer)), + saver(ioApi, "_") +{ + fileVersionMajor = VERSION_MAJOR; + fileVersionMinor = VERSION_MINOR; +} + +CMapSaverJson::~CMapSaverJson() +{ + +} + +void CMapSaverJson::addToArchive(const JsonNode& data, const std::string& filename) +{ + std::ostringstream out; + out << data; + out.flush(); + + { + auto s = out.str(); + std::unique_ptr stream = saver.addFile(filename); + + if (stream->write((const ui8*)s.c_str(), s.size()) != s.size()) + throw new std::runtime_error("CMapSaverJson::saveHeader() zip compression failed."); + } +} + +void CMapSaverJson::saveMap(const std::unique_ptr& map) +{ + this->map = map.get(); + this->mapHeader = this->map; + writeHeader(); + writeTerrain(); + writeObjects(); +} + +void CMapSaverJson::writeHeader() +{ + JsonNode header; + JsonSerializer handler(header); + + header["versionMajor"].Float() = VERSION_MAJOR; + header["versionMinor"].Float() = VERSION_MINOR; + + //todo: multilevel map save support + JsonNode & levels = header["mapLevels"]; + levels["surface"]["height"].Float() = mapHeader->height; + levels["surface"]["width"].Float() = mapHeader->width; + levels["surface"]["index"].Float() = 0; + + if(mapHeader->twoLevel) + { + levels["underground"]["height"].Float() = mapHeader->height; + levels["underground"]["width"].Float() = mapHeader->width; + levels["underground"]["index"].Float() = 1; + } + + serializeHeader(handler); + + writeTriggeredEvents(handler); + + writeTeams(handler); + + writeOptions(handler); + + addToArchive(header, HEADER_FILE_NAME); +} + +const std::string CMapSaverJson::writeTerrainTile(const TerrainTile & tile) +{ + using namespace TerrainDetail; + + std::ostringstream out; + out.setf(std::ios::dec, std::ios::basefield); + out.unsetf(std::ios::showbase); + + out << terrainCodes.at(int(tile.terType)) << (int)tile.terView << flipCodes[tile.extTileFlags % 4]; + + if(tile.roadType != ERoadType::NO_ROAD) + { + out << roadCodes.at(int(tile.roadType)) << (int)tile.roadDir << flipCodes[(tile.extTileFlags >> 4) % 4]; + } + + if(tile.riverType != ERiverType::NO_RIVER) + { + out << riverCodes.at(int(tile.riverType)) << (int)tile.riverDir << flipCodes[(tile.extTileFlags >> 2) % 4]; + } + + return out.str(); +} + +JsonNode CMapSaverJson::writeTerrainLevel(const int index) +{ + JsonNode data; + int3 pos(0,0,index); + + data.Vector().resize(map->height); + + for(pos.y = 0; pos.y < map->height; pos.y++) + { + JsonNode & row = data.Vector()[pos.y]; + + row.Vector().resize(map->width); + + for(pos.x = 0; pos.x < map->width; pos.x++) + row.Vector()[pos.x].String() = writeTerrainTile(map->getTile(pos)); + } + + return std::move(data); +} + +void CMapSaverJson::writeTerrain() +{ + //todo: multilevel map save support + + JsonNode surface = writeTerrainLevel(0); + addToArchive(surface, "surface_terrain.json"); + + if(map->twoLevel) + { + JsonNode underground = writeTerrainLevel(1); + addToArchive(underground, "underground_terrain.json"); + } +} + +void CMapSaverJson::writeObjects() +{ + JsonNode data(JsonNode::DATA_STRUCT); + + JsonSerializer handler(data); + + for(CGObjectInstance * obj : map->objects) + { + auto temp = handler.enterStruct(obj->instanceName); + + obj->serializeJson(handler); + } + + if(map->grailPos.valid()) + { + JsonNode grail(JsonNode::DATA_STRUCT); + grail["type"].String() = "grail"; + + grail["x"].Float() = map->grailPos.x; + grail["y"].Float() = map->grailPos.y; + grail["l"].Float() = map->grailPos.z; + + grail["options"]["radius"].Float() = map->grailRadius; + + std::string grailId = boost::str(boost::format("grail_%d") % map->objects.size()); + + data[grailId] = grail; + } + + addToArchive(data, OBJECTS_FILE_NAME); +} + diff --git a/lib/mapping/MapFormatJson.h b/lib/mapping/MapFormatJson.h index 3a75dff35..4930c5654 100644 --- a/lib/mapping/MapFormatJson.h +++ b/lib/mapping/MapFormatJson.h @@ -1,6 +1,5 @@ - /* - * MapFormatH3M.h, part of VCMI engine + * MapFormatJSON.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -14,17 +13,136 @@ #include "CMapService.h" #include "../JsonNode.h" -class TriggeredEvent; +#include "../filesystem/CZipSaver.h" +#include "../filesystem/CZipLoader.h" +#include "../GameConstants.h" -class DLL_LINKAGE CMapLoaderJson : public IMapPatcher +class TriggeredEvent; +struct TerrainTile; +struct PlayerInfo; +class CGObjectInstance; +class AObjectTypeHandler; + +class JsonSerializeFormat; +class JsonDeserializer; +class JsonSerializer; + +class DLL_LINKAGE CMapFormatJson +{ +public: + static const int VERSION_MAJOR; + static const int VERSION_MINOR; + + static const std::string HEADER_FILE_NAME; + static const std::string OBJECTS_FILE_NAME; + + int fileVersionMajor; + int fileVersionMinor; +protected: + + /** ptr to the map object which gets filled by data from the buffer or written to buffer */ + CMap * map; + + /** + * ptr to the map header object which gets filled by data from the buffer. + * (when loading map and mapHeader point to the same object) + */ + CMapHeader * mapHeader; + + void serializeAllowedFactions(JsonSerializeFormat & handler, std::set & value); + + ///common part of header saving/loading + void serializeHeader(JsonSerializeFormat & handler); + + ///player information saving/loading + void serializePlayerInfo(JsonSerializeFormat & handler); + + /** + * Reads team settings to header + */ + void readTeams(JsonDeserializer & handler); + + /** + * Saves team settings to header + */ + void writeTeams(JsonSerializer & handler); + + + ///common part triggered events of saving/loading + void serializeTriggeredEvents(JsonSerializeFormat & handler); + + /** + * Reads triggered events, including victory/loss conditions + */ + void readTriggeredEvents(JsonDeserializer & handler); + + /** + * Writes triggered events, including victory/loss conditions + */ + void writeTriggeredEvents(JsonSerializer & handler); + + /** + * Reads one of triggered events + */ + void readTriggeredEvent(TriggeredEvent & event, const JsonNode & source); + + /** + * Writes one of triggered events + */ + void writeTriggeredEvent(const TriggeredEvent & event, JsonNode & dest); + + + + ///common part of map attributes saving/loading + void serializeOptions(JsonSerializeFormat & handler); + + /** + * Loads map attributes except header ones + */ + void readOptions(JsonDeserializer & handler); + + /** + * Saves map attributes except header ones + */ + void writeOptions(JsonSerializer & handler); +}; + +class DLL_LINKAGE CMapPatcher : public CMapFormatJson, public IMapPatcher { public: /** * Default constructor. * + * @param stream. A stream containing the map data. + */ + CMapPatcher(JsonNode stream); + +public: //IMapPatcher + /** + * Modifies supplied map header using Json data + * + */ + void patchMapHeader(std::unique_ptr & header) override; + +private: + /** + * Reads subset of header that can be replaced by patching. + */ + void readPatchData(); + + + JsonNode input; +}; + +class DLL_LINKAGE CMapLoaderJson : public CMapFormatJson, public IMapLoader +{ +public: + /** + * Constructor. + * * @param stream a stream containing the map data */ - CMapLoaderJson(JsonNode stream); + CMapLoaderJson(CInputStream * stream); /** * Loads the VCMI/Json map file. @@ -40,52 +158,107 @@ public: */ std::unique_ptr loadMapHeader() override; - /** - * Modifies supplied map header using Json data - * - */ - void patchMapHeader(std::unique_ptr & header) override; - private: + + struct MapObjectLoader + { + MapObjectLoader(CMapLoaderJson * _owner, JsonMap::value_type & json); + CMapLoaderJson * owner; + CGObjectInstance * instance; + ObjectInstanceID id; + std::string jsonKey;//full id defined by map creator + JsonNode & configuration; + + ///constructs object (without configuration) + void construct(); + + ///configures object + void configure(); + + }; + + si32 getIdentifier(const std::string & type, const std::string & name); + + /** + * Reads the map header. + */ + void readHeader(const bool complete); + /** * Reads complete map. */ void readMap(); - /** - * Reads the map header. - */ - void readHeader(); + void readTerrainTile(const std::string & src, TerrainTile & tile); + + void readTerrainLevel(const JsonNode & src, const int index); + + void readTerrain(); /** - * Reads subset of header that can be replaced by patching. + * Loads all map objects from zip archive */ - void readPatchData(); + void readObjects(); - /** - * Reads player information. - */ - void readPlayerInfo(); + JsonNode getFromArchive(const std::string & archiveFilename); - /** - * Reads triggered events, including victory/loss conditions - */ - void readTriggeredEvents(); + CInputStream * buffer; + std::shared_ptr ioApi; - /** - * Reads one of triggered events - */ - void readTriggeredEvent(TriggeredEvent & event, const JsonNode & source); - - - /** ptr to the map object which gets filled by data from the buffer */ - CMap * map; - - /** - * ptr to the map header object which gets filled by data from the buffer. - * (when loading map and mapHeader point to the same object) - */ - std::unique_ptr mapHeader; - - const JsonNode input; + CZipLoader loader;///< object to handle zip archive operations +}; + +class DLL_LINKAGE CMapSaverJson : public CMapFormatJson, public IMapSaver +{ +public: + /** + * Constructor. + * + * @param stream a stream to save the map to, will contain zip archive + */ + CMapSaverJson(CInputOutputStream * stream); + + ~CMapSaverJson(); + + /** + * Actually saves the VCMI/Json map into stream. + */ + void saveMap(const std::unique_ptr & map) override; +private: + + /** + * Saves @data as json file with specified @filename + */ + void addToArchive(const JsonNode & data, const std::string & filename); + + /** + * Saves header to zip archive + */ + void writeHeader(); + + /** + * Encodes one tile into string + * @param tile tile to serialize + */ + const std::string writeTerrainTile(const TerrainTile & tile); + + /** + * Saves map level into json + * @param index z coordinate + */ + JsonNode writeTerrainLevel(const int index); + + /** + * Saves all terrain into zip archive + */ + void writeTerrain(); + + /** + * Saves all map objects into zip archive + */ + void writeObjects(); + + CInputOutputStream * buffer; + std::shared_ptr ioApi; + CZipSaver saver;///< object to handle zip archive operations }; diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 09d8f6182..fcfe4b697 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -565,11 +565,10 @@ void CMapGenerator::createConnections() } if (!guardPos.valid()) { - auto teleport1 = new CGMonolith; - teleport1->ID = Obj::MONOLITH_TWO_WAY; - teleport1->subID = getNextMonlithIndex(); + auto factory = VLC->objtypeh->getHandlerFor(Obj::MONOLITH_TWO_WAY, getNextMonlithIndex()); + auto teleport1 = factory->create(ObjectTemplate()); - auto teleport2 = new CGMonolith(*teleport1); + auto teleport2 = factory->create(ObjectTemplate()); zoneA->addRequiredObject (teleport1, connection.getGuardStrength()); zoneB->addRequiredObject (teleport2, connection.getGuardStrength()); @@ -579,7 +578,7 @@ void CMapGenerator::createConnections() void CMapGenerator::addHeaderInfo() { - map->version = EMapFormat::SOD; + map->version = EMapFormat::VCMI; map->width = mapGenOptions->getWidth(); map->height = mapGenOptions->getHeight(); map->twoLevel = mapGenOptions->getHasTwoLevels(); diff --git a/lib/rmg/CRmgTemplateZone.cpp b/lib/rmg/CRmgTemplateZone.cpp index 2c6fcf331..061db8569 100644 --- a/lib/rmg/CRmgTemplateZone.cpp +++ b/lib/rmg/CRmgTemplateZone.cpp @@ -1036,10 +1036,9 @@ bool CRmgTemplateZone::addMonster(CMapGenerator* gen, int3 &pos, si32 strength, amount = strength / VLC->creh->creatures[creId]->AIValue; } + auto guardFactory = VLC->objtypeh->getHandlerFor(Obj::MONSTER, creId); - auto guard = new CGCreature(); - guard->ID = Obj::MONSTER; - guard->subID = creId; + auto guard = (CGCreature *) guardFactory->create(ObjectTemplate()); guard->character = CGCreature::HOSTILE; auto hlp = new CStackInstance(creId, amount); //will be set during initialization @@ -1318,19 +1317,23 @@ void CRmgTemplateZone::initTownType (CMapGenerator* gen) { for (int i = 0; i < count; i++) { - auto town = new CGTownInstance(); - town->ID = Obj::TOWN; + si32 subType = townType; - if (this->townsAreSameType) - town->subID = townType; - else + if(totalTowns>0) { - if (townTypes.size()) - town->subID = *RandomGeneratorUtil::nextItem(townTypes, gen->rand); - else - town->subID = *RandomGeneratorUtil::nextItem(getDefaultTownTypes(), gen->rand); //it is possible to have zone with no towns allowed + if(!this->townsAreSameType) + { + if (townTypes.size()) + subType = *RandomGeneratorUtil::nextItem(townTypes, gen->rand); + else + subType = *RandomGeneratorUtil::nextItem(getDefaultTownTypes(), gen->rand); //it is possible to have zone with no towns allowed + } } + auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, subType); + auto town = (CGTownInstance *) townFactory->create(ObjectTemplate()); + town->ID = Obj::TOWN; + town->tempOwner = player; if (hasFort) town->builtBuildings.insert(BuildingID::FORT); @@ -1342,10 +1345,8 @@ void CRmgTemplateZone::initTownType (CMapGenerator* gen) town->possibleSpells.push_back(spell->id); } - if (!totalTowns) + if (totalTowns <= 0) { - //first town in zone sets the facton of entire zone - town->subID = townType; //register MAIN town of zone gen->registerZone(town->subID); //first town in zone goes in the middle @@ -1381,10 +1382,9 @@ void CRmgTemplateZone::initTownType (CMapGenerator* gen) randomizeTownType(gen); } - auto town = new CGTownInstance(); - town->ID = Obj::TOWN; + auto townFactory = VLC->objtypeh->getHandlerFor(Obj::TOWN, townType); - town->subID = townType; + CGTownInstance * town = (CGTownInstance *) townFactory->create(ObjectTemplate()); town->tempOwner = player; town->builtBuildings.insert(BuildingID::FORT); town->builtBuildings.insert(BuildingID::DEFAULT); @@ -1495,21 +1495,27 @@ void CRmgTemplateZone::paintZoneTerrain (CMapGenerator* gen, ETerrainType terrai bool CRmgTemplateZone::placeMines (CMapGenerator* gen) { - std::vector required_mines; - required_mines.push_back(Res::ERes::WOOD); - required_mines.push_back(Res::ERes::ORE); - static const Res::ERes woodOre[] = {Res::ERes::WOOD, Res::ERes::ORE}; static const Res::ERes preciousResources[] = {Res::ERes::GEMS, Res::ERes::CRYSTAL, Res::ERes::MERCURY, Res::ERes::SULFUR}; + std::array factory = + { + VLC->objtypeh->getHandlerFor(Obj::MINE, 0), + VLC->objtypeh->getHandlerFor(Obj::MINE, 1), + VLC->objtypeh->getHandlerFor(Obj::MINE, 2), + VLC->objtypeh->getHandlerFor(Obj::MINE, 3), + VLC->objtypeh->getHandlerFor(Obj::MINE, 4), + VLC->objtypeh->getHandlerFor(Obj::MINE, 5), + VLC->objtypeh->getHandlerFor(Obj::MINE, 6) + }; + for (const auto & res : woodOre) { for (int i = 0; i < mines[res]; i++) { - auto mine = new CGMine(); - mine->ID = Obj::MINE; - mine->subID = static_cast(res); + auto mine = (CGMine *) factory.at(static_cast(res))->create(ObjectTemplate()); mine->producedResource = res; + mine->tempOwner = PlayerColor::NEUTRAL; mine->producedQuantity = mine->defaultResProduction(); if (!i) addCloseObject(mine, 1500); //only firts one is close @@ -1521,20 +1527,18 @@ bool CRmgTemplateZone::placeMines (CMapGenerator* gen) { for (int i = 0; i < mines[res]; i++) { - auto mine = new CGMine(); - mine->ID = Obj::MINE; - mine->subID = static_cast(res); + auto mine = (CGMine *) factory.at(static_cast(res))->create(ObjectTemplate()); mine->producedResource = res; + mine->tempOwner = PlayerColor::NEUTRAL; mine->producedQuantity = mine->defaultResProduction(); addRequiredObject(mine, 3500); } } for (int i = 0; i < mines[Res::GOLD]; i++) { - auto mine = new CGMine(); - mine->ID = Obj::MINE; - mine->subID = static_cast(Res::GOLD); + auto mine = (CGMine *) factory.at(Res::GOLD)->create(ObjectTemplate()); mine->producedResource = Res::GOLD; + mine->tempOwner = PlayerColor::NEUTRAL; mine->producedQuantity = mine->defaultResProduction(); addRequiredObject(mine, 7000); } @@ -2105,9 +2109,8 @@ void CRmgTemplateZone::placeAndGuardObject(CMapGenerator* gen, CGObjectInstance* void CRmgTemplateZone::placeSubterraneanGate(CMapGenerator* gen, int3 pos, si32 guardStrength) { - auto gate = new CGSubterraneanGate; - gate->ID = Obj::SUBTERRANEAN_GATE; - gate->subID = 0; + auto factory = VLC->objtypeh->getHandlerFor(Obj::SUBTERRANEAN_GATE, 0); + auto gate = factory->create(ObjectTemplate()); placeObject (gen, gate, pos, true); addToConnectLater (getAccessibleOffset (gen, gate->appearance, pos)); //guard will be placed on accessibleOffset guardObject (gen, gate, guardStrength, true); @@ -2301,9 +2304,8 @@ ObjectInfo CRmgTemplateZone::getRandomObject(CMapGenerator* gen, CTreasurePileIn { oi.generateObject = [minValue]() -> CGObjectInstance * { - auto obj = new CGPandoraBox(); - obj->ID = Obj::PANDORAS_BOX; - obj->subID = 0; + auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); + auto obj = (CGPandoraBox *) factory->create(ObjectTemplate()); obj->resources[Res::GOLD] = minValue; return obj; }; @@ -2391,9 +2393,6 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) { oi.generateObject = [i, gen, this]() -> CGObjectInstance * { - auto obj = new CGHeroInstance; - obj->ID = Obj::PRISON; - std::vector possibleHeroes; for (int j = 0; j < gen->map->allowedHeroes.size(); j++) { @@ -2402,6 +2401,10 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) } auto hid = *RandomGeneratorUtil::nextItem(possibleHeroes, gen->rand); + auto factory = VLC->objtypeh->getHandlerFor(Obj::PRISON, 0); + auto obj = (CGHeroInstance *) factory->create(ObjectTemplate()); + + obj->subID = hid; //will be initialized later obj->exp = prisonExp[i]; obj->setOwner(PlayerColor::NEUTRAL); @@ -2469,9 +2472,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) { oi.generateObject = [i, gen]() -> CGObjectInstance * { - auto obj = new CGArtifact(); - obj->ID = Obj::SPELL_SCROLL; - obj->subID = 0; + auto factory = VLC->objtypeh->getHandlerFor(Obj::SPELL_SCROLL, 0); + auto obj = (CGArtifact *) factory->create(ObjectTemplate()); std::vector out; for (auto spell : VLC->spellh->objects) //spellh size appears to be greater (?) @@ -2497,9 +2499,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) { oi.generateObject = [i]() -> CGObjectInstance * { - auto obj = new CGPandoraBox(); - obj->ID = Obj::PANDORAS_BOX; - obj->subID = 0; + auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); + auto obj = (CGPandoraBox *) factory->create(ObjectTemplate()); obj->resources[Res::GOLD] = i * 5000; return obj; }; @@ -2514,9 +2515,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) { oi.generateObject = [i]() -> CGObjectInstance * { - auto obj = new CGPandoraBox(); - obj->ID = Obj::PANDORAS_BOX; - obj->subID = 0; + auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); + auto obj = (CGPandoraBox *) factory->create(ObjectTemplate()); obj->gainedExp = i * 5000; return obj; }; @@ -2562,9 +2562,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) oi.generateObject = [creature, creaturesAmount]() -> CGObjectInstance * { - auto obj = new CGPandoraBox(); - obj->ID = Obj::PANDORAS_BOX; - obj->subID = 0; + auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); + auto obj = (CGPandoraBox *) factory->create(ObjectTemplate()); auto stack = new CStackInstance(creature, creaturesAmount); obj->creatures.putStack(SlotID(0), stack); return obj; @@ -2580,9 +2579,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) { oi.generateObject = [i, gen]() -> CGObjectInstance * { - auto obj = new CGPandoraBox(); - obj->ID = Obj::PANDORAS_BOX; - obj->subID = 0; + auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); + auto obj = (CGPandoraBox *) factory->create(ObjectTemplate()); std::vector spells; for (auto spell : VLC->spellh->objects) @@ -2610,9 +2608,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) { oi.generateObject = [i,gen]() -> CGObjectInstance * { - auto obj = new CGPandoraBox(); - obj->ID = Obj::PANDORAS_BOX; - obj->subID = 0; + auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); + auto obj = (CGPandoraBox *) factory->create(ObjectTemplate()); std::vector spells; for (auto spell : VLC->spellh->objects) @@ -2640,9 +2637,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) oi.generateObject = [gen]() -> CGObjectInstance * { - auto obj = new CGPandoraBox(); - obj->ID = Obj::PANDORAS_BOX; - obj->subID = 0; + auto factory = VLC->objtypeh->getHandlerFor(Obj::PANDORAS_BOX, 0); + auto obj = (CGPandoraBox *) factory->create(ObjectTemplate()); std::vector spells; for (auto spell : VLC->spellh->objects) @@ -2713,9 +2709,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) oi.generateObject = [creature, creaturesAmount, randomAppearance, gen, this, generateArtInfo]() -> CGObjectInstance * { - auto obj = new CGSeerHut(); - obj->ID = Obj::SEER_HUT; - obj->subID = randomAppearance; + auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance); + auto obj = (CGSeerHut *) factory->create(ObjectTemplate()); obj->rewardType = CGSeerHut::CREATURE; obj->rID = creature->idNumber; obj->rVal = creaturesAmount; @@ -2752,9 +2747,9 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) oi.generateObject = [i, randomAppearance, gen, this, generateArtInfo]() -> CGObjectInstance * { - auto obj = new CGSeerHut(); - obj->ID = Obj::SEER_HUT; - obj->subID = randomAppearance; + auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance); + auto obj = (CGSeerHut *) factory->create(ObjectTemplate()); + obj->rewardType = CGSeerHut::EXPERIENCE; obj->rID = 0; //unitialized? obj->rVal = seerExpGold[i]; @@ -2777,9 +2772,8 @@ void CRmgTemplateZone::addAllPossibleObjects(CMapGenerator* gen) oi.generateObject = [i, randomAppearance, gen, this, generateArtInfo]() -> CGObjectInstance * { - auto obj = new CGSeerHut(); - obj->ID = Obj::SEER_HUT; - obj->subID = randomAppearance; + auto factory = VLC->objtypeh->getHandlerFor(Obj::SEER_HUT, randomAppearance); + auto obj = (CGSeerHut *) factory->create(ObjectTemplate()); obj->rewardType = CGSeerHut::RESOURCES; obj->rID = Res::GOLD; obj->rVal = seerExpGold[i]; diff --git a/lib/serializer/JsonDeserializer.cpp b/lib/serializer/JsonDeserializer.cpp new file mode 100644 index 000000000..f6229ded0 --- /dev/null +++ b/lib/serializer/JsonDeserializer.cpp @@ -0,0 +1,163 @@ +/* + * JsonDeserializer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + + +#include "StdInc.h" +#include "JsonDeserializer.h" + +#include "../JsonNode.h" + +JsonDeserializer::JsonDeserializer(JsonNode & root_): + JsonSerializeFormat(root_, false) +{ + +} + +void JsonDeserializer::serializeBool(const std::string & fieldName, bool & value) +{ + value = current->operator[](fieldName).Bool(); +} + +void JsonDeserializer::serializeEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) +{ + const JsonNode & tmp = current->operator[](fieldName); + + value = tmp.String() == trueValue; +} + +void JsonDeserializer::serializeFloat(const std::string & fieldName, double & value) +{ + value = current->operator[](fieldName).Float(); +} + +void JsonDeserializer::serializeIntEnum(const std::string & fieldName, const std::vector & enumMap, const si32 defaultValue, si32 & value) +{ + const std::string & valueName = current->operator[](fieldName).String(); + + si32 rawValue = vstd::find_pos(enumMap, valueName); + if(rawValue < 0) + value = defaultValue; + else + value = rawValue; +} + +void JsonDeserializer::serializeIntId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const si32 defaultValue, si32 & value) +{ + std::string identifier; + serializeString(fieldName, identifier); + + if(identifier == "") + { + value = defaultValue; + return; + } + + si32 rawId = decoder(identifier); + if(rawId >= 0) + value = rawId; + else + value = defaultValue; +} + +void JsonDeserializer::serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector & standard, std::vector & value) +{ + const JsonNode & field = current->operator[](fieldName); + if(field.isNull()) + return; + + const JsonNode & anyOf = field["anyOf"]; + const JsonNode & allOf = field["allOf"]; + const JsonNode & noneOf = field["noneOf"]; + + if(anyOf.Vector().empty() && allOf.Vector().empty()) + { + //permissive mode + value = standard; + } + else + { + //restrictive mode + value.clear(); + value.resize(standard.size(), false); + + readLICPart(anyOf, decoder, true, value); + readLICPart(allOf, decoder, true, value); + } + + readLICPart(noneOf, decoder, false, value); +} + +void JsonDeserializer::serializeLIC(const std::string & fieldName, LIC & value) +{ + const JsonNode & field = current->operator[](fieldName); + + const JsonNode & anyOf = field["anyOf"]; + const JsonNode & allOf = field["allOf"]; + const JsonNode & noneOf = field["noneOf"]; + + if(anyOf.Vector().empty()) + { + //permissive mode + value.any = value.standard; + } + else + { + //restrictive mode + value.any.clear(); + value.any.resize(value.standard.size(), false); + + readLICPart(anyOf, value.decoder, true, value.any); + } + + readLICPart(allOf, value.decoder, true, value.all); + readLICPart(noneOf, value.decoder, true, value.none); + + //remove any banned from allowed and required + for(si32 idx = 0; idx < value.none.size(); idx++) + { + if(value.none[idx]) + { + value.all[idx] = false; + value.any[idx] = false; + } + } + + //add all required to allowed + for(si32 idx = 0; idx < value.all.size(); idx++) + { + if(value.all[idx]) + { + value.any[idx] = true; + } + } +} + +void JsonDeserializer::serializeString(const std::string & fieldName, std::string & value) +{ + value = current->operator[](fieldName).String(); +} + +void JsonDeserializer::readLICPart(const JsonNode & part, const TDecoder & decoder, const bool val, std::vector & value) +{ + for(size_t index = 0; index < part.Vector().size(); index++) + { + const std::string & identifier = part.Vector()[index].String(); + + const si32 rawId = decoder(identifier); + if(rawId >= 0) + { + if(rawId < value.size()) + value[rawId] = val; + else + logGlobal->errorStream() << "JsonDeserializer::serializeLIC: id out of bounds " << rawId; + } + } +} + diff --git a/lib/serializer/JsonDeserializer.h b/lib/serializer/JsonDeserializer.h new file mode 100644 index 000000000..cfd7031a5 --- /dev/null +++ b/lib/serializer/JsonDeserializer.h @@ -0,0 +1,34 @@ +/* + * JsonDeserializer.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "JsonSerializeFormat.h" + +class JsonNode; + +class JsonDeserializer: public JsonSerializeFormat +{ +public: + JsonDeserializer(JsonNode & root_); + + void serializeBool(const std::string & fieldName, bool & value) override; + void serializeEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) override; + void serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector & standard, std::vector & value) override; + void serializeLIC(const std::string & fieldName, LIC & value) override; + void serializeString(const std::string & fieldName, std::string & value) override; + +protected: + void serializeFloat(const std::string & fieldName, double & value) override; + void serializeIntEnum(const std::string & fieldName, const std::vector & enumMap, const si32 defaultValue, si32 & value) override; + void serializeIntId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const si32 defaultValue, si32 & value) override; +private: + void readLICPart(const JsonNode & part, const TDecoder & decoder, const bool val, std::vector & value); +}; diff --git a/lib/serializer/JsonSerializeFormat.cpp b/lib/serializer/JsonSerializeFormat.cpp new file mode 100644 index 000000000..d11cff326 --- /dev/null +++ b/lib/serializer/JsonSerializeFormat.cpp @@ -0,0 +1,90 @@ +/* + * JsonSerializeFormat.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + + +#include "StdInc.h" +#include "JsonSerializeFormat.h" + +#include "../JsonNode.h" + + +//JsonStructSerializer +JsonStructSerializer::JsonStructSerializer(JsonStructSerializer&& other): + restoreState(false), + owner(other.owner), + parentNode(other.parentNode), + thisNode(other.thisNode) +{ + +} + +JsonStructSerializer::~JsonStructSerializer() +{ + if(restoreState) + owner.current = parentNode; +} + +JsonStructSerializer::JsonStructSerializer(JsonSerializeFormat& owner_, const std::string& fieldName): + restoreState(true), + owner(owner_), + parentNode(owner.current), + thisNode(&(parentNode->operator[](fieldName))) +{ + owner.current = thisNode; +} + +JsonStructSerializer::JsonStructSerializer(JsonStructSerializer & parent, const std::string & fieldName): + restoreState(true), + owner(parent.owner), + parentNode(parent.thisNode), + thisNode(&(parentNode->operator[](fieldName))) +{ + owner.current = thisNode; +} + +JsonStructSerializer JsonStructSerializer::enterStruct(const std::string & fieldName) +{ + return JsonStructSerializer(*this, fieldName); +} + +JsonNode& JsonStructSerializer::get() +{ + return *thisNode; +} + +JsonSerializeFormat * JsonStructSerializer::operator->() +{ + return &owner; +} + +JsonSerializeFormat::LIC::LIC(const std::vector & Standard, const TDecoder & Decoder, const TEncoder & Encoder): + standard(Standard), decoder(Decoder), encoder(Encoder) +{ + any.resize(standard.size(), false); + all.resize(standard.size(), false); + none.resize(standard.size(), false); +} + + +//JsonSerializeFormat +JsonSerializeFormat::JsonSerializeFormat(JsonNode & root_, const bool saving_): + saving(saving_), + root(&root_), + current(root) +{ + +} + +JsonStructSerializer JsonSerializeFormat::enterStruct(const std::string & fieldName) +{ + JsonStructSerializer res(*this, fieldName); + + return res; +} diff --git a/lib/serializer/JsonSerializeFormat.h b/lib/serializer/JsonSerializeFormat.h new file mode 100644 index 000000000..70ab8abcb --- /dev/null +++ b/lib/serializer/JsonSerializeFormat.h @@ -0,0 +1,148 @@ +/* + * JsonSerializeFormat.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +class JsonNode; + +class JsonSerializeFormat; + +class JsonStructSerializer: public boost::noncopyable +{ +public: + JsonStructSerializer(JsonStructSerializer && other); + virtual ~JsonStructSerializer(); + + JsonStructSerializer enterStruct(const std::string & fieldName); + + JsonNode & get(); + + JsonSerializeFormat * operator->(); +private: + JsonStructSerializer(JsonSerializeFormat & owner_, const std::string & fieldName); + JsonStructSerializer(JsonStructSerializer & parent, const std::string & fieldName); + + bool restoreState; + JsonSerializeFormat & owner; + JsonNode * parentNode; + JsonNode * thisNode; + friend class JsonSerializeFormat; +}; + +class JsonSerializeFormat: public boost::noncopyable +{ +public: + ///user-provided callback to resolve string identifier + ///returns resolved identifier or -1 on error + typedef std::function TDecoder; + + ///user-provided callback to get string identifier + ///may assume that object index is valid + typedef std::function TEncoder; + + struct LIC + { + LIC(const std::vector & Standard, const TDecoder & Decoder, const TEncoder & Encoder); + + const std::vector & standard; + const TDecoder & decoder; + const TEncoder & encoder; + std::vector all, any, none; + }; + + const bool saving; + + JsonSerializeFormat() = delete; + virtual ~JsonSerializeFormat() = default; + + JsonNode & getRoot() + { + return * root; + }; + + JsonNode & getCurrent() + { + return * current; + }; + + JsonStructSerializer enterStruct(const std::string & fieldName); + + template + void serializeBool(const std::string & fieldName, const T trueValue, const T falseValue, T & value) + { + bool temp = (value == trueValue); + serializeBool(fieldName, temp); + if(!saving) + value = temp ? trueValue : falseValue; + } + + virtual void serializeBool(const std::string & fieldName, bool & value) = 0; + + virtual void serializeEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) = 0; + + /** @brief Restrictive ("anyOf") simple serialization of Logical identifier condition, simple deserialization (allOf=anyOf) + * + * @param fieldName + * @param decoder resolve callback, should report errors itself and do not throw + * @param encoder encode callback, should report errors itself and do not throw + * @param value target value, must be resized properly + * + */ + virtual void serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector & standard, std::vector & value) = 0; + + /** @brief Complete serialization of Logical identifier condition + */ + virtual void serializeLIC(const std::string & fieldName, LIC & value) = 0; + + template + void serializeNumericEnum(const std::string & fieldName, const std::vector & enumMap, const T defaultValue, T & value) + { + si32 temp = value; + serializeIntEnum(fieldName, enumMap, defaultValue, temp); + if(!saving) + value = temp; + }; + + template + void serializeNumeric(const std::string & fieldName, T & value) + { + double temp = value; + serializeFloat(fieldName, temp); + if(!saving) + value = temp; + }; + + virtual void serializeString(const std::string & fieldName, std::string & value) = 0; + + template + void serializeId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const T & defaultValue, T & value) + { + const si32 tempDefault = defaultValue.num; + si32 tempValue = value.num; + serializeIntId(fieldName, decoder, encoder, tempDefault, tempValue); + if(!saving) + value = T(tempValue); + } + +protected: + JsonNode * root; + JsonNode * current; + + JsonSerializeFormat(JsonNode & root_, const bool saving_); + + virtual void serializeFloat(const std::string & fieldName, double & value) = 0; + + virtual void serializeIntEnum(const std::string & fieldName, const std::vector & enumMap, const si32 defaultValue, si32 & value) = 0; + + virtual void serializeIntId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const si32 defaultValue, si32 & value) = 0; +private: + friend class JsonStructSerializer; +}; + diff --git a/lib/serializer/JsonSerializer.cpp b/lib/serializer/JsonSerializer.cpp new file mode 100644 index 000000000..22f4215e2 --- /dev/null +++ b/lib/serializer/JsonSerializer.cpp @@ -0,0 +1,94 @@ +/* + * JsonSerializer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + + +#include "StdInc.h" +#include "JsonSerializer.h" + +#include "../JsonNode.h" + +JsonSerializer::JsonSerializer(JsonNode & root_): + JsonSerializeFormat(root_, true) +{ + +} + +void JsonSerializer::serializeBool(const std::string & fieldName, bool & value) +{ + if(value) + current->operator[](fieldName).Bool() = true; +} + +void JsonSerializer::serializeEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) +{ + current->operator[](fieldName).String() = value ? trueValue : falseValue; +} + +void JsonSerializer::serializeFloat(const std::string & fieldName, double & value) +{ + if(value != 0) + current->operator[](fieldName).Float() = value; +} + +void JsonSerializer::serializeIntEnum(const std::string & fieldName, const std::vector & enumMap, const si32 defaultValue, si32 & value) +{ + if(defaultValue != value) + current->operator[](fieldName).String() = enumMap.at(value); +} + +void JsonSerializer::serializeIntId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const si32 defaultValue, si32 & value) +{ + if(defaultValue != value) + { + std::string identifier = encoder(value); + serializeString(fieldName, identifier); + } +} + +void JsonSerializer::serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector & standard, std::vector & value) +{ + assert(standard.size() == value.size()); + if(standard == value) + return; + + writeLICPart(fieldName, "anyOf", encoder, value); +} + +void JsonSerializer::serializeLIC(const std::string & fieldName, LIC & value) +{ + if(value.any != value.standard) + { + writeLICPart(fieldName, "anyOf", value.encoder, value.any); + } + + writeLICPart(fieldName, "allOf", value.encoder, value.all); + writeLICPart(fieldName, "noneOf", value.encoder, value.none); +} + +void JsonSerializer::serializeString(const std::string & fieldName, std::string & value) +{ + if(value != "") + current->operator[](fieldName).String() = value; +} + +void JsonSerializer::writeLICPart(const std::string& fieldName, const std::string& partName, const TEncoder& encoder, const std::vector & data) +{ + auto & target = current->operator[](fieldName)[partName].Vector(); + for(si32 idx = 0; idx < data.size(); idx ++) + { + if(data[idx]) + { + JsonNode val(JsonNode::DATA_STRING); + val.String() = encoder(idx); + target.push_back(std::move(val)); + } + } +} + diff --git a/lib/serializer/JsonSerializer.h b/lib/serializer/JsonSerializer.h new file mode 100644 index 000000000..1fae5fb67 --- /dev/null +++ b/lib/serializer/JsonSerializer.h @@ -0,0 +1,35 @@ +/* + * JsonSerializer.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "JsonSerializeFormat.h" + +class JsonNode; + +class JsonSerializer: public JsonSerializeFormat +{ +public: + JsonSerializer(JsonNode & root_); + + void serializeBool(const std::string & fieldName, bool & value) override; + void serializeEnum(const std::string & fieldName, const std::string & trueValue, const std::string & falseValue, bool & value) override; + void serializeLIC(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const std::vector & standard, std::vector & value) override; + void serializeLIC(const std::string & fieldName, LIC & value) override; + void serializeString(const std::string & fieldName, std::string & value) override; + +protected: + void serializeFloat(const std::string & fieldName, double & value) override; + void serializeIntEnum(const std::string & fieldName, const std::vector & enumMap, const si32 defaultValue, si32 & value) override; + void serializeIntId(const std::string & fieldName, const TDecoder & decoder, const TEncoder & encoder, const si32 defaultValue, si32 & value) override; + +private: + void writeLICPart(const std::string & fieldName, const std::string & partName, const TEncoder & encoder, const std::vector & data); +}; diff --git a/lib/spells/CSpellHandler.cpp b/lib/spells/CSpellHandler.cpp index dbfc28262..e507ba2e0 100644 --- a/lib/spells/CSpellHandler.cpp +++ b/lib/spells/CSpellHandler.cpp @@ -799,11 +799,12 @@ const std::string CSpellHandler::getTypeName() const return "spell"; } -CSpell * CSpellHandler::loadFromJson(const JsonNode & json) +CSpell * CSpellHandler::loadFromJson(const JsonNode & json, const std::string & identifier) { using namespace SpellConfig; CSpell * spell = new CSpell(); + spell->identifier = identifier; const auto type = json["type"].String(); @@ -1077,6 +1078,27 @@ CSpellHandler::~CSpellHandler() std::vector CSpellHandler::getDefaultAllowed() const { std::vector allowedSpells; - allowedSpells.resize(GameConstants::SPELLS_QUANTITY, true); + allowedSpells.reserve(objects.size()); + + for(const CSpell * s : objects) + { + allowedSpells.push_back( !(s->isSpecialSpell() || s->isCreatureAbility())); + } + return allowedSpells; } + +si32 CSpellHandler::decodeSpell(const std::string& identifier) +{ + auto rawId = VLC->modh->identifiers.getIdentifier("core", "spell", identifier); + if(rawId) + return rawId.get(); + else + return -1; +} + +std::string CSpellHandler::encodeSpell(const si32 index) +{ + return VLC->spellh->objects[index]->identifier; +} + diff --git a/lib/spells/CSpellHandler.h b/lib/spells/CSpellHandler.h index d0f984635..480d3ea59 100644 --- a/lib/spells/CSpellHandler.h +++ b/lib/spells/CSpellHandler.h @@ -172,7 +172,7 @@ public: }; SpellID id; - std::string identifier; //??? + std::string identifier; std::string name; si32 level; @@ -360,11 +360,17 @@ public: const std::string getTypeName() const override; + ///json serialization helper + static si32 decodeSpell(const std::string & identifier); + + ///json serialization helper + static std::string encodeSpell(const si32 index); + template void serialize(Handler &h, const int version) { h & objects ; } protected: - CSpell * loadFromJson(const JsonNode & json) override; + CSpell * loadFromJson(const JsonNode & json, const std::string & identifier) override; }; diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 70ef021f4..43827ecad 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -8,6 +8,8 @@ set(test_SRCS StdInc.cpp CVcmiTestConfig.cpp CMapEditManagerTest.cpp + MapComparer.cpp + CMapFormatTest.cpp ) add_executable(vcmitest ${test_SRCS}) diff --git a/test/CMapEditManagerTest.cpp b/test/CMapEditManagerTest.cpp index 394bdaded..b1e2fd735 100644 --- a/test/CMapEditManagerTest.cpp +++ b/test/CMapEditManagerTest.cpp @@ -12,11 +12,9 @@ #include "StdInc.h" #include +#include "../lib/filesystem/ResourceID.h" #include "../lib/mapping/CMapService.h" #include "../lib/mapping/CMap.h" -#include "../lib/filesystem/Filesystem.h" -#include "../lib/filesystem/CFilesystemLoader.h" -#include "../lib/filesystem/AdapterLoaders.h" #include "../lib/JsonNode.h" #include "../lib/mapping/CMapEditManager.h" #include "../lib/int3.h" @@ -25,11 +23,12 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type) { + logGlobal->infoStream() << "CMapEditManager_DrawTerrain_Type start"; try { auto map = make_unique(); - map->width = 100; - map->height = 100; + map->width = 52; + map->height = 52; map->initTerrain(); auto editManager = map->getEditManager(); editManager->clearTerrain(); @@ -81,6 +80,9 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type) editManager->drawTerrain(ETerrainType::ROCK); BOOST_CHECK(map->getTile(int3(5, 6, 1)).terType == ETerrainType::ROCK || map->getTile(int3(7, 8, 1)).terType == ETerrainType::ROCK); + //todo: add checks here and enable, also use smaller size + #if 0 + // next check auto map2 = make_unique(); map2->width = 128; @@ -96,29 +98,23 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_Type) int3(98, 46, 1), int3(99, 46, 1)}); editManager2->getTerrainSelection().setSelection(selection); editManager2->drawTerrain(ETerrainType::ROCK); + #endif // 0 } catch(const std::exception & e) { - logGlobal-> errorStream() << e.what(); + logGlobal->errorStream() << "CMapEditManager_DrawTerrain_Type crash" << "\n" << e.what(); + throw; } + logGlobal->infoStream() << "CMapEditManager_DrawTerrain_Type finish"; } BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View) { + logGlobal->infoStream() << "CMapEditManager_DrawTerrain_View start"; try { // Load maps and json config - - #if defined(__MINGW32__) - const std::string TEST_DATA_DIR = "test/"; - #else - const std::string TEST_DATA_DIR = "."; - #endif // defined - - - auto loader = new CFilesystemLoader("test/", TEST_DATA_DIR); - dynamic_cast(CResourceHandler::get())->addLoader(loader, false); const auto originalMap = CMapService::loadMap("test/TerrainViewTest"); auto map = CMapService::loadMap("test/TerrainViewTest"); logGlobal->infoStream() << "Loaded test map successfully."; @@ -149,8 +145,10 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View) const auto & posVector = posNode.Vector(); if(posVector.size() != 3) throw std::runtime_error("A position should consist of three values x,y,z. Continue with next position."); int3 pos(posVector[0].Float(), posVector[1].Float(), posVector[2].Float()); - logGlobal->infoStream() << boost::format("Test pattern '%s' on position x '%d', y '%d', z '%d'.") % patternStr % pos.x % pos.y % pos.z; +#if 0 + logGlobal->traceStream() << boost::format("Test pattern '%s' on position x '%d', y '%d', z '%d'.") % patternStr % pos.x % pos.y % pos.z; CTerrainViewPatternUtils::printDebuggingInfoAboutTile(map.get(), pos); +#endif // 0 const auto & originalTile = originalMap->getTile(pos); editManager->getTerrainSelection().selectRange(MapRect(pos, 1, 1)); editManager->drawTerrain(originalTile.terType, &gen); @@ -171,6 +169,8 @@ BOOST_AUTO_TEST_CASE(CMapEditManager_DrawTerrain_View) } catch(const std::exception & e) { - logGlobal-> errorStream() << e.what(); + logGlobal->infoStream() << "CMapEditManager_DrawTerrain_View crash"<< "\n" << e.what(); + throw; } + logGlobal->infoStream() << "CMapEditManager_DrawTerrain_View finish"; } diff --git a/test/CMapFormatTest.cpp b/test/CMapFormatTest.cpp new file mode 100644 index 000000000..9e4ace40d --- /dev/null +++ b/test/CMapFormatTest.cpp @@ -0,0 +1,97 @@ + +/* + * CMapFormatTest.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include + +#include "../lib/filesystem/CMemoryBuffer.h" + +#include "../lib/mapping/CMap.h" +#include "../lib/rmg/CMapGenOptions.h" +#include "../lib/rmg/CMapGenerator.h" +#include "../lib/mapping/MapFormatJson.h" + +#include "../lib/VCMIDirs.h" + +#include "MapComparer.h" + + +static const int TEST_RANDOM_SEED = 1337; + +static std::unique_ptr initialMap; + +class CMapTestFixture +{ +public: + CMapTestFixture() + { + CMapGenOptions opt; + + opt.setHeight(CMapHeader::MAP_SIZE_MIDDLE); + opt.setWidth(CMapHeader::MAP_SIZE_MIDDLE); + opt.setHasTwoLevels(true); + opt.setPlayerCount(4); + + opt.setPlayerTypeForStandardPlayer(PlayerColor(0), EPlayerType::HUMAN); + opt.setPlayerTypeForStandardPlayer(PlayerColor(1), EPlayerType::AI); + opt.setPlayerTypeForStandardPlayer(PlayerColor(2), EPlayerType::AI); + opt.setPlayerTypeForStandardPlayer(PlayerColor(3), EPlayerType::AI); + + CMapGenerator gen; + + initialMap = gen.generate(&opt, TEST_RANDOM_SEED); + initialMap->name = "Test"; + }; + ~CMapTestFixture() + { + initialMap.reset(); + }; +}; + +BOOST_GLOBAL_FIXTURE(CMapTestFixture); + +BOOST_AUTO_TEST_CASE(CMapFormatVCMI_Simple) +{ + logGlobal->info("CMapFormatVCMI_Simple start"); + BOOST_TEST_CHECKPOINT("CMapFormatVCMI_Simple start"); + CMemoryBuffer serializeBuffer; + { + CMapSaverJson saver(&serializeBuffer); + saver.saveMap(initialMap); + } + BOOST_TEST_CHECKPOINT("CMapFormatVCMI_Simple serialized"); + #if 1 + { + auto path = VCMIDirs::get().userDataPath()/"test.vmap"; + boost::filesystem::remove(path); + boost::filesystem::ofstream tmp(path, boost::filesystem::ofstream::binary); + + tmp.write((const char *)serializeBuffer.getBuffer().data(),serializeBuffer.getSize()); + tmp.flush(); + tmp.close(); + + logGlobal->infoStream() << "Test map has been saved to " << path; + } + BOOST_TEST_CHECKPOINT("CMapFormatVCMI_Simple saved"); + + #endif // 1 + + serializeBuffer.seek(0); + { + CMapLoaderJson loader(&serializeBuffer); + std::unique_ptr serialized = loader.loadMap(); + + MapComparer c; + c(serialized, initialMap); + } + + logGlobal->info("CMapFormatVCMI_Simple finish"); +} diff --git a/test/CMemoryBufferTest.cpp b/test/CMemoryBufferTest.cpp new file mode 100644 index 000000000..a4935be51 --- /dev/null +++ b/test/CMemoryBufferTest.cpp @@ -0,0 +1,52 @@ + +/* + * CMemoryBufferTest.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include + +#include "../lib/filesystem/CMemoryBuffer.h" + + +struct CMemoryBufferFixture +{ + CMemoryBuffer subject; +}; + +BOOST_FIXTURE_TEST_CASE(CMemoryBuffer_Empty, CMemoryBufferFixture) +{ + BOOST_REQUIRE_EQUAL(0, subject.tell()); + BOOST_REQUIRE_EQUAL(0, subject.getSize()); + + si32 dummy = 1337; + + auto ret = subject.read((ui8 *)&dummy, sizeof(si32)); + + BOOST_CHECK_EQUAL(0, ret); + BOOST_CHECK_EQUAL(1337, dummy); + BOOST_CHECK_EQUAL(0, subject.tell()); +} + +BOOST_FIXTURE_TEST_CASE(CMemoryBuffer_Write, CMemoryBufferFixture) +{ + const si32 initial = 1337; + + subject.write((const ui8 *)&initial, sizeof(si32)); + + BOOST_CHECK_EQUAL(4, subject.tell()); + subject.seek(0); + BOOST_CHECK_EQUAL(0, subject.tell()); + + si32 current = 0; + auto ret = subject.read((ui8 *)¤t, sizeof(si32)); + BOOST_CHECK_EQUAL(sizeof(si32), ret); + BOOST_CHECK_EQUAL(initial, current); + BOOST_CHECK_EQUAL(4, subject.tell()); +} diff --git a/test/CVcmiTestConfig.cpp b/test/CVcmiTestConfig.cpp index cbe0deced..96a36ddfd 100644 --- a/test/CVcmiTestConfig.cpp +++ b/test/CVcmiTestConfig.cpp @@ -18,6 +18,9 @@ #include "../lib/VCMI_Lib.h" #include "../lib/logging/CLogger.h" #include "../lib/CConfigHandler.h" +#include "../lib/filesystem/Filesystem.h" +#include "../lib/filesystem/CFilesystemLoader.h" +#include "../lib/filesystem/AdapterLoaders.h" CVcmiTestConfig::CVcmiTestConfig() { @@ -29,6 +32,11 @@ CVcmiTestConfig::CVcmiTestConfig() logConfig.configure(); loadDLLClasses(); logGlobal->infoStream() << "Initialized global test setup."; + + const std::string TEST_DATA_DIR = "test/"; + + auto loader = new CFilesystemLoader("test/", TEST_DATA_DIR); + dynamic_cast(CResourceHandler::get())->addLoader(loader, false); } CVcmiTestConfig::~CVcmiTestConfig() diff --git a/test/MapComparer.cpp b/test/MapComparer.cpp new file mode 100644 index 000000000..e187d06b8 --- /dev/null +++ b/test/MapComparer.cpp @@ -0,0 +1,274 @@ +/* + * MapComparer.cpp, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ +#include "StdInc.h" + +#include + +#include "MapComparer.h" + +#define VCMI_CHECK_FIELD_EQUAL_P(field) BOOST_CHECK_EQUAL(actual->field, expected->field) + +#define VCMI_CHECK_FIELD_EQUAL(field) BOOST_CHECK_EQUAL(actual.field, expected.field) + +#define VCMI_REQUIRE_FIELD_EQUAL_P(field) BOOST_REQUIRE_EQUAL(actual->field, expected->field) +#define VCMI_REQUIRE_FIELD_EQUAL(field) BOOST_REQUIRE_EQUAL(actual.field, expected.field) + +template +void checkEqual(const T & actual, const T & expected) +{ + BOOST_CHECK_EQUAL(actual, expected) ; +} + +void checkEqual(const std::vector & actual, const std::vector & expected) +{ + BOOST_CHECK_EQUAL(actual.size(), expected.size()); + + for(auto actualIt = actual.begin(), expectedIt = expected.begin(); actualIt != actual.end() && expectedIt != expected.end(); actualIt++, expectedIt++) + { + checkEqual(*actualIt, *expectedIt); + } +} + +template +void checkEqual(const std::vector & actual, const std::vector & expected) +{ + BOOST_CHECK_EQUAL(actual.size(), expected.size()); + + for(auto actualIt = actual.begin(), expectedIt = expected.begin(); actualIt != actual.end() && expectedIt != expected.end(); actualIt++, expectedIt++) + { + checkEqual(*actualIt, *expectedIt); + } +} + +template +void checkEqual(const std::set & actual, const std::set & expected) +{ + BOOST_CHECK_EQUAL(actual.size(), expected.size()); + + for(auto elem : expected) + { + if(!vstd::contains(actual, elem)) + BOOST_ERROR("Required element not found "+boost::to_string(elem)); + } +} + +void checkEqual(const SHeroName & actual, const SHeroName & expected) +{ + VCMI_CHECK_FIELD_EQUAL(heroId); + VCMI_CHECK_FIELD_EQUAL(heroName); +} + +void checkEqual(const PlayerInfo & actual, const PlayerInfo & expected) +{ + VCMI_CHECK_FIELD_EQUAL(canHumanPlay); + VCMI_CHECK_FIELD_EQUAL(canComputerPlay); + VCMI_CHECK_FIELD_EQUAL(aiTactic); + + checkEqual(actual.allowedFactions, expected.allowedFactions); + + VCMI_CHECK_FIELD_EQUAL(isFactionRandom); + VCMI_CHECK_FIELD_EQUAL(mainCustomHeroPortrait); + VCMI_CHECK_FIELD_EQUAL(mainCustomHeroName); + + VCMI_CHECK_FIELD_EQUAL(mainCustomHeroId); + + checkEqual(actual.heroesNames, expected.heroesNames); + + VCMI_CHECK_FIELD_EQUAL(hasMainTown); + VCMI_CHECK_FIELD_EQUAL(generateHeroAtMainTown); + VCMI_CHECK_FIELD_EQUAL(posOfMainTown); + VCMI_CHECK_FIELD_EQUAL(team); + VCMI_CHECK_FIELD_EQUAL(hasRandomHero); +} + +void checkEqual(const EventExpression & actual, const EventExpression & expected) +{ + //todo: checkEventExpression +} + +void checkEqual(const TriggeredEvent & actual, const TriggeredEvent & expected) +{ + VCMI_CHECK_FIELD_EQUAL(identifier); + VCMI_CHECK_FIELD_EQUAL(description); + VCMI_CHECK_FIELD_EQUAL(onFulfill); + VCMI_CHECK_FIELD_EQUAL(effect.type); + VCMI_CHECK_FIELD_EQUAL(effect.toOtherMessage); + + checkEqual(actual.trigger, expected.trigger); +} + +void checkEqual(const Rumor & actual, const Rumor & expected) +{ + VCMI_CHECK_FIELD_EQUAL(name); + VCMI_CHECK_FIELD_EQUAL(text); +} + +void checkEqual(const DisposedHero & actual, const DisposedHero & expected) +{ + VCMI_CHECK_FIELD_EQUAL(heroId); + VCMI_CHECK_FIELD_EQUAL(portrait); + VCMI_CHECK_FIELD_EQUAL(name); + VCMI_CHECK_FIELD_EQUAL(players); + +} + +void checkEqual(const ObjectTemplate & actual, const ObjectTemplate & expected) +{ + VCMI_CHECK_FIELD_EQUAL(id); + VCMI_CHECK_FIELD_EQUAL(subid); + VCMI_CHECK_FIELD_EQUAL(printPriority); + VCMI_CHECK_FIELD_EQUAL(animationFile); + //VCMI_CHECK_FIELD_EQUAL(stringID); +} + +void checkEqual(const TerrainTile & actual, const TerrainTile & expected) +{ + //fatal fail here on any error + VCMI_REQUIRE_FIELD_EQUAL(terType); + VCMI_REQUIRE_FIELD_EQUAL(terView); + VCMI_REQUIRE_FIELD_EQUAL(riverType); + VCMI_REQUIRE_FIELD_EQUAL(riverDir); + VCMI_REQUIRE_FIELD_EQUAL(roadType); + VCMI_REQUIRE_FIELD_EQUAL(roadDir); + VCMI_REQUIRE_FIELD_EQUAL(extTileFlags); + + BOOST_REQUIRE_EQUAL(actual.blockingObjects.size(), expected.blockingObjects.size()); + BOOST_REQUIRE_EQUAL(actual.visitableObjects.size(), expected.visitableObjects.size()); + + VCMI_REQUIRE_FIELD_EQUAL(visitable); + VCMI_REQUIRE_FIELD_EQUAL(blocked); + +} + +void MapComparer::compareHeader() +{ + //map size parameters are vital for further checks + VCMI_REQUIRE_FIELD_EQUAL_P(height); + VCMI_REQUIRE_FIELD_EQUAL_P(width); + VCMI_REQUIRE_FIELD_EQUAL_P(twoLevel); + + VCMI_CHECK_FIELD_EQUAL_P(name); + VCMI_CHECK_FIELD_EQUAL_P(description); + VCMI_CHECK_FIELD_EQUAL_P(difficulty); + VCMI_CHECK_FIELD_EQUAL_P(levelLimit); + + VCMI_CHECK_FIELD_EQUAL_P(victoryMessage); + VCMI_CHECK_FIELD_EQUAL_P(defeatMessage); + VCMI_CHECK_FIELD_EQUAL_P(victoryIconIndex); + VCMI_CHECK_FIELD_EQUAL_P(defeatIconIndex); + + VCMI_CHECK_FIELD_EQUAL_P(howManyTeams); + + checkEqual(actual->players, expected->players); + + checkEqual(actual->allowedHeroes, expected->allowedHeroes); + + std::vector actualEvents = actual->triggeredEvents; + std::vector expectedEvents = expected->triggeredEvents; + + auto sortByIdentifier = [](const TriggeredEvent & lhs, const TriggeredEvent & rhs) -> bool + { + return lhs.identifier < rhs.identifier; + }; + boost::sort (actualEvents, sortByIdentifier); + boost::sort (expectedEvents, sortByIdentifier); + + checkEqual(actualEvents, expectedEvents); +} + +void MapComparer::compareOptions() +{ + checkEqual(actual->rumors, expected->rumors); + checkEqual(actual->disposedHeroes, expected->disposedHeroes); + //todo: compareOptions predefinedHeroes + + checkEqual(actual->allowedAbilities, expected->allowedAbilities); + checkEqual(actual->allowedArtifact, expected->allowedArtifact); + checkEqual(actual->allowedSpell, expected->allowedSpell); + //checkEqual(actual->allowedAbilities, expected->allowedAbilities); + + //todo: compareOptions events +} + +void MapComparer::compareObject(const CGObjectInstance * actual, const CGObjectInstance * expected) +{ + BOOST_CHECK_EQUAL(actual->instanceName, expected->instanceName); + BOOST_CHECK_EQUAL(typeid(actual).name(), typeid(expected).name());//todo: remove and use just comparison + + std::string actualFullID = boost::to_string(boost::format("%s(%d)|%s(%d) %d") % actual->typeName % actual->ID % actual->subTypeName % actual->subID % actual->tempOwner); + std::string expectedFullID = boost::to_string(boost::format("%s(%d)|%s(%d) %d") % expected->typeName % expected->ID % expected->subTypeName % expected->subID % expected->tempOwner); + + BOOST_CHECK_EQUAL(actualFullID, expectedFullID); + + VCMI_CHECK_FIELD_EQUAL_P(pos); + checkEqual(actual->appearance, expected->appearance); +} + +void MapComparer::compareObjects() +{ + BOOST_CHECK_EQUAL(actual->objects.size(), expected->objects.size()); + + for(size_t idx = 0; idx < expected->objects.size(); idx++) + { + auto expectedObject = expected->objects[idx]; + + BOOST_REQUIRE_EQUAL(idx, expectedObject->id.getNum()); + + { + auto it = expected->instanceNames.find(expectedObject->instanceName); + + BOOST_REQUIRE(it != expected->instanceNames.end()); + } + + { + auto it = actual->instanceNames.find(expectedObject->instanceName); + + BOOST_REQUIRE(it != expected->instanceNames.end()); + + auto actualObject = it->second; + + compareObject(actualObject, expectedObject); + } + } +} + +void MapComparer::compareTerrain() +{ + //assume map dimensions check passed + //todo: separate check for underground + + for(int x = 0; x < expected->width; x++) + for(int y = 0; y < expected->height; y++) + { + int3 coord(x,y,0); + BOOST_TEST_CHECKPOINT(coord); + + checkEqual(actual->getTile(coord), expected->getTile(coord)); + } +} + +void MapComparer::compare() +{ + BOOST_REQUIRE_NE((void *) actual, (void *) expected); //should not point to the same object + BOOST_REQUIRE_MESSAGE(actual != nullptr, "Actual map is not defined"); + BOOST_REQUIRE_MESSAGE(expected != nullptr, "Expected map is not defined"); + + compareHeader(); + compareOptions(); + compareObjects(); + compareTerrain(); +} + +void MapComparer::operator() (const std::unique_ptr& actual, const std::unique_ptr& expected) +{ + this->actual = actual.get(); + this->expected = expected.get(); + compare(); +} diff --git a/test/MapComparer.h b/test/MapComparer.h new file mode 100644 index 000000000..a8e4b882c --- /dev/null +++ b/test/MapComparer.h @@ -0,0 +1,31 @@ +/* + * MapComparer.h, part of VCMI engine + * + * Authors: listed in file AUTHORS in main folder + * + * License: GNU General Public License v2.0 or later + * Full text of license available in license.txt file, in main folder + * + */ + +#pragma once + +#include "../lib/mapping/CMap.h" + +struct MapComparer +{ + const CMap * actual; + const CMap * expected; + + void compareHeader(); + void compareOptions(); + void compareObject(const CGObjectInstance * actual, const CGObjectInstance * expected); + void compareObjects(); + void compareTerrain(); + + void compare(); + + void operator() (const std::unique_ptr& actual, const std::unique_ptr& expected); +}; + + diff --git a/test/Test.cbp b/test/Test.cbp index 67ace151e..d9fdb6e93 100644 --- a/test/Test.cbp +++ b/test/Test.cbp @@ -10,6 +10,7 @@