From 43ba3d30eaa0e9af80cb4dfa06b0d0d42bbafbab Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 6 Apr 2014 23:14:26 +0300 Subject: [PATCH 01/33] Breaking things - first commit towards configurable object(s). - New files: lib/CObjectWithReward.h/cpp - Classes that will be replaced by configurable object are now in this fil Status: far from functional, currently at "it compiles" point, some essential pieces are still missing. --- AI/VCAI/VCAI.cpp | 1 + client/CMusicHandler.cpp | 2 +- lib/CDefObjInfoHandler.cpp | 1 - lib/CGameState.h | 3 +- lib/CMakeLists.txt | 4 +- lib/CObjectHandler.cpp | 1162 +---------------------------- lib/CObjectHandler.h | 124 +-- lib/CObjectWithReward.cpp | 994 ++++++++++++++++++++++++ lib/CObjectWithReward.h | 320 ++++++++ lib/NetPacks.h | 5 +- lib/NetPacksLib.cpp | 7 +- lib/mapping/MapFormatH3M.cpp | 1 + lib/registerTypes/RegisterTypes.h | 14 +- 13 files changed, 1341 insertions(+), 1297 deletions(-) create mode 100644 lib/CObjectWithReward.cpp create mode 100644 lib/CObjectWithReward.h diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 19907235c..5b30bc818 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -3,6 +3,7 @@ #include "Goals.h" #include "../../lib/UnlockGuard.h" #include "../../lib/CObjectHandler.h" +#include "../../lib/CObjectWithReward.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" diff --git a/client/CMusicHandler.cpp b/client/CMusicHandler.cpp index e35891065..83f13bf6e 100644 --- a/client/CMusicHandler.cpp +++ b/client/CMusicHandler.cpp @@ -2,9 +2,9 @@ #include #include "CMusicHandler.h" +#include "CGameInfo.h" #include "../lib/CCreatureHandler.h" #include "../lib/CSpellHandler.h" -#include "../client/CGameInfo.h" #include "../lib/JsonNode.h" #include "../lib/GameConstants.h" #include "../lib/filesystem/Filesystem.h" diff --git a/lib/CDefObjInfoHandler.cpp b/lib/CDefObjInfoHandler.cpp index 3ee27eab8..b31d18a54 100644 --- a/lib/CDefObjInfoHandler.cpp +++ b/lib/CDefObjInfoHandler.cpp @@ -3,7 +3,6 @@ #include "filesystem/Filesystem.h" #include "filesystem/CBinaryReader.h" -//#include "../client/CGameInfo.h" #include "../lib/VCMI_Lib.h" #include "GameConstants.h" #include "StringConstants.h" diff --git a/lib/CGameState.h b/lib/CGameState.h index d93156d9d..98ca9e659 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -168,6 +168,7 @@ public: ObjectInstanceID currentSelection; //id of hero/town, 0xffffffff if none TeamID team; TResources resources; + std::set visitedObjects; // as a std::set, since most accesses here will be from visited status checks std::vector > heroes; std::vector > towns; std::vector > availableHeroes; //heroes available in taverns @@ -184,7 +185,7 @@ public: template void serialize(Handler &h, const int version) { h & color & human & currentSelection & team & resources & status; - h & heroes & towns & availableHeroes & dwellings; + h & heroes & towns & availableHeroes & dwellings & visitedObjects; h & getBonusList(); //FIXME FIXME FIXME h & status & daysWithoutCastle; h & enteredLosingCheatCode & enteredWinningCheatCode; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 902ec4f36..cbf51ca66 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -16,7 +16,7 @@ set(lib_SRCS registerTypes/TypesClientPacks1.cpp registerTypes/TypesClientPacks2.cpp registerTypes/TypesMapObjects1.cpp - registerTypes/TypesMapObjects2.cpp + registerTypes/TypesMapObjects2.cpp registerTypes/TypesPregamePacks.cpp registerTypes/TypesServerPacks.cpp @@ -68,6 +68,8 @@ set(lib_SRCS CHeroHandler.cpp CModHandler.cpp CObstacleInstance.cpp + CObjectWithReward.cpp + CObjectWithReward.h CSpellHandler.cpp CThreadHelper.cpp CTownHandler.cpp diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index a3ae8de30..096be0312 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -2739,343 +2739,6 @@ void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResu } } -bool CGVisitableOPH::wasVisited (const CGHeroInstance * h) const -{ - return vstd::contains(visitors, h->id); -} - -void CGVisitableOPH::onHeroVisit( const CGHeroInstance * h ) const -{ - if(!vstd::contains(visitors, h->id)) - { - onNAHeroVisit (h, false); - switch(ID) - { - case Obj::TREE_OF_KNOWLEDGE: - case Obj::ARENA: - case Obj::LIBRARY_OF_ENLIGHTENMENT: - case Obj::SCHOOL_OF_MAGIC: - case Obj::SCHOOL_OF_WAR: - break; - default: - cb->setObjProperty(id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - break; - } - } - else - { - onNAHeroVisit(h, true); - } -} - -void CGVisitableOPH::initObj() -{ - if(ID==Obj::TREE_OF_KNOWLEDGE) - { - switch (cb->gameState()->getRandomGenerator().nextInt(2)) - { - case 1: - treePrice[Res::GOLD] = 2000; - break; - case 2: - treePrice[Res::GEMS] = 10; - break; - default: - break; - } - } -} - -void CGVisitableOPH::treeSelected (const CGHeroInstance * h, ui32 result) const -{ - if(result) //player agreed to give res for exp - { - si64 expToGive = VLC->heroh->reqExp(h->level+1) - VLC->heroh->reqExp(h->level);; - cb->giveResources (h->getOwner(), -treePrice); - cb->changePrimSkill (h, PrimarySkill::EXPERIENCE, expToGive); - cb->setObjProperty (id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - } -} -void CGVisitableOPH::onNAHeroVisit (const CGHeroInstance * h, bool alreadyVisited) const -{ - Component::EComponentType c_id = Component::PRIM_SKILL; //most used here - int subid=0, ot=0, sound = 0; - TExpType val=1; - switch(ID) - { - case Obj::ARENA: - sound = soundBase::NOMAD; - ot = 0; - break; - case Obj::MERCENARY_CAMP: - sound = soundBase::NOMAD; - subid=PrimarySkill::ATTACK; - ot=80; - break; - case Obj::MARLETTO_TOWER: - sound = soundBase::NOMAD; - subid=PrimarySkill::DEFENSE; - ot=39; - break; - case Obj::STAR_AXIS: - sound = soundBase::gazebo; - subid=PrimarySkill::SPELL_POWER; - ot=100; - break; - case Obj::GARDEN_OF_REVELATION: - sound = soundBase::GETPROTECTION; - subid=PrimarySkill::KNOWLEDGE; - ot=59; - break; - case Obj::LEARNING_STONE: - sound = soundBase::gazebo; - c_id=Component::EXPERIENCE; - ot=143; - val=1000; - break; - case Obj::TREE_OF_KNOWLEDGE: - sound = soundBase::gazebo; - c_id = Component::EXPERIENCE; - subid = 1; - ot = 147; - val = 1; - break; - case Obj::LIBRARY_OF_ENLIGHTENMENT: - sound = soundBase::gazebo; - ot = 66; - break; - case Obj::SCHOOL_OF_MAGIC: - sound = soundBase::faerie; - ot = 71; - break; - case Obj::SCHOOL_OF_WAR: - c_id=Component::PRIM_SKILL; - sound = soundBase::MILITARY; - ot = 158; - break; - } - if (!alreadyVisited) - { - switch (ID) - { - case Obj::ARENA: - { - BlockingDialog sd(false,true); - sd.soundID = sound; - sd.text.addTxt(MetaString::ADVOB_TXT,ot); - sd.components.push_back(Component(c_id, PrimarySkill::ATTACK, 2, 0)); - sd.components.push_back(Component(c_id, PrimarySkill::DEFENSE, 2, 0)); - sd.player = h->getOwner(); - cb->showBlockingDialog(&sd); - return; - } - case Obj::MERCENARY_CAMP: - case Obj::MARLETTO_TOWER: - case Obj::STAR_AXIS: - case Obj::GARDEN_OF_REVELATION: - { - cb->changePrimSkill (h, static_cast(subid), val); - InfoWindow iw; - iw.soundID = sound; - iw.components.push_back(Component(c_id, subid, val, 0)); - iw.text.addTxt(MetaString::ADVOB_TXT,ot); - iw.player = h->getOwner(); - cb->showInfoDialog(&iw); - break; - } - case Obj::LEARNING_STONE: //give exp - { - val = h->calculateXp(val); - InfoWindow iw; - iw.soundID = sound; - iw.components.push_back (Component(c_id,subid,val,0)); - iw.player = h->getOwner(); - iw.text.addTxt(MetaString::ADVOB_TXT,ot); - cb->showInfoDialog(&iw); - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, val); - break; - } - case Obj::TREE_OF_KNOWLEDGE: - { - val = VLC->heroh->reqExp (h->level + val) - VLC->heroh->reqExp(h->level); - if(!treePrice.nonZero()) - { - cb->setObjProperty (id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - InfoWindow iw; - iw.soundID = sound; - iw.components.push_back (Component(c_id,subid,1,0)); - iw.player = h->getOwner(); - iw.text.addTxt (MetaString::ADVOB_TXT,148); - cb->showInfoDialog (&iw); - cb->changePrimSkill (h, PrimarySkill::EXPERIENCE, val); - break; - } - else - { - if(treePrice[Res::GOLD] > 0) - ot = 149; - else - ot = 151; - - if(!cb->getPlayer(h->tempOwner)->resources.canAfford(treePrice)) //not enough resources - { - ot++; - showInfoDialog(h,ot,sound); - return; - } - - BlockingDialog sd (true, false); - sd.soundID = sound; - sd.player = h->getOwner(); - sd.text.addTxt (MetaString::ADVOB_TXT,ot); - sd.addResourceComponents (treePrice); - cb->showBlockingDialog (&sd); - } - break; - } - case Obj::LIBRARY_OF_ENLIGHTENMENT: - { - int txt_id = 66; - if(h->level < 10 - 2*h->getSecSkillLevel(SecondarySkill::DIPLOMACY)) //not enough level - { - txt_id += 2; - } - else - { - cb->setObjProperty(id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - cb->changePrimSkill (h, PrimarySkill::ATTACK, 2); - cb->changePrimSkill (h, PrimarySkill::DEFENSE, 2); - cb->changePrimSkill (h, PrimarySkill::KNOWLEDGE, 2); - cb->changePrimSkill (h, PrimarySkill::SPELL_POWER, 2); - } - showInfoDialog(h,txt_id,sound); - break; - } - case Obj::SCHOOL_OF_MAGIC: - case Obj::SCHOOL_OF_WAR: - { - int skill = (ID==Obj::SCHOOL_OF_MAGIC ? 2 : 0); - if (cb->getResource (h->getOwner(), Res::GOLD) < 1000) //not enough resources - { - showInfoDialog (h->getOwner(), ot+2, sound); - } - else - { - BlockingDialog sd(true,true); - sd.soundID = sound; - sd.player = h->getOwner(); - sd.text.addTxt(MetaString::ADVOB_TXT,ot); - sd.components.push_back(Component(c_id, skill, +1, 0)); - sd.components.push_back(Component(c_id, skill+1, +1, 0)); - cb->showBlockingDialog(&sd); - } - } - break; - } - } - else - { - ot++; - showInfoDialog (h->getOwner(),ot,sound); - } -} - -const std::string & CGVisitableOPH::getHoverText() const -{ - int pom = -1; - switch(ID) - { - case Obj::ARENA: - pom = -1; - break; - case Obj::MERCENARY_CAMP: - pom = 8; - break; - case Obj::MARLETTO_TOWER: - pom = 7; - break; - case Obj::STAR_AXIS: - pom = 11; - break; - case Obj::GARDEN_OF_REVELATION: - pom = 4; - break; - case Obj::LEARNING_STONE: - pom = 5; - break; - case Obj::TREE_OF_KNOWLEDGE: - pom = 18; - break; - case Obj::LIBRARY_OF_ENLIGHTENMENT: - break; - case Obj::SCHOOL_OF_MAGIC: - pom = 9; - break; - case Obj::SCHOOL_OF_WAR: - pom = 10; - break; - default: - throw std::runtime_error("Wrong CGVisitableOPH object ID!\n"); - } - hoverName = VLC->generaltexth->names[ID]; - if(pom >= 0) - hoverName += ("\n" + VLC->generaltexth->xtrainfo[pom]); - const CGHeroInstance *h = cb->getSelectedHero (cb->getCurrentPlayer()); - if(h) - { - hoverName += "\n\n"; - bool visited = vstd::contains (visitors, h->id); - hoverName += visitedTxt (visited); - } - return hoverName; -} - -void CGVisitableOPH::arenaSelected(const CGHeroInstance * h, int primSkill ) const -{ - cb->setObjProperty(id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - cb->changePrimSkill(h, static_cast(primSkill-1), 2); -} - -void CGVisitableOPH::setPropertyDer( ui8 what, ui32 val ) -{ - if(what == ObjProperty::VISITORS) - visitors.insert(ObjectInstanceID(val)); -} - -void CGVisitableOPH::schoolSelected(const CGHeroInstance * h, ui32 which) const -{ - if(!which) //player refused to pay - return; - - int base = (ID == Obj::SCHOOL_OF_MAGIC ? 2 : 0); - cb->setObjProperty (id, ObjProperty::VISITORS, h->id.getNum()); //add to the visitors - cb->giveResource (h->getOwner(),Res::GOLD,-1000); //take 1000 gold - cb->changePrimSkill (h, static_cast(base + which-1), +1); //give appropriate skill -} - -void CGVisitableOPH::blockingDialogAnswered(const CGHeroInstance *h, ui32 answer) const -{ - switch (ID) - { - case Obj::ARENA: - arenaSelected(h, answer); - break; - - case Obj::TREE_OF_KNOWLEDGE: - treeSelected(h, answer); - break; - - case Obj::SCHOOL_OF_MAGIC: - case Obj::SCHOOL_OF_WAR: - schoolSelected(h, answer); - break; - - default: - assert(0); - break; - } -} - COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN) { ID = index; @@ -3821,102 +3484,6 @@ void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) cb->startBattleI(hero, this); } -void CGVisitableOPW::newTurn() const -{ - if (cb->getDate(Date::DAY_OF_WEEK) == 1) //first day of week = 1 - { - cb->setObjProperty(id, ObjProperty::VISITED, false); - MetaString ms; //set text to "not visited" - ms << std::pair(3,ID) << " " << std::pair(1,353); - cb->setHoverName(this,&ms); - } -} -bool CGVisitableOPW::wasVisited(PlayerColor player) const -{ - return visited; //TODO: other players should see object as unvisited -} - -void CGVisitableOPW::onHeroVisit( const CGHeroInstance * h ) const -{ - int mid=0, sound = 0; - switch (ID) - { - case Obj::MYSTICAL_GARDEN: - sound = soundBase::experience; - mid = 92; - break; - case Obj::WINDMILL: - sound = soundBase::GENIE; - mid = 170; - break; - case Obj::WATER_WHEEL: - sound = soundBase::GENIE; - mid = 164; - break; - default: - assert(0); - } - if (visited) - { - if (ID!=Obj::WINDMILL) - mid++; - else - mid--; - showInfoDialog(h,mid,sound); - } - else - { - Component::EComponentType type = Component::RESOURCE; - Res::ERes sub=Res::WOOD; - int val=0; - - switch (ID) - { - case Obj::MYSTICAL_GARDEN: - if (rand()%2) - { - sub = Res::GEMS; - val = 5; - } - else - { - sub = Res::GOLD; - val = 500; - } - break; - case Obj::WINDMILL: - mid = 170; - sub = static_cast((rand() % 5) + 1); - val = (rand() % 4) + 3; - break; - case Obj::WATER_WHEEL: - mid = 164; - sub = Res::GOLD; - if(cb->getDate(Date::DAY)<8) - val = 500; - else - val = 1000; - } - cb->giveResource(h->tempOwner, sub, val); - InfoWindow iw; - iw.soundID = sound; - iw.player = h->tempOwner; - iw.components.push_back(Component(type,sub,val,0)); - iw.text.addTxt(MetaString::ADVOB_TXT,mid); - cb->showInfoDialog(&iw); - cb->setObjProperty(id, ObjProperty::VISITED, true); - MetaString ms; //set text to "visited" - ms.addTxt(MetaString::OBJ_NAMES,ID); ms << " "; ms.addTxt(MetaString::GENERAL_TXT,352); - cb->setHoverName(this,&ms); - } -} - -void CGVisitableOPW::setPropertyDer( ui8 what, ui32 val ) -{ - if(what == ObjProperty::VISITED) - visited = val; -} - void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const { ObjectInstanceID destinationid; @@ -3926,7 +3493,7 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const if(vstd::contains(objs,Obj::MONOLITH2) && vstd::contains(objs[Obj::MONOLITH2],subID) && objs[Obj::MONOLITH2][subID].size()) destinationid = objs[Obj::MONOLITH2][subID][rand()%objs[Obj::MONOLITH2][subID].size()]; else - logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; + logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; break; case Obj::MONOLITH3://two way monolith - pick any other one case Obj::WHIRLPOOL: //Whirlpool @@ -3963,7 +3530,7 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const } } else - logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; + logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; break; case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level { @@ -3977,7 +3544,7 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const } if(destinationid == ObjectInstanceID()) { - logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( "; + logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( "; return; } if (ID == Obj::WHIRLPOOL) @@ -4160,219 +3727,6 @@ void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) cb->startBattleI(hero, this); } -void CGPickable::initObj() -{ - blockVisit = true; - switch(ID) - { - case Obj::CAMPFIRE: - val2 = cb->gameState()->getRandomGenerator().nextInt(4, 6); - val1 = val2 * 100; - type = cb->gameState()->getRandomGenerator().nextInt(5); // given resource - break; - case Obj::FLOTSAM: - switch(type = cb->gameState()->getRandomGenerator().nextInt(3)) - { - case 0: - val1 = val2 = 0; - break; - case 1: - val1 = 5; - val2 = 0; - break; - case 2: - val1 = 5; - val2 = 200; - break; - case 3: - val1 = 10; - val2 = 500; - break; - } - break; - case Obj::SEA_CHEST: - { - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - if(hlp < 20) - { - val1 = 0; - type = 0; - } - else if(hlp < 90) - { - val1 = 1500; - type = 2; - } - else - { - val1 = 1000; - val2 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE); - type = 1; - } - } - break; - case Obj::SHIPWRECK_SURVIVOR: - { - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - if(hlp < 55) - val1 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE); - else if(hlp < 75) - val1 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_MINOR); - else if(hlp < 95) - val1 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_MAJOR); - else - val1 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_RELIC); - } - break; - case Obj::TREASURE_CHEST: - { - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - if(hlp >= 95) - { - type = 1; - val1 = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE); - return; - } - else if (hlp >= 65) - { - val1 = 2000; - } - else if(hlp >= 33) - { - val1 = 1500; - } - else - { - val1 = 1000; - } - - val2 = val1 - 500; - type = 0; - break; - } - } -} - -void CGPickable::onHeroVisit( const CGHeroInstance * h ) const -{ - switch(ID) - { - case Obj::CAMPFIRE: - { - cb->giveResource(h->tempOwner,static_cast(type),val2); //non-gold resource - cb->giveResource(h->tempOwner,Res::GOLD,val1);//gold - InfoWindow iw; - iw.soundID = soundBase::experience; - iw.player = h->tempOwner; - iw.components.push_back(Component(Component::RESOURCE,Res::GOLD,val1,0)); - iw.components.push_back(Component(Component::RESOURCE,type,val2,0)); - iw.text.addTxt(MetaString::ADVOB_TXT,23); - cb->showInfoDialog(&iw); - break; - } - case Obj::FLOTSAM: - { - cb->giveResource(h->tempOwner,Res::WOOD,val1); //wood - cb->giveResource(h->tempOwner,Res::GOLD,val2);//gold - InfoWindow iw; - iw.soundID = soundBase::GENIE; - iw.player = h->tempOwner; - if(val1) - iw.components.push_back(Component(Component::RESOURCE,Res::WOOD,val1,0)); - if(val2) - iw.components.push_back(Component(Component::RESOURCE,Res::GOLD,val2,0)); - - iw.text.addTxt(MetaString::ADVOB_TXT, 51+type); - cb->showInfoDialog(&iw); - break; - } - case Obj::SEA_CHEST: - { - InfoWindow iw; - iw.soundID = soundBase::chest; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::ADVOB_TXT, 116 + type); - - if(val1) //there is gold - { - iw.components.push_back(Component(Component::RESOURCE,Res::GOLD,val1,0)); - cb->giveResource(h->tempOwner,Res::GOLD,val1); - } - if(type == 1) //art - { - //TODO: what if no space in backpack? - iw.components.push_back(Component(Component::ARTIFACT, val2, 1, 0)); - iw.text.addReplacement(MetaString::ART_NAMES, val2); - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[val2],ArtifactPosition::FIRST_AVAILABLE); - } - cb->showInfoDialog(&iw); - break; - } - case Obj::SHIPWRECK_SURVIVOR: - { - InfoWindow iw; - iw.soundID = soundBase::experience; - iw.player = h->tempOwner; - iw.components.push_back(Component(Component::ARTIFACT,val1,1,0)); - iw.text.addTxt(MetaString::ADVOB_TXT, 125); - iw.text.addReplacement(MetaString::ART_NAMES, val1); - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[val1],ArtifactPosition::FIRST_AVAILABLE); - cb->showInfoDialog(&iw); - break; - } - case Obj::TREASURE_CHEST: - { - if (subID) //not OH3 treasure chest - { - logGlobal->warnStream() << "Not supported WoG treasure chest!"; - return; - } - - if(type) //there is an artifact - { - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[val1],ArtifactPosition::FIRST_AVAILABLE); - InfoWindow iw; - iw.soundID = soundBase::treasure; - iw.player = h->tempOwner; - iw.components.push_back(Component(Component::ARTIFACT,val1,1,0)); - iw.text.addTxt(MetaString::ADVOB_TXT,145); - iw.text.addReplacement(MetaString::ART_NAMES, val1); - cb->showInfoDialog(&iw); - break; - } - else - { - BlockingDialog sd(false,true); - sd.player = h->tempOwner; - sd.text.addTxt(MetaString::ADVOB_TXT,146); - sd.components.push_back(Component(Component::RESOURCE,Res::GOLD,val1,0)); - TExpType expVal = h->calculateXp(val2); - sd.components.push_back(Component(Component::EXPERIENCE,0,expVal, 0)); - sd.soundID = soundBase::chest; - cb->showBlockingDialog(&sd); - return; - } - } - } - cb->removeObject(this); -} - -void CGPickable::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - switch(answer) - { - case 1: //player pick gold - cb->giveResource(hero->tempOwner, Res::GOLD, val1); - break; - case 2: //player pick exp - cb->changePrimSkill(hero, PrimarySkill::EXPERIENCE, hero->calculateXp(val2)); - break; - default: - throw std::runtime_error("Unhandled treasure choice"); - } - cb->removeObject(this); -} - bool CQuest::checkQuest (const CGHeroInstance * h) const { switch (missionType) @@ -5106,322 +4460,6 @@ const std::string & CGWitchHut::getHoverText() const return hoverName; } -bool CGBonusingObject::wasVisited (const CGHeroInstance * h) const -{ - return h->hasBonusFrom(Bonus::OBJECT, ID); -} - -void CGBonusingObject::onHeroVisit( const CGHeroInstance * h ) const -{ - bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); - int messageID=0; - int bonusMove = 0; - ui32 descr_id = 0; - InfoWindow iw; - iw.player = h->tempOwner; - GiveBonus gbonus; - gbonus.id = h->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - - bool second = false; - Bonus secondBonus; - - switch(ID) - { - case Obj::BUOY: - messageID = 21; - iw.soundID = soundBase::MORALE; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = +1; - descr_id = 94; - break; - case Obj::SWAN_POND: - messageID = 29; - iw.soundID = soundBase::LUCK; - gbonus.bonus.type = Bonus::LUCK; - gbonus.bonus.val = 2; - descr_id = 67; - bonusMove = -h->movement; - break; - case Obj::FAERIE_RING: - messageID = 49; - iw.soundID = soundBase::LUCK; - gbonus.bonus.type = Bonus::LUCK; - gbonus.bonus.val = 1; - descr_id = 71; - break; - case Obj::FOUNTAIN_OF_FORTUNE: - messageID = 55; - iw.soundID = soundBase::LUCK; - gbonus.bonus.type = Bonus::LUCK; - gbonus.bonus.val = rand()%5 - 1; - descr_id = 69; - gbonus.bdescr.addReplacement((gbonus.bonus.val<0 ? "-" : "+") + boost::lexical_cast(gbonus.bonus.val)); - break; - case Obj::IDOL_OF_FORTUNE: - messageID = 62; - iw.soundID = soundBase::experience; - - gbonus.bonus.val = 1; - descr_id = 68; - if(cb->getDate(Date::DAY_OF_WEEK) == 7) //7th day of week - { - gbonus.bonus.type = Bonus::MORALE; - second = true; - secondBonus = gbonus.bonus; - secondBonus.type = Bonus::LUCK; - } - else - { - gbonus.bonus.type = (cb->getDate(Date::DAY_OF_WEEK)%2) ? Bonus::LUCK : Bonus::MORALE; - } - break; - case Obj::MERMAID: - messageID = 83; - iw.soundID = soundBase::LUCK; - gbonus.bonus.type = Bonus::LUCK; - gbonus.bonus.val = 1; - descr_id = 72; - break; - case Obj::RALLY_FLAG: - iw.soundID = soundBase::MORALE; - messageID = 111; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = 1; - descr_id = 102; - - second = true; - secondBonus = gbonus.bonus; - secondBonus.type = Bonus::LUCK; - - bonusMove = 400; - break; - case Obj::OASIS: - iw.soundID = soundBase::MORALE; - messageID = 95; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = 1; - descr_id = 95; - bonusMove = 800; - break; - case Obj::TEMPLE: - messageID = 140; - iw.soundID = soundBase::temple; - gbonus.bonus.type = Bonus::MORALE; - if(cb->getDate(Date::DAY_OF_WEEK)==7) //sunday - { - gbonus.bonus.val = 2; - descr_id = 97; - } - else - { - gbonus.bonus.val = 1; - descr_id = 96; - } - break; - case Obj::WATERING_HOLE: - iw.soundID = soundBase::MORALE; - messageID = 166; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = 1; - descr_id = 100; - bonusMove = 400; - break; - case Obj::FOUNTAIN_OF_YOUTH: - iw.soundID = soundBase::MORALE; - messageID = 57; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = 1; - descr_id = 103; - bonusMove = 400; - break; - case Obj::STABLES: - iw.soundID = soundBase::STORE; - bool someUpgradeDone = false; - - for (auto i = h->Slots().begin(); i != h->Slots().end(); ++i) - { - if(i->second->type->idNumber == CreatureID::CAVALIER) - { - cb->changeStackType(StackLocation(h, i->first), VLC->creh->creatures[CreatureID::CHAMPION]); - someUpgradeDone = true; - } - } - if (someUpgradeDone) - { - messageID = 138; - iw.components.push_back(Component(Component::CREATURE,11,0,1)); - } - else - messageID = 137; - - gbonus.bonus.type = Bonus::LAND_MOVEMENT; - gbonus.bonus.val = 600; - bonusMove = 600; - gbonus.bonus.duration = Bonus::ONE_WEEK; - //gbonus.bdescr << std::pair(6, 100); - break; - } - if (descr_id != 0) - gbonus.bdescr.addTxt(MetaString::ARRAY_TXT,descr_id); - assert(messageID); - if(visited) - { - if(ID==Obj::RALLY_FLAG || ID==Obj::OASIS || ID==Obj::MERMAID || ID==Obj::STABLES) - messageID--; - else - messageID++; - } - else - { - //TODO: fix if second bonus val != main bonus val - if(gbonus.bonus.type == Bonus::MORALE || secondBonus.type == Bonus::MORALE) - iw.components.push_back(Component(Component::MORALE,0,gbonus.bonus.val,0)); - if(gbonus.bonus.type == Bonus::LUCK || secondBonus.type == Bonus::LUCK) - iw.components.push_back(Component(Component::LUCK,0,gbonus.bonus.val,0)); - cb->giveHeroBonus(&gbonus); - if(second) - { - gbonus.bonus = secondBonus; - cb->giveHeroBonus(&gbonus); - } - if(bonusMove) //swan pond - take all move points, stables - give move point this day - { - SetMovePoints smp; - smp.hid = h->id; - smp.val = h->movement + bonusMove; - cb->setMovePoints(&smp); - } - } - iw.text.addTxt(MetaString::ADVOB_TXT,messageID); - cb->showInfoDialog(&iw); -} - -const std::string & CGBonusingObject::getHoverText() const -{ - const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); - hoverName = VLC->generaltexth->names[ID]; - if(h) - { - bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); - hoverName += " " + visitedTxt(visited); - } - return hoverName; -} - -void CGBonusingObject::initObj() -{ - if(ID == Obj::BUOY || ID == Obj::MERMAID) - { - blockVisit = true; - } -} - -void CGMagicSpring::setPropertyDer(ui8 what, ui32 val) -{ - CGVisitableOPW::setPropertyDer (what, val); //set visitable if applicable - if (what == ObjProperty::LEFT_VISITED) - { - if (visitedTile == RIGHT) - visited = true; //both field were used, object is not available this week - else - visitedTile = LEFT; - } - else if (what == ObjProperty::RIGHT_VISITED) - { - if (visitedTile == LEFT) - visited = true; - else - visitedTile = RIGHT; - } - else if (what == ObjProperty::LEFTRIGHT_CLEAR) - visitedTile = CLEAR; -} -std::vector CGMagicSpring::getVisitableOffsets() const -{ - std::vector visitableTiles; - - for(int y = 0; y < 6; y++) - for (int x = 0; x < 8; x++) //starting from left - if (appearance.isVisitableAt(x, y)) - visitableTiles.push_back (int3(x, y , 0)); - - return visitableTiles; -} - -int3 CGMagicSpring::getVisitableOffset() const -{ - //FIXME: this also should stop AI from passing through already visited spring, is that ok? - auto visitableTiles = getVisitableOffsets(); - - if (visitableTiles.size() < 2) - { - logGlobal->warnStream() << "Warning: Magic Spring should have at least two visitable offsets!"; - return int3(-1,-1,-1); - } - if (visited) - return int3(-1,-1,-1); - else - { - if (visitedTile == RIGHT) - return visitableTiles[0]; //visit the other one now - else if (visitedTile == LEFT) - return visitableTiles[1]; - else - return visitableTiles[0]; //only left one? - } -} - -void CGMagicSpring::onHeroVisit(const CGHeroInstance * h) const -{ - int messageID; - - if (!visited) - { - if (h->mana > h->manaLimit()) - messageID = 76; - else - { - messageID = 74; - cb->setManaPoints (h->id, 2 * h->manaLimit());//TODO: mark left or right tile visited - if (visitedTile) //visitng the second tile - cb->setObjProperty (id, ObjProperty::VISITED, true); - else - { - auto visitableTiles = getVisitableOffsets(); - assert (visitableTiles.size() >= 2); - if (h->getPosition() == pos - visitableTiles[0]) - cb->setObjProperty (id, ObjProperty::LEFT_VISITED, true); - else if (h->getPosition() == pos - visitableTiles[1]) - cb->setObjProperty (id, ObjProperty::RIGHT_VISITED, true); - else - logGlobal->warnStream() << "Warning: hero is not on any Magic Spring visitable offsets!"; - } - } - } - else - messageID = 75; - showInfoDialog(h,messageID,soundBase::GENIE); -} -void CGMagicSpring::newTurn() const -{ - CGVisitableOPW::newTurn(); - if (cb->getDate(Date::DAY_OF_WEEK) == 1) - { - cb->setObjProperty(id, ObjProperty::LEFTRIGHT_CLEAR, false); - } -} - - -const std::string & CGMagicSpring::getHoverText() const -{ - //TODO: change hover text depending on hovered tile - hoverName = VLC->generaltexth->names[ID] + " " + visitedTxt(visited); - return hoverName; -} - void CGMagicWell::onHeroVisit( const CGHeroInstance * h ) const { int message; @@ -6023,200 +5061,6 @@ void CGGarrison::battleFinished(const CGHeroInstance *hero, const BattleResult & onHeroVisit(hero); } -void CGOnceVisitable::onHeroVisit( const CGHeroInstance * h ) const -{ - int sound = soundBase::sound_todo; - int txtid; - - switch(ID) - { - case Obj::CORPSE: - txtid = 37; - sound = soundBase::MYSTERY; - break; - case Obj::LEAN_TO: - sound = soundBase::GENIE; - txtid = 64; - break; - case Obj::WAGON: - sound = soundBase::GENIE; - txtid = 154; - break; - case Obj::WARRIORS_TOMB: - { - //ask if player wants to search the Tomb - BlockingDialog bd(true, false); - bd.soundID = soundBase::GRAVEYARD; - bd.player = h->getOwner(); - bd.text.addTxt(MetaString::ADVOB_TXT,161); - cb->showBlockingDialog(&bd); - return; - } - default: - logGlobal->errorStream() << "Error: Unknown object (" << ID <<") treated as CGOnceVisitable!"; - return; - } - - InfoWindow iw; - iw.soundID = sound; - iw.player = h->getOwner(); - - if(players.size()) //we have been already visited... - { - txtid++; - if(ID == Obj::WAGON) //wagon has extra text (for finding art) we need to omit - txtid++; - iw.text.addTxt(MetaString::ADVOB_TXT, txtid); - } - else //first visit - give bonus! - { - switch(artOrRes) - { - case 0: // first visit but empty - if (ID == Obj::CORPSE) - ++txtid; - else - txtid+=2; - iw.text.addTxt(MetaString::ADVOB_TXT, txtid); - break; - case 1: //art - iw.components.push_back(Component(Component::ARTIFACT,bonusType,0,0)); - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[bonusType],ArtifactPosition::FIRST_AVAILABLE); - iw.text.addTxt(MetaString::ADVOB_TXT, txtid); - if (ID == Obj::CORPSE) - { - iw.text << "%s"; - iw.text.addReplacement(MetaString::ART_NAMES, bonusType); - } - break; - case 2: //res - iw.text.addTxt(MetaString::ADVOB_TXT, txtid); - iw.components.push_back (Component(Component::RESOURCE, bonusType, bonusVal, 0)); - cb->giveResource(h->getOwner(), static_cast(bonusType), bonusVal); - break; - } - if(ID == Obj::WAGON && artOrRes == 1) - { - iw.text.localStrings.back().second++; - iw.text.addReplacement(MetaString::ART_NAMES, bonusType); - } - } - - cb->showInfoDialog(&iw); - cb->setObjProperty(id, 10, h->getOwner().getNum()); -} - -const std::string & CGOnceVisitable::getHoverText() const -{ - const bool visited = wasVisited(cb->getCurrentPlayer()); - hoverName = VLC->generaltexth->names[ID] + " " + visitedTxt(visited); - return hoverName; -} - -void CGOnceVisitable::initObj() -{ - switch(ID) - { - case Obj::CORPSE: - { - blockVisit = true; - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - if(hlp < 20) - { - artOrRes = 1; - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR); - } - else - { - artOrRes = 0; - } - } - break; - - case Obj::LEAN_TO: - { - artOrRes = 2; - bonusType = cb->gameState()->getRandomGenerator().nextInt(5); //any basic resource without gold - bonusVal = cb->gameState()->getRandomGenerator().nextInt(1, 4); - } - break; - - case Obj::WARRIORS_TOMB: - { - artOrRes = 1; - - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - if(hlp < 30) - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE); - else if(hlp < 80) - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_MINOR); - else if(hlp < 95) - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_MAJOR); - else - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_RELIC); - } - break; - - case Obj::WAGON: - { - int hlp = cb->gameState()->getRandomGenerator().nextInt(99); - - if(hlp < 10) - { - artOrRes = 0; // nothing... :( - } - else if(hlp < 50) //minor or treasure art - { - artOrRes = 1; - bonusType = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), CArtifact::ART_TREASURE | CArtifact::ART_MINOR); - } - else //2 - 5 of non-gold resource - { - artOrRes = 2; - bonusType = cb->gameState()->getRandomGenerator().nextInt(5); - bonusVal = cb->gameState()->getRandomGenerator().nextInt(2, 5); - } - } - break; - } -} - -void CGOnceVisitable::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - //must have been Tomb - if(answer) - { - InfoWindow iw; - iw.player = hero->getOwner(); - iw.components.push_back(Component(Component::MORALE,0,-3,0)); - - if(players.size()) //we've been already visited, player found nothing - { - iw.text.addTxt(MetaString::ADVOB_TXT,163); - } - else //first visit - give artifact - { - iw.text.addTxt(MetaString::ADVOB_TXT,162); - iw.components.push_back(Component(Component::ARTIFACT,bonusType,0,0)); - iw.text.addReplacement(MetaString::ART_NAMES, bonusType); - - cb->giveHeroNewArtifact(hero, VLC->arth->artifacts[bonusType],ArtifactPosition::FIRST_AVAILABLE); - } - - if(!hero->hasBonusFrom(Bonus::OBJECT,ID)) //we don't have modifier from this object yet - { - //ruin morale - GiveBonus gb; - gb.id = hero->id.getNum(); - gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,-3,id.getNum(),""); - gb.bdescr.addTxt(MetaString::ARRAY_TXT,104); //Warrior Tomb Visited -3 - cb->giveHeroBonus(&gb); - } - cb->showInfoDialog(&iw); - cb->setObjProperty(id, 10, hero->getOwner().getNum()); - } -} - void CBank::initObj() { index = VLC->objh->bankObjToIndex(this); diff --git a/lib/CObjectHandler.h b/lib/CObjectHandler.h index c34675e01..cf6a305be 100644 --- a/lib/CObjectHandler.h +++ b/lib/CObjectHandler.h @@ -28,7 +28,6 @@ class IGameCallback; struct BattleResult; class CGObjectInstance; class CScript; -class CObjectScript; class CGHeroInstance; class CTown; class CHero; @@ -356,7 +355,7 @@ public: //std::vector artifacts; //hero's artifacts from bag //std::map artifWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 std::set spells; //known spells (spell IDs) - + std::set visitedObjects; struct DLL_LINKAGE Patrol { @@ -423,7 +422,7 @@ public: h & static_cast(*this); h & static_cast(*this); h & exp & level & name & biography & portrait & mana & secSkills & movement - & sex & inTownGarrison & spells & patrol & moveDir & skillsInfo; + & sex & inTownGarrison & spells & patrol & moveDir & skillsInfo & visitedObjects; h & visitedTown & boat; h & type & specialty & commander; BONUS_TREE_DESERIALIZATION_FIX @@ -557,34 +556,6 @@ private: void heroAcceptsCreatures(const CGHeroInstance *h) const; }; - -class DLL_LINKAGE CGVisitableOPH : public CGObjectInstance //objects visitable only once per hero -{ -public: - std::set visitors; //ids of heroes who have visited this obj - TResources treePrice; //used only by trees of knowledge: empty, 2000 gold, 10 gems - - const std::string & getHoverText() const override; - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - bool wasVisited (const CGHeroInstance * h) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visitors & treePrice; - } -protected: - void setPropertyDer(ui8 what, ui32 val) override;//synchr -private: - void onNAHeroVisit(const CGHeroInstance * h, bool alreadyVisited) const; - ///dialog callbacks - void treeSelected(const CGHeroInstance * h, ui32 result) const; - void schoolSelected(const CGHeroInstance * h, ui32 which) const; - void arenaSelected(const CGHeroInstance * h, int primSkill) const; -}; class DLL_LINKAGE CGTownBuilding : public IObjectInterface { ///basic class for town structures handled as map objects @@ -1046,22 +1017,6 @@ public: } }; -class DLL_LINKAGE CGPickable : public CGObjectInstance //campfire, treasure chest, Flotsam, Shipwreck Survivor, Sea Chest -{ -public: - ui32 type, val1, val2; - - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & type & val1 & val2; - } -}; - class DLL_LINKAGE CGShrine : public CPlayersVisited { public: @@ -1098,24 +1053,6 @@ public: ui32 defaultResProduction(); }; -class DLL_LINKAGE CGVisitableOPW : public CGObjectInstance //objects visitable OPW -{ -public: - ui8 visited; //true if object has been visited this week - - bool wasVisited(PlayerColor player) const; - void onHeroVisit(const CGHeroInstance * h) const override; - virtual void newTurn() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visited; - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; - class DLL_LINKAGE CGTeleport : public CGObjectInstance //teleports and subterranean gates { public: @@ -1132,43 +1069,6 @@ public: } }; -class DLL_LINKAGE CGBonusingObject : public CGObjectInstance //objects giving bonuses to luck/morale/movement -{ -public: - bool wasVisited (const CGHeroInstance * h) const; - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - void initObj() override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGMagicSpring : public CGVisitableOPW -{///unfortunately, this one is quite different than others - enum EVisitedEntrance - { - CLEAR = 0, LEFT = 1, RIGHT - }; -public: - EVisitedEntrance visitedTile; //only one entrance was visited - there are two - - std::vector getVisitableOffsets() const; - int3 getVisitableOffset() const override; - void setPropertyDer(ui8 what, ui32 val) override; - void newTurn() const override; - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visitedTile & visited; - } -}; - class DLL_LINKAGE CGMagicWell : public CGObjectInstance //objects giving bonuses to luck/morale/movement { public: @@ -1290,26 +1190,6 @@ public: } }; -class DLL_LINKAGE CGOnceVisitable : public CPlayersVisited -///wagon, corpse, lean to, warriors tomb -{ -public: - ui8 artOrRes; //0 - nothing; 1 - artifact; 2 - resource - ui32 bonusType, //id of res or artifact - bonusVal; //resource amount (or not used) - - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - void initObj() override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this);; - h & artOrRes & bonusType & bonusVal; - } -}; - class DLL_LINKAGE CBank : public CArmedInstance { public: diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp new file mode 100644 index 000000000..4b29fc955 --- /dev/null +++ b/lib/CObjectWithReward.cpp @@ -0,0 +1,994 @@ +/* + * CObjectWithReward.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 "CObjectWithReward.h" +#include "CHeroHandler.h" +#include "CGeneralTextHandler.h" +#include "../client/CSoundBase.h" +#include "NetPacks.h" + +bool CRewardLimiter::heroAllowed(const CGHeroInstance * hero) const +{ + if (dayOfWeek != 0) + { + if (IObjectInterface::cb->getDate(Date::DAY_OF_WEEK) != dayOfWeek) + return false; + } + + for (auto & reqStack : creatures) + { + size_t count = 0; + for (auto slot : hero->Slots()) + { + const CStackInstance * heroStack = slot.second; + if (heroStack->type == reqStack.type) + count += heroStack->count; + } + if (count < reqStack.count) //not enough creatures of this kind + return false; + } + + if (!IObjectInterface::cb->getPlayer(hero->tempOwner)->resources.canAfford(resources)) + return false; + + if (hero->level < minLevel) + return false; + + for (size_t i=0; igetPrimSkillLevel(PrimarySkill::PrimarySkill(i))) + return false; + } + + for (auto & skill : secondary) + { + if (skill.second < hero->getSecSkillLevel(skill.first)) + return false; + } + + for (auto & art : artifacts) + { + if (!hero->hasArt(art)) + return false; + } + + return true; +} + +std::vector CObjectWithReward::getAvailableRewards(const CGHeroInstance * hero) const +{ + std::vector ret; + + for (size_t i=0; itempOwner; + iw.soundID = soundID; + iw.text = onEmpty; + cb->showInfoDialog(&iw); + onRewardGiven(h); + break; + } + case 1: // one reward. Just give it with message + { + grantReward(info[rewards[0]], h); + InfoWindow iw; + iw.player = h->tempOwner; + iw.soundID = soundID; + iw.text = onGrant; + info[rewards[0]].reward.loadComponents(iw.components); + cb->showInfoDialog(&iw); + onRewardGiven(h); + break; + } + default: // multiple rewards. Let player select + { + BlockingDialog sd(false,true); + sd.player = h->tempOwner; + sd.soundID = soundID; + sd.text = onGrant; + for (auto index : rewards) + sd.components.push_back(info[index].reward.getDisplayedComponent()); + cb->showBlockingDialog(&sd); + return; + } + } + } + else + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.soundID = soundID; + iw.text = onVisited; + cb->showInfoDialog(&iw); + } +} + +void CObjectWithReward::heroLevelUpDone(const CGHeroInstance *hero) const +{ + grantRewardAfterLevelup(info[selectedReward], hero); +} + +void CObjectWithReward::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer > 0 && answer-1 < info.size()) + { + auto list = getAvailableRewards(hero); + grantReward(info[list[answer - 1]], hero); + } + else + { + throw std::runtime_error("Unhandled choice"); + } +} + +void CObjectWithReward::onRewardGiven(const CGHeroInstance * hero) const +{ + // no implementation, virtual function for overrides +} + +void CObjectWithReward::grantReward(const CVisitInfo & info, const CGHeroInstance * hero) const +{ + assert(hero); + assert(hero->tempOwner.isValidPlayer()); + assert(stacks.empty()); + assert(info.reward.creatures.size() <= GameConstants::ARMY_SIZE); + assert(!cb->isVisitCoveredByAnotherQuery(this, hero)); + + cb->giveResources(hero->tempOwner, info.reward.resources); + + for (auto & entry : info.reward.secondary) + { + int current = hero->getSecSkillLevel(entry.first); + if( (current != 0 && current < entry.second) || + (hero->canLearnSkill() )) + { + cb->changeSecSkill(hero, entry.first, entry.second); + } + } + + for(int i=0; i< info.reward.primary.size(); i++) + if(info.reward.primary[i] > 0) + cb->changePrimSkill(hero, static_cast(i), info.reward.primary[i], false); + + + si64 expToGive = 0; + expToGive += VLC->heroh->reqExp(hero->level+info.reward.gainedLevels) - VLC->heroh->reqExp(hero->level); + expToGive += hero->calculateXp(info.reward.gainedExp); + if (expToGive) + { + cb->changePrimSkill(hero, PrimarySkill::EXPERIENCE, expToGive); + } + else + { + grantRewardAfterLevelup(info, hero); + } +} + +void CObjectWithReward::grantRewardAfterLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const +{ + if (info.reward.manaDiff || info.reward.manaPercentage >= 0) + { + si32 mana = hero->mana; + if (info.reward.manaPercentage >= 0) + mana = hero->manaLimit() * info.reward.manaPercentage / 100; + + cb->setManaPoints(hero->id, mana + info.reward.manaDiff); + } + + if(info.reward.movePoints || info.reward.movePercentage >= 0) + { + SetMovePoints smp; + smp.hid = hero->id; + smp.val = hero->movement; + + if (info.reward.movePercentage >= 0) // percent from max + smp.val = hero->maxMovePoints(hero->boat != nullptr) * info.reward.movePercentage / 100; + smp.val = std::max(0, smp.val + info.reward.movePoints); + + cb->setMovePoints(&smp); + } + + for (const Bonus & bonus : info.reward.bonuses) + { + GiveBonus gb; + gb.bonus = bonus; + gb.id = hero->id.getNum(); + cb->giveHeroBonus(&gb); + } + + for (ArtifactID art : info.reward.artifacts) + cb->giveHeroNewArtifact(hero, VLC->arth->artifacts[art],ArtifactPosition::FIRST_AVAILABLE); + + if (!info.reward.spells.empty()) + { + std::set spellsToGive(info.reward.spells.begin(), info.reward.spells.end()); + cb->changeSpells(hero, true, spellsToGive); + } + + if (!info.reward.creatures.empty()) + { + CCreatureSet creatures; + for (auto & crea : info.reward.creatures) + creatures.addToSlot(creatures.getFreeSlot(), new CStackInstance(crea.type, crea.count)); + + cb->giveCreatures(this, hero, creatures, false); + } + + onRewardGiven(hero); +} + +bool CObjectWithReward::wasVisited (PlayerColor player) const +{ + switch (visitMode) + { + case VISIT_UNLIMITED: + return false; + case VISIT_ONCE: + return numOfGrants.empty() || *boost::range::max_element(numOfGrants) == 0; + case VISIT_HERO: + return false; + case VISIT_PLAYER: + return vstd::contains(cb->getPlayer(player)->visitedObjects, ObjectInstanceID(ID)); + default: + return false; + } +} + +bool CObjectWithReward::wasVisited (const CGHeroInstance * h) const +{ + switch (visitMode) + { + case VISIT_HERO: + return vstd::contains(h->visitedObjects, ObjectInstanceID(ID)); + default: + return wasVisited(h->tempOwner); + } +} + +void CRewardInfo::loadComponents(std::vector & comps) const +{ + for (size_t i=0; iidNumber, entry.count, 0)); +} + +Component CRewardInfo::getDisplayedComponent() const +{ + std::vector comps; + loadComponents(comps); + assert(!comps.empty()); + return comps.front(); +} + +// FIXME: copy-pasted from CObjectHandler +static std::string & visitedTxt(const bool visited) +{ + int id = visited ? 352 : 353; + return VLC->generaltexth->allTexts[id]; +} + +const std::string & CObjectWithReward::getHoverText() const +{ + const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); + hoverName = VLC->generaltexth->names[ID]; + if(h && wasVisited(h)) + { + bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); + hoverName += " " + visitedTxt(visited); + } + return hoverName; +} + +void CObjectWithReward::setPropertyDer(ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::REWARD_RESET: + numOfGrants.clear(); + numOfGrants.resize(info.size(), 0); + break; + case ObjProperty::REWARD_SELECT: + selectedReward = val; + break; + case ObjProperty::REWARD_ADD_VISITOR: + //cb->getHero(ObjectInstanceID(val))->visitedObjects.insert(ObjectInstanceID(ID)); + break; + } +} + +void CObjectWithReward::newTurn() const +{ + if (cb->getDate(Date::DAY) % resetDuration == 0) + cb->setObjProperty(id, ObjProperty::REWARD_RESET, 0); +} + +CObjectWithReward::CObjectWithReward(): + soundID(soundBase::invalid), + selectMode(0), + selectedReward(0), + resetDuration(0) +{} + +/////////////////////////////////////////////////////////////////////////////////////////////////// +/// END OF CODE FOR COBJECTWITHREWARD AND RELATED CLASSES /// +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +/// Helper, selects random art class based on weights +static int selectRandomArtClass(int treasure, int minor, int major, int relic) +{ + int total = treasure + minor + major + relic; + assert(total != 0); + int hlp = IObjectInterface::cb->gameState()->getRandomGenerator().nextInt(total - 1); + + if(hlp < treasure) + return CArtifact::ART_TREASURE; + if(hlp < treasure + minor) + return CArtifact::ART_MINOR; + if(hlp < treasure + minor + major) + return CArtifact::ART_MAJOR; + return CArtifact::ART_RELIC; +} + +/// Helper, adds random artifact to reward selecting class based on weights +static void loadRandomArtifact(CVisitInfo & info, int treasure, int minor, int major, int relic) +{ + int artClass = selectRandomArtClass(treasure, minor, major, relic); + ArtifactID artID = VLC->arth->pickRandomArtifact(IObjectInterface::cb->gameState()->getRandomGenerator(), artClass); + info.reward.artifacts.push_back(artID); +} + +CGPickable::CGPickable() +{ + visitMode = VISIT_ONCE; + selectMode = SELECT_PLAYER; +} + +void CGPickable::initObj() +{ + blockVisit = true; + switch(ID) + { + case Obj::CAMPFIRE: + { + soundID = soundBase::experience; + onGrant.addTxt(MetaString::ADVOB_TXT,23); + int givenRes = cb->gameState()->getRandomGenerator().nextInt(5); + int givenAmm = cb->gameState()->getRandomGenerator().nextInt(4, 6); + + info.resize(1); + info[0].reward.resources[givenRes] = givenAmm; + info[0].reward.resources[Res::GOLD]= givenAmm * 100; + break; + } + case Obj::FLOTSAM: + { + int type = cb->gameState()->getRandomGenerator().nextInt(3); + soundID = soundBase::GENIE; + if (type == 0) + onEmpty.addTxt(MetaString::ADVOB_TXT, 51+type); + else + onGrant.addTxt(MetaString::ADVOB_TXT, 51+type); + switch(type) + { + //case 0: + case 1: + { + info.resize(1); + info[0].reward.resources[Res::WOOD] = 5; + break; + } + case 2: + { + info.resize(1); + info[0].reward.resources[Res::WOOD] = 5; + info[0].reward.resources[Res::GOLD] = 200; + break; + } + case 3: + { + info.resize(1); + info[0].reward.resources[Res::WOOD] = 10; + info[0].reward.resources[Res::GOLD] = 500; + break; + } + } + break; + } + case Obj::SEA_CHEST: + { + soundID = soundBase::chest; + int hlp = cb->gameState()->getRandomGenerator().nextInt(99); + if(hlp < 20) + { + onGrant.addTxt(MetaString::ADVOB_TXT, 116); + } + else if(hlp < 90) + { + info.resize(1); + info[0].reward.resources[Res::GOLD] = 1500; + onGrant.addTxt(MetaString::ADVOB_TXT, 118); + } + else + { + info.resize(1); + loadRandomArtifact(info[0], 100, 0, 0, 0); + info[0].reward.resources[Res::GOLD] = 1000; + onGrant.addTxt(MetaString::ADVOB_TXT, 117); + onGrant.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + } + } + break; + case Obj::SHIPWRECK_SURVIVOR: + { + soundID = soundBase::experience; + info.resize(1); + loadRandomArtifact(info[0], 55, 20, 20, 5); + onGrant.addTxt(MetaString::ADVOB_TXT, 125); + onGrant.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + } + break; + case Obj::TREASURE_CHEST: + { + int hlp = cb->gameState()->getRandomGenerator().nextInt(99); + if(hlp >= 95) + { + soundID = soundBase::treasure; + info.resize(1); + loadRandomArtifact(info[0], 100, 0, 0, 0); + onGrant.addTxt(MetaString::ADVOB_TXT,145); + onGrant.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + return; + } + else if (hlp >= 65) + { + soundID = soundBase::chest; + onGrant.addTxt(MetaString::ADVOB_TXT,146); + info.resize(2); + info[0].reward.resources[Res::GOLD] = 2000; + info[1].reward.gainedExp = 1500; + } + else if(hlp >= 33) + { + soundID = soundBase::chest; + onGrant.addTxt(MetaString::ADVOB_TXT,146); + info.resize(2); + info[0].reward.resources[Res::GOLD] = 1500; + info[1].reward.gainedExp = 1000; + } + else + { + soundID = soundBase::chest; + onGrant.addTxt(MetaString::ADVOB_TXT,146); + info.resize(2); + info[0].reward.resources[Res::GOLD] = 1000; + info[1].reward.gainedExp = 500; + } + } + break; + } +} + +void CGPickable::onRewardGiven(const CGHeroInstance * hero) const +{ + cb->removeObject(this); +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +CGBonusingObject::CGBonusingObject() +{ + visitMode = VISIT_UNLIMITED; + selectMode = SELECT_FIRST; +} + +void CGBonusingObject::initObj() +{ + auto configureBonusDuration = [&](CVisitInfo & visit, Bonus::BonusDuration duration, Bonus::BonusType type, si32 value, si32 descrID) + { + Bonus b(duration, type, Bonus::OBJECT, value, ID, descrID != 0 ? VLC->generaltexth->advobtxt[descrID] : ""); + visit.reward.bonuses.push_back(b); + }; + + auto configureBonus = [&](CVisitInfo & visit, Bonus::BonusType type, si32 value, si32 descrID) + { + configureBonusDuration(visit, Bonus::ONE_BATTLE, type, value, descrID); + }; + + auto configureMessage = [&](int onGrantID, int onVisitedID, soundBase::soundID sound) + { + onGrant.addTxt(MetaString::ADVOB_TXT, onGrantID); + onVisited.addTxt(MetaString::ADVOB_TXT, onVisitedID); + soundID = sound; + }; + + if(ID == Obj::BUOY || ID == Obj::MERMAID) + blockVisit = true; + + info.resize(1); + CVisitInfo & visit = info[0]; + + switch(ID) + { + case Obj::BUOY: + configureMessage(21, 22, soundBase::MORALE); + configureBonus(visit, Bonus::MORALE, +1, 94); + break; + case Obj::SWAN_POND: + configureMessage(29, 30, soundBase::LUCK); + configureBonus(visit, Bonus::LUCK, 2, 67); + visit.reward.movePercentage = 0; + break; + case Obj::FAERIE_RING: + configureMessage(49, 50, soundBase::LUCK); + configureBonus(visit, Bonus::LUCK, 2, 71); + break; + case Obj::FOUNTAIN_OF_FORTUNE: + selectMode = SELECT_RANDOM; + configureMessage(55, 56, soundBase::LUCK); + info.resize(5); + for (int i=0; i<5; i++) + configureBonus(info[i], Bonus::LUCK, i-1, 69); //NOTE: description have %d that should be replaced with value + break; + case Obj::IDOL_OF_FORTUNE: + + configureMessage(62, 63, soundBase::experience); + info.resize(7); + for (int i=0; i<6; i++) + { + info[i].limiter.dayOfWeek = i+1; + configureBonus(info[i], i%2 ? Bonus::MORALE : Bonus::LUCK, 1, 68); + } + info.back().limiter.dayOfWeek = 7; + configureBonus(info.back(), Bonus::MORALE, 1, 68); // on last day of week + configureBonus(info.back(), Bonus::LUCK, 1, 68); + + break; + case Obj::MERMAID: + configureMessage(83, 82, soundBase::LUCK); + configureBonus(visit, Bonus::LUCK, 1, 72); + break; + case Obj::RALLY_FLAG: + configureMessage(111, 110, soundBase::MORALE); + configureBonus(visit, Bonus::MORALE, 1, 102); + configureBonus(visit, Bonus::LUCK, 1, 102); + visit.reward.movePoints = 400; + break; + case Obj::OASIS: + configureMessage(95, 94, soundBase::MORALE); + onGrant.addTxt(MetaString::ADVOB_TXT, 95); + configureBonus(visit, Bonus::MORALE, 1, 95); + visit.reward.movePoints = 800; + break; + case Obj::TEMPLE: + configureMessage(140, 141, soundBase::temple); + info[0].limiter.dayOfWeek = 7; + info.resize(2); + configureBonus(info[0], Bonus::MORALE, 2, 96); + configureBonus(info[1], Bonus::MORALE, 1, 97); + break; + case Obj::WATERING_HOLE: + configureMessage(166, 167, soundBase::MORALE); + configureBonus(visit, Bonus::MORALE, 1, 100); + visit.reward.movePoints = 400; + break; + case Obj::FOUNTAIN_OF_YOUTH: + configureMessage(57, 58, soundBase::MORALE); + configureBonus(visit, Bonus::MORALE, 1, 103); + visit.reward.movePoints = 400; + break; + case Obj::STABLES: + configureMessage(137, 136, soundBase::STORE); + + configureBonusDuration(visit, Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, 600, 0); + visit.reward.movePoints = 600; + //TODO: upgrade champions to cavaliers +/* + bool someUpgradeDone = false; + + for (auto i = h->Slots().begin(); i != h->Slots().end(); ++i) + { + if(i->second->type->idNumber == CreatureID::CAVALIER) + { + cb->changeStackType(StackLocation(h, i->first), VLC->creh->creatures[CreatureID::CHAMPION]); + someUpgradeDone = true; + } + } + if (someUpgradeDone) + { + grantMessage.addTxt(MetaString::ADVOB_TXT, 138); + iw.components.push_back(Component(Component::CREATURE,11,0,1)); + }*/ + break; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +CGOnceVisitable::CGOnceVisitable() +{ + visitMode = VISIT_ONCE; + selectMode = SELECT_FIRST; +} + +void CGOnceVisitable::initObj() +{ + switch(ID) + { + case Obj::CORPSE: + { + onGrant.addTxt(MetaString::ADVOB_TXT, 37); + onEmpty.addTxt(MetaString::ADVOB_TXT, 38); + soundID = soundBase::MYSTERY; + blockVisit = true; + if(cb->gameState()->getRandomGenerator().nextInt(99) < 20) + { + info.resize(1); + loadRandomArtifact(info[0], 10, 10, 10, 0); + } + } + break; + + case Obj::LEAN_TO: + { + soundID = soundBase::GENIE; + onGrant.addTxt(MetaString::ADVOB_TXT, 64); + onEmpty.addTxt(MetaString::ADVOB_TXT, 65); + info.resize(1); + int type = cb->gameState()->getRandomGenerator().nextInt(5); //any basic resource without gold + int value = cb->gameState()->getRandomGenerator().nextInt(1, 4); + info[0].reward.resources[type] = value; + } + break; + + case Obj::WARRIORS_TOMB: + { + // TODO: line 161 - ask if player wants to search the Tomb + soundID = soundBase::GRAVEYARD; + onGrant.addTxt(MetaString::ADVOB_TXT, 162); + onVisited.addTxt(MetaString::ADVOB_TXT, 163); + + info.resize(2); + loadRandomArtifact(info[0], 30, 50, 25, 5); + + Bonus bonus(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::OBJECT, -3, ID); + info[0].reward.bonuses.push_back(bonus); + info[1].reward.bonuses.push_back(bonus); + } + break; + case Obj::WAGON: + { + soundID = soundBase::GENIE; + onVisited.addTxt(MetaString::ADVOB_TXT, 156); + + int hlp = cb->gameState()->getRandomGenerator().nextInt(99); + + if(hlp < 40) //minor or treasure art + { + onGrant.addTxt(MetaString::ADVOB_TXT, 155); + info.resize(1); + loadRandomArtifact(info[0], 10, 10, 0, 0); + } + else if(hlp < 90) //2 - 5 of non-gold resource + { + onGrant.addTxt(MetaString::ADVOB_TXT, 154); + info.resize(1); + int type = cb->gameState()->getRandomGenerator().nextInt(5); + int value = cb->gameState()->getRandomGenerator().nextInt(2, 5); + info[0].reward.resources[type] = value; + } + // or nothing + } + break; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +CGVisitableOPH::CGVisitableOPH() +{ + visitMode = VISIT_HERO; + selectMode = SELECT_PLAYER; +} + +void CGVisitableOPH::initObj() +{ + switch(ID) + { + case Obj::ARENA: + soundID = soundBase::NOMAD; + onGrant.addTxt(MetaString::ADVOB_TXT, 0); + info.resize(2); + info[0].reward.primary[PrimarySkill::ATTACK] = 2; + info[1].reward.primary[PrimarySkill::DEFENSE] = 2; + break; + case Obj::MERCENARY_CAMP: + info.resize(1); + info[0].reward.primary[PrimarySkill::ATTACK] = 1; + soundID = soundBase::NOMAD; + onGrant.addTxt(MetaString::ADVOB_TXT, 80); + break; + case Obj::MARLETTO_TOWER: + info.resize(1); + info[0].reward.primary[PrimarySkill::DEFENSE] = 1; + soundID = soundBase::NOMAD; + onGrant.addTxt(MetaString::ADVOB_TXT, 39); + break; + case Obj::STAR_AXIS: + info.resize(1); + info[0].reward.primary[PrimarySkill::SPELL_POWER] = 1; + soundID = soundBase::gazebo; + onGrant.addTxt(MetaString::ADVOB_TXT, 100); + break; + case Obj::GARDEN_OF_REVELATION: + info.resize(1); + info[0].reward.primary[PrimarySkill::KNOWLEDGE] = 1; + soundID = soundBase::GETPROTECTION; + onGrant.addTxt(MetaString::ADVOB_TXT, 59); + break; + case Obj::LEARNING_STONE: + info.resize(1); + info[0].reward.gainedExp = 1000; + soundID = soundBase::gazebo; + onGrant.addTxt(MetaString::ADVOB_TXT, 143); + break; + case Obj::TREE_OF_KNOWLEDGE: + soundID = soundBase::gazebo; + info.resize(1); + info[0].reward.gainedLevels = 1; + + info.resize(1); + switch (cb->gameState()->getRandomGenerator().nextInt(2)) + { + case 0: // free + break; + case 1: + info[0].limiter.resources[Res::GOLD] = 2000; + info[0].reward.resources[Res::GOLD] = -2000; + break; + case 2: + info[0].limiter.resources[Res::GEMS] = 10; + info[0].reward.resources[Res::GEMS] = -10; + break; + } + break; + case Obj::LIBRARY_OF_ENLIGHTENMENT: + { + onGrant.addTxt(MetaString::ADVOB_TXT, 66); + onVisited.addTxt(MetaString::ADVOB_TXT, 67); + onEmpty.addTxt(MetaString::ADVOB_TXT, 68); + + // Don't like this one but don't see any easier approach + CVisitInfo visit; + visit.reward.primary[PrimarySkill::ATTACK] = 2; + visit.reward.primary[PrimarySkill::DEFENSE] = 2; + visit.reward.primary[PrimarySkill::KNOWLEDGE] = 2; + visit.reward.primary[PrimarySkill::SPELL_POWER] = 2; + + static_assert(SecSkillLevel::LEVELS_SIZE == 4, "Behavior of Library of Enlignment may not be correct"); + for (int i=0; igeneraltexth->names[ID]; + if(pom >= 0) + hoverName += ("\n" + VLC->generaltexth->xtrainfo[pom]); + const CGHeroInstance *h = cb->getSelectedHero (cb->getCurrentPlayer()); + if(h) + { + hoverName += "\n\n"; + bool visited = vstd::contains (visitors, h->id); + hoverName += visitedTxt (visited); + } + return hoverName; +} +*/ + +/////////////////////////////////////////////////////////////////////////////////////////////////// + +CGVisitableOPW::CGVisitableOPW() +{ + visitMode = VISIT_ONCE; + selectMode = SELECT_RANDOM; + resetDuration = 7; +} + +void CGVisitableOPW::initObj() +{ + switch (ID) + { + case Obj::MYSTICAL_GARDEN: + soundID = soundBase::experience; + onGrant.addTxt(MetaString::ADVOB_TXT, 92); + onEmpty.addTxt(MetaString::ADVOB_TXT, 93); + info.resize(2); + info[0].reward.resources[Res::GEMS] = 5; + info[1].reward.resources[Res::GOLD] = 500; + break; + case Obj::WINDMILL: + soundID = soundBase::GENIE; + onGrant.addTxt(MetaString::ADVOB_TXT, 170); + onEmpty.addTxt(MetaString::ADVOB_TXT, 169); + // 3-6 of any resource but wood and gold + // this is UGLY. TODO: find better way to describe this + for (int resID = Res::MERCURY; resID < Res::GOLD; resID++) + { + for (int val = 3; val <=6; val++) + { + CVisitInfo visit; + visit.reward.resources[resID] = val; + info.push_back(visit); + } + } + break; + case Obj::WATER_WHEEL: + soundID = soundBase::GENIE; + onGrant.addTxt(MetaString::ADVOB_TXT, 164); + onEmpty.addTxt(MetaString::ADVOB_TXT, 165); + + info.resize(2); + info[0].limiter.dayOfWeek = 7; // double amount on sunday + info[0].reward.resources[Res::GOLD] = 1000; + info[1].reward.resources[Res::GOLD] = 500; + break; + } +} + +/////////////////////////////////////////////////////////////////////////////////////////////////// + + +std::vector CGMagicSpring::getVisitableOffsets() const +{ + std::vector visitableTiles; + + for(int y = 0; y < 6; y++) + for (int x = 0; x < 8; x++) //starting from left + if (appearance.isVisitableAt(x, y)) + visitableTiles.push_back (int3(x, y , 0)); + + return visitableTiles; +} + +int3 CGMagicSpring::getVisitableOffset() const +{ + auto visitableTiles = getVisitableOffsets(); + + if (visitableTiles.size() != info.size()) + { + logGlobal->warnStream() << "Unexpected number of visitable tiles of Magic Spring at " << pos << "!"; + return int3(-1,-1,-1); + } + + for (size_t i=0; i CGMagicSpring::getAvailableRewards(const CGHeroInstance * hero) const +{ + auto tiles = getVisitableOffsets(); + for (size_t i=0; igetPosition() && numOfGrants[i] == 0) + { + return std::vector(1, i); + } + } + // hero is either not on visitable tile (should not happen) or tile is already used + return std::vector(); +} diff --git a/lib/CObjectWithReward.h b/lib/CObjectWithReward.h new file mode 100644 index 000000000..b7fafc122 --- /dev/null +++ b/lib/CObjectWithReward.h @@ -0,0 +1,320 @@ +#pragma once + +#include "CObjectHandler.h" +#include "NetPacksBase.h" + +/* + * CObjectWithReward.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 + * + */ + +/// Limiters of rewards. Rewards will be granted to hero only if he satisfies requirements +/// Note: for this is only a test - it won't remove anything from hero (e.g. artifacts or creatures) +/// NOTE: in future should (partially) replace seer hut/quest guard quests checks +class DLL_LINKAGE CRewardLimiter +{ +public: + /// how many times this reward can be granted, 0 for unlimited + si32 numOfGrants; + + /// day of week, unused if 0, 1-7 will test for current day of week + si32 dayOfWeek; + + /// level that hero needs to have + si32 minLevel; + + /// resources player needs to have in order to trigger reward + TResources resources; + + /// skills hero needs to have + std::vector primary; + std::map secondary; + + /// artifacts that hero needs to have (equipped or in backpack) to trigger this + /// Note: does not checks for multiple copies of the same arts + std::vector artifacts; + + /// creatures that hero needs to have + std::vector creatures; + + CRewardLimiter(): + numOfGrants(1), + dayOfWeek(0), + minLevel(0) + {} + + bool heroAllowed(const CGHeroInstance * hero) const; + + template void serialize(Handler &h, const int version) + { + h & numOfGrants & dayOfWeek & minLevel & resources; + h & primary & secondary & artifacts & creatures; + } +}; + +/// Reward that can be granted to a hero +/// NOTE: eventually should replace seer hut rewards and events/pandoras +class DLL_LINKAGE CRewardInfo +{ +public: + /// resources that will be given to player + TResources resources; + + /// received experience + ui32 gainedExp; + /// received levels (converted into XP during grant) + ui32 gainedLevels; + + /// mana given to/taken from hero, fixed value + si32 manaDiff; + /// fixed value, in form of percentage from max + si32 manaPercentage; + + /// movement points, only for current day. Bonuses should be used to grant MP on any other day + si32 movePoints; + /// fixed value, in form of percentage from max + si32 movePercentage; + + /// list of bonuses, e.g. morale/luck + std::vector bonuses; + + /// skills that hero may receive or lose + std::vector primary; + std::map secondary; + + /// objects that hero may receive + std::vector artifacts; + std::vector spells; + std::vector creatures; + + /// Generates list of components that describes reward + virtual void loadComponents(std::vector & comps) const; + Component getDisplayedComponent() const; + + CRewardInfo() : + gainedExp(0), + gainedLevels(0), + manaDiff(0), + manaPercentage(-1), + movePoints(0), + movePercentage(-1) + {} + + template void serialize(Handler &h, const int version) + { + h & resources; + h & gainedExp & gainedLevels & manaDiff & movePoints; + h & primary & secondary & bonuses; + h & artifacts & spells & creatures; + } +}; + +class CVisitInfo +{ +public: + CRewardLimiter limiter; + CRewardInfo reward; + + MetaString message; + + template void serialize(Handler &h, const int version) + { + h & limiter & reward & message; + } +}; + +/// Base class that can handle granting rewards to visiting heroes. +/// Inherits from CArmedInstance for proper trasfer of armies +class DLL_LINKAGE CObjectWithReward : public CArmedInstance +{ + /// function that must be called if hero got level-up during grantReward call + void grantRewardAfterLevelup(const CVisitInfo & reward, const CGHeroInstance * hero) const; + +protected: + /// controls selection of reward granted to player + enum ESelectMode + { + SELECT_FIRST, // first reward that matches limiters + SELECT_PLAYER, // player can select from all allowed rewards + SELECT_RANDOM // reward will be selected from allowed randomly + }; + + enum EVisitMode + { + VISIT_UNLIMITED, // any number of times + VISIT_ONCE, // only once, first to visit get all the rewards + VISIT_HERO, // every hero can visit object once + VISIT_PLAYER // every player can visit object once + }; + + /// filters list of visit info and returns rewards that can be granted to current hero + virtual std::vector getAvailableRewards(const CGHeroInstance * hero) const; + + /// grants reward to hero + void grantReward(const CVisitInfo & reward, const CGHeroInstance * hero) const; + + /// Rewars that can be granted by an object + std::vector info; + + /// How many times these rewards have been granted since last reset + std::vector numOfGrants; + + /// MetaString's that contain text for messages for specific situations + MetaString onGrant; + MetaString onVisited; + MetaString onEmpty; + + /// sound that will be played alongside with *any* message + ui16 soundID; + /// how reward will be selected, uses ESelectMode enum + ui8 selectMode; + /// contols who can visit an object, uses EVisitMode enum + ui8 visitMode; + /// reward selected by player + ui16 selectedReward; + + /// object visitability info will be reset each resetDuration days + ui16 resetDuration; + +public: + void setPropertyDer(ui8 what, ui32 val) override; + const std::string & getHoverText() const override; + + /// Visitability checks. Note that hero check includes check for hero owner (returns true if object was visited by player) + bool wasVisited (PlayerColor player) const override; + bool wasVisited (const CGHeroInstance * h) const override; + + /// gives reward to player or ask for choice in case of multiple rewards + void onHeroVisit(const CGHeroInstance *h) const override; + + ///possibly resets object state + void newTurn() const override; + + /// gives second part of reward after hero level-ups for proper granting of spells/mana + void heroLevelUpDone(const CGHeroInstance *hero) const override; + + /// applies player selection of reward + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + /// function that will be called once reward is fully granted to hero + virtual void onRewardGiven(const CGHeroInstance * hero) const; + + CObjectWithReward(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & info & numOfGrants; + h & onGrant & onVisited & onEmpty; + h & soundID & selectMode & selectedReward; + } +}; + +class DLL_LINKAGE CGPickable : public CObjectWithReward //campfire, treasure chest, Flotsam, Shipwreck Survivor, Sea Chest +{ +public: + void initObj() override; + void onRewardGiven(const CGHeroInstance *hero) const; + + CGPickable(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGBonusingObject : public CObjectWithReward //objects giving bonuses to luck/morale/movement +{ +public: + void initObj() override; + + CGBonusingObject(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGOnceVisitable : public CObjectWithReward // wagon, corpse, lean to, warriors tomb +{ +public: + void initObj() override; + + CGOnceVisitable(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGVisitableOPH : public CObjectWithReward //objects visitable only once per hero +{ +public: + void initObj() override; + + CGVisitableOPH(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGVisitableOPW : public CObjectWithReward //objects visitable once per week +{ +public: + void initObj() override; + + CGVisitableOPW(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +///Special case - magic spring that has two separate visitable entrances +class DLL_LINKAGE CGMagicSpring : public CGVisitableOPW +{ +protected: + std::vector getAvailableRewards(const CGHeroInstance * hero) const override; + +public: + std::vector getVisitableOffsets() const; + int3 getVisitableOffset() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +//TODO: + +// MAX +// class DLL_LINKAGE CGPandoraBox : public CArmedInstance +// class DLL_LINKAGE CGEvent : public CGPandoraBox //event objects +// class DLL_LINKAGE CGSeerHut : public CArmedInstance, public IQuestObject //army is used when giving reward +// class DLL_LINKAGE CGQuestGuard : public CGSeerHut +// class DLL_LINKAGE CBank : public CArmedInstance +// class DLL_LINKAGE CGPyramid : public CBank + +// EXTRA +// class DLL_LINKAGE COPWBonus : public CGTownBuilding +// class DLL_LINKAGE CTownBonus : public CGTownBuilding +// class DLL_LINKAGE CGKeys : public CGObjectInstance //Base class for Keymaster and guards +// class DLL_LINKAGE CGKeymasterTent : public CGKeys +// class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject + +// POSSIBLE +// class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles +// class DLL_LINKAGE CGWitchHut : public CPlayersVisited +// class DLL_LINKAGE CGScholar : public CGObjectInstance diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 162a57cc2..84172daa3 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1026,7 +1026,10 @@ namespace ObjProperty BANK_CLEAR_CONFIG, BANK_INIT_ARMY, BANK_RESET, //magic spring - LEFT_VISITED, RIGHT_VISITED, LEFTRIGHT_CLEAR + LEFT_VISITED, RIGHT_VISITED, LEFTRIGHT_CLEAR, + + //object with reward + REWARD_RESET, REWARD_ADD_VISITOR, REWARD_SELECT }; } diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 7ab351079..7c39d5486 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -246,15 +246,14 @@ DLL_LINKAGE void GiveBonus::applyGs( CGameState *gs ) && gs->map->objects[bonus.sid]->ID == Obj::EVENT) //it's morale/luck bonus from an event without description { descr = VLC->generaltexth->arraytxt[bonus.val > 0 ? 110 : 109]; //+/-%d Temporary until next battle" - - // Some of(?) versions of H3 use %s here instead of %d. Try to replace both of them - boost::replace_first(descr,"%d",boost::lexical_cast(std::abs(bonus.val))); - boost::replace_first(descr,"%s",boost::lexical_cast(std::abs(bonus.val))); } else { bdescr.toString(descr); } + // Some of(?) versions of H3 use %s here instead of %d. Try to replace both of them + boost::replace_first(descr,"%d",boost::lexical_cast(std::abs(bonus.val))); + boost::replace_first(descr,"%s",boost::lexical_cast(std::abs(bonus.val))); } DLL_LINKAGE void ChangeObjPos::applyGs( CGameState *gs ) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 9fcbb30ee..99bb4046f 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -21,6 +21,7 @@ #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" #include "../CObjectHandler.h" +#include "../CObjectWithReward.h" #include "../CDefObjInfoHandler.h" #include "../VCMI_Lib.h" #include "../NetPacksBase.h" diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 7836bea19..8f25618e3 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -5,6 +5,7 @@ #include "../VCMI_Lib.h" #include "../CArtHandler.h" #include "../CObjectHandler.h" +#include "../CObjectWithReward.h" #include "../CGameState.h" #include "../CHeroHandler.h" #include "../CTownHandler.h" @@ -32,7 +33,6 @@ void registerTypesMapObjects1(Serializer &s) // Non-armed objects s.template registerType(); - s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); @@ -80,16 +80,16 @@ void registerTypesMapObjects2(Serializer &s) s.template registerType(); s.template registerType(); - - s.template registerType(); - - s.template registerType(); - s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); - s.template registerType(); s.template registerType(); s.template registerType(); From 8beea4ec6a8a86c921ff234af79a7cef720cf581 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 7 Apr 2014 14:56:59 +0300 Subject: [PATCH 02/33] More work on configurable objects: - maps can be started - visiting will be registered properly --- lib/CObjectWithReward.cpp | 36 ++++++++++++++++++++++++------------ lib/CObjectWithReward.h | 19 +++++++++++++------ 2 files changed, 37 insertions(+), 18 deletions(-) diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp index 4b29fc955..454ae1afc 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/CObjectWithReward.cpp @@ -71,7 +71,7 @@ std::vector CObjectWithReward::getAvailableRewards(const CGHeroInstance * { const CVisitInfo & visit = info[i]; - if (numOfGrants[i] < visit.limiter.numOfGrants && visit.limiter.heroAllowed(hero)) + if (visit.numOfGrants < visit.limiter.numOfGrants && visit.limiter.heroAllowed(hero)) { ret.push_back(i); } @@ -81,7 +81,7 @@ std::vector CObjectWithReward::getAvailableRewards(const CGHeroInstance * void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const { - if (wasVisited(h)) + if (!wasVisited(h)) { auto rewards = getAvailableRewards(h); switch (rewards.size()) @@ -252,7 +252,11 @@ bool CObjectWithReward::wasVisited (PlayerColor player) const case VISIT_UNLIMITED: return false; case VISIT_ONCE: - return numOfGrants.empty() || *boost::range::max_element(numOfGrants) == 0; + for (auto & visit : info) + { + if (visit.numOfGrants != 0) + return true; + } case VISIT_HERO: return false; case VISIT_PLAYER: @@ -324,9 +328,12 @@ const std::string & CObjectWithReward::getHoverText() const { const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); hoverName = VLC->generaltexth->names[ID]; - if(h && wasVisited(h)) + if(visitMode != VISIT_UNLIMITED) { - bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); + bool visited = wasVisited(cb->getCurrentPlayer()); + if (h) + visited |= wasVisited(h) || h->hasBonusFrom(Bonus::OBJECT,ID); + hoverName += " " + visitedTxt(visited); } return hoverName; @@ -337,21 +344,27 @@ void CObjectWithReward::setPropertyDer(ui8 what, ui32 val) switch (what) { case ObjProperty::REWARD_RESET: - numOfGrants.clear(); - numOfGrants.resize(info.size(), 0); + for (auto & visit : info) + visit.numOfGrants = 0; break; case ObjProperty::REWARD_SELECT: selectedReward = val; break; case ObjProperty::REWARD_ADD_VISITOR: - //cb->getHero(ObjectInstanceID(val))->visitedObjects.insert(ObjectInstanceID(ID)); + { + //FIXME: Not sure if modifying another object here is a good idea + CGHeroInstance * hero = cb->gameState()->getHero(ObjectInstanceID(val)); + assert(hero && hero->tempOwner.isValidPlayer()); + hero->visitedObjects.insert(ObjectInstanceID(ID)); + cb->gameState()->getPlayer(hero->tempOwner)->visitedObjects.insert(ObjectInstanceID(ID)); break; + } } } void CObjectWithReward::newTurn() const { - if (cb->getDate(Date::DAY) % resetDuration == 0) + if (resetDuration != 0 && cb->getDate(Date::DAY) % resetDuration == 0) cb->setObjProperty(id, ObjProperty::REWARD_RESET, 0); } @@ -366,7 +379,6 @@ CObjectWithReward::CObjectWithReward(): /// END OF CODE FOR COBJECTWITHREWARD AND RELATED CLASSES /// /////////////////////////////////////////////////////////////////////////////////////////////////// - /// Helper, selects random art class based on weights static int selectRandomArtClass(int treasure, int minor, int major, int relic) { @@ -973,7 +985,7 @@ int3 CGMagicSpring::getVisitableOffset() const for (size_t i=0; i CGMagicSpring::getAvailableRewards(const CGHeroInstance * hero auto tiles = getVisitableOffsets(); for (size_t i=0; igetPosition() && numOfGrants[i] == 0) + if (pos - tiles[i] == hero->getPosition() && info[i].numOfGrants == 0) { return std::vector(1, i); } diff --git a/lib/CObjectWithReward.h b/lib/CObjectWithReward.h index b7fafc122..0df704c0b 100644 --- a/lib/CObjectWithReward.h +++ b/lib/CObjectWithReward.h @@ -45,7 +45,8 @@ public: CRewardLimiter(): numOfGrants(1), dayOfWeek(0), - minLevel(0) + minLevel(0), + primary(4, 0) {} bool heroAllowed(const CGHeroInstance * hero) const; @@ -102,7 +103,8 @@ public: manaDiff(0), manaPercentage(-1), movePoints(0), - movePercentage(-1) + movePercentage(-1), + primary(4, 0) {} template void serialize(Handler &h, const int version) @@ -120,8 +122,16 @@ public: CRewardLimiter limiter; CRewardInfo reward; + /// Message that will be displayed on granting of this reward, if not empty MetaString message; + /// How many times this reward has been granted since last reset + si32 numOfGrants; + + CVisitInfo(): + numOfGrants(0) + {} + template void serialize(Handler &h, const int version) { h & limiter & reward & message; @@ -161,9 +171,6 @@ protected: /// Rewars that can be granted by an object std::vector info; - /// How many times these rewards have been granted since last reset - std::vector numOfGrants; - /// MetaString's that contain text for messages for specific situations MetaString onGrant; MetaString onVisited; @@ -209,7 +216,7 @@ public: template void serialize(Handler &h, const int version) { h & static_cast(*this); - h & info & numOfGrants; + h & info; h & onGrant & onVisited & onEmpty; h & soundID & selectMode & selectedReward; } From dab9fd7148d047ab998d417885c189555a1391b2 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 7 Apr 2014 20:32:15 +0300 Subject: [PATCH 03/33] Heroes will be properly marked as visited on visiting --- lib/CObjectWithReward.cpp | 25 ++++++++++++------------ lib/NetPacks.h | 40 +++++++++++++++++++++++++++++++-------- lib/NetPacksLib.cpp | 23 +++++++++++++++++++--- 3 files changed, 64 insertions(+), 24 deletions(-) diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp index 454ae1afc..cd7ff778a 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/CObjectWithReward.cpp @@ -39,18 +39,18 @@ bool CRewardLimiter::heroAllowed(const CGHeroInstance * hero) const if (!IObjectInterface::cb->getPlayer(hero->tempOwner)->resources.canAfford(resources)) return false; - if (hero->level < minLevel) + if (minLevel > hero->level) return false; for (size_t i=0; igetPrimSkillLevel(PrimarySkill::PrimarySkill(i))) + if (primary[i] > hero->getPrimSkillLevel(PrimarySkill::PrimarySkill(i))) return false; } for (auto & skill : secondary) { - if (skill.second < hero->getSecSkillLevel(skill.first)) + if (skill.second > hero->getSecSkillLevel(skill.first)) return false; } @@ -98,6 +98,8 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const } case 1: // one reward. Just give it with message { + //FIXME: merge into grantReward call? + cb->setObjProperty(id, ObjProperty::REWARD_SELECT, rewards[0]); grantReward(info[rewards[0]], h); InfoWindow iw; iw.player = h->tempOwner; @@ -109,7 +111,7 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const break; } default: // multiple rewards. Let player select - { + {//TODO: implement various modes BlockingDialog sd(false,true); sd.player = h->tempOwner; sd.soundID = soundID; @@ -141,6 +143,7 @@ void CObjectWithReward::blockingDialogAnswered(const CGHeroInstance *hero, ui32 if (answer > 0 && answer-1 < info.size()) { auto list = getAvailableRewards(hero); + cb->setObjProperty(id, ObjProperty::REWARD_SELECT, list[answer - 1]); grantReward(info[list[answer - 1]], hero); } else @@ -162,6 +165,10 @@ void CObjectWithReward::grantReward(const CVisitInfo & info, const CGHeroInstanc assert(info.reward.creatures.size() <= GameConstants::ARMY_SIZE); assert(!cb->isVisitCoveredByAnotherQuery(this, hero)); + //FIXME: move somewhere? + ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD, id, hero->id); + cb->sendAndApply(&cov); + cb->giveResources(hero->tempOwner, info.reward.resources); for (auto & entry : info.reward.secondary) @@ -349,16 +356,8 @@ void CObjectWithReward::setPropertyDer(ui8 what, ui32 val) break; case ObjProperty::REWARD_SELECT: selectedReward = val; + info[val].numOfGrants++; break; - case ObjProperty::REWARD_ADD_VISITOR: - { - //FIXME: Not sure if modifying another object here is a good idea - CGHeroInstance * hero = cb->gameState()->getHero(ObjectInstanceID(val)); - assert(hero && hero->tempOwner.isValidPlayer()); - hero->visitedObjects.insert(ObjectInstanceID(ID)); - cb->gameState()->getPlayer(hero->tempOwner)->visitedObjects.insert(ObjectInstanceID(ID)); - break; - } } } diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 84172daa3..fb241b334 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1025,11 +1025,8 @@ namespace ObjProperty BANK_DAYCOUNTER, BANK_CLEAR_ARTIFACTS, BANK_ADD_ARTIFACT, BANK_MULTIPLIER, BANK_CONFIG_PRESET, BANK_CLEAR_CONFIG, BANK_INIT_ARMY, BANK_RESET, - //magic spring - LEFT_VISITED, RIGHT_VISITED, LEFTRIGHT_CLEAR, - - //object with reward - REWARD_RESET, REWARD_ADD_VISITOR, REWARD_SELECT + //object with reward + REWARD_RESET, REWARD_SELECT }; } @@ -1039,7 +1036,7 @@ struct SetObjectProperty : public CPackForClient//1001 void applyCl(CClient *cl); ObjectInstanceID id; - ui8 what; //1 - owner; 2 - blockvis; 3 - first stack count; 4 - visitors; 5 - visited; 6 - ID (if 34 then also def is replaced) + ui8 what; // see ObjProperty enum ui32 val; SetObjectProperty(){type = 1001;}; SetObjectProperty(ObjectInstanceID ID, ui8 What, ui32 Val):id(ID),what(What),val(Val){type = 1001;}; @@ -1056,14 +1053,41 @@ struct SetHoverName : public CPackForClient//1002 ObjectInstanceID id; MetaString name; - SetHoverName(){type = 1002;}; - SetHoverName(ObjectInstanceID ID, MetaString& Name):id(ID),name(Name){type = 1002;}; + SetHoverName(){type = 1002;} + SetHoverName(ObjectInstanceID ID, MetaString& Name):id(ID),name(Name){type = 1002;} template void serialize(Handler &h, const int version) { h & id & name; } }; + +struct ChangeObjectVisitors : public CPackForClient // 1003 +{ + enum VisitMode + { + VISITOR_ADD, // mark hero as one that have visited this object + VISITOR_REMOVE, // unmark visitor, reversed to ADD + VISITOR_CLEAR // clear all visitors from this object (object reset) + }; + ui32 mode; // uses VisitMode enum + ObjectInstanceID object; + ObjectInstanceID hero; // note: hero owner will be also marked as "visited" this object + + DLL_LINKAGE void applyGs(CGameState *gs); + + ChangeObjectVisitors(ui32 mode, ObjectInstanceID object, ObjectInstanceID heroID = ObjectInstanceID(-1)): + mode(mode), + object(object), + hero(heroID) + { type = 1003; } + + template void serialize(Handler &h, const int version) + { + h & object & hero & mode; + } +}; + struct HeroLevelUp : public Query//2000 { void applyCl(CClient *cl); diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 7c39d5486..339b4c913 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -251,9 +251,9 @@ DLL_LINKAGE void GiveBonus::applyGs( CGameState *gs ) { bdescr.toString(descr); } - // Some of(?) versions of H3 use %s here instead of %d. Try to replace both of them - boost::replace_first(descr,"%d",boost::lexical_cast(std::abs(bonus.val))); - boost::replace_first(descr,"%s",boost::lexical_cast(std::abs(bonus.val))); + // Some of(?) versions of H3 use %s here instead of %d. Try to replace both of them + boost::replace_first(descr,"%d",boost::lexical_cast(std::abs(bonus.val))); + boost::replace_first(descr,"%s",boost::lexical_cast(std::abs(bonus.val))); } DLL_LINKAGE void ChangeObjPos::applyGs( CGameState *gs ) @@ -269,6 +269,23 @@ DLL_LINKAGE void ChangeObjPos::applyGs( CGameState *gs ) gs->map->addBlockVisTiles(obj); } +DLL_LINKAGE void ChangeObjectVisitors::applyGs( CGameState *gs ) +{ + switch (mode) { + case VISITOR_ADD: + gs->getHero(hero)->visitedObjects.insert(object); + gs->getPlayer(gs->getHero(hero)->tempOwner)->visitedObjects.insert(object); + break; + case VISITOR_CLEAR: + for (CGHeroInstance * hero : gs->map->allHeroes) + hero->visitedObjects.erase(object); // remove visit info from all heroes, including those that are not present on map + break; + case VISITOR_REMOVE: + gs->getHero(hero)->visitedObjects.erase(object); + break; + } +} + DLL_LINKAGE void PlayerEndsGame::applyGs( CGameState *gs ) { PlayerState *p = gs->getPlayer(player); From d14a16249dc55c9822b2da6ffdf72822f5334a8c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 11 Apr 2014 20:38:27 +0300 Subject: [PATCH 04/33] Core functionality for new objects in now in place, not yet tested - added selection of rewards - moved "on grant" message to reward struct - multiple minor fixes --- lib/CObjectWithReward.cpp | 212 +++++++++++++++++++++++--------------- lib/CObjectWithReward.h | 15 ++- 2 files changed, 140 insertions(+), 87 deletions(-) diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp index cd7ff778a..cec6c9c05 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/CObjectWithReward.cpp @@ -81,44 +81,58 @@ std::vector CObjectWithReward::getAvailableRewards(const CGHeroInstance * void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const { + auto grantRewardWithMessage = [&](int index) -> void + { + grantReward(index, h); + InfoWindow iw; + iw.player = h->tempOwner; + iw.soundID = soundID; + iw.text = info[index].message; + info[index].reward.loadComponents(iw.components); + cb->showInfoDialog(&iw); + }; + if (!wasVisited(h)) { auto rewards = getAvailableRewards(h); switch (rewards.size()) { - case 0: // no rewards, e.g. empty flotsam + case 0: // no available rewards, e.g. empty flotsam { InfoWindow iw; iw.player = h->tempOwner; iw.soundID = soundID; iw.text = onEmpty; cb->showInfoDialog(&iw); - onRewardGiven(h); + onRewardGiven(h); // FIXME: dummy call to properly act on empty objects (e.g. Floatsam that must be removed after visit) break; } case 1: // one reward. Just give it with message { - //FIXME: merge into grantReward call? - cb->setObjProperty(id, ObjProperty::REWARD_SELECT, rewards[0]); - grantReward(info[rewards[0]], h); - InfoWindow iw; - iw.player = h->tempOwner; - iw.soundID = soundID; - iw.text = onGrant; - info[rewards[0]].reward.loadComponents(iw.components); - cb->showInfoDialog(&iw); - onRewardGiven(h); + grantRewardWithMessage(rewards[0]); break; } - default: // multiple rewards. Let player select - {//TODO: implement various modes - BlockingDialog sd(false,true); - sd.player = h->tempOwner; - sd.soundID = soundID; - sd.text = onGrant; - for (auto index : rewards) - sd.components.push_back(info[index].reward.getDisplayedComponent()); - cb->showBlockingDialog(&sd); + default: // multiple rewards. Act according to select mode + { + switch (selectMode) { + case SELECT_PLAYER: // player must select + { + BlockingDialog sd(canRefuse, true); + sd.player = h->tempOwner; + sd.soundID = soundID; + sd.text = onSelect; + for (auto index : rewards) + sd.components.push_back(info[index].reward.getDisplayedComponent()); + cb->showBlockingDialog(&sd); + break; + } + case SELECT_FIRST: // give first available + grantRewardWithMessage(rewards[0]); + break; + case SELECT_RANDOM: // select one randomly + grantRewardWithMessage(rewards[cb->gameState()->getRandomGenerator().nextInt(rewards.size()-1)]); + break; + } return; } } @@ -140,11 +154,14 @@ void CObjectWithReward::heroLevelUpDone(const CGHeroInstance *hero) const void CObjectWithReward::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { + if (answer == 0) + return; // player refused + if (answer > 0 && answer-1 < info.size()) { + //NOTE: this relies on assumption that there won't be any changes in player/hero during blocking dialog auto list = getAvailableRewards(hero); - cb->setObjProperty(id, ObjProperty::REWARD_SELECT, list[answer - 1]); - grantReward(info[list[answer - 1]], hero); + grantReward(list[answer - 1], hero); } else { @@ -157,7 +174,16 @@ void CObjectWithReward::onRewardGiven(const CGHeroInstance * hero) const // no implementation, virtual function for overrides } -void CObjectWithReward::grantReward(const CVisitInfo & info, const CGHeroInstance * hero) const +void CObjectWithReward::grantReward(ui32 rewardID, const CGHeroInstance * hero) const +{ + ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD, id, hero->id); + cb->sendAndApply(&cov); + cb->setObjProperty(id, ObjProperty::REWARD_SELECT, rewardID); + + grantRewardBeforeLevelup(info[rewardID], hero); +} + +void CObjectWithReward::grantRewardBeforeLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const { assert(hero); assert(hero->tempOwner.isValidPlayer()); @@ -165,10 +191,6 @@ void CObjectWithReward::grantReward(const CVisitInfo & info, const CGHeroInstanc assert(info.reward.creatures.size() <= GameConstants::ARMY_SIZE); assert(!cb->isVisitCoveredByAnotherQuery(this, hero)); - //FIXME: move somewhere? - ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD, id, hero->id); - cb->sendAndApply(&cov); - cb->giveResources(hero->tempOwner, info.reward.resources); for (auto & entry : info.reward.secondary) @@ -185,7 +207,6 @@ void CObjectWithReward::grantReward(const CVisitInfo & info, const CGHeroInstanc if(info.reward.primary[i] > 0) cb->changePrimSkill(hero, static_cast(i), info.reward.primary[i], false); - si64 expToGive = 0; expToGive += VLC->heroh->reqExp(hero->level+info.reward.gainedLevels) - VLC->heroh->reqExp(hero->level); expToGive += hero->calculateXp(info.reward.gainedExp); @@ -371,7 +392,8 @@ CObjectWithReward::CObjectWithReward(): soundID(soundBase::invalid), selectMode(0), selectedReward(0), - resetDuration(0) + resetDuration(0), + canRefuse(false) {} /////////////////////////////////////////////////////////////////////////////////////////////////// @@ -416,30 +438,28 @@ void CGPickable::initObj() case Obj::CAMPFIRE: { soundID = soundBase::experience; - onGrant.addTxt(MetaString::ADVOB_TXT,23); int givenRes = cb->gameState()->getRandomGenerator().nextInt(5); int givenAmm = cb->gameState()->getRandomGenerator().nextInt(4, 6); info.resize(1); info[0].reward.resources[givenRes] = givenAmm; info[0].reward.resources[Res::GOLD]= givenAmm * 100; + info[0].message.addTxt(MetaString::ADVOB_TXT,23); break; } case Obj::FLOTSAM: { int type = cb->gameState()->getRandomGenerator().nextInt(3); soundID = soundBase::GENIE; - if (type == 0) - onEmpty.addTxt(MetaString::ADVOB_TXT, 51+type); - else - onGrant.addTxt(MetaString::ADVOB_TXT, 51+type); switch(type) { - //case 0: + case 0: + onEmpty.addTxt(MetaString::ADVOB_TXT, 51); case 1: { info.resize(1); info[0].reward.resources[Res::WOOD] = 5; + info[0].message.addTxt(MetaString::ADVOB_TXT, 52); break; } case 2: @@ -447,6 +467,7 @@ void CGPickable::initObj() info.resize(1); info[0].reward.resources[Res::WOOD] = 5; info[0].reward.resources[Res::GOLD] = 200; + info[0].message.addTxt(MetaString::ADVOB_TXT, 53); break; } case 3: @@ -454,6 +475,7 @@ void CGPickable::initObj() info.resize(1); info[0].reward.resources[Res::WOOD] = 10; info[0].reward.resources[Res::GOLD] = 500; + info[0].message.addTxt(MetaString::ADVOB_TXT, 54); break; } } @@ -465,21 +487,21 @@ void CGPickable::initObj() int hlp = cb->gameState()->getRandomGenerator().nextInt(99); if(hlp < 20) { - onGrant.addTxt(MetaString::ADVOB_TXT, 116); + onEmpty.addTxt(MetaString::ADVOB_TXT, 116); } else if(hlp < 90) { info.resize(1); info[0].reward.resources[Res::GOLD] = 1500; - onGrant.addTxt(MetaString::ADVOB_TXT, 118); + info[0].message.addTxt(MetaString::ADVOB_TXT, 118); } else { info.resize(1); loadRandomArtifact(info[0], 100, 0, 0, 0); info[0].reward.resources[Res::GOLD] = 1000; - onGrant.addTxt(MetaString::ADVOB_TXT, 117); - onGrant.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + info[0].message.addTxt(MetaString::ADVOB_TXT, 117); + info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); } } break; @@ -488,8 +510,8 @@ void CGPickable::initObj() soundID = soundBase::experience; info.resize(1); loadRandomArtifact(info[0], 55, 20, 20, 5); - onGrant.addTxt(MetaString::ADVOB_TXT, 125); - onGrant.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + info[0].message.addTxt(MetaString::ADVOB_TXT, 125); + info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); } break; case Obj::TREASURE_CHEST: @@ -500,14 +522,14 @@ void CGPickable::initObj() soundID = soundBase::treasure; info.resize(1); loadRandomArtifact(info[0], 100, 0, 0, 0); - onGrant.addTxt(MetaString::ADVOB_TXT,145); - onGrant.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + info[0].message.addTxt(MetaString::ADVOB_TXT,145); + info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); return; } else if (hlp >= 65) { soundID = soundBase::chest; - onGrant.addTxt(MetaString::ADVOB_TXT,146); + onSelect.addTxt(MetaString::ADVOB_TXT,146); info.resize(2); info[0].reward.resources[Res::GOLD] = 2000; info[1].reward.gainedExp = 1500; @@ -515,7 +537,7 @@ void CGPickable::initObj() else if(hlp >= 33) { soundID = soundBase::chest; - onGrant.addTxt(MetaString::ADVOB_TXT,146); + onSelect.addTxt(MetaString::ADVOB_TXT,146); info.resize(2); info[0].reward.resources[Res::GOLD] = 1500; info[1].reward.gainedExp = 1000; @@ -523,7 +545,7 @@ void CGPickable::initObj() else { soundID = soundBase::chest; - onGrant.addTxt(MetaString::ADVOB_TXT,146); + onSelect.addTxt(MetaString::ADVOB_TXT,146); info.resize(2); info[0].reward.resources[Res::GOLD] = 1000; info[1].reward.gainedExp = 500; @@ -559,9 +581,9 @@ void CGBonusingObject::initObj() configureBonusDuration(visit, Bonus::ONE_BATTLE, type, value, descrID); }; - auto configureMessage = [&](int onGrantID, int onVisitedID, soundBase::soundID sound) + auto configureMessage = [&](CVisitInfo & visit, int onGrantID, int onVisitedID, soundBase::soundID sound) { - onGrant.addTxt(MetaString::ADVOB_TXT, onGrantID); + visit.message.addTxt(MetaString::ADVOB_TXT, onGrantID); onVisited.addTxt(MetaString::ADVOB_TXT, onVisitedID); soundID = sound; }; @@ -575,28 +597,28 @@ void CGBonusingObject::initObj() switch(ID) { case Obj::BUOY: - configureMessage(21, 22, soundBase::MORALE); + configureMessage(visit, 21, 22, soundBase::MORALE); configureBonus(visit, Bonus::MORALE, +1, 94); break; case Obj::SWAN_POND: - configureMessage(29, 30, soundBase::LUCK); + configureMessage(visit, 29, 30, soundBase::LUCK); configureBonus(visit, Bonus::LUCK, 2, 67); visit.reward.movePercentage = 0; break; case Obj::FAERIE_RING: - configureMessage(49, 50, soundBase::LUCK); + configureMessage(visit, 49, 50, soundBase::LUCK); configureBonus(visit, Bonus::LUCK, 2, 71); break; case Obj::FOUNTAIN_OF_FORTUNE: selectMode = SELECT_RANDOM; - configureMessage(55, 56, soundBase::LUCK); + configureMessage(visit, 55, 56, soundBase::LUCK); info.resize(5); for (int i=0; i<5; i++) configureBonus(info[i], Bonus::LUCK, i-1, 69); //NOTE: description have %d that should be replaced with value break; case Obj::IDOL_OF_FORTUNE: - configureMessage(62, 63, soundBase::experience); + configureMessage(visit, 62, 63, soundBase::experience); info.resize(7); for (int i=0; i<6; i++) { @@ -609,40 +631,41 @@ void CGBonusingObject::initObj() break; case Obj::MERMAID: - configureMessage(83, 82, soundBase::LUCK); + configureMessage(visit, 83, 82, soundBase::LUCK); configureBonus(visit, Bonus::LUCK, 1, 72); break; case Obj::RALLY_FLAG: - configureMessage(111, 110, soundBase::MORALE); + configureMessage(visit, 111, 110, soundBase::MORALE); configureBonus(visit, Bonus::MORALE, 1, 102); configureBonus(visit, Bonus::LUCK, 1, 102); visit.reward.movePoints = 400; break; case Obj::OASIS: - configureMessage(95, 94, soundBase::MORALE); - onGrant.addTxt(MetaString::ADVOB_TXT, 95); + configureMessage(visit, 95, 94, soundBase::MORALE); configureBonus(visit, Bonus::MORALE, 1, 95); visit.reward.movePoints = 800; break; case Obj::TEMPLE: - configureMessage(140, 141, soundBase::temple); info[0].limiter.dayOfWeek = 7; info.resize(2); configureBonus(info[0], Bonus::MORALE, 2, 96); configureBonus(info[1], Bonus::MORALE, 1, 97); + + configureMessage(visit, 140, 141, soundBase::temple); + configureMessage(info[1], 140, 141, soundBase::temple); break; case Obj::WATERING_HOLE: - configureMessage(166, 167, soundBase::MORALE); + configureMessage(visit, 166, 167, soundBase::MORALE); configureBonus(visit, Bonus::MORALE, 1, 100); visit.reward.movePoints = 400; break; case Obj::FOUNTAIN_OF_YOUTH: - configureMessage(57, 58, soundBase::MORALE); + configureMessage(visit, 57, 58, soundBase::MORALE); configureBonus(visit, Bonus::MORALE, 1, 103); visit.reward.movePoints = 400; break; case Obj::STABLES: - configureMessage(137, 136, soundBase::STORE); + configureMessage(visit, 137, 136, soundBase::STORE); configureBonusDuration(visit, Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, 600, 0); visit.reward.movePoints = 600; @@ -681,7 +704,6 @@ void CGOnceVisitable::initObj() { case Obj::CORPSE: { - onGrant.addTxt(MetaString::ADVOB_TXT, 37); onEmpty.addTxt(MetaString::ADVOB_TXT, 38); soundID = soundBase::MYSTERY; blockVisit = true; @@ -689,6 +711,7 @@ void CGOnceVisitable::initObj() { info.resize(1); loadRandomArtifact(info[0], 10, 10, 10, 0); + info[0].message.addTxt(MetaString::ADVOB_TXT, 37); } } break; @@ -696,21 +719,19 @@ void CGOnceVisitable::initObj() case Obj::LEAN_TO: { soundID = soundBase::GENIE; - onGrant.addTxt(MetaString::ADVOB_TXT, 64); onEmpty.addTxt(MetaString::ADVOB_TXT, 65); info.resize(1); int type = cb->gameState()->getRandomGenerator().nextInt(5); //any basic resource without gold int value = cb->gameState()->getRandomGenerator().nextInt(1, 4); info[0].reward.resources[type] = value; + info[0].message.addTxt(MetaString::ADVOB_TXT, 64); } break; case Obj::WARRIORS_TOMB: { - // TODO: line 161 - ask if player wants to search the Tomb soundID = soundBase::GRAVEYARD; - onGrant.addTxt(MetaString::ADVOB_TXT, 162); - onVisited.addTxt(MetaString::ADVOB_TXT, 163); + onSelect.addTxt(MetaString::ADVOB_TXT, 161); info.resize(2); loadRandomArtifact(info[0], 30, 50, 25, 5); @@ -718,6 +739,8 @@ void CGOnceVisitable::initObj() Bonus bonus(Bonus::ONE_BATTLE, Bonus::MORALE, Bonus::OBJECT, -3, ID); info[0].reward.bonuses.push_back(bonus); info[1].reward.bonuses.push_back(bonus); + info[0].message.addTxt(MetaString::ADVOB_TXT, 162); + info[1].message.addTxt(MetaString::ADVOB_TXT, 163); } break; case Obj::WAGON: @@ -729,17 +752,17 @@ void CGOnceVisitable::initObj() if(hlp < 40) //minor or treasure art { - onGrant.addTxt(MetaString::ADVOB_TXT, 155); info.resize(1); loadRandomArtifact(info[0], 10, 10, 0, 0); + info[0].message.addTxt(MetaString::ADVOB_TXT, 155); } else if(hlp < 90) //2 - 5 of non-gold resource { - onGrant.addTxt(MetaString::ADVOB_TXT, 154); info.resize(1); int type = cb->gameState()->getRandomGenerator().nextInt(5); int value = cb->gameState()->getRandomGenerator().nextInt(2, 5); info[0].reward.resources[type] = value; + info[0].message.addTxt(MetaString::ADVOB_TXT, 154); } // or nothing } @@ -761,64 +784,76 @@ void CGVisitableOPH::initObj() { case Obj::ARENA: soundID = soundBase::NOMAD; - onGrant.addTxt(MetaString::ADVOB_TXT, 0); info.resize(2); info[0].reward.primary[PrimarySkill::ATTACK] = 2; info[1].reward.primary[PrimarySkill::DEFENSE] = 2; + onSelect.addTxt(MetaString::ADVOB_TXT, 0); + onVisited.addTxt(MetaString::ADVOB_TXT, 1); + canRefuse = true; break; case Obj::MERCENARY_CAMP: info.resize(1); info[0].reward.primary[PrimarySkill::ATTACK] = 1; soundID = soundBase::NOMAD; - onGrant.addTxt(MetaString::ADVOB_TXT, 80); + info[0].message.addTxt(MetaString::ADVOB_TXT, 80); + onVisited.addTxt(MetaString::ADVOB_TXT, 81); break; case Obj::MARLETTO_TOWER: info.resize(1); info[0].reward.primary[PrimarySkill::DEFENSE] = 1; soundID = soundBase::NOMAD; - onGrant.addTxt(MetaString::ADVOB_TXT, 39); + info[0].message.addTxt(MetaString::ADVOB_TXT, 39); + onVisited.addTxt(MetaString::ADVOB_TXT, 40); break; case Obj::STAR_AXIS: info.resize(1); info[0].reward.primary[PrimarySkill::SPELL_POWER] = 1; soundID = soundBase::gazebo; - onGrant.addTxt(MetaString::ADVOB_TXT, 100); + info[0].message.addTxt(MetaString::ADVOB_TXT, 100); + onVisited.addTxt(MetaString::ADVOB_TXT, 101); break; case Obj::GARDEN_OF_REVELATION: info.resize(1); info[0].reward.primary[PrimarySkill::KNOWLEDGE] = 1; soundID = soundBase::GETPROTECTION; - onGrant.addTxt(MetaString::ADVOB_TXT, 59); + info[0].message.addTxt(MetaString::ADVOB_TXT, 59); + onVisited.addTxt(MetaString::ADVOB_TXT, 60); break; case Obj::LEARNING_STONE: info.resize(1); info[0].reward.gainedExp = 1000; soundID = soundBase::gazebo; - onGrant.addTxt(MetaString::ADVOB_TXT, 143); + info[0].message.addTxt(MetaString::ADVOB_TXT, 143); + onVisited.addTxt(MetaString::ADVOB_TXT, 144); break; case Obj::TREE_OF_KNOWLEDGE: soundID = soundBase::gazebo; info.resize(1); + canRefuse = true; info[0].reward.gainedLevels = 1; - + onVisited.addTxt(MetaString::ADVOB_TXT, 147); info.resize(1); switch (cb->gameState()->getRandomGenerator().nextInt(2)) { case 0: // free + onSelect.addTxt(MetaString::ADVOB_TXT, 148); break; case 1: info[0].limiter.resources[Res::GOLD] = 2000; info[0].reward.resources[Res::GOLD] = -2000; + onSelect.addTxt(MetaString::ADVOB_TXT, 149); + onEmpty.addTxt(MetaString::ADVOB_TXT, 150); break; case 2: info[0].limiter.resources[Res::GEMS] = 10; info[0].reward.resources[Res::GEMS] = -10; + onSelect.addTxt(MetaString::ADVOB_TXT, 151); + onEmpty.addTxt(MetaString::ADVOB_TXT, 152); break; } break; case Obj::LIBRARY_OF_ENLIGHTENMENT: { - onGrant.addTxt(MetaString::ADVOB_TXT, 66); onVisited.addTxt(MetaString::ADVOB_TXT, 67); onEmpty.addTxt(MetaString::ADVOB_TXT, 68); @@ -834,6 +869,7 @@ void CGVisitableOPH::initObj() { visit.limiter.minLevel = 10 - i * 2; visit.limiter.secondary[SecondarySkill::DIPLOMACY] = i; + visit.message.addTxt(MetaString::ADVOB_TXT, 66); info.push_back(visit); } soundID = soundBase::gazebo; @@ -843,15 +879,21 @@ void CGVisitableOPH::initObj() info.resize(2); info[0].reward.primary[PrimarySkill::SPELL_POWER] = 1; info[1].reward.primary[PrimarySkill::KNOWLEDGE] = 1; + onSelect.addTxt(MetaString::ADVOB_TXT, 71); + onVisited.addTxt(MetaString::ADVOB_TXT, 72); + onEmpty.addTxt(MetaString::ADVOB_TXT, 73); soundID = soundBase::faerie; - onGrant.addTxt(MetaString::ADVOB_TXT, 71); + canRefuse = true; break; case Obj::SCHOOL_OF_WAR: info.resize(2); info[0].reward.primary[PrimarySkill::ATTACK] = 1; info[1].reward.primary[PrimarySkill::DEFENSE] = 1; + onSelect.addTxt(MetaString::ADVOB_TXT, 158); + onVisited.addTxt(MetaString::ADVOB_TXT, 159); + onEmpty.addTxt(MetaString::ADVOB_TXT, 160); soundID = soundBase::MILITARY; - onGrant.addTxt(MetaString::ADVOB_TXT, 158); + canRefuse = true; break; } } @@ -922,15 +964,15 @@ void CGVisitableOPW::initObj() { case Obj::MYSTICAL_GARDEN: soundID = soundBase::experience; - onGrant.addTxt(MetaString::ADVOB_TXT, 92); onEmpty.addTxt(MetaString::ADVOB_TXT, 93); info.resize(2); info[0].reward.resources[Res::GEMS] = 5; info[1].reward.resources[Res::GOLD] = 500; + info[0].message.addTxt(MetaString::ADVOB_TXT, 92); + info[1].message.addTxt(MetaString::ADVOB_TXT, 92); break; case Obj::WINDMILL: soundID = soundBase::GENIE; - onGrant.addTxt(MetaString::ADVOB_TXT, 170); onEmpty.addTxt(MetaString::ADVOB_TXT, 169); // 3-6 of any resource but wood and gold // this is UGLY. TODO: find better way to describe this @@ -940,25 +982,31 @@ void CGVisitableOPW::initObj() { CVisitInfo visit; visit.reward.resources[resID] = val; + visit.message.addTxt(MetaString::ADVOB_TXT, 92); info.push_back(visit); } } break; case Obj::WATER_WHEEL: soundID = soundBase::GENIE; - onGrant.addTxt(MetaString::ADVOB_TXT, 164); onEmpty.addTxt(MetaString::ADVOB_TXT, 165); info.resize(2); info[0].limiter.dayOfWeek = 7; // double amount on sunday info[0].reward.resources[Res::GOLD] = 1000; info[1].reward.resources[Res::GOLD] = 500; + info[0].message.addTxt(MetaString::ADVOB_TXT, 164); + info[1].message.addTxt(MetaString::ADVOB_TXT, 164); break; } } /////////////////////////////////////////////////////////////////////////////////////////////////// +void CGMagicSpring::initObj() +{ + +} std::vector CGMagicSpring::getVisitableOffsets() const { diff --git a/lib/CObjectWithReward.h b/lib/CObjectWithReward.h index 0df704c0b..71079102f 100644 --- a/lib/CObjectWithReward.h +++ b/lib/CObjectWithReward.h @@ -145,6 +145,8 @@ class DLL_LINKAGE CObjectWithReward : public CArmedInstance /// function that must be called if hero got level-up during grantReward call void grantRewardAfterLevelup(const CVisitInfo & reward, const CGHeroInstance * hero) const; + /// grants reward to hero + void grantRewardBeforeLevelup(const CVisitInfo & reward, const CGHeroInstance * hero) const; protected: /// controls selection of reward granted to player enum ESelectMode @@ -165,14 +167,13 @@ protected: /// filters list of visit info and returns rewards that can be granted to current hero virtual std::vector getAvailableRewards(const CGHeroInstance * hero) const; - /// grants reward to hero - void grantReward(const CVisitInfo & reward, const CGHeroInstance * hero) const; + void grantReward(ui32 rewardID, const CGHeroInstance * hero) const; /// Rewars that can be granted by an object std::vector info; /// MetaString's that contain text for messages for specific situations - MetaString onGrant; + MetaString onSelect; MetaString onVisited; MetaString onEmpty; @@ -188,6 +189,9 @@ protected: /// object visitability info will be reset each resetDuration days ui16 resetDuration; + /// if true - player can refuse visiting an object (e.g. Tomb) + bool canRefuse; + public: void setPropertyDer(ui8 what, ui32 val) override; const std::string & getHoverText() const override; @@ -216,8 +220,8 @@ public: template void serialize(Handler &h, const int version) { h & static_cast(*this); - h & info; - h & onGrant & onVisited & onEmpty; + h & info & canRefuse; + h & onSelect & onVisited & onEmpty; h & soundID & selectMode & selectedReward; } }; @@ -295,6 +299,7 @@ protected: std::vector getAvailableRewards(const CGHeroInstance * hero) const override; public: + void initObj() override; std::vector getVisitableOffsets() const; int3 getVisitableOffset() const override; From ba5092d6690c10c125856a5c27e2c17a9be38c44 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 11 Apr 2014 22:49:25 +0300 Subject: [PATCH 05/33] Some bugfixing, basic functionality of map objects seems to be working --- lib/CObjectWithReward.cpp | 26 ++++++++++++++++++-------- lib/NetPacks.h | 3 +++ lib/registerTypes/RegisterTypes.h | 15 ++++++++------- 3 files changed, 29 insertions(+), 15 deletions(-) diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp index cec6c9c05..2478ba3af 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/CObjectWithReward.cpp @@ -84,12 +84,16 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const auto grantRewardWithMessage = [&](int index) -> void { grantReward(index, h); - InfoWindow iw; - iw.player = h->tempOwner; - iw.soundID = soundID; - iw.text = info[index].message; - info[index].reward.loadComponents(iw.components); - cb->showInfoDialog(&iw); + // show message only if it is not empty + if (!info[index].message.toString().empty()) + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.soundID = soundID; + iw.text = info[index].message; + info[index].reward.loadComponents(iw.components); + cb->showInfoDialog(&iw); + } }; if (!wasVisited(h)) @@ -102,7 +106,10 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const InfoWindow iw; iw.player = h->tempOwner; iw.soundID = soundID; - iw.text = onEmpty; + if (!onEmpty.toString().empty()) + iw.text = onEmpty; + else + iw.text = onVisited; cb->showInfoDialog(&iw); onRewardGiven(h); // FIXME: dummy call to properly act on empty objects (e.g. Floatsam that must be removed after visit) break; @@ -142,7 +149,10 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const InfoWindow iw; iw.player = h->tempOwner; iw.soundID = soundID; - iw.text = onVisited; + if (!onVisited.toString().empty()) + iw.text = onVisited; + else + iw.text = onEmpty; cb->showInfoDialog(&iw); } } diff --git a/lib/NetPacks.h b/lib/NetPacks.h index fb241b334..543a34e26 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1076,6 +1076,9 @@ struct ChangeObjectVisitors : public CPackForClient // 1003 DLL_LINKAGE void applyGs(CGameState *gs); + ChangeObjectVisitors() + { type = 1003; } + ChangeObjectVisitors(ui32 mode, ObjectInstanceID object, ObjectInstanceID heroID = ObjectInstanceID(-1)): mode(mode), object(object), diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 8f25618e3..703e727ef 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -5,7 +5,7 @@ #include "../VCMI_Lib.h" #include "../CArtHandler.h" #include "../CObjectHandler.h" -#include "../CObjectWithReward.h" +#include "../CObjectWithReward.h" #include "../CGameState.h" #include "../CHeroHandler.h" #include "../CTownHandler.h" @@ -80,12 +80,12 @@ void registerTypesMapObjects2(Serializer &s) s.template registerType(); s.template registerType(); - s.template registerType(); - s.template registerType(); - s.template registerType(); - s.template registerType(); - s.template registerType(); - s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); @@ -188,6 +188,7 @@ void registerTypesClientPacks1(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); } template From a4e7987835633efe441fb3583a46f6abb2bce60c Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Apr 2014 14:35:48 +0300 Subject: [PATCH 06/33] Fixed some missing elements, mostly messages --- lib/CObjectWithReward.cpp | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp index 2478ba3af..5bdc7b445 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/CObjectWithReward.cpp @@ -598,15 +598,13 @@ void CGBonusingObject::initObj() soundID = sound; }; - if(ID == Obj::BUOY || ID == Obj::MERMAID) - blockVisit = true; - info.resize(1); CVisitInfo & visit = info[0]; switch(ID) { case Obj::BUOY: + blockVisit = true; configureMessage(visit, 21, 22, soundBase::MORALE); configureBonus(visit, Bonus::MORALE, +1, 94); break; @@ -621,26 +619,30 @@ void CGBonusingObject::initObj() break; case Obj::FOUNTAIN_OF_FORTUNE: selectMode = SELECT_RANDOM; - configureMessage(visit, 55, 56, soundBase::LUCK); info.resize(5); for (int i=0; i<5; i++) + { configureBonus(info[i], Bonus::LUCK, i-1, 69); //NOTE: description have %d that should be replaced with value + configureMessage(info[i], 55, 56, soundBase::LUCK); + } break; case Obj::IDOL_OF_FORTUNE: - configureMessage(visit, 62, 63, soundBase::experience); info.resize(7); for (int i=0; i<6; i++) { info[i].limiter.dayOfWeek = i+1; configureBonus(info[i], i%2 ? Bonus::MORALE : Bonus::LUCK, 1, 68); + configureMessage(info[i], 62, 63, soundBase::experience); } info.back().limiter.dayOfWeek = 7; configureBonus(info.back(), Bonus::MORALE, 1, 68); // on last day of week configureBonus(info.back(), Bonus::LUCK, 1, 68); + configureMessage(info.back(), 62, 63, soundBase::experience); break; case Obj::MERMAID: + blockVisit = true; configureMessage(visit, 83, 82, soundBase::LUCK); configureBonus(visit, Bonus::LUCK, 1, 72); break; @@ -725,7 +727,6 @@ void CGOnceVisitable::initObj() } } break; - case Obj::LEAN_TO: { soundID = soundBase::GENIE; @@ -737,7 +738,6 @@ void CGOnceVisitable::initObj() info[0].message.addTxt(MetaString::ADVOB_TXT, 64); } break; - case Obj::WARRIORS_TOMB: { soundID = soundBase::GRAVEYARD; @@ -1015,7 +1015,13 @@ void CGVisitableOPW::initObj() void CGMagicSpring::initObj() { - + CVisitInfo visit; // TODO: "player above max mana" limiter + visit.reward.manaPercentage = 200; + visit.message.addTxt(MetaString::ADVOB_TXT, 74); + info.push_back(visit); // two rewards, one for each entrance + info.push_back(visit); + onEmpty.addTxt(MetaString::ADVOB_TXT, 75); + soundID = soundBase::GENIE; } std::vector CGMagicSpring::getVisitableOffsets() const From cb5b5a05c1278e160a1092b9850db49ecb121fcb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Apr 2014 14:47:20 +0300 Subject: [PATCH 07/33] Player can refuse single reward (e.g. Tree of Knowledge) --- lib/CObjectWithReward.cpp | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp index 5bdc7b445..08b3ef62c 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/CObjectWithReward.cpp @@ -95,6 +95,16 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const cb->showInfoDialog(&iw); } }; + auto selectRewardsMessage = [&](std::vector rewards) -> void + { + BlockingDialog sd(canRefuse, rewards.size() > 1); + sd.player = h->tempOwner; + sd.soundID = soundID; + sd.text = onSelect; + for (auto index : rewards) + sd.components.push_back(info[index].reward.getDisplayedComponent()); + cb->showBlockingDialog(&sd); + }; if (!wasVisited(h)) { @@ -116,23 +126,18 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const } case 1: // one reward. Just give it with message { - grantRewardWithMessage(rewards[0]); + if (canRefuse) + selectRewardsMessage(rewards); + else + grantRewardWithMessage(rewards[0]); break; } default: // multiple rewards. Act according to select mode { switch (selectMode) { case SELECT_PLAYER: // player must select - { - BlockingDialog sd(canRefuse, true); - sd.player = h->tempOwner; - sd.soundID = soundID; - sd.text = onSelect; - for (auto index : rewards) - sd.components.push_back(info[index].reward.getDisplayedComponent()); - cb->showBlockingDialog(&sd); + selectRewardsMessage(rewards); break; - } case SELECT_FIRST: // give first available grantRewardWithMessage(rewards[0]); break; @@ -445,7 +450,7 @@ void CGPickable::initObj() blockVisit = true; switch(ID) { - case Obj::CAMPFIRE: + case Obj::CAMPFIRE: //FIXME: campfire is not functioning correctly in game (no visible message) { soundID = soundBase::experience; int givenRes = cb->gameState()->getRandomGenerator().nextInt(5); From 2c8af871fc27017c1b1a1b7f420efb1ad5aa1c48 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Apr 2014 16:16:23 +0300 Subject: [PATCH 08/33] Added possibility to add custom components to reward description. Used to properly display morale/luck rewards since bonuses don't provide components associated with them. --- lib/CObjectWithReward.cpp | 7 +++++++ lib/CObjectWithReward.h | 3 +++ 2 files changed, 10 insertions(+) diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp index 08b3ef62c..8e864650b 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/CObjectWithReward.cpp @@ -322,6 +322,9 @@ bool CObjectWithReward::wasVisited (const CGHeroInstance * h) const void CRewardInfo::loadComponents(std::vector & comps) const { + for (auto comp : extraComponents) + comps.push_back(comp); + for (size_t i=0; igeneraltexth->advobtxt[descrID] : ""); visit.reward.bonuses.push_back(b); + if (type == Bonus::MORALE) + visit.reward.extraComponents.push_back(Component(Component::MORALE, 0, value, 0)); + if (type == Bonus::LUCK) + visit.reward.extraComponents.push_back(Component(Component::LUCK, 0, value, 0)); }; auto configureBonus = [&](CVisitInfo & visit, Bonus::BonusType type, si32 value, si32 descrID) diff --git a/lib/CObjectWithReward.h b/lib/CObjectWithReward.h index 71079102f..c522c9f61 100644 --- a/lib/CObjectWithReward.h +++ b/lib/CObjectWithReward.h @@ -93,6 +93,9 @@ public: std::vector spells; std::vector creatures; + /// list of components that will be added to reward description. First entry in list will override displayed component + std::vector extraComponents; + /// Generates list of components that describes reward virtual void loadComponents(std::vector & comps) const; Component getDisplayedComponent() const; From a6c0886d03f3c59c99149aebfc8b495b0fb137eb Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 12 Apr 2014 16:47:48 +0300 Subject: [PATCH 09/33] It is possible to remove object as part of "reward" --- lib/CObjectWithReward.cpp | 40 +++++++++++++++++++++++++++------------ lib/CObjectWithReward.h | 9 ++++++--- 2 files changed, 34 insertions(+), 15 deletions(-) diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp index 8e864650b..c40de6abc 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/CObjectWithReward.cpp @@ -226,10 +226,10 @@ void CObjectWithReward::grantRewardBeforeLevelup(const CVisitInfo & info, const expToGive += VLC->heroh->reqExp(hero->level+info.reward.gainedLevels) - VLC->heroh->reqExp(hero->level); expToGive += hero->calculateXp(info.reward.gainedExp); if (expToGive) - { cb->changePrimSkill(hero, PrimarySkill::EXPERIENCE, expToGive); - } - else + + // hero is not blocked by levelup dialog - grant remainer immediately + if (!cb->isVisitCoveredByAnotherQuery(this, hero)) { grantRewardAfterLevelup(info, hero); } @@ -286,6 +286,9 @@ void CObjectWithReward::grantRewardAfterLevelup(const CVisitInfo & info, const C } onRewardGiven(hero); + + if (info.reward.removeObject) + cb->removeObject(this); } bool CObjectWithReward::wasVisited (PlayerColor player) const @@ -314,7 +317,7 @@ bool CObjectWithReward::wasVisited (const CGHeroInstance * h) const switch (visitMode) { case VISIT_HERO: - return vstd::contains(h->visitedObjects, ObjectInstanceID(ID)); + return vstd::contains(h->visitedObjects, ObjectInstanceID(ID)) || h->hasBonusFrom(Bonus::OBJECT, ID); default: return wasVisited(h->tempOwner); } @@ -378,7 +381,7 @@ const std::string & CObjectWithReward::getHoverText() const { bool visited = wasVisited(cb->getCurrentPlayer()); if (h) - visited |= wasVisited(h) || h->hasBonusFrom(Bonus::OBJECT,ID); + visited |= wasVisited(h); hoverName += " " + visitedTxt(visited); } @@ -463,6 +466,7 @@ void CGPickable::initObj() info[0].reward.resources[givenRes] = givenAmm; info[0].reward.resources[Res::GOLD]= givenAmm * 100; info[0].message.addTxt(MetaString::ADVOB_TXT,23); + info[0].reward.removeObject = true; break; } case Obj::FLOTSAM: @@ -472,12 +476,15 @@ void CGPickable::initObj() switch(type) { case 0: - onEmpty.addTxt(MetaString::ADVOB_TXT, 51); + info.resize(1); + info[0].message.addTxt(MetaString::ADVOB_TXT, 51); + info[0].reward.removeObject = true; case 1: { info.resize(1); info[0].reward.resources[Res::WOOD] = 5; info[0].message.addTxt(MetaString::ADVOB_TXT, 52); + info[0].reward.removeObject = true; break; } case 2: @@ -486,6 +493,7 @@ void CGPickable::initObj() info[0].reward.resources[Res::WOOD] = 5; info[0].reward.resources[Res::GOLD] = 200; info[0].message.addTxt(MetaString::ADVOB_TXT, 53); + info[0].reward.removeObject = true; break; } case 3: @@ -494,6 +502,7 @@ void CGPickable::initObj() info[0].reward.resources[Res::WOOD] = 10; info[0].reward.resources[Res::GOLD] = 500; info[0].message.addTxt(MetaString::ADVOB_TXT, 54); + info[0].reward.removeObject = true; break; } } @@ -505,13 +514,16 @@ void CGPickable::initObj() int hlp = cb->gameState()->getRandomGenerator().nextInt(99); if(hlp < 20) { - onEmpty.addTxt(MetaString::ADVOB_TXT, 116); + info.resize(1); + info[0].message.addTxt(MetaString::ADVOB_TXT, 116); + info[0].reward.removeObject = true; } else if(hlp < 90) { info.resize(1); info[0].reward.resources[Res::GOLD] = 1500; info[0].message.addTxt(MetaString::ADVOB_TXT, 118); + info[0].reward.removeObject = true; } else { @@ -520,6 +532,7 @@ void CGPickable::initObj() info[0].reward.resources[Res::GOLD] = 1000; info[0].message.addTxt(MetaString::ADVOB_TXT, 117); info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + info[0].reward.removeObject = true; } } break; @@ -530,6 +543,7 @@ void CGPickable::initObj() loadRandomArtifact(info[0], 55, 20, 20, 5); info[0].message.addTxt(MetaString::ADVOB_TXT, 125); info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + info[0].reward.removeObject = true; } break; case Obj::TREASURE_CHEST: @@ -542,6 +556,7 @@ void CGPickable::initObj() loadRandomArtifact(info[0], 100, 0, 0, 0); info[0].message.addTxt(MetaString::ADVOB_TXT,145); info[0].message.addReplacement(MetaString::ART_NAMES, info[0].reward.artifacts.back()); + info[0].reward.removeObject = true; return; } else if (hlp >= 65) @@ -551,6 +566,8 @@ void CGPickable::initObj() info.resize(2); info[0].reward.resources[Res::GOLD] = 2000; info[1].reward.gainedExp = 1500; + info[0].reward.removeObject = true; + info[1].reward.removeObject = true; } else if(hlp >= 33) { @@ -559,6 +576,8 @@ void CGPickable::initObj() info.resize(2); info[0].reward.resources[Res::GOLD] = 1500; info[1].reward.gainedExp = 1000; + info[0].reward.removeObject = true; + info[1].reward.removeObject = true; } else { @@ -567,17 +586,14 @@ void CGPickable::initObj() info.resize(2); info[0].reward.resources[Res::GOLD] = 1000; info[1].reward.gainedExp = 500; + info[0].reward.removeObject = true; + info[1].reward.removeObject = true; } } break; } } -void CGPickable::onRewardGiven(const CGHeroInstance * hero) const -{ - cb->removeObject(this); -} - /////////////////////////////////////////////////////////////////////////////////////////////////// CGBonusingObject::CGBonusingObject() diff --git a/lib/CObjectWithReward.h b/lib/CObjectWithReward.h index c522c9f61..bae21f885 100644 --- a/lib/CObjectWithReward.h +++ b/lib/CObjectWithReward.h @@ -96,6 +96,9 @@ public: /// list of components that will be added to reward description. First entry in list will override displayed component std::vector extraComponents; + /// if set to true, object will be removed after granting reward + bool removeObject; + /// Generates list of components that describes reward virtual void loadComponents(std::vector & comps) const; Component getDisplayedComponent() const; @@ -107,12 +110,13 @@ public: manaPercentage(-1), movePoints(0), movePercentage(-1), - primary(4, 0) + primary(4, 0), + removeObject(false) {} template void serialize(Handler &h, const int version) { - h & resources; + h & resources & extraComponents & removeObject; h & gainedExp & gainedLevels & manaDiff & movePoints; h & primary & secondary & bonuses; h & artifacts & spells & creatures; @@ -233,7 +237,6 @@ class DLL_LINKAGE CGPickable : public CObjectWithReward //campfire, treasure che { public: void initObj() override; - void onRewardGiven(const CGHeroInstance *hero) const; CGPickable(); From 7725232253b56828b268178364723da838d8e651 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 25 Apr 2014 17:59:05 +0300 Subject: [PATCH 10/33] backup of current progress, started design of "Object configurer" that will be responsible for configuring of ObjectWithReward's from json config. --- lib/CDefObjInfoHandler.h | 57 +++++++++++++++++++++++++++++++++++++++ lib/CMakeLists.txt | 4 +-- lib/CObjectWithReward.cpp | 4 ++- lib/CObjectWithReward.h | 7 ++++- 4 files changed, 68 insertions(+), 4 deletions(-) diff --git a/lib/CDefObjInfoHandler.h b/lib/CDefObjInfoHandler.h index ccc41bb7f..60761912b 100644 --- a/lib/CDefObjInfoHandler.h +++ b/lib/CDefObjInfoHandler.h @@ -108,3 +108,60 @@ public: h & objects; } }; + +class IObjectInfo +{ +public: + virtual bool givesResources() const = 0; + + virtual bool givesExperience() const = 0; + virtual bool givesMana() const = 0; + virtual bool givesMovement() const = 0; + + virtual bool givesPrimarySkills() const = 0; + virtual bool givesSecondarySkills() const = 0; + + virtual bool givesArtifacts() const = 0; + virtual bool givesCreatures() const = 0; + virtual bool givesSpells() const = 0; + + virtual bool givesBonuses() const = 0; +}; + +class CGObjectInstance; + +class IObjectTypeHandler +{ +public: + virtual CGObjectInstance * create(ui32 id, ui32 subID) const = 0; + + virtual bool handlesID(ui32 id) const = 0; + + virtual void configureObject(CGObjectInstance * object) const = 0; + + virtual IObjectInfo * getObjectInfo(ui32 id, ui32 subID) const = 0; +}; + +typedef std::shared_ptr TObjectTypeHandler; + +class CObjectTypesHandler +{ + /// list of object handlers, each of them handles 1 or more object type + std::vector objectTypes; + +public: + /// returns handler for specified object (ID-based). ObjectHandler keeps ownership + IObjectTypeHandler * getHandlerFor(CObjectTemplate tmpl) const; + + /// creates object based on specified template + CGObjectInstance * createObject(CObjectTemplate tmpl); + + template + CObjectClass * createObjectTyped(CObjectTemplate tmpl) + { + auto objInst = createObject(tmpl); + auto objClass = dynamic_cast(objInst); + assert(objClass); + return objClass; + } +} diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index cbf51ca66..6f9a24937 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -69,7 +69,7 @@ set(lib_SRCS CModHandler.cpp CObstacleInstance.cpp CObjectWithReward.cpp - CObjectWithReward.h + CObjectConstructor.cpp CSpellHandler.cpp CThreadHelper.cpp CTownHandler.cpp @@ -119,5 +119,5 @@ set_target_properties(vcmi PROPERTIES ${PCH_PROPERTIES}) cotire(vcmi) if (NOT APPLE) # Already inside vcmiclient bundle - install(TARGETS vcmi DESTINATION ${LIB_DIR}) + install(TARGETS vcmi DESTINATION ${LIB_DIR}) endif() diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp index c40de6abc..ef6f119d3 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/CObjectWithReward.cpp @@ -447,7 +447,7 @@ static void loadRandomArtifact(CVisitInfo & info, int treasure, int minor, int m CGPickable::CGPickable() { - visitMode = VISIT_ONCE; + visitMode = VISIT_UNLIMITED; selectMode = SELECT_PLAYER; } @@ -935,6 +935,8 @@ void CGVisitableOPH::initObj() break; } } + +//TODO: re-enable. Probably in some different form but still necessary /* const std::string & CGVisitableOPH::getHoverText() const { diff --git a/lib/CObjectWithReward.h b/lib/CObjectWithReward.h index bae21f885..877e649c5 100644 --- a/lib/CObjectWithReward.h +++ b/lib/CObjectWithReward.h @@ -13,6 +13,8 @@ * */ +class CRandomRewardObjectInfo; + /// Limiters of rewards. Rewards will be granted to hero only if he satisfies requirements /// Note: for this is only a test - it won't remove anything from hero (e.g. artifacts or creatures) /// NOTE: in future should (partially) replace seer hut/quest guard quests checks @@ -165,7 +167,7 @@ protected: enum EVisitMode { - VISIT_UNLIMITED, // any number of times + VISIT_UNLIMITED, // any number of times. Side effect - object hover text won't contain visited/not visited text VISIT_ONCE, // only once, first to visit get all the rewards VISIT_HERO, // every hero can visit object once VISIT_PLAYER // every player can visit object once @@ -231,6 +233,9 @@ public: h & onSelect & onVisited & onEmpty; h & soundID & selectMode & selectedReward; } + + // for configuration/object setup + friend class CRandomRewardObjectInfo; }; class DLL_LINKAGE CGPickable : public CObjectWithReward //campfire, treasure chest, Flotsam, Shipwreck Survivor, Sea Chest From 153dd1f5f998523106369e51f054d13b7424808b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 27 Apr 2014 15:19:23 +0300 Subject: [PATCH 11/33] - Added new files that should have been in last commit --- lib/CMakeLists.txt | 2 +- lib/CObjectConstructor.cpp | 12 ++++++++ lib/CObjectConstructor.h | 57 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 lib/CObjectConstructor.cpp create mode 100644 lib/CObjectConstructor.h diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 6f9a24937..a4010b48b 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -68,8 +68,8 @@ set(lib_SRCS CHeroHandler.cpp CModHandler.cpp CObstacleInstance.cpp - CObjectWithReward.cpp CObjectConstructor.cpp + CObjectWithReward.cpp CSpellHandler.cpp CThreadHelper.cpp CTownHandler.cpp diff --git a/lib/CObjectConstructor.cpp b/lib/CObjectConstructor.cpp new file mode 100644 index 000000000..8b5edc482 --- /dev/null +++ b/lib/CObjectConstructor.cpp @@ -0,0 +1,12 @@ +#include "StdInc.h" +#include "CObjectConstructor.h" + +/* + * CObjectConstructor.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 + * + */ diff --git a/lib/CObjectConstructor.h b/lib/CObjectConstructor.h new file mode 100644 index 000000000..86ed80cb7 --- /dev/null +++ b/lib/CObjectConstructor.h @@ -0,0 +1,57 @@ +#pragma once + +#include "CObjectWithReward.h" +#include "CDefObjInfoHandler.h" +#include "JsonNode.h" + +/* + * CObjectConstructor.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 CRandomRewardObjectInfo : public IObjectInfo +{ + JsonNode objectConfig; + +public: + bool givesResources() const override; + + bool givesExperience() const override; + bool givesMana() const override; + bool givesMovement() const override; + + bool givesPrimarySkills() const override; + bool givesSecondarySkills() const override; + + bool givesArtifacts() const override; + bool givesCreatures() const override; + bool givesSpells() const override; + + bool givesBonuses() const override; + + void configureObject(CObjectWithReward * object) const; + + CRandomRewardObjectInfo() + {} + + void init(JsonNode objectConfig); +}; + +class CObjectWithRewardConstructor : public IObjectTypeHandler +{ + std::map > objectConfigs; + +public: + CGObjectInstance * create(ui32 id, ui32 subID) const override; + + bool handlesID(ui32 id) const override; + + void configureObject(CGObjectInstance * object) const override; + + IObjectInfo * getObjectInfo(ui32 id, ui32 subID) const override; +}; From d805376ab80d91293ddbf15d35e2796cb30d1d33 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 27 Apr 2014 20:37:53 +0300 Subject: [PATCH 12/33] Mostly final interface for object type handler Partial implementation of object with reward constructor --- lib/CDefObjInfoHandler.cpp | 7 +++-- lib/CDefObjInfoHandler.h | 22 ++++++++------ lib/CObjectConstructor.cpp | 62 ++++++++++++++++++++++++++++++++++++++ lib/CObjectConstructor.h | 26 +++++++++++----- 4 files changed, 96 insertions(+), 21 deletions(-) diff --git a/lib/CDefObjInfoHandler.cpp b/lib/CDefObjInfoHandler.cpp index b31d18a54..04dbe3f75 100644 --- a/lib/CDefObjInfoHandler.cpp +++ b/lib/CDefObjInfoHandler.cpp @@ -182,8 +182,8 @@ void ObjectTemplate::readMap(CBinaryReader & reader) void ObjectTemplate::readJson(const JsonNode &node) { - id = Obj(node["basebase"].Float()); // temporary, should be removed and determined indirectly via object type parent (e.g. base->base) - subid = node["base"].Float(); + //id = Obj(node["basebase"].Float()); // temporary, should be removed and determined indirectly via object type parent (e.g. base->base) + //subid = node["base"].Float(); animationFile = node["animation"].String(); const JsonVector & visitDirs = node["visitableFrom"].Vector(); @@ -347,7 +347,7 @@ CDefObjInfoHandler::CDefObjInfoHandler() { readTextFile("Data/Objects.txt"); readTextFile("Data/Heroes.txt"); - +/* // TODO: merge into modding system JsonNode node = JsonUtils::assembleFromFiles("config/objectTemplates.json"); node.setMeta("core"); @@ -371,6 +371,7 @@ CDefObjInfoHandler::CDefObjInfoHandler() // merge new templates into storage objects.insert(objects.end(), newTemplates.begin(), newTemplates.end()); +*/ } void CDefObjInfoHandler::eraseAll(Obj type, si32 subtype) diff --git a/lib/CDefObjInfoHandler.h b/lib/CDefObjInfoHandler.h index 60761912b..d5c05de48 100644 --- a/lib/CDefObjInfoHandler.h +++ b/lib/CDefObjInfoHandler.h @@ -130,38 +130,40 @@ public: class CGObjectInstance; -class IObjectTypeHandler +class IObjectTypesHandler { public: - virtual CGObjectInstance * create(ui32 id, ui32 subID) const = 0; + virtual std::vector getTemplates(si32 type, si32 subType) const = 0; - virtual bool handlesID(ui32 id) const = 0; + virtual CGObjectInstance * create(ObjectTemplate tmpl) const = 0; + + virtual bool handlesID(ObjectTemplate tmpl) const = 0; virtual void configureObject(CGObjectInstance * object) const = 0; - virtual IObjectInfo * getObjectInfo(ui32 id, ui32 subID) const = 0; + virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const = 0; }; -typedef std::shared_ptr TObjectTypeHandler; +typedef std::shared_ptr TObjectTypeHandler; -class CObjectTypesHandler +class CObjectGroupsHandler { /// list of object handlers, each of them handles 1 or more object type std::vector objectTypes; public: /// returns handler for specified object (ID-based). ObjectHandler keeps ownership - IObjectTypeHandler * getHandlerFor(CObjectTemplate tmpl) const; + IObjectTypesHandler * getHandlerFor(ObjectTemplate tmpl) const; /// creates object based on specified template - CGObjectInstance * createObject(CObjectTemplate tmpl); + CGObjectInstance * createObject(ObjectTemplate tmpl); template - CObjectClass * createObjectTyped(CObjectTemplate tmpl) + CObjectClass * createObjectTyped(ObjectTemplate tmpl) { auto objInst = createObject(tmpl); auto objClass = dynamic_cast(objInst); assert(objClass); return objClass; } -} +}; diff --git a/lib/CObjectConstructor.cpp b/lib/CObjectConstructor.cpp index 8b5edc482..a5a5fb198 100644 --- a/lib/CObjectConstructor.cpp +++ b/lib/CObjectConstructor.cpp @@ -10,3 +10,65 @@ * Full text of license available in license.txt file, in main folder * */ + +CObjectWithRewardConstructor::CObjectWithRewardConstructor() +{ +} + +void CObjectWithRewardConstructor::init(const JsonNode & config) +{ + int id = config["id"].Float(); + std::string name = config["name"].String(); + for (auto & entry : config["types"].Struct()) // for each object type + { + JsonNode typeConf = entry.second; + + int subID = typeConf["id"].Float(); + + objectInfos[id][subID].info.init(typeConf["properties"]); + for (auto entry : typeConf["templates"].Struct()) + { + ObjectTemplate tmpl; + tmpl.id = Obj(id); + tmpl.subid = subID; + tmpl.readJson(entry.second); + objectInfos[id][subID].templates.push_back(tmpl); + } + } +} + +std::vector CObjectWithRewardConstructor::getTemplates(si32 type, si32 subType) const +{ + assert(handlesID(type, subtype)); + return objectInfos.at(type).at(subType).templates; +} + +CGObjectInstance * CObjectWithRewardConstructor::create(ObjectTemplate tmpl) const +{ + assert(handlesID(tmpl)); + auto ret = new CObjectWithReward(); + ret->appearance = tmpl; + return ret; +} + +bool CObjectWithRewardConstructor::handlesID(si32 id, si32 subID) const +{ + return objectInfos.count(id) && objectInfos.at(id).count(subID); +} + +bool CObjectWithRewardConstructor::handlesID(ObjectTemplate tmpl) const +{ + return handlesID(tmpl.id, tmpl.subid); +} + +void CObjectWithRewardConstructor::configureObject(CGObjectInstance * object) const +{ + assert(handlesID(object->appearance)); + objectInfos.at(object->ID).at(object->subID).info.configureObject(dynamic_cast(object)); +} + +const IObjectInfo * CObjectWithRewardConstructor::getObjectInfo(ObjectTemplate tmpl) const +{ + assert(handlesID(tmpl)); + return &objectInfos.at(tmpl.id).at(tmpl.subid).info; +} diff --git a/lib/CObjectConstructor.h b/lib/CObjectConstructor.h index 86ed80cb7..ceef728b2 100644 --- a/lib/CObjectConstructor.h +++ b/lib/CObjectConstructor.h @@ -16,8 +16,7 @@ class CRandomRewardObjectInfo : public IObjectInfo { - JsonNode objectConfig; - + JsonNode parameters; public: bool givesResources() const override; @@ -39,19 +38,30 @@ public: CRandomRewardObjectInfo() {} - void init(JsonNode objectConfig); + void init(const JsonNode & objectConfig); }; -class CObjectWithRewardConstructor : public IObjectTypeHandler +class CObjectWithRewardConstructor : public IObjectTypesHandler { - std::map > objectConfigs; + struct ObjectInfo + { + CRandomRewardObjectInfo info; + std::vector templates; + }; + std::map > objectInfos; public: - CGObjectInstance * create(ui32 id, ui32 subID) const override; + CObjectWithRewardConstructor(); + void init(const JsonNode & config); - bool handlesID(ui32 id) const override; + std::vector getTemplates(si32 type, si32 subType) const override; + + CGObjectInstance * create(ObjectTemplate tmpl) const override; + + bool handlesID(si32 id, si32 subID) const; + bool handlesID(ObjectTemplate tmpl) const override; void configureObject(CGObjectInstance * object) const override; - IObjectInfo * getObjectInfo(ui32 id, ui32 subID) const override; + const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const override; }; From b5160acbac2f531a6f1bc8a93c5c27a4e0a6c3ee Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Fri, 16 May 2014 23:50:02 +0300 Subject: [PATCH 13/33] Finalization of object type handler interface - updated code to use new interface - removed old DefObjHandler (todo - rename file) Summary: - most code but loading is now in place - type names may deserve improvements (some of them are too similar) - still barely compiles and not tested --- client/CGameInfo.cpp | 2 +- client/CGameInfo.h | 5 +- lib/CArtHandler.cpp | 7 ++- lib/CCreatureHandler.cpp | 5 +- lib/CDefObjInfoHandler.cpp | 106 +++++++++++++++++++------------------ lib/CDefObjInfoHandler.h | 84 ++++++++++++----------------- lib/CGameState.cpp | 24 +++++---- lib/CHeroHandler.cpp | 5 +- lib/CObjectConstructor.cpp | 92 ++++++++++++++++++-------------- lib/CObjectConstructor.h | 14 +---- lib/CObjectHandler.cpp | 8 +-- lib/CTownHandler.cpp | 9 ++-- lib/NetPacksLib.cpp | 4 +- lib/VCMI_Lib.cpp | 6 +-- lib/VCMI_Lib.h | 6 +-- lib/rmg/CMapGenerator.cpp | 2 +- 16 files changed, 184 insertions(+), 195 deletions(-) diff --git a/client/CGameInfo.cpp b/client/CGameInfo.cpp index 06786663f..cf2f596f0 100644 --- a/client/CGameInfo.cpp +++ b/client/CGameInfo.cpp @@ -31,5 +31,5 @@ void CGameInfo::setFromLib() heroh = VLC->heroh; objh = VLC->objh; spellh = VLC->spellh; - dobjinfo = VLC->dobjinfo; + objtypeh = VLC->objtypeh; } diff --git a/client/CGameInfo.h b/client/CGameInfo.h index 4ea111a62..f56429555 100644 --- a/client/CGameInfo.h +++ b/client/CGameInfo.h @@ -23,7 +23,7 @@ class CBuildingHandler; class CObjectHandler; class CSoundHandler; class CMusicHandler; -class CDefObjInfoHandler; +class CObjectTypesHandler; class CTownHandler; class CGeneralTextHandler; class CConsoleHandler; @@ -57,11 +57,10 @@ public: ConstTransitivePtr creh; ConstTransitivePtr spellh; ConstTransitivePtr objh; - ConstTransitivePtr dobjinfo; + ConstTransitivePtr objtypeh; CGeneralTextHandler * generaltexth; CMapHandler * mh; CTownHandler * townh; - //CTownHandler * townh; void setFromLib(); diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index f7c5393f8..e3008a582 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -648,7 +648,7 @@ void CArtHandler::afterLoadFinalization() } //Note: "10" is used here because H3 text files don't define any template for art with ID 0 - ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::ARTIFACT, 10).front(); + ObjectTemplate base = VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, 10)->getTemplates().front(); for (CArtifact * art : artifacts) { if (!art->advMapDef.empty()) @@ -656,10 +656,9 @@ void CArtHandler::afterLoadFinalization() base.animationFile = art->advMapDef; base.subid = art->id; - // replace existing (if any) and add new template. + // add new template. // Necessary for objects added via mods that don't have any templates in H3 - VLC->dobjinfo->eraseAll(Obj::ARTIFACT, art->id); - VLC->dobjinfo->registerTemplate(base); + VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->id)->addTemplate(base); } } } diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index dfba235b8..3f8bd9397 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -1114,7 +1114,7 @@ void CCreatureHandler::buildBonusTreeForTiers() void CCreatureHandler::afterLoadFinalization() { - ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::MONSTER, 0).front(); + ObjectTemplate base = VLC->objtypeh->getHandlerFor(Obj::MONSTER, 0)->getTemplates().front(); for (CCreature * crea : creatures) { if (!crea->advMapDef.empty()) @@ -1124,8 +1124,7 @@ void CCreatureHandler::afterLoadFinalization() // replace existing (if any) and add new template. // Necessary for objects added via mods that don't have any templates in H3 - VLC->dobjinfo->eraseAll(Obj::MONSTER, crea->idNumber); - VLC->dobjinfo->registerTemplate(base); + VLC->objtypeh->getHandlerFor(Obj::MONSTER, crea->idNumber)->addTemplate(base); } } } diff --git a/lib/CDefObjInfoHandler.cpp b/lib/CDefObjInfoHandler.cpp index 04dbe3f75..b75ba812d 100644 --- a/lib/CDefObjInfoHandler.cpp +++ b/lib/CDefObjInfoHandler.cpp @@ -7,6 +7,7 @@ #include "GameConstants.h" #include "StringConstants.h" #include "CGeneralTextHandler.h" +#include "CObjectHandler.h" #include "CModHandler.h" #include "JsonNode.h" @@ -327,7 +328,7 @@ bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const { return allowedTerrains.count(terrain) != 0; } - +/* void CDefObjInfoHandler::readTextFile(std::string path) { CLegacyConfigParser parser(path); @@ -347,81 +348,82 @@ CDefObjInfoHandler::CDefObjInfoHandler() { readTextFile("Data/Objects.txt"); readTextFile("Data/Heroes.txt"); -/* - // TODO: merge into modding system - JsonNode node = JsonUtils::assembleFromFiles("config/objectTemplates.json"); - node.setMeta("core"); - std::vector newTemplates; - newTemplates.reserve(node.Struct().size()); - - // load all new templates - for (auto & entry : node.Struct()) - { - JsonUtils::validate(entry.second, "vcmi:objectTemplate", entry.first); - - ObjectTemplate templ; - templ.stringID = entry.first; - templ.readJson(entry.second); - newTemplates.push_back(templ); - } - - // erase old ones to avoid conflicts - for (auto & entry : newTemplates) - eraseAll(entry.id, entry.subid); - - // merge new templates into storage - objects.insert(objects.end(), newTemplates.begin(), newTemplates.end()); +} */ +void CObjectTypesHandler::init() +{ + } -void CDefObjInfoHandler::eraseAll(Obj type, si32 subtype) +TObjectTypeHandler CObjectTypesHandler::getHandlerFor(si32 type, si32 subtype) const { - auto it = std::remove_if(objects.begin(), objects.end(), [&](const ObjectTemplate & obj) + if (objectTypes.count(type)) { - return obj.id == type && obj.subid == subtype; - }); - objects.erase(it, objects.end()); + if (objectTypes.at(type).count(subtype)) + return objectTypes.at(type).at(subtype); + } + assert(0); // FIXME: throw error? + return nullptr; } -void CDefObjInfoHandler::registerTemplate(ObjectTemplate obj) +void AObjectTypeHandler::init(si32 type, si32 subtype) { - objects.push_back(obj); + this->type = type; + this->subtype = subtype; } -std::vector CDefObjInfoHandler::pickCandidates(Obj type, si32 subtype) const +void AObjectTypeHandler::load(const JsonNode & input) { - std::vector ret; - - std::copy_if(objects.begin(), objects.end(), std::back_inserter(ret), [&](const ObjectTemplate & obj) + for (auto entry : input["templates"].Struct()) { - return obj.id == type && obj.subid == subtype; - }); - if (ret.empty()) - logGlobal->errorStream() << "Failed to find template for " << type << ":" << subtype; + JsonNode data = input["base"]; + JsonUtils::merge(data, entry.second); - assert(!ret.empty()); // Can't create object of this type/subtype - return ret; + ObjectTemplate tmpl; + tmpl.id = Obj(type); + tmpl.subid = subtype; + tmpl.stringID = entry.first; // FIXME: create "fullID" - type.object.template? + tmpl.readJson(data); + templates.push_back(tmpl); + } } -std::vector CDefObjInfoHandler::pickCandidates(Obj type, si32 subtype, ETerrainType terrain) const +bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const { - std::vector ret = pickCandidates(type, subtype); + return true; // by default - accept all. +} + +void AObjectTypeHandler::addTemplate(const ObjectTemplate & templ) +{ + templates.push_back(templ); +} + +std::vector AObjectTypeHandler::getTemplates() const +{ + return templates; +} + +std::vector AObjectTypeHandler::getTemplates(si32 terrainType) const// FIXME: replace with ETerrainType +{ + std::vector ret = getTemplates(); std::vector filtered; std::copy_if(ret.begin(), ret.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj) { - return obj.canBePlacedAt(terrain); + return obj.canBePlacedAt(ETerrainType(terrainType)); }); // it is possible that there are no templates usable on specific terrain. In this case - return list before filtering return filtered.empty() ? ret : filtered; } -std::vector CDefObjInfoHandler::pickCandidates(Obj type, si32 subtype, ETerrainType terrain, std::function filter) const +ObjectTemplate AObjectTypeHandler::selectTemplate(si32 terrainType, CGObjectInstance * object) const { - std::vector ret = pickCandidates(type, subtype, terrain); - std::vector filtered; - - std::copy_if(ret.begin(), ret.end(), std::back_inserter(filtered), filter); - // it is possible that there are no templates usable on specific terrain. In this case - return list before filtering - return filtered.empty() ? ret : filtered; + std::vector ret = getTemplates(terrainType); + for (auto & tmpl : ret) + { + if (objectFilter(object, tmpl)) + return tmpl; + } + // FIXME: no matches found. Warn? Ask for torches? Die? + return ret.front(); } diff --git a/lib/CDefObjInfoHandler.h b/lib/CDefObjInfoHandler.h index d5c05de48..f32486a11 100644 --- a/lib/CDefObjInfoHandler.h +++ b/lib/CDefObjInfoHandler.h @@ -78,37 +78,6 @@ public: } }; -class DLL_LINKAGE CDefObjInfoHandler -{ - /// list of all object templates loaded from text files - /// actual object have ObjectTemplate as member "appearance" - std::vector objects; - - /// reads one of H3 text files that contain object templates description - void readTextFile(std::string path); -public: - - CDefObjInfoHandler(); - - /// Erases all templates with given type/subtype - void eraseAll(Obj type, si32 subtype); - - /// Add new template into the list - void registerTemplate(ObjectTemplate obj); - - /// picks all possible candidates for specific pair - std::vector pickCandidates(Obj type, si32 subtype) const; - /// picks all candidates for and of possible - also filters them by terrain - std::vector pickCandidates(Obj type, si32 subtype, ETerrainType terrain) const; - /// as above, but also filters out templates that are not applicable according to accepted test - std::vector pickCandidates(Obj type, si32 subtype, ETerrainType terrain, std::function filter) const; - - template void serialize(Handler &h, const int version) - { - h & objects; - } -}; - class IObjectInfo { public: @@ -130,40 +99,57 @@ public: class CGObjectInstance; -class IObjectTypesHandler +class AObjectTypeHandler { + si32 type; + si32 subtype; + + std::vector templates; +protected: + void init(si32 type, si32 subtype); + + /// loads templates from Json structure using fields "base" and "templates" + void load(const JsonNode & input); + + virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; public: - virtual std::vector getTemplates(si32 type, si32 subType) const = 0; + void addTemplate(const ObjectTemplate & templ); + + /// returns all templates, without any filters + std::vector getTemplates() const; + + /// returns all templates that can be placed on specific terrain type + std::vector getTemplates(si32 terrainType) const; + + /// returns template suitable for object. If returned template is not equal to current one + /// it must be replaced with this one (and properly updated on all clients) + ObjectTemplate selectTemplate(si32 terrainType, CGObjectInstance * object) const; + virtual CGObjectInstance * create(ObjectTemplate tmpl) const = 0; - virtual bool handlesID(ObjectTemplate tmpl) const = 0; - virtual void configureObject(CGObjectInstance * object) const = 0; virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const = 0; }; -typedef std::shared_ptr TObjectTypeHandler; +typedef std::shared_ptr TObjectTypeHandler; -class CObjectGroupsHandler +class CObjectTypesHandler { - /// list of object handlers, each of them handles 1 or more object type - std::vector objectTypes; + /// list of object handlers, each of them handles only one type + std::map > objectTypes; public: + void init(); + /// returns handler for specified object (ID-based). ObjectHandler keeps ownership - IObjectTypesHandler * getHandlerFor(ObjectTemplate tmpl) const; + TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const; - /// creates object based on specified template - CGObjectInstance * createObject(ObjectTemplate tmpl); - - template - CObjectClass * createObjectTyped(ObjectTemplate tmpl) + template void serialize(Handler &h, const int version) { - auto objInst = createObject(tmpl); - auto objClass = dynamic_cast(objInst); - assert(objClass); - return objClass; + //h & objects; + if (!h.saving) + init(); // TODO: implement serialization } }; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 5c87ec1b6..1447c65cd 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -342,9 +342,11 @@ static CGObjectInstance * createObject(Obj id, int subid, int3 pos, PlayerColor switch(id) { case Obj::HERO: - nobj = new CGHeroInstance(); - nobj->appearance = VLC->dobjinfo->pickCandidates(id, VLC->heroh->heroes[subid]->heroClass->id).front(); - break; + { + auto handler = VLC->objtypeh->getHandlerFor(id, VLC->heroh->heroes[subid]->heroClass->id); + nobj = handler->create(handler->getTemplates().front()); + break; + } case Obj::TOWN: nobj = new CGTownInstance; break; @@ -357,7 +359,7 @@ static CGObjectInstance * createObject(Obj id, int subid, int3 pos, PlayerColor nobj->pos = pos; nobj->tempOwner = owner; if (id != Obj::HERO) - nobj->appearance = VLC->dobjinfo->pickCandidates(id, subid).front(); + nobj->appearance = VLC->objtypeh->getHandlerFor(id, subid)->getTemplates().front(); return nobj; } @@ -654,7 +656,7 @@ void CGameState::randomizeObject(CGObjectInstance *cur) const TerrainTile &tile = map->getTile(cur->visitablePos()); CGTownInstance *t = dynamic_cast(cur); t->town = VLC->townh->factions[t->subID]->town; - t->appearance = VLC->dobjinfo->pickCandidates(Obj::TOWN, t->subID, tile.terType).front(); + t->appearance = VLC->objtypeh->getHandlerFor(Obj::TOWN, t->subID)->selectTemplate(tile.terType, t); t->updateAppearance(); } return; @@ -675,12 +677,12 @@ void CGameState::randomizeObject(CGObjectInstance *cur) { const TerrainTile &tile = map->getTile(cur->visitablePos()); CGTownInstance *t = dynamic_cast(cur); - if(!t) {logGlobal->warnStream()<<"Wrong random town at "<pos; return;} + if(!t) {logGlobal->warnStream()<<"Wrong random town at "<pos; return;} cur->ID = ran.first; cur->subID = ran.second; //FIXME: copy-pasted from above t->town = VLC->townh->factions[t->subID]->town; - t->appearance = VLC->dobjinfo->pickCandidates(Obj::TOWN,t->subID, tile.terType).front(); + t->appearance = VLC->objtypeh->getHandlerFor(Obj::TOWN, t->subID)->selectTemplate(tile.terType, t); t->updateAppearance(); t->randomizeArmy(t->subID); @@ -693,7 +695,7 @@ void CGameState::randomizeObject(CGObjectInstance *cur) ran.second != cur->appearance.subid) { const TerrainTile &tile = map->getTile(cur->visitablePos()); - cur->appearance = VLC->dobjinfo->pickCandidates(Obj(ran.first),ran.second, tile.terType).front(); + cur->appearance = VLC->objtypeh->getHandlerFor(ran.first, ran.second)->selectTemplate(tile.terType, cur); } } //we have to replace normal random object @@ -3315,9 +3317,9 @@ void CPathfinder::calculatePaths(int3 src /*= int3(-1,-1,-1)*/, int movement /*= int maxMovePointsLand = hero->maxMovePoints(true); int maxMovePointsWater = hero->maxMovePoints(false); - auto maxMovePoints = [&](CGPathNode *cp) -> int - { - return cp->land ? maxMovePointsLand : maxMovePointsWater; + auto maxMovePoints = [&](CGPathNode *cp) -> int + { + return cp->land ? maxMovePointsLand : maxMovePointsWater; }; out.hero = hero; diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 77f990943..8e53b3ea6 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -229,7 +229,7 @@ void CHeroClassHandler::afterLoadFinalization() } } - ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::HERO, 0).front(); + ObjectTemplate base = VLC->objtypeh->getHandlerFor(Obj::HERO, 0)->getTemplates().front(); for (CHeroClass * hc : heroClasses) { base.animationFile = hc->imageMapMale; @@ -237,8 +237,7 @@ void CHeroClassHandler::afterLoadFinalization() // replace existing (if any) and add new template. // Necessary for objects added via mods that don't have any templates in H3 - VLC->dobjinfo->eraseAll(Obj::HERO, hc->id); - VLC->dobjinfo->registerTemplate(base); + VLC->objtypeh->getHandlerFor(Obj::HERO, base.subid)->addTemplate(base); } } diff --git a/lib/CObjectConstructor.cpp b/lib/CObjectConstructor.cpp index a5a5fb198..3ce38894b 100644 --- a/lib/CObjectConstructor.cpp +++ b/lib/CObjectConstructor.cpp @@ -11,64 +11,78 @@ * */ +void CRandomRewardObjectInfo::init(const JsonNode & objectConfig) +{ + parameters = objectConfig; +} + +void CRandomRewardObjectInfo::configureObject(CObjectWithReward * object) const +{ + +} + +bool CRandomRewardObjectInfo::givesResources() const +{ +} + +bool CRandomRewardObjectInfo::givesExperience() const +{ +} + +bool CRandomRewardObjectInfo::givesMana() const +{ +} + +bool CRandomRewardObjectInfo::givesMovement() const +{ +} + +bool CRandomRewardObjectInfo::givesPrimarySkills() const +{ +} + +bool CRandomRewardObjectInfo::givesSecondarySkills() const +{ +} + +bool CRandomRewardObjectInfo::givesArtifacts() const +{ +} + +bool CRandomRewardObjectInfo::givesCreatures() const +{ +} + +bool CRandomRewardObjectInfo::givesSpells() const +{ +} + +bool CRandomRewardObjectInfo::givesBonuses() const +{ +} + CObjectWithRewardConstructor::CObjectWithRewardConstructor() { } void CObjectWithRewardConstructor::init(const JsonNode & config) { - int id = config["id"].Float(); - std::string name = config["name"].String(); - for (auto & entry : config["types"].Struct()) // for each object type - { - JsonNode typeConf = entry.second; - - int subID = typeConf["id"].Float(); - - objectInfos[id][subID].info.init(typeConf["properties"]); - for (auto entry : typeConf["templates"].Struct()) - { - ObjectTemplate tmpl; - tmpl.id = Obj(id); - tmpl.subid = subID; - tmpl.readJson(entry.second); - objectInfos[id][subID].templates.push_back(tmpl); - } - } -} - -std::vector CObjectWithRewardConstructor::getTemplates(si32 type, si32 subType) const -{ - assert(handlesID(type, subtype)); - return objectInfos.at(type).at(subType).templates; + objectInfo.init(config); } CGObjectInstance * CObjectWithRewardConstructor::create(ObjectTemplate tmpl) const { - assert(handlesID(tmpl)); auto ret = new CObjectWithReward(); ret->appearance = tmpl; return ret; } -bool CObjectWithRewardConstructor::handlesID(si32 id, si32 subID) const -{ - return objectInfos.count(id) && objectInfos.at(id).count(subID); -} - -bool CObjectWithRewardConstructor::handlesID(ObjectTemplate tmpl) const -{ - return handlesID(tmpl.id, tmpl.subid); -} - void CObjectWithRewardConstructor::configureObject(CGObjectInstance * object) const { - assert(handlesID(object->appearance)); - objectInfos.at(object->ID).at(object->subID).info.configureObject(dynamic_cast(object)); + objectInfo.configureObject(dynamic_cast(object)); } const IObjectInfo * CObjectWithRewardConstructor::getObjectInfo(ObjectTemplate tmpl) const { - assert(handlesID(tmpl)); - return &objectInfos.at(tmpl.id).at(tmpl.subid).info; + return &objectInfo; } diff --git a/lib/CObjectConstructor.h b/lib/CObjectConstructor.h index ceef728b2..bec5d7208 100644 --- a/lib/CObjectConstructor.h +++ b/lib/CObjectConstructor.h @@ -41,26 +41,16 @@ public: void init(const JsonNode & objectConfig); }; -class CObjectWithRewardConstructor : public IObjectTypesHandler +class CObjectWithRewardConstructor : public AObjectTypeHandler { - struct ObjectInfo - { - CRandomRewardObjectInfo info; - std::vector templates; - }; - std::map > objectInfos; + CRandomRewardObjectInfo objectInfo; public: CObjectWithRewardConstructor(); void init(const JsonNode & config); - std::vector getTemplates(si32 type, si32 subType) const override; - CGObjectInstance * create(ObjectTemplate tmpl) const override; - bool handlesID(si32 id, si32 subID) const; - bool handlesID(ObjectTemplate tmpl) const override; - void configureObject(CGObjectInstance * object) const override; const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const override; diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index 096be0312..f4c118f95 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -727,7 +727,7 @@ void CGHeroInstance::initHero() type = VLC->heroh->heroes[subID]; if (ID == Obj::HERO) - appearance = VLC->dobjinfo->pickCandidates(Obj::HERO, type->heroClass->id).front(); + appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->id)->getTemplates().front(); if(!vstd::contains(spells, SpellID::PRESET)) //hero starts with a spell { @@ -3493,7 +3493,7 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const if(vstd::contains(objs,Obj::MONOLITH2) && vstd::contains(objs[Obj::MONOLITH2],subID) && objs[Obj::MONOLITH2][subID].size()) destinationid = objs[Obj::MONOLITH2][subID][rand()%objs[Obj::MONOLITH2][subID].size()]; else - logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; + logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; break; case Obj::MONOLITH3://two way monolith - pick any other one case Obj::WHIRLPOOL: //Whirlpool @@ -3530,7 +3530,7 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const } } else - logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; + logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; break; case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level { @@ -3544,7 +3544,7 @@ void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const } if(destinationid == ObjectInstanceID()) { - logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( "; + logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( "; return; } if (ID == Obj::WHIRLPOOL) diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 0f1d18017..0846efdd4 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -711,7 +711,7 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod void CTownHandler::afterLoadFinalization() { initializeRequirements(); - ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::TOWN, 0).front(); + ObjectTemplate base = VLC->objtypeh->getHandlerFor(Obj::TOWN, 0)->getTemplates().front(); for (CFaction * fact : factions) { if (fact->town) @@ -721,13 +721,12 @@ void CTownHandler::afterLoadFinalization() // replace existing (if any) and add new template. // Necessary for objects added via mods that don't have any templates in H3 - VLC->dobjinfo->eraseAll(Obj::TOWN, fact->index); - VLC->dobjinfo->registerTemplate(base); + VLC->objtypeh->getHandlerFor(Obj::TOWN, fact->index)->addTemplate(base); assert(fact->town->dwellings.size() == fact->town->dwellingNames.size()); for (size_t i=0; itown->dwellings.size(); i++) { - ObjectTemplate base = VLC->dobjinfo->pickCandidates(Obj::CREATURE_GENERATOR1, 0).front(); + ObjectTemplate base = VLC->objtypeh->getHandlerFor(Obj::CREATURE_GENERATOR1, 0)->getTemplates().front(); //both unupgraded and upgraded get same dwelling for (auto cre : fact->town->creatures[i]) @@ -736,7 +735,7 @@ void CTownHandler::afterLoadFinalization() base.animationFile = fact->town->dwellings[i]; if (VLC->objh->cregens.count(cre) == 0) { - VLC->dobjinfo->registerTemplate(base); + VLC->objtypeh->getHandlerFor(Obj::CREATURE_GENERATOR1, 80 + cre)->addTemplate(base); VLC->objh->cregens[80 + cre] = cre; //map of dwelling -> creature id } } diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 339b4c913..8ee35e925 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -582,7 +582,7 @@ DLL_LINKAGE void GiveHero::applyGs( CGameState *gs ) //bonus system h->detachFrom(&gs->globalEffects); h->attachTo(gs->getPlayer(player)); - h->appearance = VLC->dobjinfo->pickCandidates(Obj::HERO, h->type->heroClass->id).front(); + h->appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, h->type->heroClass->id)->getTemplates().front(); gs->map->removeBlockVisTiles(h,true); h->setOwner(player); @@ -623,7 +623,7 @@ DLL_LINKAGE void NewObject::applyGs( CGameState *gs ) o->subID = subID; o->pos = pos; const TerrainTile &t = gs->map->getTile(pos); - o->appearance = VLC->dobjinfo->pickCandidates(o->ID, o->subID, t.terType).front(); + o->appearance = VLC->objtypeh->getHandlerFor(o->ID, o->subID)->selectTemplate(t.terType, o); id = o->id = ObjectInstanceID(gs->map->objects.size()); o->hoverName = VLC->generaltexth->names[ID]; diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 8c6af64b3..ab644757a 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -109,7 +109,7 @@ void LibClasses::init() createHandler(objh, "Object", pomtime); - createHandler(dobjinfo, "Def information", pomtime); + createHandler(objtypeh, "Object types information", pomtime); createHandler(spellh, "Spell", pomtime); @@ -135,7 +135,7 @@ void LibClasses::clear() delete creh; delete townh; delete objh; - delete dobjinfo; + delete objtypeh; delete spellh; delete modh; delete bth; @@ -152,7 +152,7 @@ void LibClasses::makeNull() creh = nullptr; townh = nullptr; objh = nullptr; - dobjinfo = nullptr; + objtypeh = nullptr; spellh = nullptr; modh = nullptr; bth = nullptr; diff --git a/lib/VCMI_Lib.h b/lib/VCMI_Lib.h index b1b52b9bf..cf728fe55 100644 --- a/lib/VCMI_Lib.h +++ b/lib/VCMI_Lib.h @@ -16,7 +16,7 @@ class CCreatureHandler; class CSpellHandler; class CBuildingHandler; class CObjectHandler; -class CDefObjInfoHandler; +class CObjectTypesHandler; class CTownHandler; class CGeneralTextHandler; class CModHandler; @@ -42,7 +42,7 @@ public: CCreatureHandler * creh; CSpellHandler * spellh; CObjectHandler * objh; - CDefObjInfoHandler * dobjinfo; + CObjectTypesHandler * objtypeh; CTownHandler * townh; CGeneralTextHandler * generaltexth; CModHandler * modh; @@ -60,7 +60,7 @@ public: template void serialize(Handler &h, const int version) { - h & heroh & arth & creh & townh & objh & dobjinfo & spellh & modh & IS_AI_ENABLED; + h & heroh & arth & creh & townh & objh & objtypeh & spellh & modh & IS_AI_ENABLED; h & bth; if(!h.saving) { diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 810e53fe2..771887765 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -166,7 +166,7 @@ void CMapGenerator::genTowns() } town->subID = townId; town->tempOwner = owner; - town->appearance = VLC->dobjinfo->pickCandidates(town->ID, town->subID, map->getTile(townPos[side]).terType).front(); + town->appearance = VLC->objtypeh->getHandlerFor(town->ID, town->subID)->selectTemplate(map->getTile(townPos[side]).terType, town); town->builtBuildings.insert(BuildingID::FORT); town->builtBuildings.insert(BuildingID::DEFAULT); editManager->insertObject(town, int3(townPos[side].x, townPos[side].y + (i / 2) * 5, 0)); From 419a2797c8bd11df94edede2eb0da871e3d4513d Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 17 May 2014 17:50:11 +0300 Subject: [PATCH 14/33] Implemented basic loading from JSON --- lib/CDefObjInfoHandler.h | 3 +- lib/CModHandler.cpp | 13 +++ lib/CModHandler.h | 1 + lib/CObjectConstructor.cpp | 180 ++++++++++++++++++++++++++++++++++++- lib/CObjectConstructor.h | 4 +- 5 files changed, 195 insertions(+), 6 deletions(-) diff --git a/lib/CDefObjInfoHandler.h b/lib/CDefObjInfoHandler.h index f32486a11..3c314af89 100644 --- a/lib/CDefObjInfoHandler.h +++ b/lib/CDefObjInfoHandler.h @@ -16,6 +16,7 @@ class CBinaryReader; class CLegacyConfigParser; class JsonNode; +class CRandomGenerator; class DLL_LINKAGE ObjectTemplate { @@ -128,7 +129,7 @@ public: virtual CGObjectInstance * create(ObjectTemplate tmpl) const = 0; - virtual void configureObject(CGObjectInstance * object) const = 0; + virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0; virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const = 0; }; diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index b797066d9..bfb529db7 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -120,6 +120,19 @@ void CIdentifierStorage::tryRequestIdentifier(std::string type, const JsonNode & requestIdentifier(ObjectCallback(name.meta, pair.first, type, pair.second, callback, true)); } +boost::optional CIdentifierStorage::getIdentifier(std::string scope, std::string type, std::string name, bool silent) +{ + auto pair = splitString(name, ':'); // remoteScope:name + auto idList = getPossibleIdentifiers(ObjectCallback(scope, pair.first, type, pair.second, std::function(), silent)); + + if (idList.size() == 1) + return idList.front().id; + if (!silent) + logGlobal->errorStream() << "Failed to resolve identifier " << name << " from mod " << scope; + + return boost::optional(); +} + boost::optional CIdentifierStorage::getIdentifier(std::string type, const JsonNode & name, bool silent) { auto pair = splitString(name.String(), ':'); // remoteScope:name diff --git a/lib/CModHandler.h b/lib/CModHandler.h index dfd042466..c845bc8cc 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -70,6 +70,7 @@ public: void tryRequestIdentifier(std::string type, const JsonNode & name, const std::function & callback); /// get identifier immediately. If identifier is not know and not silent call will result in error message + boost::optional getIdentifier(std::string scope, std::string type, std::string name, bool silent = false); boost::optional getIdentifier(std::string type, const JsonNode & name, bool silent = false); boost::optional getIdentifier(const JsonNode & name, bool silent = false); diff --git a/lib/CObjectConstructor.cpp b/lib/CObjectConstructor.cpp index 3ce38894b..6838f9c95 100644 --- a/lib/CObjectConstructor.cpp +++ b/lib/CObjectConstructor.cpp @@ -1,6 +1,10 @@ #include "StdInc.h" #include "CObjectConstructor.h" +#include "CRandomGenerator.h" +#include "StringConstants.h" +#include "CCreatureHandler.h" + /* * CObjectConstructor.cpp, part of VCMI engine * @@ -11,54 +15,224 @@ * */ +namespace { + si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0) + { + if (value.isNull()) + return defaultValue; + if (value.getType() == JsonNode::DATA_FLOAT) + return value.Float(); + si32 min = value["min"].Float(); + si32 max = value["max"].Float(); + return rng.getIntRange(min, max)(); + } + + TResources loadResources(const JsonNode & value, CRandomGenerator & rng) + { + TResources ret; + for (size_t i=0; i loadPrimary(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (auto & name : PrimarySkill::names) + { + ret.push_back(loadValue(value[name], rng)); + } + return ret; + } + + std::map loadSecondary(const JsonNode & value, CRandomGenerator & rng) + { + std::map ret; + for (auto & pair : value.Struct()) + { + SecondarySkill id(VLC->modh->identifiers.getIdentifier(pair.second.meta, "skill", pair.first).get()); + ret[id] = loadValue(pair.second, rng); + } + return ret; + } + + std::vector loadArtifacts(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (const JsonNode & entry : value.Vector()) + { + ArtifactID art(VLC->modh->identifiers.getIdentifier("artifact", entry).get()); + ret.push_back(art); + } + return ret; + } + + std::vector loadSpells(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (const JsonNode & entry : value.Vector()) + { + SpellID spell(VLC->modh->identifiers.getIdentifier("spell", entry).get()); + ret.push_back(spell); + } + return ret; + } + + std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (auto & pair : value.Struct()) + { + CStackBasicDescriptor stack; + stack.type = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier(pair.second.meta, "creature", pair.first).get()]; + stack.count = loadValue(pair.second, rng); + ret.push_back(stack); + } + return ret; + } + + std::vector loadBonuses(const JsonNode & value) + { + std::vector ret; + for (const JsonNode & entry : value.Vector()) + { + Bonus * bonus = JsonUtils::parseBonus(entry); + ret.push_back(*bonus); + delete bonus; + } + return ret; + } + + std::vector loadComponents(const JsonNode & value) + { + //TODO + } + + MetaString loadMessage(const JsonNode & value) + { + MetaString ret; + if (value.getType() == JsonNode::DATA_FLOAT) + ret.addTxt(MetaString::ADVOB_TXT, value.Float()); + else + ret << value.String(); + return ret; + } + + bool testForKey(const JsonNode & value, const std::string & key) + { + for( auto & reward : value["rewards"].Vector() ) + { + if (!reward[key].isNull()) + return true; + } + return false; + } +} + void CRandomRewardObjectInfo::init(const JsonNode & objectConfig) { parameters = objectConfig; } -void CRandomRewardObjectInfo::configureObject(CObjectWithReward * object) const +void CRandomRewardObjectInfo::configureObject(CObjectWithReward * object, CRandomGenerator & rng) const { + for (const JsonNode & reward : parameters["rewards"].Vector()) + { + const JsonNode & limiter = reward["limiter"]; + CVisitInfo info; + // load limiter + info.limiter.numOfGrants = loadValue(limiter["numOfGrants"], rng); + info.limiter.dayOfWeek = loadValue(limiter["dayOfWeek"], rng); + info.limiter.minLevel = loadValue(limiter["minLevel"], rng); + info.limiter.resources = loadResources(limiter["resources"], rng); + info.limiter.primary = loadPrimary(limiter["primary"], rng); + info.limiter.secondary = loadSecondary(limiter["secondary"], rng); + info.limiter.artifacts = loadArtifacts(limiter["artifacts"], rng); + info.limiter.creatures = loadCreatures(limiter["creatures"], rng); + + info.reward.resources = loadResources(reward["resources"], rng); + + info.reward.gainedExp = loadValue(reward["gainedExp"], rng); + info.reward.gainedLevels = loadValue(reward["gainedLevels"], rng); + + info.reward.manaDiff = loadValue(reward["manaPoints"], rng); + info.reward.manaPercentage = loadValue(reward["manaPercentage"], rng, -1); + + info.reward.movePoints = loadValue(reward["movePoints"], rng); + info.reward.movePercentage = loadValue(reward["movePercentage"], rng, -1); + + info.reward.bonuses = loadBonuses(reward["bonuses"]); + + info.reward.primary = loadPrimary(reward["primary"], rng); + info.reward.secondary = loadSecondary(reward["secondary"], rng); + + info.reward.artifacts = loadArtifacts(reward["artifacts"], rng); + info.reward.spells = loadSpells(reward["spells"], rng); + info.reward.creatures = loadCreatures(reward["creatures"], rng); + } + + object->onSelect = loadMessage(parameters["onSelectMessage"]); + object->onVisited = loadMessage(parameters["onVisitedMessage"]); + object->onEmpty = loadMessage(parameters["onEmptyMessage"]); + + //TODO: visitMode and selectMode + + object->soundID = parameters["soundID"].Float(); + object->resetDuration = parameters["resetDuration"].Float(); + object->canRefuse =parameters["canRefuse"].Bool(); } bool CRandomRewardObjectInfo::givesResources() const { + return testForKey(parameters, "resources"); } bool CRandomRewardObjectInfo::givesExperience() const { + return testForKey(parameters, "gainedExp") || testForKey(parameters, "gainedLevels"); } bool CRandomRewardObjectInfo::givesMana() const { + return testForKey(parameters, "manaPoints") || testForKey(parameters, "manaPercentage"); } bool CRandomRewardObjectInfo::givesMovement() const { + return testForKey(parameters, "movePoints") || testForKey(parameters, "movePercentage"); } bool CRandomRewardObjectInfo::givesPrimarySkills() const { + return testForKey(parameters, "primary"); } bool CRandomRewardObjectInfo::givesSecondarySkills() const { + return testForKey(parameters, "secondary"); } bool CRandomRewardObjectInfo::givesArtifacts() const { + return testForKey(parameters, "artifacts"); } bool CRandomRewardObjectInfo::givesCreatures() const { + return testForKey(parameters, "spells"); } bool CRandomRewardObjectInfo::givesSpells() const { + return testForKey(parameters, "creatures"); } bool CRandomRewardObjectInfo::givesBonuses() const { + return testForKey(parameters, "bonuses"); } CObjectWithRewardConstructor::CObjectWithRewardConstructor() @@ -77,9 +251,9 @@ CGObjectInstance * CObjectWithRewardConstructor::create(ObjectTemplate tmpl) con return ret; } -void CObjectWithRewardConstructor::configureObject(CGObjectInstance * object) const +void CObjectWithRewardConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const { - objectInfo.configureObject(dynamic_cast(object)); + objectInfo.configureObject(dynamic_cast(object), rng); } const IObjectInfo * CObjectWithRewardConstructor::getObjectInfo(ObjectTemplate tmpl) const diff --git a/lib/CObjectConstructor.h b/lib/CObjectConstructor.h index bec5d7208..27a88c22f 100644 --- a/lib/CObjectConstructor.h +++ b/lib/CObjectConstructor.h @@ -33,7 +33,7 @@ public: bool givesBonuses() const override; - void configureObject(CObjectWithReward * object) const; + void configureObject(CObjectWithReward * object, CRandomGenerator & rng) const; CRandomRewardObjectInfo() {} @@ -51,7 +51,7 @@ public: CGObjectInstance * create(ObjectTemplate tmpl) const override; - void configureObject(CGObjectInstance * object) const override; + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override; const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const override; }; From 6bd6be0835d8884854f27559b7d84119f6fdae17 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 24 May 2014 01:56:51 +0300 Subject: [PATCH 15/33] Object class handler is now a proper "handler" - Some changes in interfaces - Fixed some missing fields in serialization - Moved object names to new handler --- client/CGameInfo.h | 4 +- client/mapHandler.cpp | 2 +- lib/CDefObjInfoHandler.cpp | 147 +++++++++++++++++++++++++++++++++--- lib/CDefObjInfoHandler.h | 94 +++++++++++++++++++---- lib/CGameState.cpp | 54 +++---------- lib/CGeneralTextHandler.cpp | 1 - lib/CGeneralTextHandler.h | 1 - lib/CObjectConstructor.cpp | 28 +++++++ lib/CObjectConstructor.h | 2 +- lib/CObjectHandler.cpp | 51 ++++++++++--- lib/CObjectHandler.h | 9 ++- lib/CObjectWithReward.cpp | 10 +-- lib/CObjectWithReward.h | 10 ++- lib/NetPacksLib.cpp | 4 +- lib/VCMI_Lib.h | 4 +- lib/rmg/CMapGenerator.cpp | 2 +- 16 files changed, 322 insertions(+), 101 deletions(-) diff --git a/client/CGameInfo.h b/client/CGameInfo.h index f56429555..d0393568e 100644 --- a/client/CGameInfo.h +++ b/client/CGameInfo.h @@ -23,7 +23,7 @@ class CBuildingHandler; class CObjectHandler; class CSoundHandler; class CMusicHandler; -class CObjectTypesHandler; +class CObjectClassesHandler; class CTownHandler; class CGeneralTextHandler; class CConsoleHandler; @@ -57,7 +57,7 @@ public: ConstTransitivePtr creh; ConstTransitivePtr spellh; ConstTransitivePtr objh; - ConstTransitivePtr objtypeh; + ConstTransitivePtr objtypeh; CGeneralTextHandler * generaltexth; CMapHandler * mh; CTownHandler * townh; diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 01ab922ff..efb2cfc01 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -1073,7 +1073,7 @@ void CMapHandler::getTerrainDescr( const int3 &pos, std::string & out, bool terN } if(t.hasFavourableWinds()) - out = CGI->generaltexth->names[Obj::FAVORABLE_WINDS]; + out = CGI->objtypeh->getObjectName(Obj::FAVORABLE_WINDS); else if(terName) out = CGI->generaltexth->terrainNames[t.terType]; } diff --git a/lib/CDefObjInfoHandler.cpp b/lib/CDefObjInfoHandler.cpp index b75ba812d..a100a6456 100644 --- a/lib/CDefObjInfoHandler.cpp +++ b/lib/CDefObjInfoHandler.cpp @@ -11,6 +11,8 @@ #include "CModHandler.h" #include "JsonNode.h" +#include "CObjectConstructor.h" + /* * CDefObjInfoHandler.cpp, part of VCMI engine * @@ -350,29 +352,153 @@ CDefObjInfoHandler::CDefObjInfoHandler() readTextFile("Data/Heroes.txt"); } */ -void CObjectTypesHandler::init() -{ +CObjectClassesHandler::CObjectClassesHandler() +{ + // list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code + handlerConstructors["configurable"] = std::make_shared; + +#define SET_HANDLER(STRING, TYPENAME) handlerConstructors[STRING] = std::make_shared > + + SET_HANDLER("", CGObjectInstance); + SET_HANDLER("generic", CGObjectInstance); + + SET_HANDLER("market", CGMarket); + SET_HANDLER("bank", CBank); + SET_HANDLER("cartographer", CCartographer); + SET_HANDLER("artifact", CGArtifact); + SET_HANDLER("blackMarket", CGBlackMarket); + SET_HANDLER("boat", CGBoat); + SET_HANDLER("bonusingObject", CGBonusingObject); + SET_HANDLER("borderGate", CGBorderGate); + SET_HANDLER("borderGuard", CGBorderGuard); + SET_HANDLER("monster", CGCreature); + SET_HANDLER("denOfThieves", CGDenOfthieves); + SET_HANDLER("dwelling", CGDwelling); + SET_HANDLER("event", CGEvent); + SET_HANDLER("garrison", CGGarrison); + SET_HANDLER("hero", CGHeroInstance); + SET_HANDLER("heroPlaceholder", CGHeroPlaceholder); + SET_HANDLER("keymaster", CGKeymasterTent); + SET_HANDLER("lighthouse", CGLighthouse); + SET_HANDLER("magi", CGMagi); + SET_HANDLER("magicSpring", CGMagicSpring); + SET_HANDLER("magicWell", CGMagicWell); + SET_HANDLER("market", CGMarket); + SET_HANDLER("mine", CGMine); + SET_HANDLER("obelisk", CGObelisk); + SET_HANDLER("observatory", CGObservatory); + SET_HANDLER("onceVisitable", CGOnceVisitable); + SET_HANDLER("pandora", CGPandoraBox); + SET_HANDLER("pickable", CGPickable); + SET_HANDLER("pyramid", CGPyramid); + SET_HANDLER("questGuard", CGQuestGuard); + SET_HANDLER("resource", CGResource); + SET_HANDLER("scholar", CGScholar); + SET_HANDLER("seerHut", CGSeerHut); + SET_HANDLER("shipyard", CGShipyard); + SET_HANDLER("shrine", CGShrine); + SET_HANDLER("sign", CGSignBottle); + SET_HANDLER("siren", CGSirens); + SET_HANDLER("teleport", CGTeleport); + SET_HANDLER("town", CGTownInstance); + SET_HANDLER("university", CGUniversity); + SET_HANDLER("oncePerHero", CGVisitableOPH); + SET_HANDLER("oncePerWeek", CGVisitableOPW); + SET_HANDLER("witch", CGWitchHut); + +#undef SET_HANDLER } -TObjectTypeHandler CObjectTypesHandler::getHandlerFor(si32 type, si32 subtype) const +static std::vector readTextFile(std::string path) { - if (objectTypes.count(type)) + //TODO +} + +std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) +{ + std::vector ret(dataSize);// create storage for 256 objects + + auto parseFile = [&](std::string filename) { - if (objectTypes.at(type).count(subtype)) - return objectTypes.at(type).at(subtype); + auto entries = readTextFile(filename); + for (JsonNode & entry : entries) + { + si32 id = entry["basebase"].Float(); + si32 subid = entry["base"].Float(); + + entry.Struct().erase("basebase"); + entry.Struct().erase("base"); + + if (ret[id].Vector().size() <= subid) + ret[id].Vector().resize(subid+1); + ret[id]["legacyTypes"].Vector()[subid][entry["animation"].String()].swap(entry); + } + }; + + parseFile("Data/Objects.txt"); + parseFile("Data/Heroes.txt"); + + CLegacyConfigParser parser("Data/ObjNames.txt"); + for (size_t i=0; i<256; i++) + { + ret[i]["name"].String() = parser.readString(); + parser.endLine(); + } + return ret; +} + +CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json) +{ + auto obj = new ObjectContainter(); + obj->name = json["name"].String(); + obj->handlerName = json["handler"].String(); + obj->base = json["base"]; + for (auto entry : json["types"].Struct()) + { + auto handler = handlerConstructors.at(obj->handlerName)(); + handler->init(entry.second); + } + return obj; +} + +void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) +{ +} + +void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) +{ +} + +std::vector CObjectClassesHandler::getDefaultAllowed() const +{ + return std::vector(); //TODO? +} + +TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) const +{ + if (objects.count(type)) + { + if (objects.at(type)->objects.count(subtype)) + return objects.at(type)->objects.at(subtype); } assert(0); // FIXME: throw error? return nullptr; } -void AObjectTypeHandler::init(si32 type, si32 subtype) +std::string CObjectClassesHandler::getObjectName(si32 type) const +{ + assert(objects.count(type)); + return objects.at(type)->name; +} + +void AObjectTypeHandler::setType(si32 type, si32 subtype) { this->type = type; this->subtype = subtype; } -void AObjectTypeHandler::load(const JsonNode & input) +void AObjectTypeHandler::init(const JsonNode & input) { for (auto entry : input["templates"].Struct()) { @@ -416,7 +542,7 @@ std::vector AObjectTypeHandler::getTemplates(si32 terrainType) c return filtered.empty() ? ret : filtered; } -ObjectTemplate AObjectTypeHandler::selectTemplate(si32 terrainType, CGObjectInstance * object) const +boost::optional AObjectTypeHandler::getOverride(si32 terrainType, const CGObjectInstance * object) const { std::vector ret = getTemplates(terrainType); for (auto & tmpl : ret) @@ -424,6 +550,5 @@ ObjectTemplate AObjectTypeHandler::selectTemplate(si32 terrainType, CGObjectInst if (objectFilter(object, tmpl)) return tmpl; } - // FIXME: no matches found. Warn? Ask for torches? Die? - return ret.front(); + return boost::optional(); } diff --git a/lib/CDefObjInfoHandler.h b/lib/CDefObjInfoHandler.h index 3c314af89..6911eaed5 100644 --- a/lib/CDefObjInfoHandler.h +++ b/lib/CDefObjInfoHandler.h @@ -2,6 +2,7 @@ #include "GameConstants.h" #include "../lib/ConstTransitivePtr.h" +#include "IHandlerBase.h" /* * CDefObjInfoHandler.h, part of VCMI engine @@ -105,15 +106,17 @@ class AObjectTypeHandler si32 type; si32 subtype; + JsonNode base; /// describes base template + std::vector templates; protected: - void init(si32 type, si32 subtype); - - /// loads templates from Json structure using fields "base" and "templates" - void load(const JsonNode & input); + void setType(si32 type, si32 subtype); virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; public: + /// loads templates from Json structure using fields "base" and "templates" + virtual void init(const JsonNode & input); + void addTemplate(const ObjectTemplate & templ); /// returns all templates, without any filters @@ -122,35 +125,96 @@ public: /// returns all templates that can be placed on specific terrain type std::vector getTemplates(si32 terrainType) const; - /// returns template suitable for object. If returned template is not equal to current one - /// it must be replaced with this one (and properly updated on all clients) - ObjectTemplate selectTemplate(si32 terrainType, CGObjectInstance * object) const; - + /// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle) + /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server) + boost::optional getOverride(si32 terrainType, const CGObjectInstance * object) const; + /// Creates object and set up core properties (like ID/subID). Object is NOT initialized + /// to allow creating objects before game start (e.g. map loading) virtual CGObjectInstance * create(ObjectTemplate tmpl) const = 0; + /// Configures object properties. Should be re-entrable, resetting state of the object if necessarily virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0; + /// Returns object configuration, if available. Othervice returns NULL virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const = 0; + + template void serialize(Handler &h, const int version) + { + h & type & subtype & templates; + } +}; + +template +class CDefaultObjectTypeHandler : public AObjectTypeHandler +{ + CGObjectInstance * create(ObjectTemplate tmpl) const + { + auto obj = new ObjectType(); + obj->ID = tmpl.id; + obj->subID = tmpl.subid; + obj->appearance = tmpl; + return obj; + } + + virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const + { + } + + virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const + { + return nullptr; + } }; typedef std::shared_ptr TObjectTypeHandler; -class CObjectTypesHandler +class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase { - /// list of object handlers, each of them handles only one type - std::map > objectTypes; + /// Small internal structure that contains information on specific group of objects + /// (creating separate entity is overcomplicating at least at this point) + struct ObjectContainter + { + si32 id; + std::string name; // human-readable name + std::string handlerName; // ID of handler that controls this object, shoul be determined using hadlerConstructor map + + JsonNode base; + std::map objects; + + template void serialize(Handler &h, const int version) + { + h & base & objects; + } + }; + + /// list of object handlers, each of them handles only one type + std::map objects; + + /// map that is filled during contruction with all known handlers. Not serializeable + std::map > handlerConstructors; + + ObjectContainter * loadFromJson(const JsonNode & json); public: - void init(); + CObjectClassesHandler(); + + virtual std::vector loadLegacyData(size_t dataSize); + + virtual void loadObject(std::string scope, std::string name, const JsonNode & data); + virtual void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index); + + virtual void afterLoadFinalization(){}; + + virtual std::vector getDefaultAllowed() const; /// returns handler for specified object (ID-based). ObjectHandler keeps ownership TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const; + std::string getObjectName(si32 type) const; + template void serialize(Handler &h, const int version) { - //h & objects; - if (!h.saving) - init(); // TODO: implement serialization + h & objects; } }; diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 1447c65cd..fa83a1bf9 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -166,6 +166,10 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst { dst = VLC->arth->artifacts[ser]->EventText(); } + else if (type == OBJ_NAMES) + { + dst = VLC->objtypeh->getObjectName(ser); + } else { std::vector *vec; @@ -177,9 +181,6 @@ void MetaString::getLocalString(const std::pair &txt, std::string &dst case XTRAINFO_TXT: vec = &VLC->generaltexth->xtrainfo; break; - case OBJ_NAMES: - vec = &VLC->generaltexth->names; - break; case RES_NAMES: vec = &VLC->generaltexth->restypes; break; @@ -651,58 +652,25 @@ void CGameState::randomizeObject(CGObjectInstance *cur) std::pair ran = pickObject(cur); if(ran.first == Obj::NO_OBJ || ran.second<0) //this is not a random object, or we couldn't find anything { - if(cur->ID==Obj::TOWN) //town - set def - { - const TerrainTile &tile = map->getTile(cur->visitablePos()); - CGTownInstance *t = dynamic_cast(cur); - t->town = VLC->townh->factions[t->subID]->town; - t->appearance = VLC->objtypeh->getHandlerFor(Obj::TOWN, t->subID)->selectTemplate(tile.terType, t); - t->updateAppearance(); - } + if(cur->ID==Obj::TOWN) + cur->setType(cur->ID, cur->subID); // update def, if necessary return; } else if(ran.first==Obj::HERO)//special code for hero { CGHeroInstance *h = dynamic_cast(cur); - if(!h) {logGlobal->warnStream()<<"Wrong random hero at "<pos; return;} - cur->ID = ran.first; - cur->subID = ran.second; - h->type = VLC->heroh->heroes[ran.second]; - h->portrait = h->type->imageIndex; - h->randomizeArmy(h->type->heroClass->faction); + cur->setType(ran.first, ran.second); map->heroesOnMap.push_back(h); - return; //TODO: maybe we should do something with definfo? + return; } else if(ran.first==Obj::TOWN)//special code for town { - const TerrainTile &tile = map->getTile(cur->visitablePos()); CGTownInstance *t = dynamic_cast(cur); - if(!t) {logGlobal->warnStream()<<"Wrong random town at "<pos; return;} - cur->ID = ran.first; - cur->subID = ran.second; - //FIXME: copy-pasted from above - t->town = VLC->townh->factions[t->subID]->town; - t->appearance = VLC->objtypeh->getHandlerFor(Obj::TOWN, t->subID)->selectTemplate(tile.terType, t); - t->updateAppearance(); - - t->randomizeArmy(t->subID); + cur->setType(ran.first, ran.second); map->towns.push_back(t); return; } - else - { - if (ran.first != cur->appearance.id || - ran.second != cur->appearance.subid) - { - const TerrainTile &tile = map->getTile(cur->visitablePos()); - cur->appearance = VLC->objtypeh->getHandlerFor(ran.first, ran.second)->selectTemplate(tile.terType, cur); - } - } - //we have to replace normal random object - cur->ID = ran.first; - cur->subID = ran.second; - map->removeBlockVisTiles(cur, true); //recalculate blockvis tiles - picked object might have different than random placeholder - map->addBlockVisTiles(cur); + cur->setType(ran.first, ran.second); } int CGameState::getDate(Date::EDateType mode) const @@ -1078,7 +1046,7 @@ void CGameState::randomizeMapObjects() if(!obj) continue; randomizeObject(obj); - obj->hoverName = VLC->generaltexth->names[obj->ID]; + obj->hoverName = VLC->objtypeh->getObjectName(obj->ID); //handle Favouring Winds - mark tiles under it if(obj->ID == Obj::FAVORABLE_WINDS) diff --git a/lib/CGeneralTextHandler.cpp b/lib/CGeneralTextHandler.cpp index e2321551b..6cd415dc8 100644 --- a/lib/CGeneralTextHandler.cpp +++ b/lib/CGeneralTextHandler.cpp @@ -287,7 +287,6 @@ CGeneralTextHandler::CGeneralTextHandler() readToVector("DATA/HEROSCRN.TXT", heroscrn); readToVector("DATA/TENTCOLR.TXT", tentColors); readToVector("DATA/SKILLLEV.TXT", levels); - readToVector("DATA/OBJNAMES.TXT", names); localizedTexts = JsonNode(ResourceID("config/translate.json", EResType::TEXT)); diff --git a/lib/CGeneralTextHandler.h b/lib/CGeneralTextHandler.h index 3107900e8..269331b0e 100644 --- a/lib/CGeneralTextHandler.h +++ b/lib/CGeneralTextHandler.h @@ -112,7 +112,6 @@ public: std::vector victoryConditions; //objects - std::vector names; //vector of objects; i-th object in vector has subnumber i std::vector creGens; //names of creatures' generators std::vector creGens4; //names of multiple creatures' generators std::vector advobtxt; diff --git a/lib/CObjectConstructor.cpp b/lib/CObjectConstructor.cpp index 6838f9c95..a011167fe 100644 --- a/lib/CObjectConstructor.cpp +++ b/lib/CObjectConstructor.cpp @@ -138,8 +138,32 @@ void CRandomRewardObjectInfo::init(const JsonNode & objectConfig) void CRandomRewardObjectInfo::configureObject(CObjectWithReward * object, CRandomGenerator & rng) const { + std::map thrownDice; + for (const JsonNode & reward : parameters["rewards"].Vector()) { + if (!reward["appearChance"].isNull()) + { + JsonNode chance = reward["appearChance"]; + si32 diceID = chance["dice"].Float(); + + if (thrownDice.count(diceID) == 0) + thrownDice[diceID] = rng.getIntRange(1, 100)(); + + if (!chance["min"].isNull()) + { + int min = chance["min"].Float(); + if (min > thrownDice[diceID]) + continue; + } + if (!chance["max"].isNull()) + { + int max = chance["max"].Float(); + if (max < thrownDice[diceID]) + continue; + } + } + const JsonNode & limiter = reward["limiter"]; CVisitInfo info; // load limiter @@ -172,6 +196,9 @@ void CRandomRewardObjectInfo::configureObject(CObjectWithReward * object, CRando info.reward.artifacts = loadArtifacts(reward["artifacts"], rng); info.reward.spells = loadSpells(reward["spells"], rng); info.reward.creatures = loadCreatures(reward["creatures"], rng); + + info.message = loadMessage(reward["message"]); + info.selectChance = loadValue(reward["selectChance"], rng); } object->onSelect = loadMessage(parameters["onSelectMessage"]); @@ -241,6 +268,7 @@ CObjectWithRewardConstructor::CObjectWithRewardConstructor() void CObjectWithRewardConstructor::init(const JsonNode & config) { + AObjectTypeHandler::init(config); objectInfo.init(config); } diff --git a/lib/CObjectConstructor.h b/lib/CObjectConstructor.h index 27a88c22f..8c080b44b 100644 --- a/lib/CObjectConstructor.h +++ b/lib/CObjectConstructor.h @@ -47,7 +47,7 @@ class CObjectWithRewardConstructor : public AObjectTypeHandler public: CObjectWithRewardConstructor(); - void init(const JsonNode & config); + void init(const JsonNode & config) override; CGObjectInstance * create(ObjectTemplate tmpl) const override; diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index f4c118f95..885d5c28e 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -372,6 +372,19 @@ bool CGObjectInstance::operator<(const CGObjectInstance & cmp) const //screen p return false; } +void CGObjectInstance::setType(si32 ID, si32 subID) +{ + const TerrainTile &tile = cb->gameState()->map->getTile(visitablePos()); + + this->ID = Obj(ID); + this->subID = subID; + this->appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(tile.terType).front(); + + //recalculate blockvis tiles - new appearance might have different blockmap than before + cb->gameState()->map->removeBlockVisTiles(this, true); + cb->gameState()->map->addBlockVisTiles(this); +} + void CGObjectInstance::initObj() { switch(ID) @@ -456,7 +469,7 @@ int3 CGObjectInstance::getVisitableOffset() const void CGObjectInstance::getNameVis( std::string &hname ) const { const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); - hname = VLC->generaltexth->names[ID]; + hname = VLC->objtypeh->getObjectName(ID); if(h) { const bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); @@ -720,6 +733,15 @@ void CGHeroInstance::initHero(HeroTypeID SUBID) initHero(); } +void CGHeroInstance::setType(si32 ID, si32 subID) +{ + assert(ID == Obj::HERO); // just in case + CGObjectInstance::setType(ID, subID); + type = VLC->heroh->heroes[subID]; + portrait = type->imageIndex; + randomizeArmy(type->heroClass->faction); +} + void CGHeroInstance::initHero() { assert(validTypes(true)); @@ -913,7 +935,7 @@ const std::string & CGHeroInstance::getHoverText() const return hoverName; } else - hoverName = VLC->generaltexth->names[ID]; + hoverName = VLC->objtypeh->getObjectName(ID); return hoverName; } @@ -2497,6 +2519,15 @@ std::vector CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode return IMarket::availableItemsIds(mode); } +void CGTownInstance::setType(si32 ID, si32 subID) +{ + assert(ID == Obj::TOWN); // just in case + CGObjectInstance::setType(ID, subID); + town = VLC->townh->factions[subID]->town; + randomizeArmy(subID); + updateAppearance(); +} + void CGTownInstance::updateAppearance() { if (!hasFort()) @@ -4126,10 +4157,10 @@ const std::string & CGSeerHut::getHoverText() const boost::algorithm::replace_first(hoverName,"%s", seerName); } else //just seer hut - hoverName = VLC->generaltexth->names[ID]; + hoverName = VLC->objtypeh->getObjectName(ID); break; case Obj::QUEST_GUARD: - hoverName = VLC->generaltexth->names[ID]; + hoverName = VLC->objtypeh->getObjectName(ID); break; default: logGlobal->debugStream() << "unrecognized quest object"; @@ -4448,7 +4479,7 @@ void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const const std::string & CGWitchHut::getHoverText() const { - hoverName = VLC->generaltexth->names[ID]; + hoverName = VLC->objtypeh->getObjectName(ID); if(wasVisited(cb->getLocalPlayer())) { hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s) @@ -4907,7 +4938,7 @@ void CGShrine::initObj() const std::string & CGShrine::getHoverText() const { - hoverName = VLC->generaltexth->names[ID]; + hoverName = VLC->objtypeh->getObjectName(ID); if(wasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current { hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s) @@ -5509,7 +5540,7 @@ const std::string& CGKeys::getHoverText() const const std::string CGKeys::getName() const { std::string name; - name = VLC->generaltexth->tentColors[subID] + " " + VLC->generaltexth->names[ID]; + name = VLC->generaltexth->tentColors[subID] + " " + VLC->objtypeh->getObjectName(ID); return name; } @@ -5545,7 +5576,7 @@ void CGBorderGuard::getVisitText (MetaString &text, std::vector &comp void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const { if (!onHover) - text << VLC->generaltexth->tentColors[subID] << " " << VLC->generaltexth->names[Obj::KEYMASTER]; + text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER); } bool CGBorderGuard::checkQuest (const CGHeroInstance * h) const @@ -5945,7 +5976,7 @@ void CGObelisk::initObj() const std::string & CGObelisk::getHoverText() const { bool visited = wasVisited(cb->getLocalPlayer()); - hoverName = VLC->generaltexth->names[ID] + " " + visitedTxt(visited); + hoverName = VLC->objtypeh->getObjectName(ID) + " " + visitedTxt(visited); return hoverName; } @@ -5998,7 +6029,7 @@ void CGLighthouse::initObj() const std::string & CGLighthouse::getHoverText() const { - hoverName = VLC->generaltexth->names[ID]; + hoverName = VLC->objtypeh->getObjectName(ID); //TODO: owned by %s player return hoverName; } diff --git a/lib/CObjectHandler.h b/lib/CObjectHandler.h index cf6a305be..102b46f28 100644 --- a/lib/CObjectHandler.h +++ b/lib/CObjectHandler.h @@ -234,6 +234,8 @@ public: //CGObjectInstance& operator=(const CGObjectInstance & right); virtual const std::string & getHoverText() const; + void setType(si32 ID, si32 subID); + ///IObjectInterface void initObj() override; void onHeroVisit(const CGHeroInstance * h) const override; @@ -355,7 +357,7 @@ public: //std::vector artifacts; //hero's artifacts from bag //std::map artifWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 std::set spells; //known spells (spell IDs) - std::set visitedObjects; + std::set visitedObjects; struct DLL_LINKAGE Patrol { @@ -422,7 +424,7 @@ public: h & static_cast(*this); h & static_cast(*this); h & exp & level & name & biography & portrait & mana & secSkills & movement - & sex & inTownGarrison & spells & patrol & moveDir & skillsInfo & visitedObjects; + & sex & inTownGarrison & spells & patrol & moveDir & skillsInfo & visitedObjects; h & visitedTown & boat; h & type & specialty & commander; BONUS_TREE_DESERIALIZATION_FIX @@ -474,6 +476,8 @@ public: ////////////////////////////////////////////////////////////////////////// + void setType(si32 ID, si32 subID); + void initHero(); void initHero(HeroTypeID SUBID); @@ -698,6 +702,7 @@ public: bool allowsTrade(EMarketMode::EMarketMode mode) const; std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; + void setType(si32 ID, si32 subID); void updateAppearance(); ////////////////////////////////////////////////////////////////////////// diff --git a/lib/CObjectWithReward.cpp b/lib/CObjectWithReward.cpp index ef6f119d3..21ab6347d 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/CObjectWithReward.cpp @@ -121,7 +121,6 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const else iw.text = onVisited; cb->showInfoDialog(&iw); - onRewardGiven(h); // FIXME: dummy call to properly act on empty objects (e.g. Floatsam that must be removed after visit) break; } case 1: // one reward. Just give it with message @@ -141,7 +140,7 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const case SELECT_FIRST: // give first available grantRewardWithMessage(rewards[0]); break; - case SELECT_RANDOM: // select one randomly + case SELECT_RANDOM: // select one randomly //TODO: use weights grantRewardWithMessage(rewards[cb->gameState()->getRandomGenerator().nextInt(rewards.size()-1)]); break; } @@ -376,7 +375,7 @@ static std::string & visitedTxt(const bool visited) const std::string & CObjectWithReward::getHoverText() const { const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); - hoverName = VLC->generaltexth->names[ID]; + hoverName = VLC->objtypeh->getObjectName(ID); if(visitMode != VISIT_UNLIMITED) { bool visited = wasVisited(cb->getCurrentPlayer()); @@ -975,7 +974,7 @@ const std::string & CGVisitableOPH::getHoverText() const default: throw std::runtime_error("Wrong CGVisitableOPH object ID!\n"); } - hoverName = VLC->generaltexth->names[ID]; + hoverName = VLC->objtypeh->getObjectName(ID); if(pom >= 0) hoverName += ("\n" + VLC->generaltexth->xtrainfo[pom]); const CGHeroInstance *h = cb->getSelectedHero (cb->getCurrentPlayer()); @@ -1015,7 +1014,6 @@ void CGVisitableOPW::initObj() soundID = soundBase::GENIE; onEmpty.addTxt(MetaString::ADVOB_TXT, 169); // 3-6 of any resource but wood and gold - // this is UGLY. TODO: find better way to describe this for (int resID = Res::MERCURY; resID < Res::GOLD; resID++) { for (int val = 3; val <=6; val++) @@ -1045,7 +1043,7 @@ void CGVisitableOPW::initObj() void CGMagicSpring::initObj() { - CVisitInfo visit; // TODO: "player above max mana" limiter + CVisitInfo visit; // TODO: "player above max mana" limiter. Use logical expressions for limiters? visit.reward.manaPercentage = 200; visit.message.addTxt(MetaString::ADVOB_TXT, 74); info.push_back(visit); // two rewards, one for each entrance diff --git a/lib/CObjectWithReward.h b/lib/CObjectWithReward.h index 877e649c5..cadc7a581 100644 --- a/lib/CObjectWithReward.h +++ b/lib/CObjectWithReward.h @@ -119,6 +119,7 @@ public: template void serialize(Handler &h, const int version) { h & resources & extraComponents & removeObject; + h & manaPercentage & movePercentage; h & gainedExp & gainedLevels & manaDiff & movePoints; h & primary & secondary & bonuses; h & artifacts & spells & creatures; @@ -134,6 +135,9 @@ public: /// Message that will be displayed on granting of this reward, if not empty MetaString message; + /// Chance for this reward to be selected in case of random choice + si32 selectChance; + /// How many times this reward has been granted since last reset si32 numOfGrants; @@ -143,7 +147,7 @@ public: template void serialize(Handler &h, const int version) { - h & limiter & reward & message; + h & limiter & reward & message & selectChance & numOfGrants; } }; @@ -229,8 +233,8 @@ public: template void serialize(Handler &h, const int version) { h & static_cast(*this); - h & info & canRefuse; - h & onSelect & onVisited & onEmpty; + h & info & canRefuse & resetDuration; + h & onSelect & onVisited & onEmpty & visitMode; h & soundID & selectMode & selectedReward; } diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 8ee35e925..9afc62ecb 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -623,9 +623,9 @@ DLL_LINKAGE void NewObject::applyGs( CGameState *gs ) o->subID = subID; o->pos = pos; const TerrainTile &t = gs->map->getTile(pos); - o->appearance = VLC->objtypeh->getHandlerFor(o->ID, o->subID)->selectTemplate(t.terType, o); + o->appearance = VLC->objtypeh->getHandlerFor(o->ID, o->subID)->getTemplates(t.terType).front(); id = o->id = ObjectInstanceID(gs->map->objects.size()); - o->hoverName = VLC->generaltexth->names[ID]; + o->hoverName = VLC->objtypeh->getObjectName(ID); gs->map->objects.push_back(o); gs->map->addBlockVisTiles(o); diff --git a/lib/VCMI_Lib.h b/lib/VCMI_Lib.h index cf728fe55..3cd5e60f6 100644 --- a/lib/VCMI_Lib.h +++ b/lib/VCMI_Lib.h @@ -16,7 +16,7 @@ class CCreatureHandler; class CSpellHandler; class CBuildingHandler; class CObjectHandler; -class CObjectTypesHandler; +class CObjectClassesHandler; class CTownHandler; class CGeneralTextHandler; class CModHandler; @@ -42,7 +42,7 @@ public: CCreatureHandler * creh; CSpellHandler * spellh; CObjectHandler * objh; - CObjectTypesHandler * objtypeh; + CObjectClassesHandler * objtypeh; CTownHandler * townh; CGeneralTextHandler * generaltexth; CModHandler * modh; diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 771887765..d0c9308b0 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -166,7 +166,7 @@ void CMapGenerator::genTowns() } town->subID = townId; town->tempOwner = owner; - town->appearance = VLC->objtypeh->getHandlerFor(town->ID, town->subID)->selectTemplate(map->getTile(townPos[side]).terType, town); + town->appearance = VLC->objtypeh->getHandlerFor(town->ID, town->subID)->getTemplates(map->getTile(townPos[side]).terType).front(); town->builtBuildings.insert(BuildingID::FORT); town->builtBuildings.insert(BuildingID::DEFAULT); editManager->insertObject(town, int3(townPos[side].x, townPos[side].y + (i / 2) * 5, 0)); From 7f276185bd3db5e436b7473d0f81ae13bfda82ce Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 24 May 2014 02:07:54 +0300 Subject: [PATCH 16/33] Moving files: - new config for objects (config/objects/generic.json) - renamed lib/CDefObjectHandler to lib/CObjectClassesHandler --- AI/VCAI/VCAI.h | 2 +- CCallback.cpp | 2 +- client/CMT.cpp | 2 +- client/CPlayerInterface.cpp | 2 +- client/CPreGame.cpp | 2 +- client/Client.cpp | 2 +- client/Graphics.cpp | 2 +- client/NetPacksClient.cpp | 2 +- client/mapHandler.cpp | 2 +- config/objects/generic.json | 168 ++++++++++++++++++ lib/CGameState.cpp | 2 +- lib/CMakeLists.txt | 2 +- lib/CModHandler.cpp | 2 +- ...oHandler.cpp => CObjectClassesHandler.cpp} | 10 +- ...jInfoHandler.h => CObjectClassesHandler.h} | 2 +- lib/CObjectConstructor.h | 2 +- lib/CObjectHandler.cpp | 4 +- lib/CObjectHandler.h | 2 +- lib/CTownHandler.cpp | 2 +- lib/GameConstants.cpp | 2 +- lib/IGameCallback.cpp | 2 +- lib/NetPacksLib.cpp | 2 +- lib/VCMI_Lib.cpp | 2 +- lib/mapping/CMap.cpp | 128 ++++++------- lib/mapping/CMapEditManager.cpp | 2 +- lib/mapping/MapFormatH3M.cpp | 4 +- lib/mapping/MapFormatH3M.h | 2 +- lib/registerTypes/RegisterTypes.cpp | 2 +- lib/registerTypes/TypesClientPacks1.cpp | 2 +- lib/registerTypes/TypesClientPacks2.cpp | 2 +- lib/registerTypes/TypesMapObjects1.cpp | 2 +- lib/registerTypes/TypesMapObjects2.cpp | 2 +- lib/registerTypes/TypesPregamePacks.cpp | 2 +- lib/registerTypes/TypesServerPacks.cpp | 2 +- lib/rmg/CMapGenerator.cpp | 2 +- server/CGameHandler.cpp | 2 +- server/CVCMIServer.cpp | 2 +- 37 files changed, 274 insertions(+), 104 deletions(-) create mode 100644 config/objects/generic.json rename lib/{CDefObjInfoHandler.cpp => CObjectClassesHandler.cpp} (95%) rename lib/{CDefObjInfoHandler.h => CObjectClassesHandler.h} (96%) diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 1faedb19d..eaefd1234 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -4,7 +4,7 @@ #include "Goals.h" #include "../../lib/AI_Base.h" #include "../../CCallback.h" -#include "../../lib/CDefObjInfoHandler.h" +#include "../../lib/CObjectClassesHandler.h" #include "../../lib/CThreadHelper.h" diff --git a/CCallback.cpp b/CCallback.cpp index fb5e807d2..ccf876ff8 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -9,7 +9,7 @@ #include "client/Client.h" #include "lib/mapping/CMap.h" #include "lib/CBuildingHandler.h" -#include "lib/CDefObjInfoHandler.h" +#include "lib/CObjectClassesHandler.h" #include "lib/CGeneralTextHandler.h" #include "lib/CHeroHandler.h" #include "lib/CObjectHandler.h" diff --git a/client/CMT.cpp b/client/CMT.cpp index aa6ce1ae8..816557786 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -44,7 +44,7 @@ #ifdef _WIN32 #include "SDL_syswm.h" #endif -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/CObjectClassesHandler.h" #include "../lib/UnlockGuard.h" #include "CMT.h" diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 789c49a1e..997a14813 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1,5 +1,5 @@ #include "StdInc.h" -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/CObjectClassesHandler.h" #include "CAdvmapInterface.h" #include "battle/CBattleInterface.h" #include "battle/CBattleInterfaceClasses.h" diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index 04f2aed07..8b4f2b743 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -11,7 +11,7 @@ #include "gui/CCursorHandler.h" #include "CAnimation.h" #include "CDefHandler.h" -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CHeroHandler.h" diff --git a/client/Client.cpp b/client/Client.cpp index d0e9bba1b..1d1153179 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -11,7 +11,7 @@ #include "../lib/BattleState.h" #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" diff --git a/client/Graphics.cpp b/client/Graphics.cpp index f9bebafb1..b758cc523 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -18,7 +18,7 @@ #include "CBitmapHandler.h" #include "../lib/CObjectHandler.h" #include "../lib/CSpellHandler.h" -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/CObjectClassesHandler.h" #include "../lib/CGameState.h" #include "../lib/JsonNode.h" #include "../lib/vcmi_endian.h" diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 48a27020c..c41170dc7 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -9,7 +9,7 @@ #include "CGameInfo.h" #include "../lib/Connection.h" #include "../lib/CGeneralTextHandler.h" -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/CObjectClassesHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CObjectHandler.h" #include "../lib/VCMI_Lib.h" diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index efb2cfc01..1301f5082 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -4,7 +4,7 @@ #include "CBitmapHandler.h" #include "gui/SDL_Extensions.h" #include "CGameInfo.h" -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/CObjectClassesHandler.h" #include "../lib/CGameState.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" diff --git a/config/objects/generic.json b/config/objects/generic.json new file mode 100644 index 000000000..03258aca1 --- /dev/null +++ b/config/objects/generic.json @@ -0,0 +1,168 @@ +{ + "altarOfSacrifice" : { "id" :2, "handler": "market" }, + "arena" : { "id" :4, "handler": "oncePerHero" }, + "artifact" : { "id" :5, "handler": "artifact" }, + "pandoraBox" : { "id" :6, "handler": "pandora" }, + "blackMarket" : { "id" :7, "handler": "blackMarket" }, + "boat" : { "id" :8, "handler": "boat" }, + "borderGuard" : { "id" :9, "handler": "borderGuard" }, + "keymasterTent" : { "id" :10, "handler": "keymaster" }, + "buoy" : { "id" :11, "handler": "bonusingObject" }, + "campfire" : { "id" :12, "handler": "pickable" }, + "cartographer" : { "id" :13, "handler": "cartographer" }, + "swanPond" : { "id" :14, "handler": "bonusingObject" }, + "coverOfDarkness" : { "id" :15, "handler": "observatory" }, + "creatureBank" : { "id" :16, "handler": "bank" }, + "creatureGeneratorCommon" : { "id" :17, "handler": "dwelling" }, + "creatureGeneratorSpecial" : { "id" :20, "handler": "dwelling" }, + "cursedGround" : { "id" :21, "handler": "generic" }, + "corpse" : { "id" :22, "handler": "onceVisitable" }, + "marlettoTower" : { "id" :23, "handler": "oncePerHero" }, + "derelictShip" : { "id" :24, "handler": "bank" }, + "dragonUtopia" : { "id" :25, "handler": "bank" }, + "event" : { "id" :26, "handler": "event" }, + "eyeOfTheMagi" : { "id" :27, "handler": "magi" }, + "faerieRing" : { "id" :28, "handler": "bonusingObject" }, + "flotsam" : { "id" :29, "handler": "pickable" }, + "fountainOfFortune" : { "id" :30, "handler": "bonusingObject" }, + "fountainOfYouth" : { "id" :31, "handler": "bonusingObject" }, + "gardenOfRevelation" : { "id" :32, "handler": "oncePerHero" }, + "garrison" : { "id" :33, "handler": "garrison" }, + "hero" : { "id" :34, "handler": "hero" }, + "hillFort" : { "id" :35, "handler": "generic" }, + "grail" : { "id" :36, "handler": "generic" }, + "hutOfTheMagi" : { "id" :37, "handler": "magi" }, + "idolOfFortune" : { "id" :38, "handler": "bonusingObject" }, + "leanTo" : { "id" :39, "handler": "onceVisitable" }, + "libraryOfEnlightenment" : { "id" :41, "handler": "oncePerHero" }, + "lighthouse" : { "id" :42, "handler": "lighthouse" }, + "monolithOneWayEntrance" : { "id" :43, "handler": "teleport" }, + "monolithOneWayExit" : { "id" :44, "handler": "teleport" }, + "monolithTwoWay" : { "id" :45, "handler": "teleport" }, + "magicPlains" : { "id" :46, "handler": "generic" }, + "schoolOfMagic" : { "id" :47, "handler": "oncePerHero" }, + "magicSpring" : { "id" :48, "handler": "magicSpring" }, + "magicWell" : { "id" :49, "handler": "magicWell" }, + "mercenaryCamp" : { "id" :51, "handler": "oncePerHero" }, + "mermaids" : { "id" :52, "handler": "bonusingObject" }, + "mine" : { "id" :53, "handler": "mine" }, + "monster" : { "id" :54, "handler": "monster" }, + "mysticalGarden" : { "id" :55, "handler": "oncePerWeek" }, + "oasis" : { "id" :56, "handler": "bonusingObject" }, + "obelisk" : { "id" :57, "handler": "obelisk" }, + "redwoodObservatory" : { "id" :58, "handler": "observatory" }, + "oceanBottle" : { "id" :59, "handler": "sign" }, + "pillarOfFire" : { "id" :60, "handler": "observatory" }, + "starAxis" : { "id" :61, "handler": "oncePerHero" }, + "prison" : { "id" :62, "handler": "hero" }, + "pyramid" : { "id" :63, "handler": "pyramid" }, + "rallyFlag" : { "id" :64, "handler": "bonusingObject" }, + "randomArtifact" : { "id" :65, "handler": "artifact" }, + "randomArtifactTreasure" : { "id" :66, "handler": "artifact" }, + "randomArtifactMinor" : { "id" :67, "handler": "artifact" }, + "randomArtifactMajor" : { "id" :68, "handler": "artifact" }, + "randomArtifactRelic" : { "id" :69, "handler": "artifact" }, + "randomHero" : { "id" :70, "handler": "hero" }, + "randomMonster" : { "id" :71, "handler": "monster" }, + "randomMonsterLevel1" : { "id" :72, "handler": "monster" }, + "randomMonsterLevel2" : { "id" :73, "handler": "monster" }, + "randomMonsterLevel3" : { "id" :74, "handler": "monster" }, + "randomMonsterLevel4" : { "id" :75, "handler": "monster" }, + "randomResource" : { "id" :76, "handler": "resource" }, + "randomTown" : { "id" :77, "handler": "town" }, + "refugeeCamp" : { "id" :78, "handler": "dwelling" }, + "resource" : { "id" :79, "handler": "resource" }, + "sanctuary" : { "id" :80, "handler": "generic" }, + "scholar" : { "id" :81, "handler": "scholar" }, + "seaChest" : { "id" :82, "handler": "pickable" }, + "seerHut" : { "id" :83, "handler": "seerHut" }, + "crypt" : { "id" :84, "handler": "bank" }, + "shipwreck" : { "id" :85, "handler": "bank" }, + "shipwreckSurvivor" : { "id" :86, "handler": "pickable" }, + "shipyard" : { "id" :87, "handler": "shipyard" }, + "shrineOfMagicLevel1" : { "id" :88, "handler": "shrine" }, + "shrineOfMagicLevel2" : { "id" :89, "handler": "shrine" }, + "shrineOfMagicLevel3" : { "id" :90, "handler": "shrine" }, + "sign" : { "id" :91, "handler": "sign" }, + "sirens" : { "id" :92, "handler": "siren" }, + "spellScroll" : { "id" :93, "handler": "artifact" }, + "stables" : { "id" :94, "handler": "bonusingObject" }, + "tavern" : { "id" :95, "handler": "generic" }, + "temple" : { "id" :96, "handler": "bonusingObject" }, + "denOfThieves" : { "id" :97, "handler": "denOfThieves" }, + "town" : { "id" :98, "handler": "town" }, + "tradingPost" : { "id" :99, "handler": "market" }, + "learningStone" : { "id" :100, "handler": "oncePerHero" }, + "treasureChest" : { "id" :101, "handler": "pickable" }, + "treeOfKnowledge" : { "id" :102, "handler": "oncePerHero" }, + "subterraneanGate" : { "id" :103, "handler": "teleport" }, + "university" : { "id" :104, "handler": "university" }, + "wagon" : { "id" :105, "handler": "onceVisitable" }, + "warMachineFactory" : { "id" :106, "handler": "dwelling" }, + "schoolOfWar" : { "id" :107, "handler": "oncePerHero" }, + "warriorTomb" : { "id" :108, "handler": "onceVisitable" }, + "waterWheel" : { "id" :109, "handler": "oncePerWeek" }, + "wateringHole" : { "id" :110, "handler": "bonusingObject" }, + "whirlpool" : { "id" :111, "handler": "teleport" }, + "windmill" : { "id" :112, "handler": "oncePerWeek" }, + "witchHut" : { "id" :113, "handler": "witch" }, + "cactus" : { "id" :116, "handler": "generic" }, + "canyon" : { "id" :117, "handler": "generic" }, + "crater" : { "id" :118, "handler": "generic" }, + "deadVegetation" : { "id" :119, "handler": "generic" }, + "flowers" : { "id" :120, "handler": "generic" }, + "frozenLake" : { "id" :121, "handler": "generic" }, + "hole" : { "id" :124, "handler": "generic" }, + "kelp" : { "id" :125, "handler": "generic" }, + "lake" : { "id" :126, "handler": "generic" }, + "lavaFlow" : { "id" :127, "handler": "generic" }, + "lavaLake" : { "id" :128, "handler": "generic" }, + "mushrooms" : { "id" :129, "handler": "generic" }, + "log" : { "id" :130, "handler": "generic" }, + "mandrake" : { "id" :131, "handler": "generic" }, + "moss" : { "id" :132, "handler": "generic" }, + "mound" : { "id" :133, "handler": "generic" }, + "mountain" : { "id" :134, "handler": "generic" }, + "oakTrees" : { "id" :135, "handler": "generic" }, + "outcropping" : { "id" :136, "handler": "generic" }, + "pineTrees" : { "id" :137, "handler": "generic" }, + "riverDelta" : { "id" :143, "handler": "generic" }, + "rock" : { "id" :147, "handler": "generic" }, + "sandDune" : { "id" :148, "handler": "generic" }, + "sandPit" : { "id" :149, "handler": "generic" }, + "shrub" : { "id" :150, "handler": "generic" }, + "skull" : { "id" :151, "handler": "generic" }, + "stump" : { "id" :153, "handler": "generic" }, + "trees" : { "id" :155, "handler": "generic" }, + "volcano" : { "id" :158, "handler": "generic" }, + "reef" : { "id" :161, "handler": "generic" }, + "randomMonsterLevel5" : { "id" :162, "handler": "monster" }, + "randomMonsterLevel6" : { "id" :163, "handler": "monster" }, + "randomMonsterLevel7" : { "id" :164, "handler": "monster" }, + "lake" : { "id" :177, "handler": "generic" }, + "trees" : { "id" :199, "handler": "generic" }, + "desertHills" : { "id" :206, "handler": "generic" }, + "dirtHills" : { "id" :207, "handler": "generic" }, + "grassHills" : { "id" :208, "handler": "generic" }, + "roughHills" : { "id" :209, "handler": "generic" }, + "subterraneanRocks" : { "id" :210, "handler": "generic" }, + "swampFoliage" : { "id" :211, "handler": "generic" }, + "borderGate" : { "id" :212, "handler": "borderGate" }, + "freelancer'SGuild" : { "id" :213, "handler": "market" }, + "heroPlaceholder" : { "id" :214, "handler": "heroPlaceholder" }, + "questGuard" : { "id" :215, "handler": "questGuard" }, + "randomDwelling" : { "id" :216, "handler": "dwelling" }, + "garrison" : { "id" :219, "handler": "garrison" }, + "mine" : { "id" :220, "handler": "mine" }, + "tradingPost" : { "id" :221, "handler": "market" }, + "cloverField" : { "id" :222, "handler": "generic" }, + "cursedGround" : { "id" :223, "handler": "generic" }, + "evilFog" : { "id" :224, "handler": "generic" }, + "favorableWinds" : { "id" :225, "handler": "generic" }, + "fieryFields" : { "id" :226, "handler": "generic" }, + "holyGround" : { "id" :227, "handler": "generic" }, + "lucidPools" : { "id" :228, "handler": "generic" }, + "magicClouds" : { "id" :229, "handler": "generic" }, + "magicPlains" : { "id" :230, "handler": "generic" }, + "rocklands" : { "id" :231, "handler": "generic" } +} diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index fa83a1bf9..5649705c1 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2,7 +2,7 @@ #include "CGameState.h" #include "mapping/CCampaignHandler.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "CArtHandler.h" #include "CBuildingHandler.h" #include "CGeneralTextHandler.h" diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index a4010b48b..f612854b9 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -62,7 +62,7 @@ set(lib_SRCS CConsoleHandler.cpp CCreatureHandler.cpp CCreatureSet.cpp - CDefObjInfoHandler.cpp + CObjectClassesHandler.cpp CGameInterface.cpp CGeneralTextHandler.cpp CHeroHandler.cpp diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index bfb529db7..b1685df8b 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -1,6 +1,6 @@ #include "StdInc.h" #include "CModHandler.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "JsonNode.h" #include "filesystem/Filesystem.h" #include "filesystem/AdapterLoaders.h" diff --git a/lib/CDefObjInfoHandler.cpp b/lib/CObjectClassesHandler.cpp similarity index 95% rename from lib/CDefObjInfoHandler.cpp rename to lib/CObjectClassesHandler.cpp index a100a6456..0d2302cf4 100644 --- a/lib/CDefObjInfoHandler.cpp +++ b/lib/CObjectClassesHandler.cpp @@ -1,5 +1,5 @@ #include "StdInc.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "filesystem/Filesystem.h" #include "filesystem/CBinaryReader.h" @@ -14,7 +14,7 @@ #include "CObjectConstructor.h" /* - * CDefObjInfoHandler.cpp, part of VCMI engine + * CObjectClassesHandler.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -331,7 +331,7 @@ bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const return allowedTerrains.count(terrain) != 0; } /* -void CDefObjInfoHandler::readTextFile(std::string path) +void CObjectClassesHandler::readTextFile(std::string path) { CLegacyConfigParser parser(path); size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else @@ -346,7 +346,7 @@ void CDefObjInfoHandler::readTextFile(std::string path) } } -CDefObjInfoHandler::CDefObjInfoHandler() +CObjectClassesHandler::CObjectClassesHandler() { readTextFile("Data/Objects.txt"); readTextFile("Data/Heroes.txt"); @@ -464,10 +464,12 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { + } void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { + } std::vector CObjectClassesHandler::getDefaultAllowed() const diff --git a/lib/CDefObjInfoHandler.h b/lib/CObjectClassesHandler.h similarity index 96% rename from lib/CDefObjInfoHandler.h rename to lib/CObjectClassesHandler.h index 6911eaed5..1632c2f04 100644 --- a/lib/CDefObjInfoHandler.h +++ b/lib/CObjectClassesHandler.h @@ -5,7 +5,7 @@ #include "IHandlerBase.h" /* - * CDefObjInfoHandler.h, part of VCMI engine + * CObjectClassesHandler.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/CObjectConstructor.h b/lib/CObjectConstructor.h index 8c080b44b..31dcf76b1 100644 --- a/lib/CObjectConstructor.h +++ b/lib/CObjectConstructor.h @@ -1,7 +1,7 @@ #pragma once #include "CObjectWithReward.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "JsonNode.h" /* diff --git a/lib/CObjectHandler.cpp b/lib/CObjectHandler.cpp index 885d5c28e..60c3adba6 100644 --- a/lib/CObjectHandler.cpp +++ b/lib/CObjectHandler.cpp @@ -11,9 +11,9 @@ #include "StdInc.h" #include "CObjectHandler.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "CGeneralTextHandler.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "CHeroHandler.h" #include "CSpellHandler.h" #include "CModHandler.h" diff --git a/lib/CObjectHandler.h b/lib/CObjectHandler.h index 102b46f28..a5d21b0cd 100644 --- a/lib/CObjectHandler.h +++ b/lib/CObjectHandler.h @@ -2,7 +2,7 @@ #include "../lib/CCreatureSet.h" #include "../lib/CTownHandler.h" -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/CObjectClassesHandler.h" #include "CArtHandler.h" #include "../lib/ConstTransitivePtr.h" #include "int3.h" diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 0846efdd4..0735b7d83 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -11,7 +11,7 @@ #include "CArtHandler.h" #include "CSpellHandler.h" #include "filesystem/Filesystem.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "CObjectHandler.h" /* diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 82bb431a1..4d9b9239d 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -13,7 +13,7 @@ #include "StdInc.h" #include "VCMI_Lib.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "CArtHandler.h" #include "CCreatureHandler.h" #include "CSpellHandler.h" diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index cd47cc630..79e643b4b 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -25,7 +25,7 @@ #include "CBuildingHandler.h" #include "GameConstants.h" #include "CModHandler.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "CBonusTypeHandler.h" #include "Connection.h" diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 9afc62ecb..f715b06ad 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -2,7 +2,7 @@ #include "NetPacks.h" #include "CGeneralTextHandler.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "CArtHandler.h" #include "CHeroHandler.h" #include "CObjectHandler.h" diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index ab644757a..c8e277332 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -14,7 +14,7 @@ #include "CArtHandler.h" #include "CBonusTypeHandler.h" #include "CCreatureHandler.h" -#include "CDefObjInfoHandler.h" +#include "CObjectClassesHandler.h" #include "CHeroHandler.h" #include "CObjectHandler.h" #include "CTownHandler.h" diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index c6b190fce..2000e2451 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -6,7 +6,7 @@ #include "../CCreatureHandler.h" #include "../CTownHandler.h" #include "../CHeroHandler.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" #include "../CGeneralTextHandler.h" #include "../CSpellHandler.h" #include "CMapEditManager.h" @@ -336,72 +336,72 @@ bool CMap::isWaterTile(const int3 &pos) const return isInTheMap(pos) && getTile(pos).terType == ETerrainType::WATER; } -bool CMap::checkForVisitableDir(const int3 & src, const TerrainTile *pom, const int3 & dst ) const -{ - for(ui32 b=0; bvisitableObjects.size(); ++b) //checking destination tile - { - if(!vstd::contains(pom->blockingObjects, pom->visitableObjects[b])) //this visitable object is not blocking, ignore - continue; - - const CGObjectInstance * obj = pom->visitableObjects[b]; - - if (!obj->appearance.isVisitableFrom(src.x - dst.x, src.y - dst.y)) - return false; - } - return true; +bool CMap::checkForVisitableDir(const int3 & src, const TerrainTile *pom, const int3 & dst ) const +{ + for(ui32 b=0; bvisitableObjects.size(); ++b) //checking destination tile + { + if(!vstd::contains(pom->blockingObjects, pom->visitableObjects[b])) //this visitable object is not blocking, ignore + continue; + + const CGObjectInstance * obj = pom->visitableObjects[b]; + + if (!obj->appearance.isVisitableFrom(src.x - dst.x, src.y - dst.y)) + return false; + } + return true; } int3 CMap::guardingCreaturePosition (int3 pos) const -{ - - const int3 originalPos = pos; - // Give monster at position priority. - if (!isInTheMap(pos)) - return int3(-1, -1, -1); - const TerrainTile &posTile = getTile(pos); - if (posTile.visitable) - { - for (CGObjectInstance* obj : posTile.visitableObjects) - { - if(obj->blockVisit) - { - if (obj->ID == Obj::MONSTER) // Monster - return pos; - else - return int3(-1, -1, -1); //blockvis objects are not guarded by neighbouring creatures - } - } - } - - // See if there are any monsters adjacent. - bool water = posTile.isWater(); - - pos -= int3(1, 1, 0); // Start with top left. - for (int dx = 0; dx < 3; dx++) - { - for (int dy = 0; dy < 3; dy++) - { - if (isInTheMap(pos)) - { - const auto & tile = getTile(pos); - if (tile.visitable && (tile.isWater() == water)) - { - for (CGObjectInstance* obj : tile.visitableObjects) - { - if (obj->ID == Obj::MONSTER && checkForVisitableDir(pos, &posTile, originalPos)) // Monster being able to attack investigated tile - { - return pos; - } - } - } - } - - pos.y++; - } - pos.y -= 3; - pos.x++; - } - +{ + + const int3 originalPos = pos; + // Give monster at position priority. + if (!isInTheMap(pos)) + return int3(-1, -1, -1); + const TerrainTile &posTile = getTile(pos); + if (posTile.visitable) + { + for (CGObjectInstance* obj : posTile.visitableObjects) + { + if(obj->blockVisit) + { + if (obj->ID == Obj::MONSTER) // Monster + return pos; + else + return int3(-1, -1, -1); //blockvis objects are not guarded by neighbouring creatures + } + } + } + + // See if there are any monsters adjacent. + bool water = posTile.isWater(); + + pos -= int3(1, 1, 0); // Start with top left. + for (int dx = 0; dx < 3; dx++) + { + for (int dy = 0; dy < 3; dy++) + { + if (isInTheMap(pos)) + { + const auto & tile = getTile(pos); + if (tile.visitable && (tile.isWater() == water)) + { + for (CGObjectInstance* obj : tile.visitableObjects) + { + if (obj->ID == Obj::MONSTER && checkForVisitableDir(pos, &posTile, originalPos)) // Monster being able to attack investigated tile + { + return pos; + } + } + } + } + + pos.y++; + } + pos.y -= 3; + pos.x++; + } + return int3(-1, -1, -1); } diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index ef24689cd..9413b938c 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -3,7 +3,7 @@ #include "../JsonNode.h" #include "../filesystem/Filesystem.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" #include "../VCMI_Lib.h" MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 99bb4046f..80e4085cd 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -21,8 +21,8 @@ #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" #include "../CObjectHandler.h" -#include "../CObjectWithReward.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectWithReward.h" +#include "../CObjectClassesHandler.h" #include "../VCMI_Lib.h" #include "../NetPacksBase.h" diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index 6a44449e0..0c61400a2 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -14,7 +14,7 @@ #include "CMapService.h" #include "../GameConstants.h" #include "../ResourceSet.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" #include "../int3.h" diff --git a/lib/registerTypes/RegisterTypes.cpp b/lib/registerTypes/RegisterTypes.cpp index 390fc320d..e3ffbdfd3 100644 --- a/lib/registerTypes/RegisterTypes.cpp +++ b/lib/registerTypes/RegisterTypes.cpp @@ -17,7 +17,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" // For reference: peak memory usage by gcc during compilation of register type templates // registerTypesMapObjects: 1.9 Gb diff --git a/lib/registerTypes/TypesClientPacks1.cpp b/lib/registerTypes/TypesClientPacks1.cpp index 97e461493..44101ad82 100644 --- a/lib/registerTypes/TypesClientPacks1.cpp +++ b/lib/registerTypes/TypesClientPacks1.cpp @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" template void registerTypesClientPacks1>(CISer& s); diff --git a/lib/registerTypes/TypesClientPacks2.cpp b/lib/registerTypes/TypesClientPacks2.cpp index be4b656b5..37e137992 100644 --- a/lib/registerTypes/TypesClientPacks2.cpp +++ b/lib/registerTypes/TypesClientPacks2.cpp @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" template void registerTypesClientPacks2>(CISer& s); diff --git a/lib/registerTypes/TypesMapObjects1.cpp b/lib/registerTypes/TypesMapObjects1.cpp index 726ce6092..c0952f6ff 100644 --- a/lib/registerTypes/TypesMapObjects1.cpp +++ b/lib/registerTypes/TypesMapObjects1.cpp @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" template void registerTypesMapObjects1>(CISer& s); diff --git a/lib/registerTypes/TypesMapObjects2.cpp b/lib/registerTypes/TypesMapObjects2.cpp index d77ed5822..197e2f5fe 100644 --- a/lib/registerTypes/TypesMapObjects2.cpp +++ b/lib/registerTypes/TypesMapObjects2.cpp @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" template void registerTypesMapObjects2>(CISer& s); diff --git a/lib/registerTypes/TypesPregamePacks.cpp b/lib/registerTypes/TypesPregamePacks.cpp index 1fd97e76b..b4d73bf4f 100644 --- a/lib/registerTypes/TypesPregamePacks.cpp +++ b/lib/registerTypes/TypesPregamePacks.cpp @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" template void registerTypesPregamePacks>(CISer& s); template void registerTypesPregamePacks>(COSer& s); diff --git a/lib/registerTypes/TypesServerPacks.cpp b/lib/registerTypes/TypesServerPacks.cpp index caabdcd1e..eff72a34e 100644 --- a/lib/registerTypes/TypesServerPacks.cpp +++ b/lib/registerTypes/TypesServerPacks.cpp @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" template void registerTypesServerPacks>(CISer& s); template void registerTypesServerPacks>(COSer& s); diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index d0c9308b0..fa45e4d78 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -17,7 +17,7 @@ #include "../CGeneralTextHandler.h" #include "../mapping/CMapEditManager.h" #include "../CObjectHandler.h" -#include "../CDefObjInfoHandler.h" +#include "../CObjectClassesHandler.h" #include "../CTownHandler.h" #include "../StringConstants.h" #include "CRmgTemplate.h" diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 34db00278..c257a02ed 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -8,7 +8,7 @@ #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CBuildingHandler.h" -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/CObjectClassesHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CObjectHandler.h" #include "../lib/CSpellHandler.h" diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index e1e47c6f4..f5851ea45 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -8,7 +8,7 @@ #include "../lib/Connection.h" #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" -#include "../lib/CDefObjInfoHandler.h" +#include "../lib/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" From 67f11b1a01ade387819db3b16d5a987587840c4e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 24 May 2014 15:45:29 +0300 Subject: [PATCH 17/33] - First part of objects configuration - split objects into 3 files (for now) - integrated object class handler into mod handler --- config/gameConfig.json | 7 ++ config/objects/generic.json | 183 +++++++++++++-------------------- config/objects/moddables.json | 136 ++++++++++++++++++++++++ config/objects/rewardable.json | 44 ++++++++ lib/CModHandler.cpp | 5 +- lib/CObjectClassesHandler.cpp | 40 +++---- lib/CObjectClassesHandler.h | 6 +- 7 files changed, 284 insertions(+), 137 deletions(-) create mode 100644 config/objects/moddables.json create mode 100644 config/objects/rewardable.json diff --git a/config/gameConfig.json b/config/gameConfig.json index 8a5fe9ff1..f2dba20c9 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -45,6 +45,13 @@ "config/heroes/special.json" ], + "objects" : + [ + "config/objects/generic.json", + "config/objects/moddables.json", + "config/objects/rewardable.json" + ], + "artifacts" : [ "config/artifacts.json" diff --git a/config/objects/generic.json b/config/objects/generic.json index 03258aca1..de557a71c 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -1,111 +1,98 @@ { + /// These are objects that can not be configured, either due to + /// their hardcoded status or because they don't have any configurable functionality + "altarOfSacrifice" : { "id" :2, "handler": "market" }, - "arena" : { "id" :4, "handler": "oncePerHero" }, - "artifact" : { "id" :5, "handler": "artifact" }, - "pandoraBox" : { "id" :6, "handler": "pandora" }, + "tradingPost" : { "id" :221, "handler": "market" }, + "tradingPost" : { "id" :99, "handler": "market" }, + "freelancer'SGuild" : { "id" :213, "handler": "market" }, + "blackMarket" : { "id" :7, "handler": "blackMarket" }, - "boat" : { "id" :8, "handler": "boat" }, - "borderGuard" : { "id" :9, "handler": "borderGuard" }, - "keymasterTent" : { "id" :10, "handler": "keymaster" }, - "buoy" : { "id" :11, "handler": "bonusingObject" }, - "campfire" : { "id" :12, "handler": "pickable" }, - "cartographer" : { "id" :13, "handler": "cartographer" }, - "swanPond" : { "id" :14, "handler": "bonusingObject" }, - "coverOfDarkness" : { "id" :15, "handler": "observatory" }, - "creatureBank" : { "id" :16, "handler": "bank" }, - "creatureGeneratorCommon" : { "id" :17, "handler": "dwelling" }, - "creatureGeneratorSpecial" : { "id" :20, "handler": "dwelling" }, - "cursedGround" : { "id" :21, "handler": "generic" }, - "corpse" : { "id" :22, "handler": "onceVisitable" }, - "marlettoTower" : { "id" :23, "handler": "oncePerHero" }, + + "crypt" : { "id" :84, "handler": "bank" }, + "shipwreck" : { "id" :85, "handler": "bank" }, "derelictShip" : { "id" :24, "handler": "bank" }, "dragonUtopia" : { "id" :25, "handler": "bank" }, + + "pandoraBox" : { "id" :6, "handler": "pandora" }, "event" : { "id" :26, "handler": "event" }, - "eyeOfTheMagi" : { "id" :27, "handler": "magi" }, - "faerieRing" : { "id" :28, "handler": "bonusingObject" }, - "flotsam" : { "id" :29, "handler": "pickable" }, - "fountainOfFortune" : { "id" :30, "handler": "bonusingObject" }, - "fountainOfYouth" : { "id" :31, "handler": "bonusingObject" }, - "gardenOfRevelation" : { "id" :32, "handler": "oncePerHero" }, - "garrison" : { "id" :33, "handler": "garrison" }, - "hero" : { "id" :34, "handler": "hero" }, - "hillFort" : { "id" :35, "handler": "generic" }, - "grail" : { "id" :36, "handler": "generic" }, - "hutOfTheMagi" : { "id" :37, "handler": "magi" }, - "idolOfFortune" : { "id" :38, "handler": "bonusingObject" }, - "leanTo" : { "id" :39, "handler": "onceVisitable" }, - "libraryOfEnlightenment" : { "id" :41, "handler": "oncePerHero" }, - "lighthouse" : { "id" :42, "handler": "lighthouse" }, - "monolithOneWayEntrance" : { "id" :43, "handler": "teleport" }, - "monolithOneWayExit" : { "id" :44, "handler": "teleport" }, - "monolithTwoWay" : { "id" :45, "handler": "teleport" }, - "magicPlains" : { "id" :46, "handler": "generic" }, - "schoolOfMagic" : { "id" :47, "handler": "oncePerHero" }, - "magicSpring" : { "id" :48, "handler": "magicSpring" }, - "magicWell" : { "id" :49, "handler": "magicWell" }, - "mercenaryCamp" : { "id" :51, "handler": "oncePerHero" }, - "mermaids" : { "id" :52, "handler": "bonusingObject" }, - "mine" : { "id" :53, "handler": "mine" }, - "monster" : { "id" :54, "handler": "monster" }, - "mysticalGarden" : { "id" :55, "handler": "oncePerWeek" }, - "oasis" : { "id" :56, "handler": "bonusingObject" }, - "obelisk" : { "id" :57, "handler": "obelisk" }, + "redwoodObservatory" : { "id" :58, "handler": "observatory" }, - "oceanBottle" : { "id" :59, "handler": "sign" }, "pillarOfFire" : { "id" :60, "handler": "observatory" }, - "starAxis" : { "id" :61, "handler": "oncePerHero" }, + "coverOfDarkness" : { "id" :15, "handler": "observatory" }, + + "subterraneanGate" : { "id" :103, "handler": "teleport" }, + "whirlpool" : { "id" :111, "handler": "teleport" }, + + "refugeeCamp" : { "id" :78, "handler": "dwelling" }, + "warMachineFactory" : { "id" :106, "handler": "dwelling" }, + + "shrineOfMagicLevel1" : { "id" :88, "handler": "shrine" }, + "shrineOfMagicLevel2" : { "id" :89, "handler": "shrine" }, + "shrineOfMagicLevel3" : { "id" :90, "handler": "shrine" }, + + "eyeOfTheMagi" : { "id" :27, "handler": "magi" }, + "hutOfTheMagi" : { "id" :37, "handler": "magi" }, + + "lighthouse" : { "id" :42, "handler": "lighthouse" }, + "magicWell" : { "id" :49, "handler": "magicWell" }, + "obelisk" : { "id" :57, "handler": "obelisk" }, + "oceanBottle" : { "id" :59, "handler": "sign" }, "prison" : { "id" :62, "handler": "hero" }, "pyramid" : { "id" :63, "handler": "pyramid" }, - "rallyFlag" : { "id" :64, "handler": "bonusingObject" }, + "scholar" : { "id" :81, "handler": "scholar" }, + "shipyard" : { "id" :87, "handler": "shipyard" }, + "sign" : { "id" :91, "handler": "sign" }, + "sirens" : { "id" :92, "handler": "siren" }, + "denOfThieves" : { "id" :97, "handler": "denOfThieves" }, + "university" : { "id" :104, "handler": "university" }, + "witchHut" : { "id" :113, "handler": "witch" }, + "questGuard" : { "id" :215, "handler": "questGuard" }, + + /// Random objects + "randomResource" : { "id" :76, "handler": "resource" }, + "randomTown" : { "id" :77, "handler": "town" }, + "randomHero" : { "id" :70, "handler": "hero" }, + + "randomDwelling" : { "id" :216, "handler": "dwelling" }, + "randomArtifact" : { "id" :65, "handler": "artifact" }, "randomArtifactTreasure" : { "id" :66, "handler": "artifact" }, "randomArtifactMinor" : { "id" :67, "handler": "artifact" }, "randomArtifactMajor" : { "id" :68, "handler": "artifact" }, "randomArtifactRelic" : { "id" :69, "handler": "artifact" }, - "randomHero" : { "id" :70, "handler": "hero" }, + "randomMonster" : { "id" :71, "handler": "monster" }, "randomMonsterLevel1" : { "id" :72, "handler": "monster" }, "randomMonsterLevel2" : { "id" :73, "handler": "monster" }, "randomMonsterLevel3" : { "id" :74, "handler": "monster" }, "randomMonsterLevel4" : { "id" :75, "handler": "monster" }, - "randomResource" : { "id" :76, "handler": "resource" }, - "randomTown" : { "id" :77, "handler": "town" }, - "refugeeCamp" : { "id" :78, "handler": "dwelling" }, - "resource" : { "id" :79, "handler": "resource" }, - "sanctuary" : { "id" :80, "handler": "generic" }, - "scholar" : { "id" :81, "handler": "scholar" }, - "seaChest" : { "id" :82, "handler": "pickable" }, - "seerHut" : { "id" :83, "handler": "seerHut" }, - "crypt" : { "id" :84, "handler": "bank" }, - "shipwreck" : { "id" :85, "handler": "bank" }, - "shipwreckSurvivor" : { "id" :86, "handler": "pickable" }, - "shipyard" : { "id" :87, "handler": "shipyard" }, - "shrineOfMagicLevel1" : { "id" :88, "handler": "shrine" }, - "shrineOfMagicLevel2" : { "id" :89, "handler": "shrine" }, - "shrineOfMagicLevel3" : { "id" :90, "handler": "shrine" }, - "sign" : { "id" :91, "handler": "sign" }, - "sirens" : { "id" :92, "handler": "siren" }, - "spellScroll" : { "id" :93, "handler": "artifact" }, - "stables" : { "id" :94, "handler": "bonusingObject" }, + "randomMonsterLevel5" : { "id" :162, "handler": "monster" }, + "randomMonsterLevel6" : { "id" :163, "handler": "monster" }, + "randomMonsterLevel7" : { "id" :164, "handler": "monster" }, + + /// Classes without dedicated object + "hillFort" : { "id" :35, "handler": "generic" }, + "grail" : { "id" :36, "handler": "generic" }, "tavern" : { "id" :95, "handler": "generic" }, - "temple" : { "id" :96, "handler": "bonusingObject" }, - "denOfThieves" : { "id" :97, "handler": "denOfThieves" }, - "town" : { "id" :98, "handler": "town" }, - "tradingPost" : { "id" :99, "handler": "market" }, - "learningStone" : { "id" :100, "handler": "oncePerHero" }, - "treasureChest" : { "id" :101, "handler": "pickable" }, - "treeOfKnowledge" : { "id" :102, "handler": "oncePerHero" }, - "subterraneanGate" : { "id" :103, "handler": "teleport" }, - "university" : { "id" :104, "handler": "university" }, - "wagon" : { "id" :105, "handler": "onceVisitable" }, - "warMachineFactory" : { "id" :106, "handler": "dwelling" }, - "schoolOfWar" : { "id" :107, "handler": "oncePerHero" }, - "warriorTomb" : { "id" :108, "handler": "onceVisitable" }, - "waterWheel" : { "id" :109, "handler": "oncePerWeek" }, - "wateringHole" : { "id" :110, "handler": "bonusingObject" }, - "whirlpool" : { "id" :111, "handler": "teleport" }, - "windmill" : { "id" :112, "handler": "oncePerWeek" }, - "witchHut" : { "id" :113, "handler": "witch" }, + "sanctuary" : { "id" :80, "handler": "generic" }, + + /// Passive objects, terrain overlays + "cursedGround" : { "id" :21, "handler": "generic" }, + "magicPlains" : { "id" :46, "handler": "generic" }, + "swampFoliage" : { "id" :211, "handler": "generic" }, + "cloverField" : { "id" :222, "handler": "generic" }, + "cursedGround" : { "id" :223, "handler": "generic" }, + "evilFog" : { "id" :224, "handler": "generic" }, + "favorableWinds" : { "id" :225, "handler": "generic" }, + "fieryFields" : { "id" :226, "handler": "generic" }, + "holyGround" : { "id" :227, "handler": "generic" }, + "lucidPools" : { "id" :228, "handler": "generic" }, + "magicClouds" : { "id" :229, "handler": "generic" }, + "magicPlains" : { "id" :230, "handler": "generic" }, + "rocklands" : { "id" :231, "handler": "generic" } + + /// Decorations "cactus" : { "id" :116, "handler": "generic" }, "canyon" : { "id" :117, "handler": "generic" }, "crater" : { "id" :118, "handler": "generic" }, @@ -136,9 +123,6 @@ "trees" : { "id" :155, "handler": "generic" }, "volcano" : { "id" :158, "handler": "generic" }, "reef" : { "id" :161, "handler": "generic" }, - "randomMonsterLevel5" : { "id" :162, "handler": "monster" }, - "randomMonsterLevel6" : { "id" :163, "handler": "monster" }, - "randomMonsterLevel7" : { "id" :164, "handler": "monster" }, "lake" : { "id" :177, "handler": "generic" }, "trees" : { "id" :199, "handler": "generic" }, "desertHills" : { "id" :206, "handler": "generic" }, @@ -146,23 +130,4 @@ "grassHills" : { "id" :208, "handler": "generic" }, "roughHills" : { "id" :209, "handler": "generic" }, "subterraneanRocks" : { "id" :210, "handler": "generic" }, - "swampFoliage" : { "id" :211, "handler": "generic" }, - "borderGate" : { "id" :212, "handler": "borderGate" }, - "freelancer'SGuild" : { "id" :213, "handler": "market" }, - "heroPlaceholder" : { "id" :214, "handler": "heroPlaceholder" }, - "questGuard" : { "id" :215, "handler": "questGuard" }, - "randomDwelling" : { "id" :216, "handler": "dwelling" }, - "garrison" : { "id" :219, "handler": "garrison" }, - "mine" : { "id" :220, "handler": "mine" }, - "tradingPost" : { "id" :221, "handler": "market" }, - "cloverField" : { "id" :222, "handler": "generic" }, - "cursedGround" : { "id" :223, "handler": "generic" }, - "evilFog" : { "id" :224, "handler": "generic" }, - "favorableWinds" : { "id" :225, "handler": "generic" }, - "fieryFields" : { "id" :226, "handler": "generic" }, - "holyGround" : { "id" :227, "handler": "generic" }, - "lucidPools" : { "id" :228, "handler": "generic" }, - "magicClouds" : { "id" :229, "handler": "generic" }, - "magicPlains" : { "id" :230, "handler": "generic" }, - "rocklands" : { "id" :231, "handler": "generic" } } diff --git a/config/objects/moddables.json b/config/objects/moddables.json new file mode 100644 index 000000000..7a0e217fd --- /dev/null +++ b/config/objects/moddables.json @@ -0,0 +1,136 @@ +{ + /// These are objects that have subtypes that change various aspects of their mechanics + /// Should be made configurable (either directly or via other parts of modding system ASAP) + /// Editing these objects either directly or via mod may have negative effect on game + + // subtype: artifact ID + "artifact" : { + "id" :5, + "handler": "artifact", + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VV", "VA"] + } + } + }, + + // subtype: hero CLASS (not hero). + "hero" : { + "id" :34, + "handler": "hero", + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VV", "AV"] + } + } + }, + + // subtype: creatures + "monster" : { + "id" :54, + "handler": "monster" + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VV", "VA"] + } + } + }, + + // subtype: resource ID + "resource" : { + "id" :79, + "handler": "resource", + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VA" ] + } + } + }, + + // subtype: faction + "town" : { + "id" :98, + "handler": "town", + "base" : { + "base" : { + "visitableFrom" : [ "---", "+++", "+++" ], + "mask" : [ + "VVVVV", // a LOT of just visible rows due to towns like Tower + "VVVVV", + "VVVVV", + "VVBBBV", + "VBBBBB", + "VBBABB" + ] + } + } + }, + + // subtype: one of 3 possible boats + "boat" : { + "id" :8, + "handler": "boat", + "base" : { + "base" : { + "visitableFrom" : [ "+++", "+-+", "+++" ], + "mask" : [ "VVV", "VAV" ] + } + } + }, + + // subtype: color of guard + "borderGuard" : { "id" :9, "handler": "borderGuard" }, + "borderGate" : { "id" :212, "handler": "borderGate" }, + "keymasterTent" : { "id" :10, "handler": "keymaster" }, + + // subtype: different content + "creatureBank" : { "id" :16, "handler": "bank" }, + + // subtype: different revealed areas + "cartographer" : { "id" :13, "handler": "cartographer" }, + + // subtype: 0 = normal, 1 = anti-magic + "garrisonHorizontal" : { "id" :33, "handler": "garrison" }, + "garrisonVertical" : { "id" :219, "handler": "garrison" }, + + // Subtype: paired monoliths + "monolithOneWayEntrance" : { "id" :43, "handler": "teleport" }, + "monolithOneWayExit" : { "id" :44, "handler": "teleport" }, + "monolithTwoWay" : { "id" :45, "handler": "teleport" }, + + // subtype: resource ID + "mine" : { "id" :53, "handler": "mine" }, + "abandonedMine" : { "id" :220, "handler": "mine" }, + + // subtype: different appearance. That's all? + "seerHut" : { "id" :83, "handler": "seerHut" }, + + // subtype: level + "randomDwellingLvl" : { "id" :217, "handler": "dwelling" }, + + // subtype: faction ID + "randomDwellingFaction" : { "id" :218, "handler": "dwelling" }, + + // subtype: not well defined, describes various dwellings that can be placed as random + "creatureGeneratorCommon" : { + "id" :17, + "handler": "dwelling", + "base" : { + "base" : { + "visitableFrom" : [ "---", "+++", "+++" ], + "mask" : [ "VVV", "VBB", "VAA" ] + } + } + }, + + // subtype: unique special dwellings - golem factory, elemental conflux + "creatureGeneratorSpecial" : { "id" :20, "handler": "dwelling" }, + + // don't have subtypes (at least now), but closely connected to this objects + "spellScroll" : { "id" :93, "handler": "artifact" }, + "heroPlaceholder" : { "id" :214, "handler": "heroPlaceholder" } +} diff --git a/config/objects/rewardable.json b/config/objects/rewardable.json new file mode 100644 index 000000000..0f447efc5 --- /dev/null +++ b/config/objects/rewardable.json @@ -0,0 +1,44 @@ +{ + /// These are objects that covered by concept of "configurable object" + /// Most or even all of their configuration located in this file + "magicSpring" : { "id" :48, "handler": "magicSpring" }, + + "mysticalGarden" : { "id" :55, "handler": "oncePerWeek" }, + "windmill" : { "id" :112, "handler": "oncePerWeek" }, + "waterWheel" : { "id" :109, "handler": "oncePerWeek" }, + + "leanTo" : { "id" :39, "handler": "onceVisitable" }, + "corpse" : { "id" :22, "handler": "onceVisitable" }, + "wagon" : { "id" :105, "handler": "onceVisitable" }, + "warriorTomb" : { "id" :108, "handler": "onceVisitable" }, + + "campfire" : { "id" :12, "handler": "pickable" }, + "flotsam" : { "id" :29, "handler": "pickable" }, + "seaChest" : { "id" :82, "handler": "pickable" }, + "shipwreckSurvivor" : { "id" :86, "handler": "pickable" }, + "treasureChest" : { "id" :101, "handler": "pickable" }, + + "arena" : { "id" :4, "handler": "oncePerHero" }, + "marlettoTower" : { "id" :23, "handler": "oncePerHero" }, + "gardenOfRevelation" : { "id" :32, "handler": "oncePerHero" }, + "libraryOfEnlightenment" : { "id" :41, "handler": "oncePerHero" }, + "mercenaryCamp" : { "id" :51, "handler": "oncePerHero" }, + "starAxis" : { "id" :61, "handler": "oncePerHero" }, + "learningStone" : { "id" :100, "handler": "oncePerHero" }, + "treeOfKnowledge" : { "id" :102, "handler": "oncePerHero" }, + "schoolOfMagic" : { "id" :47, "handler": "oncePerHero" }, + "schoolOfWar" : { "id" :107, "handler": "oncePerHero" }, + + "buoy" : { "id" :11, "handler": "bonusingObject" }, + "swanPond" : { "id" :14, "handler": "bonusingObject" }, + "faerieRing" : { "id" :28, "handler": "bonusingObject" }, + "fountainOfFortune" : { "id" :30, "handler": "bonusingObject" }, + "fountainOfYouth" : { "id" :31, "handler": "bonusingObject" }, + "idolOfFortune" : { "id" :38, "handler": "bonusingObject" }, + "mermaids" : { "id" :52, "handler": "bonusingObject" }, + "oasis" : { "id" :56, "handler": "bonusingObject" }, + "stables" : { "id" :94, "handler": "bonusingObject" }, + "temple" : { "id" :96, "handler": "bonusingObject" }, + "rallyFlag" : { "id" :64, "handler": "bonusingObject" }, + "wateringHole" : { "id" :110, "handler": "bonusingObject" }, +} diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index b1685df8b..eb4c56551 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -355,10 +355,11 @@ CContentHandler::CContentHandler() handlers.insert(std::make_pair("artifacts", ContentTypeHandler(VLC->arth, "artifact"))); handlers.insert(std::make_pair("creatures", ContentTypeHandler(VLC->creh, "creature"))); handlers.insert(std::make_pair("factions", ContentTypeHandler(VLC->townh, "faction"))); + handlers.insert(std::make_pair("objects", ContentTypeHandler(VLC->objtypeh, "object"))); handlers.insert(std::make_pair("heroes", ContentTypeHandler(VLC->heroh, "hero"))); - handlers.insert(std::make_pair("spells", ContentTypeHandler(VLC->spellh, "spell"))); + handlers.insert(std::make_pair("spells", ContentTypeHandler(VLC->spellh, "spell"))); - //TODO: bonuses, something else? + //TODO: any other types of moddables? } bool CContentHandler::preloadModData(std::string modName, JsonNode modConfig, bool validate) diff --git a/lib/CObjectClassesHandler.cpp b/lib/CObjectClassesHandler.cpp index 0d2302cf4..06e92a51e 100644 --- a/lib/CObjectClassesHandler.cpp +++ b/lib/CObjectClassesHandler.cpp @@ -330,28 +330,6 @@ bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const { return allowedTerrains.count(terrain) != 0; } -/* -void CObjectClassesHandler::readTextFile(std::string path) -{ - CLegacyConfigParser parser(path); - size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else - parser.endLine(); - - for (size_t i=0; i readTextFile(std::string path) std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) { + objects.resize(dataSize); + std::vector ret(dataSize);// create storage for 256 objects auto parseFile = [&](std::string filename) @@ -436,8 +416,8 @@ std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) } }; - parseFile("Data/Objects.txt"); - parseFile("Data/Heroes.txt"); + //parseFile("Data/Objects.txt"); + //parseFile("Data/Heroes.txt"); CLegacyConfigParser parser("Data/ObjNames.txt"); for (size_t i=0; i<256; i++) @@ -464,12 +444,22 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { + auto object = loadFromJson(data); + object->id = objects.size(); + objects.push_back(object); + VLC->modh->identifiers.registerObject(scope, "object", name, object->id); } void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { + auto object = loadFromJson(data); + object->id = index; + assert(objects[index] == nullptr); // ensure that this id was not loaded before + objects[index] = object; + + VLC->modh->identifiers.registerObject(scope, "object", name, object->id); } std::vector CObjectClassesHandler::getDefaultAllowed() const @@ -479,7 +469,7 @@ std::vector CObjectClassesHandler::getDefaultAllowed() const TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) const { - if (objects.count(type)) + if (objects.size() > type) { if (objects.at(type)->objects.count(subtype)) return objects.at(type)->objects.at(subtype); diff --git a/lib/CObjectClassesHandler.h b/lib/CObjectClassesHandler.h index 1632c2f04..f463db53d 100644 --- a/lib/CObjectClassesHandler.h +++ b/lib/CObjectClassesHandler.h @@ -114,6 +114,9 @@ protected: virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; public: + /// returns true if type is not configurable and new objects can be created without valid config + virtual bool confFree(); + /// loads templates from Json structure using fields "base" and "templates" virtual void init(const JsonNode & input); @@ -145,6 +148,7 @@ public: } }; +/// Class that is used for objects that do not have dedicated handler template class CDefaultObjectTypeHandler : public AObjectTypeHandler { @@ -190,7 +194,7 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase }; /// list of object handlers, each of them handles only one type - std::map objects; + std::vector objects; /// map that is filled during contruction with all known handlers. Not serializeable std::map > handlerConstructors; From dc7f8201613b1c5e5ccb6c3862f1ac1b55b97cf6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 3 Jun 2014 21:43:33 +0300 Subject: [PATCH 18/33] Implemented object schema. Commiting current progress before sync with upstream. --- config/defaultMods.json | 3 +- config/objects/generic.json | 216 ++++++++++++++--------------- config/objects/moddables.json | 52 +++---- config/objects/rewardable.json | 70 +++++----- config/schemas/objectTemplate.json | 10 +- lib/CArtHandler.cpp | 1 + lib/CObjectClassesHandler.cpp | 110 ++++++++++----- lib/CObjectClassesHandler.h | 16 ++- 8 files changed, 261 insertions(+), 217 deletions(-) diff --git a/config/defaultMods.json b/config/defaultMods.json index 5866db878..4082d1f53 100644 --- a/config/defaultMods.json +++ b/config/defaultMods.json @@ -8,7 +8,8 @@ "creature" : 150, "faction" : 9, "hero" : 156, - "spell" : 81, + "spell" : 81, + "object" : 256, "mapVersion" : 28 // max supported version, SoD }, diff --git a/config/objects/generic.json b/config/objects/generic.json index de557a71c..9ba8fe83c 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -2,132 +2,132 @@ /// These are objects that can not be configured, either due to /// their hardcoded status or because they don't have any configurable functionality - "altarOfSacrifice" : { "id" :2, "handler": "market" }, - "tradingPost" : { "id" :221, "handler": "market" }, - "tradingPost" : { "id" :99, "handler": "market" }, - "freelancer'SGuild" : { "id" :213, "handler": "market" }, + "altarOfSacrifice" : { "index" :2, "handler": "market" }, + "tradingPost" : { "index" :221, "handler": "market" }, + "tradingPostDUPLICATE" : { "index" :99, "handler": "market" }, + "freelancersGuild" : { "index" :213, "handler": "market" }, - "blackMarket" : { "id" :7, "handler": "blackMarket" }, + "blackMarket" : { "index" :7, "handler": "blackMarket" }, - "crypt" : { "id" :84, "handler": "bank" }, - "shipwreck" : { "id" :85, "handler": "bank" }, - "derelictShip" : { "id" :24, "handler": "bank" }, - "dragonUtopia" : { "id" :25, "handler": "bank" }, + "crypt" : { "index" :84, "handler": "bank" }, + "shipwreck" : { "index" :85, "handler": "bank" }, + "derelictShip" : { "index" :24, "handler": "bank" }, + "dragonUtopia" : { "index" :25, "handler": "bank" }, - "pandoraBox" : { "id" :6, "handler": "pandora" }, - "event" : { "id" :26, "handler": "event" }, + "pandoraBox" : { "index" :6, "handler": "pandora" }, + "event" : { "index" :26, "handler": "event" }, - "redwoodObservatory" : { "id" :58, "handler": "observatory" }, - "pillarOfFire" : { "id" :60, "handler": "observatory" }, - "coverOfDarkness" : { "id" :15, "handler": "observatory" }, + "redwoodObservatory" : { "index" :58, "handler": "observatory" }, + "pillarOfFire" : { "index" :60, "handler": "observatory" }, + "coverOfDarkness" : { "index" :15, "handler": "observatory" }, - "subterraneanGate" : { "id" :103, "handler": "teleport" }, - "whirlpool" : { "id" :111, "handler": "teleport" }, + "subterraneanGate" : { "index" :103, "handler": "teleport" }, + "whirlpool" : { "index" :111, "handler": "teleport" }, - "refugeeCamp" : { "id" :78, "handler": "dwelling" }, - "warMachineFactory" : { "id" :106, "handler": "dwelling" }, + "refugeeCamp" : { "index" :78, "handler": "dwelling" }, + "warMachineFactory" : { "index" :106, "handler": "dwelling" }, - "shrineOfMagicLevel1" : { "id" :88, "handler": "shrine" }, - "shrineOfMagicLevel2" : { "id" :89, "handler": "shrine" }, - "shrineOfMagicLevel3" : { "id" :90, "handler": "shrine" }, + "shrineOfMagicLevel1" : { "index" :88, "handler": "shrine" }, + "shrineOfMagicLevel2" : { "index" :89, "handler": "shrine" }, + "shrineOfMagicLevel3" : { "index" :90, "handler": "shrine" }, - "eyeOfTheMagi" : { "id" :27, "handler": "magi" }, - "hutOfTheMagi" : { "id" :37, "handler": "magi" }, + "eyeOfTheMagi" : { "index" :27, "handler": "magi" }, + "hutOfTheMagi" : { "index" :37, "handler": "magi" }, - "lighthouse" : { "id" :42, "handler": "lighthouse" }, - "magicWell" : { "id" :49, "handler": "magicWell" }, - "obelisk" : { "id" :57, "handler": "obelisk" }, - "oceanBottle" : { "id" :59, "handler": "sign" }, - "prison" : { "id" :62, "handler": "hero" }, - "pyramid" : { "id" :63, "handler": "pyramid" }, - "scholar" : { "id" :81, "handler": "scholar" }, - "shipyard" : { "id" :87, "handler": "shipyard" }, - "sign" : { "id" :91, "handler": "sign" }, - "sirens" : { "id" :92, "handler": "siren" }, - "denOfThieves" : { "id" :97, "handler": "denOfThieves" }, - "university" : { "id" :104, "handler": "university" }, - "witchHut" : { "id" :113, "handler": "witch" }, - "questGuard" : { "id" :215, "handler": "questGuard" }, + "lighthouse" : { "index" :42, "handler": "lighthouse" }, + "magicWell" : { "index" :49, "handler": "magicWell" }, + "obelisk" : { "index" :57, "handler": "obelisk" }, + "oceanBottle" : { "index" :59, "handler": "sign" }, + "prison" : { "index" :62, "handler": "hero" }, + "pyramid" : { "index" :63, "handler": "pyramid" }, + "scholar" : { "index" :81, "handler": "scholar" }, + "shipyard" : { "index" :87, "handler": "shipyard" }, + "sign" : { "index" :91, "handler": "sign" }, + "sirens" : { "index" :92, "handler": "siren" }, + "denOfThieves" : { "index" :97, "handler": "denOfThieves" }, + "university" : { "index" :104, "handler": "university" }, + "witchHut" : { "index" :113, "handler": "witch" }, + "questGuard" : { "index" :215, "handler": "questGuard" }, /// Random objects - "randomResource" : { "id" :76, "handler": "resource" }, - "randomTown" : { "id" :77, "handler": "town" }, - "randomHero" : { "id" :70, "handler": "hero" }, + "randomResource" : { "index" :76, "handler": "resource" }, + "randomTown" : { "index" :77, "handler": "town" }, + "randomHero" : { "index" :70, "handler": "hero" }, - "randomDwelling" : { "id" :216, "handler": "dwelling" }, + "randomDwelling" : { "index" :216, "handler": "dwelling" }, - "randomArtifact" : { "id" :65, "handler": "artifact" }, - "randomArtifactTreasure" : { "id" :66, "handler": "artifact" }, - "randomArtifactMinor" : { "id" :67, "handler": "artifact" }, - "randomArtifactMajor" : { "id" :68, "handler": "artifact" }, - "randomArtifactRelic" : { "id" :69, "handler": "artifact" }, + "randomArtifact" : { "index" :65, "handler": "artifact" }, + "randomArtifactTreasure" : { "index" :66, "handler": "artifact" }, + "randomArtifactMinor" : { "index" :67, "handler": "artifact" }, + "randomArtifactMajor" : { "index" :68, "handler": "artifact" }, + "randomArtifactRelic" : { "index" :69, "handler": "artifact" }, - "randomMonster" : { "id" :71, "handler": "monster" }, - "randomMonsterLevel1" : { "id" :72, "handler": "monster" }, - "randomMonsterLevel2" : { "id" :73, "handler": "monster" }, - "randomMonsterLevel3" : { "id" :74, "handler": "monster" }, - "randomMonsterLevel4" : { "id" :75, "handler": "monster" }, - "randomMonsterLevel5" : { "id" :162, "handler": "monster" }, - "randomMonsterLevel6" : { "id" :163, "handler": "monster" }, - "randomMonsterLevel7" : { "id" :164, "handler": "monster" }, + "randomMonster" : { "index" :71, "handler": "monster" }, + "randomMonsterLevel1" : { "index" :72, "handler": "monster" }, + "randomMonsterLevel2" : { "index" :73, "handler": "monster" }, + "randomMonsterLevel3" : { "index" :74, "handler": "monster" }, + "randomMonsterLevel4" : { "index" :75, "handler": "monster" }, + "randomMonsterLevel5" : { "index" :162, "handler": "monster" }, + "randomMonsterLevel6" : { "index" :163, "handler": "monster" }, + "randomMonsterLevel7" : { "index" :164, "handler": "monster" }, /// Classes without dedicated object - "hillFort" : { "id" :35, "handler": "generic" }, - "grail" : { "id" :36, "handler": "generic" }, - "tavern" : { "id" :95, "handler": "generic" }, - "sanctuary" : { "id" :80, "handler": "generic" }, + "hillFort" : { "index" :35, "handler": "generic" }, + "grail" : { "index" :36, "handler": "generic" }, + "tavern" : { "index" :95, "handler": "generic" }, + "sanctuary" : { "index" :80, "handler": "generic" }, /// Passive objects, terrain overlays - "cursedGround" : { "id" :21, "handler": "generic" }, - "magicPlains" : { "id" :46, "handler": "generic" }, - "swampFoliage" : { "id" :211, "handler": "generic" }, - "cloverField" : { "id" :222, "handler": "generic" }, - "cursedGround" : { "id" :223, "handler": "generic" }, - "evilFog" : { "id" :224, "handler": "generic" }, - "favorableWinds" : { "id" :225, "handler": "generic" }, - "fieryFields" : { "id" :226, "handler": "generic" }, - "holyGround" : { "id" :227, "handler": "generic" }, - "lucidPools" : { "id" :228, "handler": "generic" }, - "magicClouds" : { "id" :229, "handler": "generic" }, - "magicPlains" : { "id" :230, "handler": "generic" }, - "rocklands" : { "id" :231, "handler": "generic" } + "cursedGround" : { "index" :21, "handler": "generic" }, + "magicPlains" : { "index" :46, "handler": "generic" }, + "swampFoliage" : { "index" :211, "handler": "generic" }, + "cloverField" : { "index" :222, "handler": "generic" }, + "cursedGroundDUPLICATE" : { "index" :223, "handler": "generic" }, + "evilFog" : { "index" :224, "handler": "generic" }, + "favorableWinds" : { "index" :225, "handler": "generic" }, + "fieryFields" : { "index" :226, "handler": "generic" }, + "holyGround" : { "index" :227, "handler": "generic" }, + "lucidPools" : { "index" :228, "handler": "generic" }, + "magicClouds" : { "index" :229, "handler": "generic" }, + "magicPlainsDUPLICATE" : { "index" :230, "handler": "generic" }, + "rocklands" : { "index" :231, "handler": "generic" }, /// Decorations - "cactus" : { "id" :116, "handler": "generic" }, - "canyon" : { "id" :117, "handler": "generic" }, - "crater" : { "id" :118, "handler": "generic" }, - "deadVegetation" : { "id" :119, "handler": "generic" }, - "flowers" : { "id" :120, "handler": "generic" }, - "frozenLake" : { "id" :121, "handler": "generic" }, - "hole" : { "id" :124, "handler": "generic" }, - "kelp" : { "id" :125, "handler": "generic" }, - "lake" : { "id" :126, "handler": "generic" }, - "lavaFlow" : { "id" :127, "handler": "generic" }, - "lavaLake" : { "id" :128, "handler": "generic" }, - "mushrooms" : { "id" :129, "handler": "generic" }, - "log" : { "id" :130, "handler": "generic" }, - "mandrake" : { "id" :131, "handler": "generic" }, - "moss" : { "id" :132, "handler": "generic" }, - "mound" : { "id" :133, "handler": "generic" }, - "mountain" : { "id" :134, "handler": "generic" }, - "oakTrees" : { "id" :135, "handler": "generic" }, - "outcropping" : { "id" :136, "handler": "generic" }, - "pineTrees" : { "id" :137, "handler": "generic" }, - "riverDelta" : { "id" :143, "handler": "generic" }, - "rock" : { "id" :147, "handler": "generic" }, - "sandDune" : { "id" :148, "handler": "generic" }, - "sandPit" : { "id" :149, "handler": "generic" }, - "shrub" : { "id" :150, "handler": "generic" }, - "skull" : { "id" :151, "handler": "generic" }, - "stump" : { "id" :153, "handler": "generic" }, - "trees" : { "id" :155, "handler": "generic" }, - "volcano" : { "id" :158, "handler": "generic" }, - "reef" : { "id" :161, "handler": "generic" }, - "lake" : { "id" :177, "handler": "generic" }, - "trees" : { "id" :199, "handler": "generic" }, - "desertHills" : { "id" :206, "handler": "generic" }, - "dirtHills" : { "id" :207, "handler": "generic" }, - "grassHills" : { "id" :208, "handler": "generic" }, - "roughHills" : { "id" :209, "handler": "generic" }, - "subterraneanRocks" : { "id" :210, "handler": "generic" }, + "cactus" : { "index" :116, "handler": "generic" }, + "canyon" : { "index" :117, "handler": "generic" }, + "crater" : { "index" :118, "handler": "generic" }, + "deadVegetation" : { "index" :119, "handler": "generic" }, + "flowers" : { "index" :120, "handler": "generic" }, + "frozenLake" : { "index" :121, "handler": "generic" }, + "hole" : { "index" :124, "handler": "generic" }, + "kelp" : { "index" :125, "handler": "generic" }, + "lake" : { "index" :126, "handler": "generic" }, + "lavaFlow" : { "index" :127, "handler": "generic" }, + "lavaLake" : { "index" :128, "handler": "generic" }, + "mushrooms" : { "index" :129, "handler": "generic" }, + "log" : { "index" :130, "handler": "generic" }, + "mandrake" : { "index" :131, "handler": "generic" }, + "moss" : { "index" :132, "handler": "generic" }, + "mound" : { "index" :133, "handler": "generic" }, + "mountain" : { "index" :134, "handler": "generic" }, + "oakTrees" : { "index" :135, "handler": "generic" }, + "outcropping" : { "index" :136, "handler": "generic" }, + "pineTrees" : { "index" :137, "handler": "generic" }, + "riverDelta" : { "index" :143, "handler": "generic" }, + "rock" : { "index" :147, "handler": "generic" }, + "sandDune" : { "index" :148, "handler": "generic" }, + "sandPit" : { "index" :149, "handler": "generic" }, + "shrub" : { "index" :150, "handler": "generic" }, + "skull" : { "index" :151, "handler": "generic" }, + "stump" : { "index" :153, "handler": "generic" }, + "trees" : { "index" :155, "handler": "generic" }, + "volcano" : { "index" :158, "handler": "generic" }, + "reef" : { "index" :161, "handler": "generic" }, + "lakeDUPLICATE" : { "index" :177, "handler": "generic" }, + "treesDUPLICATE" : { "index" :199, "handler": "generic" }, + "desertHills" : { "index" :206, "handler": "generic" }, + "dirtHills" : { "index" :207, "handler": "generic" }, + "grassHills" : { "index" :208, "handler": "generic" }, + "roughHills" : { "index" :209, "handler": "generic" }, + "subterraneanRocks" : { "index" :210, "handler": "generic" } } diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 7a0e217fd..50ec815f2 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -5,7 +5,7 @@ // subtype: artifact ID "artifact" : { - "id" :5, + "index" :5, "handler": "artifact", "base" : { "base" : { @@ -17,7 +17,7 @@ // subtype: hero CLASS (not hero). "hero" : { - "id" :34, + "index" :34, "handler": "hero", "base" : { "base" : { @@ -29,8 +29,8 @@ // subtype: creatures "monster" : { - "id" :54, - "handler": "monster" + "index" :54, + "handler": "monster", "base" : { "base" : { "visitableFrom" : [ "+++", "+-+", "+++" ], @@ -41,7 +41,7 @@ // subtype: resource ID "resource" : { - "id" :79, + "index" :79, "handler": "resource", "base" : { "base" : { @@ -53,7 +53,7 @@ // subtype: faction "town" : { - "id" :98, + "index" :98, "handler": "town", "base" : { "base" : { @@ -72,7 +72,7 @@ // subtype: one of 3 possible boats "boat" : { - "id" :8, + "index" :8, "handler": "boat", "base" : { "base" : { @@ -83,41 +83,41 @@ }, // subtype: color of guard - "borderGuard" : { "id" :9, "handler": "borderGuard" }, - "borderGate" : { "id" :212, "handler": "borderGate" }, - "keymasterTent" : { "id" :10, "handler": "keymaster" }, + "borderGuard" : { "index" :9, "handler": "borderGuard" }, + "borderGate" : { "index" :212, "handler": "borderGate" }, + "keymasterTent" : { "index" :10, "handler": "keymaster" }, // subtype: different content - "creatureBank" : { "id" :16, "handler": "bank" }, + "creatureBank" : { "index" :16, "handler": "bank" }, // subtype: different revealed areas - "cartographer" : { "id" :13, "handler": "cartographer" }, + "cartographer" : { "index" :13, "handler": "cartographer" }, // subtype: 0 = normal, 1 = anti-magic - "garrisonHorizontal" : { "id" :33, "handler": "garrison" }, - "garrisonVertical" : { "id" :219, "handler": "garrison" }, + "garrisonHorizontal" : { "index" :33, "handler": "garrison" }, + "garrisonVertical" : { "index" :219, "handler": "garrison" }, // Subtype: paired monoliths - "monolithOneWayEntrance" : { "id" :43, "handler": "teleport" }, - "monolithOneWayExit" : { "id" :44, "handler": "teleport" }, - "monolithTwoWay" : { "id" :45, "handler": "teleport" }, + "monolithOneWayEntrance" : { "index" :43, "handler": "teleport" }, + "monolithOneWayExit" : { "index" :44, "handler": "teleport" }, + "monolithTwoWay" : { "index" :45, "handler": "teleport" }, // subtype: resource ID - "mine" : { "id" :53, "handler": "mine" }, - "abandonedMine" : { "id" :220, "handler": "mine" }, + "mine" : { "index" :53, "handler": "mine" }, + "abandonedMine" : { "index" :220, "handler": "mine" }, // subtype: different appearance. That's all? - "seerHut" : { "id" :83, "handler": "seerHut" }, + "seerHut" : { "index" :83, "handler": "seerHut" }, // subtype: level - "randomDwellingLvl" : { "id" :217, "handler": "dwelling" }, + "randomDwellingLvl" : { "index" :217, "handler": "dwelling" }, // subtype: faction ID - "randomDwellingFaction" : { "id" :218, "handler": "dwelling" }, + "randomDwellingFaction" : { "index" :218, "handler": "dwelling" }, // subtype: not well defined, describes various dwellings that can be placed as random "creatureGeneratorCommon" : { - "id" :17, + "index" :17, "handler": "dwelling", "base" : { "base" : { @@ -128,9 +128,9 @@ }, // subtype: unique special dwellings - golem factory, elemental conflux - "creatureGeneratorSpecial" : { "id" :20, "handler": "dwelling" }, + "creatureGeneratorSpecial" : { "index" :20, "handler": "dwelling" }, // don't have subtypes (at least now), but closely connected to this objects - "spellScroll" : { "id" :93, "handler": "artifact" }, - "heroPlaceholder" : { "id" :214, "handler": "heroPlaceholder" } + "spellScroll" : { "index" :93, "handler": "artifact" }, + "heroPlaceholder" : { "index" :214, "handler": "heroPlaceholder" } } diff --git a/config/objects/rewardable.json b/config/objects/rewardable.json index 0f447efc5..7b6a06984 100644 --- a/config/objects/rewardable.json +++ b/config/objects/rewardable.json @@ -1,44 +1,44 @@ { /// These are objects that covered by concept of "configurable object" /// Most or even all of their configuration located in this file - "magicSpring" : { "id" :48, "handler": "magicSpring" }, + "magicSpring" : { "index" :48, "handler": "magicSpring" }, - "mysticalGarden" : { "id" :55, "handler": "oncePerWeek" }, - "windmill" : { "id" :112, "handler": "oncePerWeek" }, - "waterWheel" : { "id" :109, "handler": "oncePerWeek" }, + "mysticalGarden" : { "index" :55, "handler": "oncePerWeek" }, + "windmill" : { "index" :112, "handler": "oncePerWeek" }, + "waterWheel" : { "index" :109, "handler": "oncePerWeek" }, - "leanTo" : { "id" :39, "handler": "onceVisitable" }, - "corpse" : { "id" :22, "handler": "onceVisitable" }, - "wagon" : { "id" :105, "handler": "onceVisitable" }, - "warriorTomb" : { "id" :108, "handler": "onceVisitable" }, + "leanTo" : { "index" :39, "handler": "onceVisitable" }, + "corpse" : { "index" :22, "handler": "onceVisitable" }, + "wagon" : { "index" :105, "handler": "onceVisitable" }, + "warriorTomb" : { "index" :108, "handler": "onceVisitable" }, - "campfire" : { "id" :12, "handler": "pickable" }, - "flotsam" : { "id" :29, "handler": "pickable" }, - "seaChest" : { "id" :82, "handler": "pickable" }, - "shipwreckSurvivor" : { "id" :86, "handler": "pickable" }, - "treasureChest" : { "id" :101, "handler": "pickable" }, + "campfire" : { "index" :12, "handler": "pickable" }, + "flotsam" : { "index" :29, "handler": "pickable" }, + "seaChest" : { "index" :82, "handler": "pickable" }, + "shipwreckSurvivor" : { "index" :86, "handler": "pickable" }, + "treasureChest" : { "index" :101, "handler": "pickable" }, - "arena" : { "id" :4, "handler": "oncePerHero" }, - "marlettoTower" : { "id" :23, "handler": "oncePerHero" }, - "gardenOfRevelation" : { "id" :32, "handler": "oncePerHero" }, - "libraryOfEnlightenment" : { "id" :41, "handler": "oncePerHero" }, - "mercenaryCamp" : { "id" :51, "handler": "oncePerHero" }, - "starAxis" : { "id" :61, "handler": "oncePerHero" }, - "learningStone" : { "id" :100, "handler": "oncePerHero" }, - "treeOfKnowledge" : { "id" :102, "handler": "oncePerHero" }, - "schoolOfMagic" : { "id" :47, "handler": "oncePerHero" }, - "schoolOfWar" : { "id" :107, "handler": "oncePerHero" }, + "arena" : { "index" :4, "handler": "oncePerHero" }, + "marlettoTower" : { "index" :23, "handler": "oncePerHero" }, + "gardenOfRevelation" : { "index" :32, "handler": "oncePerHero" }, + "libraryOfEnlightenment" : { "index" :41, "handler": "oncePerHero" }, + "mercenaryCamp" : { "index" :51, "handler": "oncePerHero" }, + "starAxis" : { "index" :61, "handler": "oncePerHero" }, + "learningStone" : { "index" :100, "handler": "oncePerHero" }, + "treeOfKnowledge" : { "index" :102, "handler": "oncePerHero" }, + "schoolOfMagic" : { "index" :47, "handler": "oncePerHero" }, + "schoolOfWar" : { "index" :107, "handler": "oncePerHero" }, - "buoy" : { "id" :11, "handler": "bonusingObject" }, - "swanPond" : { "id" :14, "handler": "bonusingObject" }, - "faerieRing" : { "id" :28, "handler": "bonusingObject" }, - "fountainOfFortune" : { "id" :30, "handler": "bonusingObject" }, - "fountainOfYouth" : { "id" :31, "handler": "bonusingObject" }, - "idolOfFortune" : { "id" :38, "handler": "bonusingObject" }, - "mermaids" : { "id" :52, "handler": "bonusingObject" }, - "oasis" : { "id" :56, "handler": "bonusingObject" }, - "stables" : { "id" :94, "handler": "bonusingObject" }, - "temple" : { "id" :96, "handler": "bonusingObject" }, - "rallyFlag" : { "id" :64, "handler": "bonusingObject" }, - "wateringHole" : { "id" :110, "handler": "bonusingObject" }, + "buoy" : { "index" :11, "handler": "bonusingObject" }, + "swanPond" : { "index" :14, "handler": "bonusingObject" }, + "faerieRing" : { "index" :28, "handler": "bonusingObject" }, + "fountainOfFortune" : { "index" :30, "handler": "bonusingObject" }, + "fountainOfYouth" : { "index" :31, "handler": "bonusingObject" }, + "idolOfFortune" : { "index" :38, "handler": "bonusingObject" }, + "mermaids" : { "index" :52, "handler": "bonusingObject" }, + "oasis" : { "index" :56, "handler": "bonusingObject" }, + "stables" : { "index" :94, "handler": "bonusingObject" }, + "temple" : { "index" :96, "handler": "bonusingObject" }, + "rallyFlag" : { "index" :64, "handler": "bonusingObject" }, + "wateringHole" : { "index" :110, "handler": "bonusingObject" }, } diff --git a/config/schemas/objectTemplate.json b/config/schemas/objectTemplate.json index 649262ba7..bd66f1922 100644 --- a/config/schemas/objectTemplate.json +++ b/config/schemas/objectTemplate.json @@ -3,18 +3,10 @@ "$schema": "http://json-schema.org/draft-04/schema", "title" : "VCMI map object template format", "description" : "Description of map object tempate that describes appearence of object instance", - "required": ["basebase", "base", "animation", "mask" ], + "required": [ "animation", "mask" ], "additionalProperties" : false, "properties":{ - "basebase": { - "type" : "number", - "description": "Base object type, e.g. town or hero" - }, - "base": { - "type" : "number", - "description": "Object subtype, e.g. Castle, Rampart, Cleric, Demon" - }, "animation": { "type" : "string", "description": "Path to def file with animation of this object", diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index e3008a582..1cc1f7e77 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -658,6 +658,7 @@ void CArtHandler::afterLoadFinalization() // add new template. // Necessary for objects added via mods that don't have any templates in H3 + VLC->objtypeh->createObject(art->Name(), JsonNode(), Obj::ARTIFACT, art->id.num); VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->id)->addTemplate(base); } } diff --git a/lib/CObjectClassesHandler.cpp b/lib/CObjectClassesHandler.cpp index 06e92a51e..146ebae9e 100644 --- a/lib/CObjectClassesHandler.cpp +++ b/lib/CObjectClassesHandler.cpp @@ -333,11 +333,12 @@ bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const CObjectClassesHandler::CObjectClassesHandler() { - // list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code - handlerConstructors["configurable"] = std::make_shared; - +#define SET_HANDLER_CLASS(STRING, CLASSNAME) handlerConstructors[STRING] = std::make_shared; #define SET_HANDLER(STRING, TYPENAME) handlerConstructors[STRING] = std::make_shared > + // list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code + SET_HANDLER_CLASS("configurable", CObjectWithRewardConstructor); + SET_HANDLER("", CGObjectInstance); SET_HANDLER("generic", CGObjectInstance); @@ -385,39 +386,34 @@ CObjectClassesHandler::CObjectClassesHandler() SET_HANDLER("oncePerWeek", CGVisitableOPW); SET_HANDLER("witch", CGWitchHut); +#undef SET_HANDLER_CLASS #undef SET_HANDLER } -static std::vector readTextFile(std::string path) +template +void readTextFile(Container objects, std::string path) { - //TODO + CLegacyConfigParser parser(path); + size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else + parser.endLine(); + + for (size_t i=0; i CObjectClassesHandler::loadLegacyData(size_t dataSize) { - objects.resize(dataSize); + readTextFile(legacyTemplates, "Data/Objects.txt"); + readTextFile(legacyTemplates, "Data/Heroes.txt"); std::vector ret(dataSize);// create storage for 256 objects - - auto parseFile = [&](std::string filename) - { - auto entries = readTextFile(filename); - for (JsonNode & entry : entries) - { - si32 id = entry["basebase"].Float(); - si32 subid = entry["base"].Float(); - - entry.Struct().erase("basebase"); - entry.Struct().erase("base"); - - if (ret[id].Vector().size() <= subid) - ret[id].Vector().resize(subid+1); - ret[id]["legacyTypes"].Vector()[subid][entry["animation"].String()].swap(entry); - } - }; - - //parseFile("Data/Objects.txt"); - //parseFile("Data/Heroes.txt"); + assert(dataSize == 256); CLegacyConfigParser parser("Data/ObjNames.txt"); for (size_t i=0; i<256; i++) @@ -428,16 +424,48 @@ std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) return ret; } +/// selects preferred ID (or subID) for new object +template +si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 defaultID) +{ + if (!fixedID.isNull() && fixedID.Float() < defaultID) + return fixedID.Float(); // H3M object with fixed ID + + if (map.empty()) + return defaultID; // no objects loaded, keep gap for H3M objects + if (map.rbegin()->first > defaultID) + return map.rbegin()->first + 1; // some modded objects loaded, return next available + + return defaultID; // some H3M objects loaded, first modded found +} + +void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContainter * obj) +{ + auto handler = handlerConstructors.at(obj->handlerName)(); + handler->init(entry); + + si32 id = selectNextID(entry["index"], obj->objects, 256); + handler->setType(obj->id, id); + + if (handler->getTemplates().empty()) + { + auto range = legacyTemplates.equal_range(std::make_pair(obj->id, si32(entry["index"].Float()))); + for (auto & templ : boost::make_iterator_range(range.first, range.second)) + handler->addTemplate(templ.second); + } + obj->objects[id] = handler; +} + CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json) { auto obj = new ObjectContainter(); obj->name = json["name"].String(); obj->handlerName = json["handler"].String(); - obj->base = json["base"]; + obj->base = json["base"]; // FIXME: when this data will be actually merged? + obj->id = selectNextID(json["index"], objects, 256); for (auto entry : json["types"].Struct()) { - auto handler = handlerConstructors.at(obj->handlerName)(); - handler->init(entry.second); + loadObjectEntry(entry.second, obj); } return obj; } @@ -445,8 +473,7 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) { auto object = loadFromJson(data); - object->id = objects.size(); - objects.push_back(object); + objects[object->id] = object; VLC->modh->identifiers.registerObject(scope, "object", name, object->id); } @@ -454,7 +481,6 @@ void CObjectClassesHandler::loadObject(std::string scope, std::string name, cons void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) { auto object = loadFromJson(data); - object->id = index; assert(objects[index] == nullptr); // ensure that this id was not loaded before objects[index] = object; @@ -462,6 +488,18 @@ void CObjectClassesHandler::loadObject(std::string scope, std::string name, cons VLC->modh->identifiers.registerObject(scope, "object", name, object->id); } +void CObjectClassesHandler::createObject(std::string name, JsonNode config, si32 ID, boost::optional subID) +{ + assert(objects.count(ID)); + if (subID) + { + assert(objects.at(ID)->objects.count(subID.get()) == 0); + assert(config["index"].isNull()); + config["index"].Float() = subID.get(); + } + loadObjectEntry(config, objects[ID]); +} + std::vector CObjectClassesHandler::getDefaultAllowed() const { return std::vector(); //TODO? @@ -469,15 +507,21 @@ std::vector CObjectClassesHandler::getDefaultAllowed() const TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) const { - if (objects.size() > type) + if (objects.count(type)) { if (objects.at(type)->objects.count(subtype)) return objects.at(type)->objects.at(subtype); } + logGlobal->errorStream() << "Failed to find object of type " << type << ":" << subtype; assert(0); // FIXME: throw error? return nullptr; } +void CObjectClassesHandler::afterLoadFinalization() +{ + legacyTemplates.clear(); // whatever left there is no longer needed +} + std::string CObjectClassesHandler::getObjectName(si32 type) const { assert(objects.count(type)); diff --git a/lib/CObjectClassesHandler.h b/lib/CObjectClassesHandler.h index f463db53d..50bc1936d 100644 --- a/lib/CObjectClassesHandler.h +++ b/lib/CObjectClassesHandler.h @@ -110,12 +110,10 @@ class AObjectTypeHandler std::vector templates; protected: - void setType(si32 type, si32 subtype); virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; public: - /// returns true if type is not configurable and new objects can be created without valid config - virtual bool confFree(); + void setType(si32 type, si32 subtype); /// loads templates from Json structure using fields "base" and "templates" virtual void init(const JsonNode & input); @@ -193,12 +191,18 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase } }; + typedef std::multimap, ObjectTemplate> TTemplatesContainer; + /// list of object handlers, each of them handles only one type - std::vector objects; + std::map objects; /// map that is filled during contruction with all known handlers. Not serializeable std::map > handlerConstructors; + /// container with H3 templates, used only during loading + TTemplatesContainer legacyTemplates; + + void loadObjectEntry(const JsonNode & entry, ObjectContainter * obj); ObjectContainter * loadFromJson(const JsonNode & json); public: CObjectClassesHandler(); @@ -208,7 +212,9 @@ public: virtual void loadObject(std::string scope, std::string name, const JsonNode & data); virtual void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index); - virtual void afterLoadFinalization(){}; + void createObject(std::string name, JsonNode config, si32 ID, boost::optional subID = boost::optional()); + + virtual void afterLoadFinalization(); virtual std::vector getDefaultAllowed() const; From 7e057b6df888fd126e00d71c3a6141440d34d2ba Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 4 Jun 2014 11:25:13 +0300 Subject: [PATCH 19/33] Bugfixing, game can finally load to main menu without crash --- config/schemas/object.json | 2 +- config/schemas/objectType.json | 2 +- lib/CArtHandler.cpp | 11 +++++------ lib/CCreatureHandler.cpp | 11 ++++------- lib/CHeroHandler.cpp | 14 +++++++------- lib/CObjectClassesHandler.cpp | 29 +++++++++++++++++++++++++++-- lib/CObjectClassesHandler.h | 14 ++++++++------ lib/CTownHandler.cpp | 30 +++++++++++++++--------------- 8 files changed, 68 insertions(+), 45 deletions(-) diff --git a/config/schemas/object.json b/config/schemas/object.json index f20358bc0..82e743798 100644 --- a/config/schemas/object.json +++ b/config/schemas/object.json @@ -19,7 +19,7 @@ }, "base": { - "$ref" : "vcmi:objectType" + "type" : "object" }, "types": { "type":"object", diff --git a/config/schemas/objectType.json b/config/schemas/objectType.json index db60d6c91..d4923e374 100644 --- a/config/schemas/objectType.json +++ b/config/schemas/objectType.json @@ -15,7 +15,7 @@ }, "base": { - "$ref" : "vcmi:objectTemplate" + "type" : "object" }, "types": { "type":"object", diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 1cc1f7e77..b5505e224 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -647,19 +647,18 @@ void CArtHandler::afterLoadFinalization() } } - //Note: "10" is used here because H3 text files don't define any template for art with ID 0 - ObjectTemplate base = VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, 10)->getTemplates().front(); for (CArtifact * art : artifacts) { + VLC->objtypeh->createObject(art->Name(), JsonNode(), Obj::ARTIFACT, art->id.num); + if (!art->advMapDef.empty()) { - base.animationFile = art->advMapDef; - base.subid = art->id; + 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->createObject(art->Name(), JsonNode(), Obj::ARTIFACT, art->id.num); - VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->id)->addTemplate(base); + VLC->objtypeh->getHandlerFor(Obj::ARTIFACT, art->id)->addTemplate(templ); } } } diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 3f8bd9397..610c78258 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -1114,17 +1114,14 @@ void CCreatureHandler::buildBonusTreeForTiers() void CCreatureHandler::afterLoadFinalization() { - ObjectTemplate base = VLC->objtypeh->getHandlerFor(Obj::MONSTER, 0)->getTemplates().front(); for (CCreature * crea : creatures) { + VLC->objtypeh->createObject(crea->nameSing, JsonNode(), Obj::MONSTER, crea->idNumber.num); if (!crea->advMapDef.empty()) { - base.animationFile = crea->advMapDef; - base.subid = crea->idNumber; - - // replace existing (if any) and add new template. - // Necessary for objects added via mods that don't have any templates in H3 - VLC->objtypeh->getHandlerFor(Obj::MONSTER, crea->idNumber)->addTemplate(base); + JsonNode templ; + templ["animation"].String() = crea->advMapDef; + VLC->objtypeh->getHandlerFor(Obj::MONSTER, crea->idNumber)->addTemplate(templ); } } } diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index ca5efc556..0e26bdd4e 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -231,15 +231,15 @@ void CHeroClassHandler::afterLoadFinalization() } } - ObjectTemplate base = VLC->objtypeh->getHandlerFor(Obj::HERO, 0)->getTemplates().front(); for (CHeroClass * hc : heroClasses) { - base.animationFile = hc->imageMapMale; - base.subid = hc->id; - - // replace existing (if any) and add new template. - // Necessary for objects added via mods that don't have any templates in H3 - VLC->objtypeh->getHandlerFor(Obj::HERO, base.subid)->addTemplate(base); + VLC->objtypeh->createObject(hc->identifier, JsonNode(), Obj::HERO, hc->id); + if (!hc->imageMapMale.empty()) + { + JsonNode templ; + templ["animation"].String() = hc->imageMapMale; + VLC->objtypeh->getHandlerFor(Obj::HERO, hc->id)->addTemplate(templ); + } } } diff --git a/lib/CObjectClassesHandler.cpp b/lib/CObjectClassesHandler.cpp index 146ebae9e..4126c8ceb 100644 --- a/lib/CObjectClassesHandler.cpp +++ b/lib/CObjectClassesHandler.cpp @@ -444,7 +444,7 @@ void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContai auto handler = handlerConstructors.at(obj->handlerName)(); handler->init(entry); - si32 id = selectNextID(entry["index"], obj->objects, 256); + si32 id = selectNextID(entry["index"], obj->objects, 1000); handler->setType(obj->id, id); if (handler->getTemplates().empty()) @@ -517,6 +517,18 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) return nullptr; } +void CObjectClassesHandler::beforeValidate(JsonNode & object) +{ + for (auto & entry : object["types"].Struct()) + { + JsonUtils::inherit(entry.second, object["base"]); + for (auto & templ : entry.second["templates"].Struct()) + { + JsonUtils::inherit(templ.second, entry.second["base"]); + } + } +} + void CObjectClassesHandler::afterLoadFinalization() { legacyTemplates.clear(); // whatever left there is no longer needed @@ -555,11 +567,24 @@ bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemp return true; // by default - accept all. } -void AObjectTypeHandler::addTemplate(const ObjectTemplate & templ) +void AObjectTypeHandler::addTemplate(ObjectTemplate templ) { + templ.id = Obj(type); + templ.subid = subtype; templates.push_back(templ); } +void AObjectTypeHandler::addTemplate(JsonNode config) +{ + JsonUtils::inherit(config, base); + ObjectTemplate tmpl; + tmpl.id = Obj(type); + tmpl.subid = subtype; + tmpl.stringID = ""; // TODO? + tmpl.readJson(config); + addTemplate(tmpl); +} + std::vector AObjectTypeHandler::getTemplates() const { return templates; diff --git a/lib/CObjectClassesHandler.h b/lib/CObjectClassesHandler.h index 50bc1936d..57ecd07c7 100644 --- a/lib/CObjectClassesHandler.h +++ b/lib/CObjectClassesHandler.h @@ -118,7 +118,8 @@ public: /// loads templates from Json structure using fields "base" and "templates" virtual void init(const JsonNode & input); - void addTemplate(const ObjectTemplate & templ); + void addTemplate(ObjectTemplate templ); + void addTemplate(JsonNode config); /// returns all templates, without any filters std::vector getTemplates() const; @@ -207,16 +208,17 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase public: CObjectClassesHandler(); - virtual std::vector loadLegacyData(size_t dataSize); + std::vector loadLegacyData(size_t dataSize) override; - virtual void loadObject(std::string scope, std::string name, const JsonNode & data); - virtual void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index); + 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 createObject(std::string name, JsonNode config, si32 ID, boost::optional subID = boost::optional()); - virtual void afterLoadFinalization(); + void beforeValidate(JsonNode & object) override; + void afterLoadFinalization() override; - virtual std::vector getDefaultAllowed() const; + std::vector getDefaultAllowed() const override; /// returns handler for specified object (ID-based). ObjectHandler keeps ownership TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const; diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 625033b8c..d183039e2 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -759,31 +759,31 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod void CTownHandler::afterLoadFinalization() { initializeRequirements(); - ObjectTemplate base = VLC->objtypeh->getHandlerFor(Obj::TOWN, 0)->getTemplates().front(); for (CFaction * fact : factions) { if (fact->town) { - base.animationFile = fact->town->clientInfo.advMapCastle; - base.subid = fact->index; - - // replace existing (if any) and add new template. - // Necessary for objects added via mods that don't have any templates in H3 - VLC->objtypeh->getHandlerFor(Obj::TOWN, fact->index)->addTemplate(base); + VLC->objtypeh->createObject(fact->identifier, JsonNode(), Obj::TOWN, fact->index); + if (!fact->town->clientInfo.advMapCastle.empty()) + { + JsonNode templ; + templ["animation"].String() = fact->town->clientInfo.advMapCastle; + VLC->objtypeh->getHandlerFor(Obj::TOWN, fact->index)->addTemplate(templ); + } assert(fact->town->dwellings.size() == fact->town->dwellingNames.size()); for (size_t i=0; itown->dwellings.size(); i++) { - ObjectTemplate base = VLC->objtypeh->getHandlerFor(Obj::CREATURE_GENERATOR1, 0)->getTemplates().front(); - //both unupgraded and upgraded get same dwelling - for (auto cre : fact->town->creatures[i]) - { - base.subid = 80 + cre; - base.animationFile = fact->town->dwellings[i]; + for (auto cre : fact->town->creatures[i]) + { if (VLC->objh->cregens.count(cre) == 0) { - VLC->objtypeh->getHandlerFor(Obj::CREATURE_GENERATOR1, 80 + cre)->addTemplate(base); + JsonNode templ; + templ["animation"].String() = fact->town->dwellings[i]; + + VLC->objtypeh->createObject("", JsonNode(), Obj::CREATURE_GENERATOR1, 80 + cre); + VLC->objtypeh->getHandlerFor(Obj::CREATURE_GENERATOR1, 80 + cre)->addTemplate(templ); VLC->objh->cregens[80 + cre] = cre; //map of dwelling -> creature id } } @@ -823,4 +823,4 @@ std::set CTownHandler::getAllowedFactions() const allowedFactions.insert(i); return allowedFactions; -} +} From e9b41cd3c6daa440c015077b19514980606872f5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 4 Jun 2014 14:15:58 +0300 Subject: [PATCH 20/33] Minor fixes, no more missing objects & templates --- lib/CArtHandler.cpp | 3 +++ lib/CCreatureHandler.cpp | 4 ++++ lib/CObjectClassesHandler.cpp | 23 +++++++++++++++++++++-- lib/CObjectClassesHandler.h | 1 + 4 files changed, 29 insertions(+), 2 deletions(-) diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index b5505e224..c10577279 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -660,6 +660,9 @@ void CArtHandler::afterLoadFinalization() // 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->eraseObject(Obj::ARTIFACT, art->id); } } diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index 610c78258..a3f7ae46d 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -1123,6 +1123,10 @@ void CCreatureHandler::afterLoadFinalization() 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->eraseObject(Obj::MONSTER, crea->idNumber.num); } } diff --git a/lib/CObjectClassesHandler.cpp b/lib/CObjectClassesHandler.cpp index 4126c8ceb..429463766 100644 --- a/lib/CObjectClassesHandler.cpp +++ b/lib/CObjectClassesHandler.cpp @@ -391,7 +391,7 @@ CObjectClassesHandler::CObjectClassesHandler() } template -void readTextFile(Container objects, std::string path) +void readTextFile(Container & objects, std::string path) { CLegacyConfigParser parser(path); size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else @@ -449,10 +449,14 @@ void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContai if (handler->getTemplates().empty()) { - auto range = legacyTemplates.equal_range(std::make_pair(obj->id, si32(entry["index"].Float()))); + auto range = legacyTemplates.equal_range(std::make_pair(obj->id, id)); for (auto & templ : boost::make_iterator_range(range.first, range.second)) + { handler->addTemplate(templ.second); + } + legacyTemplates.erase(range.first, range.second); } + obj->objects[id] = handler; } @@ -500,6 +504,13 @@ void CObjectClassesHandler::createObject(std::string name, JsonNode config, si32 loadObjectEntry(config, objects[ID]); } +void CObjectClassesHandler::eraseObject(si32 ID, si32 subID) +{ + assert(objects.count(ID)); + assert(objects.at(ID)->objects.count(subID)); + objects.at(ID)->objects.erase(subID); +} + std::vector CObjectClassesHandler::getDefaultAllowed() const { return std::vector(); //TODO? @@ -532,6 +543,14 @@ void CObjectClassesHandler::beforeValidate(JsonNode & object) void CObjectClassesHandler::afterLoadFinalization() { legacyTemplates.clear(); // whatever left there is no longer needed + for (auto entry : objects) + { + for (auto obj : entry.second->objects) + { + if (obj.second->getTemplates().empty()) + logGlobal->warnStream() << "No templates found for " << entry.first << ":" << obj.first; + } + } } std::string CObjectClassesHandler::getObjectName(si32 type) const diff --git a/lib/CObjectClassesHandler.h b/lib/CObjectClassesHandler.h index 57ecd07c7..b28f95e8a 100644 --- a/lib/CObjectClassesHandler.h +++ b/lib/CObjectClassesHandler.h @@ -214,6 +214,7 @@ public: void loadObject(std::string scope, std::string name, const JsonNode & data, size_t index) override; void createObject(std::string name, JsonNode config, si32 ID, boost::optional subID = boost::optional()); + void eraseObject(si32 ID, si32 subID); void beforeValidate(JsonNode & object) override; void afterLoadFinalization() override; From 971a93b4f1c1d0de1c7b251ce64ea33a7be495b9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 4 Jun 2014 20:43:54 +0300 Subject: [PATCH 21/33] Fixed missing serialization of new types --- lib/CObjectClassesHandler.cpp | 1 + lib/CObjectClassesHandler.h | 2 + lib/registerTypes/RegisterTypes.h | 58 ++++++++++++++++++++++++++ lib/registerTypes/TypesMapObjects1.cpp | 8 ++++ 4 files changed, 69 insertions(+) diff --git a/lib/CObjectClassesHandler.cpp b/lib/CObjectClassesHandler.cpp index 429463766..9230d021f 100644 --- a/lib/CObjectClassesHandler.cpp +++ b/lib/CObjectClassesHandler.cpp @@ -337,6 +337,7 @@ CObjectClassesHandler::CObjectClassesHandler() #define SET_HANDLER(STRING, TYPENAME) handlerConstructors[STRING] = std::make_shared > // list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code + //WARNING: should be in sync with registerTypesMapObjectTypes function SET_HANDLER_CLASS("configurable", CObjectWithRewardConstructor); SET_HANDLER("", CGObjectInstance); diff --git a/lib/CObjectClassesHandler.h b/lib/CObjectClassesHandler.h index b28f95e8a..684dd6ff7 100644 --- a/lib/CObjectClassesHandler.h +++ b/lib/CObjectClassesHandler.h @@ -113,6 +113,8 @@ protected: virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; public: + virtual ~AObjectTypeHandler(){} + void setType(si32 type, si32 subtype); /// loads templates from Json structure using fields "base" and "templates" diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 703e727ef..79e6a7ccb 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -10,6 +10,8 @@ #include "../CHeroHandler.h" #include "../CTownHandler.h" #include "../CModHandler.h" //needed? +#include "../CObjectClassesHandler.h" +#include "../CObjectConstructor.h" /* * RegisterTypes.h, part of VCMI engine @@ -72,6 +74,61 @@ void registerTypesMapObjects1(Serializer &s) s.template registerType(); } +template +void registerTypesMapObjectTypes(Serializer &s) +{ + s.template registerType(); + +#define REGISTER_GENERIC_HANDLER(TYPENAME) s.template registerType >() + + REGISTER_GENERIC_HANDLER(CGObjectInstance); + REGISTER_GENERIC_HANDLER(CGMarket); + REGISTER_GENERIC_HANDLER(CBank); + REGISTER_GENERIC_HANDLER(CCartographer); + REGISTER_GENERIC_HANDLER(CGArtifact); + REGISTER_GENERIC_HANDLER(CGBlackMarket); + REGISTER_GENERIC_HANDLER(CGBoat); + REGISTER_GENERIC_HANDLER(CGBonusingObject); + REGISTER_GENERIC_HANDLER(CGBorderGate); + REGISTER_GENERIC_HANDLER(CGBorderGuard); + REGISTER_GENERIC_HANDLER(CGCreature); + REGISTER_GENERIC_HANDLER(CGDenOfthieves); + REGISTER_GENERIC_HANDLER(CGDwelling); + REGISTER_GENERIC_HANDLER(CGEvent); + REGISTER_GENERIC_HANDLER(CGGarrison); + REGISTER_GENERIC_HANDLER(CGHeroInstance); + REGISTER_GENERIC_HANDLER(CGHeroPlaceholder); + REGISTER_GENERIC_HANDLER(CGKeymasterTent); + REGISTER_GENERIC_HANDLER(CGLighthouse); + REGISTER_GENERIC_HANDLER(CGMagi); + REGISTER_GENERIC_HANDLER(CGMagicSpring); + REGISTER_GENERIC_HANDLER(CGMagicWell); + REGISTER_GENERIC_HANDLER(CGMarket); + REGISTER_GENERIC_HANDLER(CGMine); + REGISTER_GENERIC_HANDLER(CGObelisk); + REGISTER_GENERIC_HANDLER(CGObservatory); + REGISTER_GENERIC_HANDLER(CGOnceVisitable); + REGISTER_GENERIC_HANDLER(CGPandoraBox); + REGISTER_GENERIC_HANDLER(CGPickable); + REGISTER_GENERIC_HANDLER(CGPyramid); + REGISTER_GENERIC_HANDLER(CGQuestGuard); + REGISTER_GENERIC_HANDLER(CGResource); + REGISTER_GENERIC_HANDLER(CGScholar); + REGISTER_GENERIC_HANDLER(CGSeerHut); + REGISTER_GENERIC_HANDLER(CGShipyard); + REGISTER_GENERIC_HANDLER(CGShrine); + REGISTER_GENERIC_HANDLER(CGSignBottle); + REGISTER_GENERIC_HANDLER(CGSirens); + REGISTER_GENERIC_HANDLER(CGTeleport); + REGISTER_GENERIC_HANDLER(CGTownInstance); + REGISTER_GENERIC_HANDLER(CGUniversity); + REGISTER_GENERIC_HANDLER(CGVisitableOPH); + REGISTER_GENERIC_HANDLER(CGVisitableOPW); + REGISTER_GENERIC_HANDLER(CGWitchHut); + +#undef REGISTER_GENERIC_HANDLER +} + template void registerTypesMapObjects2(Serializer &s) { @@ -302,6 +359,7 @@ void registerTypes(Serializer &s) { registerTypesMapObjects1(s); registerTypesMapObjects2(s); + registerTypesMapObjectTypes(s); registerTypesClientPacks1(s); registerTypesClientPacks2(s); registerTypesServerPacks(s); diff --git a/lib/registerTypes/TypesMapObjects1.cpp b/lib/registerTypes/TypesMapObjects1.cpp index c0952f6ff..310c647e0 100644 --- a/lib/registerTypes/TypesMapObjects1.cpp +++ b/lib/registerTypes/TypesMapObjects1.cpp @@ -18,6 +18,14 @@ #include "../NetPacks.h" #include "../CObjectClassesHandler.h" +template void registerTypesMapObjectTypes>(CISer& s); +template void registerTypesMapObjectTypes>(COSer& s); +template void registerTypesMapObjectTypes>(CISer& s); +template void registerTypesMapObjectTypes>(COSer& s); +template void registerTypesMapObjectTypes(CSaveFile & s); +template void registerTypesMapObjectTypes(CLoadFile & s); +template void registerTypesMapObjectTypes(CTypeList & s); +template void registerTypesMapObjectTypes(CLoadIntegrityValidator & s); template void registerTypesMapObjects1>(CISer& s); template void registerTypesMapObjects1>(COSer& s); From 1d5d4e32480ba990d9144326385cd511460470a6 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Wed, 4 Jun 2014 22:59:28 +0300 Subject: [PATCH 22/33] Fixed broken in some cases behavior of "base" entries. Maps with new objects seems to be working correctly now, objects are correctly initialized with base values even with null input --- config/objects/moddables.json | 6 +++--- lib/CObjectClassesHandler.cpp | 12 +++++++++--- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 50ec815f2..7bf54f971 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -59,9 +59,9 @@ "base" : { "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ - "VVVVV", // a LOT of just visible rows due to towns like Tower - "VVVVV", - "VVVVV", + "VVVVVV", // a LOT of just visible rows due to towns like Tower + "VVVVVV", + "VVVVVV", "VVBBBV", "VBBBBB", "VBBABB" diff --git a/lib/CObjectClassesHandler.cpp b/lib/CObjectClassesHandler.cpp index 9230d021f..7bcd209f3 100644 --- a/lib/CObjectClassesHandler.cpp +++ b/lib/CObjectClassesHandler.cpp @@ -495,6 +495,7 @@ void CObjectClassesHandler::loadObject(std::string scope, std::string name, cons void CObjectClassesHandler::createObject(std::string name, JsonNode config, si32 ID, boost::optional subID) { + config.setType(JsonNode::DATA_STRUCT); // ensure that input is not NULL assert(objects.count(ID)); if (subID) { @@ -502,6 +503,9 @@ void CObjectClassesHandler::createObject(std::string name, JsonNode config, si32 assert(config["index"].isNull()); config["index"].Float() = subID.get(); } + + JsonUtils::inherit(config, objects.at(ID)->base); + loadObjectEntry(config, objects[ID]); } @@ -568,16 +572,17 @@ void AObjectTypeHandler::setType(si32 type, si32 subtype) void AObjectTypeHandler::init(const JsonNode & input) { + base = input["base"]; for (auto entry : input["templates"].Struct()) { - JsonNode data = input["base"]; - JsonUtils::merge(data, entry.second); + entry.second.setType(JsonNode::DATA_STRUCT); + JsonUtils::inherit(entry.second, base); ObjectTemplate tmpl; tmpl.id = Obj(type); tmpl.subid = subtype; tmpl.stringID = entry.first; // FIXME: create "fullID" - type.object.template? - tmpl.readJson(data); + tmpl.readJson(entry.second); templates.push_back(tmpl); } } @@ -596,6 +601,7 @@ void AObjectTypeHandler::addTemplate(ObjectTemplate templ) void AObjectTypeHandler::addTemplate(JsonNode config) { + config.setType(JsonNode::DATA_STRUCT); // ensure that input is not null JsonUtils::inherit(config, base); ObjectTemplate tmpl; tmpl.id = Obj(type); From 0afdfa529c35caae7dc0b92e8581b248ae9c9eef Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jun 2014 14:19:47 +0300 Subject: [PATCH 23/33] Moved all object-related files to lib/mapObjects directory. Renamed some classes to more readable names --- AI/VCAI/AIUtility.cpp | 2 +- AI/VCAI/AIUtility.h | 2 +- AI/VCAI/Fuzzy.cpp | 2 +- AI/VCAI/Goals.h | 2 +- AI/VCAI/VCAI.cpp | 4 +- AI/VCAI/VCAI.h | 4 +- CCallback.cpp | 4 +- client/AdventureMapClasses.cpp | 2 +- client/CAdvmapInterface.cpp | 2 +- client/CCastleInterface.cpp | 2 +- client/CHeroWindow.cpp | 2 +- client/CKingdomInterface.cpp | 2 +- client/CMT.cpp | 4 +- client/CPlayerInterface.cpp | 4 +- client/CPreGame.cpp | 4 +- client/CQuestLog.cpp | 2 +- client/CSpellWindow.cpp | 2 +- client/Client.cpp | 4 +- client/GUIClasses.cpp | 2 +- client/Graphics.cpp | 6 +- client/NetPacksClient.cpp | 4 +- client/battle/CBattleInterface.cpp | 2 +- client/battle/CBattleInterfaceClasses.cpp | 2 +- client/mapHandler.cpp | 4 +- lib/BattleState.cpp | 2 +- lib/BattleState.h | 2 +- lib/CArtHandler.cpp | 2 +- lib/CCreatureSet.cpp | 2 +- lib/CGameInfoCallback.cpp | 2 +- lib/CGameState.cpp | 4 +- lib/CGameState.h | 6 +- lib/CHeroHandler.cpp | 2 +- lib/CMakeLists.txt | 10 +- lib/CModHandler.cpp | 4 +- lib/CTownHandler.cpp | 4 +- lib/Connection.h | 2 +- lib/GameConstants.cpp | 2 +- lib/NetPacksLib.cpp | 4 +- lib/VCMI_Lib.cpp | 4 +- .../CObjectClassesHandler.cpp | 312 +--------------- lib/{ => mapObjects}/CObjectClassesHandler.h | 64 +--- lib/{ => mapObjects}/CObjectHandler.cpp | 0 lib/{ => mapObjects}/CObjectHandler.h | 2 +- .../CRewardableConstructor.cpp} | 18 +- .../CRewardableConstructor.h} | 8 +- .../CRewardableObject.cpp} | 34 +- .../CRewardableObject.h} | 24 +- lib/mapObjects/ObjectTemplate.cpp | 333 ++++++++++++++++++ lib/mapObjects/ObjectTemplate.h | 79 +++++ lib/mapping/CCampaignHandler.cpp | 2 +- lib/mapping/CMap.cpp | 2 +- lib/mapping/CMap.h | 2 +- lib/mapping/CMapEditManager.cpp | 2 +- lib/mapping/MapFormatH3M.cpp | 6 +- lib/mapping/MapFormatH3M.h | 2 +- lib/registerTypes/RegisterTypes.cpp | 4 +- lib/registerTypes/RegisterTypes.h | 20 +- lib/registerTypes/TypesClientPacks1.cpp | 4 +- lib/registerTypes/TypesClientPacks2.cpp | 4 +- lib/registerTypes/TypesMapObjects1.cpp | 4 +- lib/registerTypes/TypesMapObjects2.cpp | 4 +- lib/registerTypes/TypesPregamePacks.cpp | 4 +- lib/registerTypes/TypesServerPacks.cpp | 4 +- lib/rmg/CMapGenerator.cpp | 4 +- lib/rmg/CMapGenerator.h | 2 +- server/CGameHandler.cpp | 4 +- server/CVCMIServer.cpp | 4 +- server/NetPacksServer.cpp | 2 +- 68 files changed, 560 insertions(+), 516 deletions(-) rename lib/{ => mapObjects}/CObjectClassesHandler.cpp (54%) rename lib/{ => mapObjects}/CObjectClassesHandler.h (71%) rename lib/{ => mapObjects}/CObjectHandler.cpp (100%) rename lib/{ => mapObjects}/CObjectHandler.h (97%) rename lib/{CObjectConstructor.cpp => mapObjects/CRewardableConstructor.cpp} (92%) rename lib/{CObjectConstructor.h => mapObjects/CRewardableConstructor.h} (86%) rename lib/{CObjectWithReward.cpp => mapObjects/CRewardableObject.cpp} (96%) rename lib/{CObjectWithReward.h => mapObjects/CRewardableObject.h} (93%) create mode 100644 lib/mapObjects/ObjectTemplate.cpp create mode 100644 lib/mapObjects/ObjectTemplate.h diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index 3e9b6a65b..3e449fed6 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -3,7 +3,7 @@ #include "VCAI.h" #include "../../lib/UnlockGuard.h" -#include "../../lib/CObjectHandler.h" +#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" diff --git a/AI/VCAI/AIUtility.h b/AI/VCAI/AIUtility.h index 29e14c63f..d4fab2843 100644 --- a/AI/VCAI/AIUtility.h +++ b/AI/VCAI/AIUtility.h @@ -5,7 +5,7 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" #include "../../lib/CSpellHandler.h" -#include "../../lib/CObjectHandler.h" +#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/Connection.h" #include "../../lib/CGameState.h" #include "../../lib/mapping/CMap.h" diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 4c8fe068f..11dc47969 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -2,7 +2,7 @@ #include "Fuzzy.h" #include -#include "../../lib/CObjectHandler.h" +#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/VCMI_Lib.h" #include "../../CCallback.h" diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index 096b63283..5cdb1ebbf 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -1,7 +1,7 @@ #pragma once #include "../../lib/VCMI_Lib.h" -#include "../../lib/CObjectHandler.h" +#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/CBuildingHandler.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 2a7b13280..5f5ec993f 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -2,8 +2,8 @@ #include "VCAI.h" #include "Goals.h" #include "../../lib/UnlockGuard.h" -#include "../../lib/CObjectHandler.h" -#include "../../lib/CObjectWithReward.h" +#include "../../lib/mapObjects/CObjectHandler.h" +#include "../../lib/mapObjects/CRewardableObject.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 92139afb1..33d664f65 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -4,7 +4,7 @@ #include "Goals.h" #include "../../lib/AI_Base.h" #include "../../CCallback.h" -#include "../../lib/CObjectClassesHandler.h" +#include "../../lib/mapObjects/CObjectClassesHandler.h" #include "../../lib/CThreadHelper.h" @@ -14,7 +14,7 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" #include "../../lib/CSpellHandler.h" -#include "../../lib/CObjectHandler.h" +#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/Connection.h" #include "../../lib/CGameState.h" #include "../../lib/mapping/CMap.h" diff --git a/CCallback.cpp b/CCallback.cpp index ccf876ff8..73b0f7d90 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -9,10 +9,10 @@ #include "client/Client.h" #include "lib/mapping/CMap.h" #include "lib/CBuildingHandler.h" -#include "lib/CObjectClassesHandler.h" +#include "lib/mapObjects/CObjectClassesHandler.h" #include "lib/CGeneralTextHandler.h" #include "lib/CHeroHandler.h" -#include "lib/CObjectHandler.h" +#include "lib/mapObjects/CObjectHandler.h" #include "lib/Connection.h" #include "lib/NetPacks.h" #include "client/mapHandler.h" diff --git a/client/AdventureMapClasses.cpp b/client/AdventureMapClasses.cpp index c1c5ae64b..b7774244c 100644 --- a/client/AdventureMapClasses.cpp +++ b/client/AdventureMapClasses.cpp @@ -6,7 +6,7 @@ #include "../lib/filesystem/Filesystem.h" #include "../lib/mapping/CMap.h" #include "../lib/CModHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CGameState.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" diff --git a/client/CAdvmapInterface.cpp b/client/CAdvmapInterface.cpp index eab31d8e1..d717cb67c 100644 --- a/client/CAdvmapInterface.cpp +++ b/client/CAdvmapInterface.cpp @@ -17,7 +17,7 @@ #include "CDefHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CTownHandler.h" #include "../lib/mapping/CMap.h" #include "../lib/JsonNode.h" diff --git a/client/CCastleInterface.cpp b/client/CCastleInterface.cpp index 1a3759986..679feccc5 100644 --- a/client/CCastleInterface.cpp +++ b/client/CCastleInterface.cpp @@ -7,7 +7,7 @@ #include "../lib/CCreatureHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CModHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" #include "CAdvmapInterface.h" diff --git a/client/CHeroWindow.cpp b/client/CHeroWindow.cpp index 721c8da0a..dbd5187df 100644 --- a/client/CHeroWindow.cpp +++ b/client/CHeroWindow.cpp @@ -20,7 +20,7 @@ #include "CDefHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/NetPacksBase.h" #include "gui/CGuiHandler.h" diff --git a/client/CKingdomInterface.cpp b/client/CKingdomInterface.cpp index 4d62e8523..9ee5b9912 100644 --- a/client/CKingdomInterface.cpp +++ b/client/CKingdomInterface.cpp @@ -5,7 +5,7 @@ #include "../lib/CCreatureHandler.h" //creatures name for objects list #include "../lib/CGeneralTextHandler.h" #include "../lib/CModHandler.h" //for buildings per turn -#include "../lib/CObjectHandler.h" //Hero/Town objects +#include "../lib/mapObjects/CObjectHandler.h" //Hero/Town objects #include "../lib/CHeroHandler.h" // only for calculating required xp? worth it? #include "../lib/CTownHandler.h" #include "CAnimation.h" //CAnimImage diff --git a/client/CMT.cpp b/client/CMT.cpp index 7deaab93d..d22c4a7d6 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -34,7 +34,7 @@ #include "CMessage.h" #include "../lib/CModHandler.h" #include "../lib/CTownHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CScriptingModule.h" #include "../lib/GameConstants.h" @@ -44,7 +44,7 @@ #ifdef _WIN32 #include "SDL_syswm.h" #endif -#include "../lib/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/UnlockGuard.h" #include "CMT.h" diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 997a14813..82a72b37f 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1,5 +1,5 @@ #include "StdInc.h" -#include "../lib/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "CAdvmapInterface.h" #include "battle/CBattleInterface.h" #include "battle/CBattleInterfaceClasses.h" @@ -21,7 +21,7 @@ #include "../lib/CArtHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/Connection.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index 575c519a5..d8ff91dd5 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -11,11 +11,11 @@ #include "gui/CCursorHandler.h" #include "CAnimation.h" #include "CDefHandler.h" -#include "../lib/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/mapping/CCampaignHandler.h" #include "../lib/CCreatureHandler.h" #include "../lib/JsonNode.h" diff --git a/client/CQuestLog.cpp b/client/CQuestLog.cpp index e573c761a..94c8edb75 100644 --- a/client/CQuestLog.cpp +++ b/client/CQuestLog.cpp @@ -16,7 +16,7 @@ #include "../lib/CGameState.h" #include "../lib/CArtHandler.h" #include "../lib/NetPacksBase.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "gui/CGuiHandler.h" #include "gui/CIntObjectClasses.h" diff --git a/client/CSpellWindow.cpp b/client/CSpellWindow.cpp index 1f88df9e2..a43df7c10 100644 --- a/client/CSpellWindow.cpp +++ b/client/CSpellWindow.cpp @@ -3,7 +3,7 @@ #include "Graphics.h" #include "CDefHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CGeneralTextHandler.h" #include "CVideoHandler.h" diff --git a/client/Client.cpp b/client/Client.cpp index 5e806d7cf..bd3658487 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -11,11 +11,11 @@ #include "../lib/BattleState.h" #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" -#include "../lib/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CBuildingHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/Connection.h" diff --git a/client/GUIClasses.cpp b/client/GUIClasses.cpp index 924df2be5..da38becdb 100644 --- a/client/GUIClasses.cpp +++ b/client/GUIClasses.cpp @@ -24,7 +24,7 @@ #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CModHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CondSh.h" diff --git a/client/Graphics.cpp b/client/Graphics.cpp index b758cc523..c11fe0669 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -12,13 +12,13 @@ #include "../CCallback.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CCreatureHandler.h" #include "CBitmapHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" -#include "../lib/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGameState.h" #include "../lib/JsonNode.h" #include "../lib/vcmi_endian.h" diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 6c6e3fd54..4a701e148 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -9,9 +9,9 @@ #include "CGameInfo.h" #include "../lib/Connection.h" #include "../lib/CGeneralTextHandler.h" -#include "../lib/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/VCMI_Lib.h" #include "../lib/mapping/CMap.h" #include "../lib/VCMIDirs.h" diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 54b1375e1..52c773112 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -6,7 +6,7 @@ #include "../CAdvmapInterface.h" #include "../CAnimation.h" #include "../CBitmapHandler.h" -#include "../../lib/CObjectHandler.h" +#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/CHeroHandler.h" # include "../CDefHandler.h" #include "../../lib/CSpellHandler.h" diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index 9e45f198b..b9fb761cd 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -16,7 +16,7 @@ #include "../../lib/CGeneralTextHandler.h" #include "../../lib/NetPacks.h" #include "../../lib/CCreatureHandler.h" -#include "../../lib/CObjectHandler.h" +#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/BattleState.h" #include "../CMusicHandler.h" #include "../CVideoHandler.h" diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 1b0c90fe2..80265dad1 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -14,12 +14,12 @@ #include "CBitmapHandler.h" #include "gui/SDL_Extensions.h" #include "CGameInfo.h" -#include "../lib/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGameState.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" #include "Graphics.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/mapping/CMap.h" #include "CDefHandler.h" #include "../lib/CConfigHandler.h" diff --git a/lib/BattleState.cpp b/lib/BattleState.cpp index ee392e608..8b8395157 100644 --- a/lib/BattleState.cpp +++ b/lib/BattleState.cpp @@ -13,7 +13,7 @@ #include #include "VCMI_Lib.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "CHeroHandler.h" #include "CCreatureHandler.h" #include "CSpellHandler.h" diff --git a/lib/BattleState.h b/lib/BattleState.h index f62490052..27a993c84 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -4,7 +4,7 @@ #include "BattleHex.h" #include "HeroBonus.h" #include "CCreatureSet.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "CCreatureHandler.h" #include "CObstacleInstance.h" #include "ConstTransitivePtr.h" diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index c10577279..0fb855e68 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -16,7 +16,7 @@ #include "VCMI_Lib.h" #include "CModHandler.h" #include "CSpellHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "NetPacksBase.h" #include "GameConstants.h" #include "CRandomGenerator.h" diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index 3a1d85072..e1813b01b 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -4,7 +4,7 @@ #include "CCreatureHandler.h" #include "VCMI_Lib.h" #include "CModHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "IGameCallback.h" #include "CGameState.h" #include "CGeneralTextHandler.h" diff --git a/lib/CGameInfoCallback.cpp b/lib/CGameInfoCallback.cpp index 39da01b9a..eb62696ff 100644 --- a/lib/CGameInfoCallback.cpp +++ b/lib/CGameInfoCallback.cpp @@ -12,7 +12,7 @@ #include "CGameInfoCallback.h" #include "CGameState.h" // PlayerState -#include "CObjectHandler.h" // for CGObjectInstance +#include "mapObjects/CObjectHandler.h" // for CGObjectInstance #include "StartInfo.h" // for StartInfo #include "BattleState.h" // for BattleInfo #include "NetPacks.h" // for InfoWindow diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index 2c8a8ef2e..f570e766f 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -2,14 +2,14 @@ #include "CGameState.h" #include "mapping/CCampaignHandler.h" -#include "CObjectClassesHandler.h" +#include "mapObjects/CObjectClassesHandler.h" #include "CArtHandler.h" #include "CBuildingHandler.h" #include "CGeneralTextHandler.h" #include "CTownHandler.h" #include "CSpellHandler.h" #include "CHeroHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "CCreatureHandler.h" #include "CModHandler.h" #include "VCMI_Lib.h" diff --git a/lib/CGameState.h b/lib/CGameState.h index ad5089506..b4197492a 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -14,7 +14,7 @@ #include "IGameCallback.h" #include "ResourceSet.h" #include "int3.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "CRandomGenerator.h" /* @@ -167,7 +167,7 @@ public: ObjectInstanceID currentSelection; //id of hero/town, 0xffffffff if none TeamID team; TResources resources; - std::set visitedObjects; // as a std::set, since most accesses here will be from visited status checks + std::set visitedObjects; // as a std::set, since most accesses here will be from visited status checks std::vector > heroes; std::vector > towns; std::vector > availableHeroes; //heroes available in taverns @@ -184,7 +184,7 @@ public: template void serialize(Handler &h, const int version) { h & color & human & currentSelection & team & resources & status; - h & heroes & towns & availableHeroes & dwellings & visitedObjects; + h & heroes & towns & availableHeroes & dwellings & visitedObjects; h & getBonusList(); //FIXME FIXME FIXME h & status & daysWithoutCastle; h & enteredLosingCheatCode & enteredWinningCheatCode; diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 0e26bdd4e..9938b468b 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -10,7 +10,7 @@ #include "CCreatureHandler.h" #include "CModHandler.h" #include "CTownHandler.h" -#include "CObjectHandler.h" //for hero specialty +#include "mapObjects/CObjectHandler.h" //for hero specialty #include /* diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index d444198a9..75b43edc7 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -9,7 +9,6 @@ set(lib_SRCS IGameCallback.cpp CGameInfoCallback.cpp CGameState.cpp - CObjectHandler.cpp Connection.cpp NetPacksLib.cpp @@ -33,6 +32,12 @@ set(lib_SRCS filesystem/Filesystem.cpp filesystem/ResourceID.cpp + mapObjects/CObjectClassesHandler.cpp + mapObjects/CObjectHandler.cpp + mapObjects/CRewardableConstructor.cpp + mapObjects/CRewardableObject.cpp + mapObjects/ObjectTemplate.cpp + logging/CBasicLogConfigurator.cpp logging/CLogger.cpp @@ -63,14 +68,11 @@ set(lib_SRCS CConsoleHandler.cpp CCreatureHandler.cpp CCreatureSet.cpp - CObjectClassesHandler.cpp CGameInterface.cpp CGeneralTextHandler.cpp CHeroHandler.cpp CModHandler.cpp CObstacleInstance.cpp - CObjectConstructor.cpp - CObjectWithReward.cpp CRandomGenerator.cpp CSpellHandler.cpp CThreadHelper.cpp diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 20388038f..456a0575b 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -1,6 +1,6 @@ #include "StdInc.h" #include "CModHandler.h" -#include "CObjectClassesHandler.h" +#include "mapObjects/CObjectClassesHandler.h" #include "JsonNode.h" #include "filesystem/Filesystem.h" #include "filesystem/AdapterLoaders.h" @@ -10,7 +10,7 @@ #include "CArtHandler.h" #include "CTownHandler.h" #include "CHeroHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "StringConstants.h" #include "CStopWatch.h" #include "IHandlerBase.h" diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index d183039e2..16a4b2cbc 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -11,8 +11,8 @@ #include "CArtHandler.h" #include "CSpellHandler.h" #include "filesystem/Filesystem.h" -#include "CObjectClassesHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectClassesHandler.h" +#include "mapObjects/CObjectHandler.h" /* * CTownHandler.cpp, part of VCMI engine diff --git a/lib/Connection.h b/lib/Connection.h index 9044b20eb..01a960a12 100644 --- a/lib/Connection.h +++ b/lib/Connection.h @@ -24,7 +24,7 @@ #include "ConstTransitivePtr.h" #include "CCreatureSet.h" //for CStackInstance -#include "CObjectHandler.h" //for CArmedInstance +#include "mapObjects/CObjectHandler.h" //for CArmedInstance #include "mapping/CCampaignHandler.h" //for CCampaignState #include "rmg/CMapGenerator.h" // for CMapGenOptions diff --git a/lib/GameConstants.cpp b/lib/GameConstants.cpp index 4d9b9239d..beca83dfc 100644 --- a/lib/GameConstants.cpp +++ b/lib/GameConstants.cpp @@ -13,7 +13,7 @@ #include "StdInc.h" #include "VCMI_Lib.h" -#include "CObjectClassesHandler.h" +#include "mapObjects/CObjectClassesHandler.h" #include "CArtHandler.h" #include "CCreatureHandler.h" #include "CSpellHandler.h" diff --git a/lib/NetPacksLib.cpp b/lib/NetPacksLib.cpp index 6fe99937f..3ab330f04 100644 --- a/lib/NetPacksLib.cpp +++ b/lib/NetPacksLib.cpp @@ -2,10 +2,10 @@ #include "NetPacks.h" #include "CGeneralTextHandler.h" -#include "CObjectClassesHandler.h" +#include "mapObjects/CObjectClassesHandler.h" #include "CArtHandler.h" #include "CHeroHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "CModHandler.h" #include "VCMI_Lib.h" #include "mapping/CMap.h" diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index c8e277332..706c21886 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -14,9 +14,9 @@ #include "CArtHandler.h" #include "CBonusTypeHandler.h" #include "CCreatureHandler.h" -#include "CObjectClassesHandler.h" +#include "mapObjects/CObjectClassesHandler.h" #include "CHeroHandler.h" -#include "CObjectHandler.h" +#include "mapObjects/CObjectHandler.h" #include "CTownHandler.h" #include "CBuildingHandler.h" #include "CSpellHandler.h" diff --git a/lib/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp similarity index 54% rename from lib/CObjectClassesHandler.cpp rename to lib/mapObjects/CObjectClassesHandler.cpp index 7bcd209f3..7077c4974 100644 --- a/lib/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -11,7 +11,7 @@ #include "CModHandler.h" #include "JsonNode.h" -#include "CObjectConstructor.h" +#include "CRewardableConstructor.h" /* * CObjectClassesHandler.cpp, part of VCMI engine @@ -23,314 +23,6 @@ * */ -static bool isVisitableFromTop(int identifier, int type) -{ - if(type == 2 || type == 3 || type == 4 || type == 5) //creature, hero, artifact, resource - return true; - - static const Obj visitableFromTop[] = - {Obj::FLOTSAM, - Obj::SEA_CHEST, - Obj::SHIPWRECK_SURVIVOR, - Obj::BUOY, - Obj::OCEAN_BOTTLE, - Obj::BOAT, - Obj::WHIRLPOOL, - Obj::GARRISON, - Obj::GARRISON2, - Obj::SCHOLAR, - Obj::CAMPFIRE, - Obj::BORDERGUARD, - Obj::BORDER_GATE, - Obj::QUEST_GUARD, - Obj::CORPSE - }; - if (vstd::find_pos(visitableFromTop, identifier) != -1) - return true; - return false; -} - -ObjectTemplate::ObjectTemplate(): - visitDir(8|16|32|64|128), // all but top - id(Obj::NO_OBJ), - subid(0), - printPriority(0) -{ -} - -void ObjectTemplate::readTxt(CLegacyConfigParser & parser) -{ - std::string data = parser.readString(); - std::vector strings; - boost::split(strings, data, boost::is_any_of(" ")); - assert(strings.size() == 9); - - animationFile = strings[0]; - stringID = strings[0]; - - std::string & blockStr = strings[1]; //block map, 0 = blocked, 1 = unblocked - std::string & visitStr = strings[2]; //visit map, 1 = visitable, 0 = not visitable - - assert(blockStr.size() == 6*8); - assert(visitStr.size() == 6*8); - - setSize(8, 6); - for (size_t i=0; i<6; i++) // 6 rows - { - for (size_t j=0; j<8; j++) // 8 columns - { - auto & tile = usedTiles[i][j]; - tile |= VISIBLE; // assume that all tiles are visible - if (blockStr[i*8 + j] == '0') - tile |= BLOCKED; - - if (visitStr[i*8 + j] == '1') - tile |= VISITABLE; - } - } - - // strings[3] most likely - terrains on which this object can be placed in editor. - // e.g. Whirpool can be placed manually only on water while mines can be placed everywhere despite terrain-specific gfx - // so these two fields can be interpreted as "strong affinity" and "weak affinity" towards terrains - std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain - - assert(terrStr.size() == 9); // all terrains but rock - for (size_t i=0; i<9; i++) - { - if (terrStr[8-i] == '1') - allowedTerrains.insert(ETerrainType(i)); - } - - id = Obj(boost::lexical_cast(strings[5])); - subid = boost::lexical_cast(strings[6]); - int type = boost::lexical_cast(strings[7]); - printPriority = boost::lexical_cast(strings[8]) * 100; // to have some space in future - - if (isVisitableFromTop(id, type)) - visitDir = 0xff; - else - visitDir = (8|16|32|64|128); - - readMsk(); -} - -void ObjectTemplate::readMsk() -{ - ResourceID resID("SPRITES/" + animationFile, EResType::MASK); - - if (CResourceHandler::get()->existsResource(resID)) - { - auto msk = CResourceHandler::get()->load(resID)->readAll(); - setSize(msk.first.get()[0], msk.first.get()[1]); - } - else //maximum possible size of H3 object //TODO: remove hardcode and move this data into modding system - { - setSize(8, 6); - } -} - -void ObjectTemplate::readMap(CBinaryReader & reader) -{ - animationFile = reader.readString(); - - setSize(8, 6); - ui8 blockMask[6]; - ui8 visitMask[6]; - for(auto & byte : blockMask) - byte = reader.readUInt8(); - for(auto & byte : visitMask) - byte = reader.readUInt8(); - - for (size_t i=0; i<6; i++) // 6 rows - { - for (size_t j=0; j<8; j++) // 8 columns - { - auto & tile = usedTiles[5 - i][7 - j]; - tile |= VISIBLE; // assume that all tiles are visible - if (((blockMask[i] >> j) & 1 ) == 0) - tile |= BLOCKED; - - if (((visitMask[i] >> j) & 1 ) != 0) - tile |= VISITABLE; - } - } - - reader.readUInt16(); - ui16 terrMask = reader.readUInt16(); - for (size_t i=0; i<9; i++) - { - if (((terrMask >> i) & 1 ) != 0) - allowedTerrains.insert(ETerrainType(i)); - } - - id = Obj(reader.readUInt32()); - subid = reader.readUInt32(); - int type = reader.readUInt8(); - printPriority = reader.readUInt8() * 100; // to have some space in future - - if (isVisitableFromTop(id, type)) - visitDir = 0xff; - else - visitDir = (8|16|32|64|128); - - reader.skip(16); - readMsk(); - - if (id == Obj::EVENT) - { - setSize(1,1); - usedTiles[0][0] = VISITABLE; - } -} - -void ObjectTemplate::readJson(const JsonNode &node) -{ - //id = Obj(node["basebase"].Float()); // temporary, should be removed and determined indirectly via object type parent (e.g. base->base) - //subid = node["base"].Float(); - animationFile = node["animation"].String(); - - const JsonVector & visitDirs = node["visitableFrom"].Vector(); - if (!visitDirs.empty()) - { - if (visitDirs[0].String()[0] == '+') visitDir |= 1; - if (visitDirs[0].String()[1] == '+') visitDir |= 2; - if (visitDirs[0].String()[2] == '+') visitDir |= 4; - if (visitDirs[1].String()[2] == '+') visitDir |= 8; - if (visitDirs[2].String()[2] == '+') visitDir |= 16; - if (visitDirs[2].String()[1] == '+') visitDir |= 32; - if (visitDirs[2].String()[0] == '+') visitDir |= 64; - if (visitDirs[1].String()[0] == '+') visitDir |= 128; - } - else - visitDir = 0x00; - - if (!node["allowedTerrains"].isNull()) - { - for (auto & entry : node["allowedTerrains"].Vector()) - allowedTerrains.insert(ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.String()))); - } - else - { - for (size_t i=0; i< GameConstants::TERRAIN_TYPES; i++) - allowedTerrains.insert(ETerrainType(i)); - } - - auto charToTile = [&](const char & ch) -> ui8 - { - switch (ch) - { - case ' ' : return 0; - case '0' : return 0; - case 'V' : return VISIBLE; - case 'B' : return VISIBLE | BLOCKED; - case 'H' : return BLOCKED; - case 'A' : return VISIBLE | BLOCKED | VISITABLE; - case 'T' : return BLOCKED | VISITABLE; - default: - logGlobal->errorStream() << "Unrecognized char " << ch << " in template mask"; - return 0; - } - }; - - const JsonVector & mask = node["mask"].Vector(); - - size_t height = mask.size(); - size_t width = 0; - for (auto & line : mask) - vstd::amax(width, line.String().size()); - - setSize(width, height); - - for (size_t i=0; i= getWidth() || Y >= getHeight()) - return false; - return true; -} - -bool ObjectTemplate::isVisitableAt(si32 X, si32 Y) const -{ - if (isWithin(X, Y)) - return usedTiles[Y][X] & VISITABLE; - return false; -} - -bool ObjectTemplate::isVisibleAt(si32 X, si32 Y) const -{ - if (isWithin(X, Y)) - return usedTiles[Y][X] & VISIBLE; - return false; -} - -bool ObjectTemplate::isBlockedAt(si32 X, si32 Y) const -{ - if (isWithin(X, Y)) - return usedTiles[Y][X] & BLOCKED; - return false; -} - -bool ObjectTemplate::isVisitableFrom(si8 X, si8 Y) const -{ - // visitDir uses format - // 1 2 3 - // 8 4 - // 7 6 5 - int dirMap[3][3] = - { - { visitDir & 1, visitDir & 2, visitDir & 4 }, - { visitDir & 128, 1 , visitDir & 8 }, - { visitDir & 64, visitDir & 32, visitDir & 16 } - }; - // map input values to range 0..2 - int dx = X < 0 ? 0 : X == 0 ? 1 : 2; - int dy = Y < 0 ? 0 : Y == 0 ? 1 : 2; - - return dirMap[dy][dx] != 0; -} - -bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const -{ - return allowedTerrains.count(terrain) != 0; -} - CObjectClassesHandler::CObjectClassesHandler() { #define SET_HANDLER_CLASS(STRING, CLASSNAME) handlerConstructors[STRING] = std::make_shared; @@ -338,7 +30,7 @@ CObjectClassesHandler::CObjectClassesHandler() // list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code //WARNING: should be in sync with registerTypesMapObjectTypes function - SET_HANDLER_CLASS("configurable", CObjectWithRewardConstructor); + SET_HANDLER_CLASS("configurable", CRewardableConstructor); SET_HANDLER("", CGObjectInstance); SET_HANDLER("generic", CGObjectInstance); diff --git a/lib/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h similarity index 71% rename from lib/CObjectClassesHandler.h rename to lib/mapObjects/CObjectClassesHandler.h index 684dd6ff7..eea4bae4a 100644 --- a/lib/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -3,6 +3,7 @@ #include "GameConstants.h" #include "../lib/ConstTransitivePtr.h" #include "IHandlerBase.h" +#include "ObjectTemplate.h" /* * CObjectClassesHandler.h, part of VCMI engine @@ -14,72 +15,9 @@ * */ -class CBinaryReader; -class CLegacyConfigParser; class JsonNode; class CRandomGenerator; -class DLL_LINKAGE ObjectTemplate -{ - enum EBlockMapBits - { - VISIBLE = 1, - VISITABLE = 2, - BLOCKED = 4 - }; - - /// tiles that are covered by this object, uses EBlockMapBits enum as flags - std::vector> usedTiles; - /// directions from which object can be entered, format same as for moveDir in CGHeroInstance(but 0 - 7) - ui8 visitDir; - /// list of terrains on which this object can be placed - std::set allowedTerrains; - -public: - /// H3 ID/subID of this object - Obj id; - si32 subid; - /// print priority, objects with higher priority will be print first, below everything else - si32 printPriority; - /// animation file that should be used to display object - std::string animationFile; - - /// string ID, equals to def base name for h3m files (lower case, no extension) or specified in mod data - std::string stringID; - - ui32 getWidth() const; - ui32 getHeight() const; - void setSize(ui32 width, ui32 height); - - bool isVisitable() const; - - // Checks object used tiles - // Position is relative to bottom-right corner of the object, can not be negative - bool isWithin(si32 X, si32 Y) const; - bool isVisitableAt(si32 X, si32 Y) const; - bool isVisibleAt(si32 X, si32 Y) const; - bool isBlockedAt(si32 X, si32 Y) const; - - // Checks if object is visitable from certain direction. X and Y must be between -1..+1 - bool isVisitableFrom(si8 X, si8 Y) const; - - // Checks if object can be placed on specific terrain - bool canBePlacedAt(ETerrainType terrain) const; - - ObjectTemplate(); - - void readTxt(CLegacyConfigParser & parser); - void readMsk(); - void readMap(CBinaryReader & reader); - void readJson(const JsonNode & node); - - template void serialize(Handler &h, const int version) - { - h & usedTiles & allowedTerrains & animationFile & stringID; - h & id & subid & printPriority & visitDir; - } -}; - class IObjectInfo { public: diff --git a/lib/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp similarity index 100% rename from lib/CObjectHandler.cpp rename to lib/mapObjects/CObjectHandler.cpp diff --git a/lib/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h similarity index 97% rename from lib/CObjectHandler.h rename to lib/mapObjects/CObjectHandler.h index 3dce812fb..b33790271 100644 --- a/lib/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -2,7 +2,7 @@ #include "../lib/CCreatureSet.h" #include "../lib/CTownHandler.h" -#include "../lib/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "CArtHandler.h" #include "../lib/ConstTransitivePtr.h" #include "int3.h" diff --git a/lib/CObjectConstructor.cpp b/lib/mapObjects/CRewardableConstructor.cpp similarity index 92% rename from lib/CObjectConstructor.cpp rename to lib/mapObjects/CRewardableConstructor.cpp index a011167fe..a03a66465 100644 --- a/lib/CObjectConstructor.cpp +++ b/lib/mapObjects/CRewardableConstructor.cpp @@ -1,5 +1,5 @@ #include "StdInc.h" -#include "CObjectConstructor.h" +#include "CRewardableConstructor.h" #include "CRandomGenerator.h" #include "StringConstants.h" @@ -136,7 +136,7 @@ void CRandomRewardObjectInfo::init(const JsonNode & objectConfig) parameters = objectConfig; } -void CRandomRewardObjectInfo::configureObject(CObjectWithReward * object, CRandomGenerator & rng) const +void CRandomRewardObjectInfo::configureObject(CRewardableObject * object, CRandomGenerator & rng) const { std::map thrownDice; @@ -262,29 +262,29 @@ bool CRandomRewardObjectInfo::givesBonuses() const return testForKey(parameters, "bonuses"); } -CObjectWithRewardConstructor::CObjectWithRewardConstructor() +CRewardableConstructor::CRewardableConstructor() { } -void CObjectWithRewardConstructor::init(const JsonNode & config) +void CRewardableConstructor::init(const JsonNode & config) { AObjectTypeHandler::init(config); objectInfo.init(config); } -CGObjectInstance * CObjectWithRewardConstructor::create(ObjectTemplate tmpl) const +CGObjectInstance * CRewardableConstructor::create(ObjectTemplate tmpl) const { - auto ret = new CObjectWithReward(); + auto ret = new CRewardableObject(); ret->appearance = tmpl; return ret; } -void CObjectWithRewardConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +void CRewardableConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const { - objectInfo.configureObject(dynamic_cast(object), rng); + objectInfo.configureObject(dynamic_cast(object), rng); } -const IObjectInfo * CObjectWithRewardConstructor::getObjectInfo(ObjectTemplate tmpl) const +const IObjectInfo * CRewardableConstructor::getObjectInfo(ObjectTemplate tmpl) const { return &objectInfo; } diff --git a/lib/CObjectConstructor.h b/lib/mapObjects/CRewardableConstructor.h similarity index 86% rename from lib/CObjectConstructor.h rename to lib/mapObjects/CRewardableConstructor.h index 31dcf76b1..d374295a7 100644 --- a/lib/CObjectConstructor.h +++ b/lib/mapObjects/CRewardableConstructor.h @@ -1,6 +1,6 @@ #pragma once -#include "CObjectWithReward.h" +#include "mapObjects/CRewardableObject.h" #include "CObjectClassesHandler.h" #include "JsonNode.h" @@ -33,7 +33,7 @@ public: bool givesBonuses() const override; - void configureObject(CObjectWithReward * object, CRandomGenerator & rng) const; + void configureObject(CRewardableObject * object, CRandomGenerator & rng) const; CRandomRewardObjectInfo() {} @@ -41,12 +41,12 @@ public: void init(const JsonNode & objectConfig); }; -class CObjectWithRewardConstructor : public AObjectTypeHandler +class CRewardableConstructor : public AObjectTypeHandler { CRandomRewardObjectInfo objectInfo; public: - CObjectWithRewardConstructor(); + CRewardableConstructor(); void init(const JsonNode & config) override; CGObjectInstance * create(ObjectTemplate tmpl) const override; diff --git a/lib/CObjectWithReward.cpp b/lib/mapObjects/CRewardableObject.cpp similarity index 96% rename from lib/CObjectWithReward.cpp rename to lib/mapObjects/CRewardableObject.cpp index 21ab6347d..e97009e7b 100644 --- a/lib/CObjectWithReward.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -1,5 +1,5 @@ /* - * CObjectWithReward.cpp, part of VCMI engine + * CRewardableObject.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -9,7 +9,7 @@ */ #include "StdInc.h" -#include "CObjectWithReward.h" +#include "CRewardableObject.h" #include "CHeroHandler.h" #include "CGeneralTextHandler.h" #include "../client/CSoundBase.h" @@ -63,7 +63,7 @@ bool CRewardLimiter::heroAllowed(const CGHeroInstance * hero) const return true; } -std::vector CObjectWithReward::getAvailableRewards(const CGHeroInstance * hero) const +std::vector CRewardableObject::getAvailableRewards(const CGHeroInstance * hero) const { std::vector ret; @@ -79,7 +79,7 @@ std::vector CObjectWithReward::getAvailableRewards(const CGHeroInstance * return ret; } -void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const +void CRewardableObject::onHeroVisit(const CGHeroInstance *h) const { auto grantRewardWithMessage = [&](int index) -> void { @@ -161,12 +161,12 @@ void CObjectWithReward::onHeroVisit(const CGHeroInstance *h) const } } -void CObjectWithReward::heroLevelUpDone(const CGHeroInstance *hero) const +void CRewardableObject::heroLevelUpDone(const CGHeroInstance *hero) const { grantRewardAfterLevelup(info[selectedReward], hero); } -void CObjectWithReward::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +void CRewardableObject::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const { if (answer == 0) return; // player refused @@ -183,12 +183,12 @@ void CObjectWithReward::blockingDialogAnswered(const CGHeroInstance *hero, ui32 } } -void CObjectWithReward::onRewardGiven(const CGHeroInstance * hero) const +void CRewardableObject::onRewardGiven(const CGHeroInstance * hero) const { // no implementation, virtual function for overrides } -void CObjectWithReward::grantReward(ui32 rewardID, const CGHeroInstance * hero) const +void CRewardableObject::grantReward(ui32 rewardID, const CGHeroInstance * hero) const { ChangeObjectVisitors cov(ChangeObjectVisitors::VISITOR_ADD, id, hero->id); cb->sendAndApply(&cov); @@ -197,7 +197,7 @@ void CObjectWithReward::grantReward(ui32 rewardID, const CGHeroInstance * hero) grantRewardBeforeLevelup(info[rewardID], hero); } -void CObjectWithReward::grantRewardBeforeLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const +void CRewardableObject::grantRewardBeforeLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const { assert(hero); assert(hero->tempOwner.isValidPlayer()); @@ -234,7 +234,7 @@ void CObjectWithReward::grantRewardBeforeLevelup(const CVisitInfo & info, const } } -void CObjectWithReward::grantRewardAfterLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const +void CRewardableObject::grantRewardAfterLevelup(const CVisitInfo & info, const CGHeroInstance * hero) const { if (info.reward.manaDiff || info.reward.manaPercentage >= 0) { @@ -290,7 +290,7 @@ void CObjectWithReward::grantRewardAfterLevelup(const CVisitInfo & info, const C cb->removeObject(this); } -bool CObjectWithReward::wasVisited (PlayerColor player) const +bool CRewardableObject::wasVisited (PlayerColor player) const { switch (visitMode) { @@ -311,7 +311,7 @@ bool CObjectWithReward::wasVisited (PlayerColor player) const } } -bool CObjectWithReward::wasVisited (const CGHeroInstance * h) const +bool CRewardableObject::wasVisited (const CGHeroInstance * h) const { switch (visitMode) { @@ -372,7 +372,7 @@ static std::string & visitedTxt(const bool visited) return VLC->generaltexth->allTexts[id]; } -const std::string & CObjectWithReward::getHoverText() const +const std::string & CRewardableObject::getHoverText() const { const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); hoverName = VLC->objtypeh->getObjectName(ID); @@ -387,7 +387,7 @@ const std::string & CObjectWithReward::getHoverText() const return hoverName; } -void CObjectWithReward::setPropertyDer(ui8 what, ui32 val) +void CRewardableObject::setPropertyDer(ui8 what, ui32 val) { switch (what) { @@ -402,13 +402,13 @@ void CObjectWithReward::setPropertyDer(ui8 what, ui32 val) } } -void CObjectWithReward::newTurn() const +void CRewardableObject::newTurn() const { if (resetDuration != 0 && cb->getDate(Date::DAY) % resetDuration == 0) cb->setObjProperty(id, ObjProperty::REWARD_RESET, 0); } -CObjectWithReward::CObjectWithReward(): +CRewardableObject::CRewardableObject(): soundID(soundBase::invalid), selectMode(0), selectedReward(0), @@ -417,7 +417,7 @@ CObjectWithReward::CObjectWithReward(): {} /////////////////////////////////////////////////////////////////////////////////////////////////// -/// END OF CODE FOR COBJECTWITHREWARD AND RELATED CLASSES /// +/// END OF CODE FOR CREWARDABLEOBJECT AND RELATED CLASSES /// /////////////////////////////////////////////////////////////////////////////////////////////////// /// Helper, selects random art class based on weights diff --git a/lib/CObjectWithReward.h b/lib/mapObjects/CRewardableObject.h similarity index 93% rename from lib/CObjectWithReward.h rename to lib/mapObjects/CRewardableObject.h index cadc7a581..50ed240dc 100644 --- a/lib/CObjectWithReward.h +++ b/lib/mapObjects/CRewardableObject.h @@ -4,7 +4,7 @@ #include "NetPacksBase.h" /* - * CObjectWithReward.h, part of VCMI engine + * CRewardableObject.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -153,7 +153,7 @@ public: /// Base class that can handle granting rewards to visiting heroes. /// Inherits from CArmedInstance for proper trasfer of armies -class DLL_LINKAGE CObjectWithReward : public CArmedInstance +class DLL_LINKAGE CRewardableObject : public CArmedInstance { /// function that must be called if hero got level-up during grantReward call void grantRewardAfterLevelup(const CVisitInfo & reward, const CGHeroInstance * hero) const; @@ -228,7 +228,7 @@ public: /// function that will be called once reward is fully granted to hero virtual void onRewardGiven(const CGHeroInstance * hero) const; - CObjectWithReward(); + CRewardableObject(); template void serialize(Handler &h, const int version) { @@ -242,7 +242,7 @@ public: friend class CRandomRewardObjectInfo; }; -class DLL_LINKAGE CGPickable : public CObjectWithReward //campfire, treasure chest, Flotsam, Shipwreck Survivor, Sea Chest +class DLL_LINKAGE CGPickable : public CRewardableObject //campfire, treasure chest, Flotsam, Shipwreck Survivor, Sea Chest { public: void initObj() override; @@ -251,11 +251,11 @@ public: template void serialize(Handler &h, const int version) { - h & static_cast(*this); + h & static_cast(*this); } }; -class DLL_LINKAGE CGBonusingObject : public CObjectWithReward //objects giving bonuses to luck/morale/movement +class DLL_LINKAGE CGBonusingObject : public CRewardableObject //objects giving bonuses to luck/morale/movement { public: void initObj() override; @@ -268,7 +268,7 @@ public: } }; -class DLL_LINKAGE CGOnceVisitable : public CObjectWithReward // wagon, corpse, lean to, warriors tomb +class DLL_LINKAGE CGOnceVisitable : public CRewardableObject // wagon, corpse, lean to, warriors tomb { public: void initObj() override; @@ -277,11 +277,11 @@ public: template void serialize(Handler &h, const int version) { - h & static_cast(*this); + h & static_cast(*this); } }; -class DLL_LINKAGE CGVisitableOPH : public CObjectWithReward //objects visitable only once per hero +class DLL_LINKAGE CGVisitableOPH : public CRewardableObject //objects visitable only once per hero { public: void initObj() override; @@ -290,11 +290,11 @@ public: template void serialize(Handler &h, const int version) { - h & static_cast(*this); + h & static_cast(*this); } }; -class DLL_LINKAGE CGVisitableOPW : public CObjectWithReward //objects visitable once per week +class DLL_LINKAGE CGVisitableOPW : public CRewardableObject //objects visitable once per week { public: void initObj() override; @@ -303,7 +303,7 @@ public: template void serialize(Handler &h, const int version) { - h & static_cast(*this); + h & static_cast(*this); } }; diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp new file mode 100644 index 000000000..34c2aaa3f --- /dev/null +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -0,0 +1,333 @@ +#include "StdInc.h" +#include "CObjectClassesHandler.h" + +#include "filesystem/Filesystem.h" +#include "filesystem/CBinaryReader.h" +#include "../lib/VCMI_Lib.h" +#include "GameConstants.h" +#include "StringConstants.h" +#include "CGeneralTextHandler.h" +#include "CObjectHandler.h" +#include "CModHandler.h" +#include "JsonNode.h" + +#include "CRewardableConstructor.h" + +/* + * ObjectTemplate.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 + * + */ + +static bool isVisitableFromTop(int identifier, int type) +{ + if(type == 2 || type == 3 || type == 4 || type == 5) //creature, hero, artifact, resource + return true; + + static const Obj visitableFromTop[] = + {Obj::FLOTSAM, + Obj::SEA_CHEST, + Obj::SHIPWRECK_SURVIVOR, + Obj::BUOY, + Obj::OCEAN_BOTTLE, + Obj::BOAT, + Obj::WHIRLPOOL, + Obj::GARRISON, + Obj::GARRISON2, + Obj::SCHOLAR, + Obj::CAMPFIRE, + Obj::BORDERGUARD, + Obj::BORDER_GATE, + Obj::QUEST_GUARD, + Obj::CORPSE + }; + if (vstd::find_pos(visitableFromTop, identifier) != -1) + return true; + return false; +} + +ObjectTemplate::ObjectTemplate(): + visitDir(8|16|32|64|128), // all but top + id(Obj::NO_OBJ), + subid(0), + printPriority(0) +{ +} + +void ObjectTemplate::readTxt(CLegacyConfigParser & parser) +{ + std::string data = parser.readString(); + std::vector strings; + boost::split(strings, data, boost::is_any_of(" ")); + assert(strings.size() == 9); + + animationFile = strings[0]; + stringID = strings[0]; + + std::string & blockStr = strings[1]; //block map, 0 = blocked, 1 = unblocked + std::string & visitStr = strings[2]; //visit map, 1 = visitable, 0 = not visitable + + assert(blockStr.size() == 6*8); + assert(visitStr.size() == 6*8); + + setSize(8, 6); + for (size_t i=0; i<6; i++) // 6 rows + { + for (size_t j=0; j<8; j++) // 8 columns + { + auto & tile = usedTiles[i][j]; + tile |= VISIBLE; // assume that all tiles are visible + if (blockStr[i*8 + j] == '0') + tile |= BLOCKED; + + if (visitStr[i*8 + j] == '1') + tile |= VISITABLE; + } + } + + // strings[3] most likely - terrains on which this object can be placed in editor. + // e.g. Whirpool can be placed manually only on water while mines can be placed everywhere despite terrain-specific gfx + // so these two fields can be interpreted as "strong affinity" and "weak affinity" towards terrains + std::string & terrStr = strings[4]; // allowed terrains, 1 = object can be placed on this terrain + + assert(terrStr.size() == 9); // all terrains but rock + for (size_t i=0; i<9; i++) + { + if (terrStr[8-i] == '1') + allowedTerrains.insert(ETerrainType(i)); + } + + id = Obj(boost::lexical_cast(strings[5])); + subid = boost::lexical_cast(strings[6]); + int type = boost::lexical_cast(strings[7]); + printPriority = boost::lexical_cast(strings[8]) * 100; // to have some space in future + + if (isVisitableFromTop(id, type)) + visitDir = 0xff; + else + visitDir = (8|16|32|64|128); + + readMsk(); +} + +void ObjectTemplate::readMsk() +{ + ResourceID resID("SPRITES/" + animationFile, EResType::MASK); + + if (CResourceHandler::get()->existsResource(resID)) + { + auto msk = CResourceHandler::get()->load(resID)->readAll(); + setSize(msk.first.get()[0], msk.first.get()[1]); + } + else //maximum possible size of H3 object //TODO: remove hardcode and move this data into modding system + { + setSize(8, 6); + } +} + +void ObjectTemplate::readMap(CBinaryReader & reader) +{ + animationFile = reader.readString(); + + setSize(8, 6); + ui8 blockMask[6]; + ui8 visitMask[6]; + for(auto & byte : blockMask) + byte = reader.readUInt8(); + for(auto & byte : visitMask) + byte = reader.readUInt8(); + + for (size_t i=0; i<6; i++) // 6 rows + { + for (size_t j=0; j<8; j++) // 8 columns + { + auto & tile = usedTiles[5 - i][7 - j]; + tile |= VISIBLE; // assume that all tiles are visible + if (((blockMask[i] >> j) & 1 ) == 0) + tile |= BLOCKED; + + if (((visitMask[i] >> j) & 1 ) != 0) + tile |= VISITABLE; + } + } + + reader.readUInt16(); + ui16 terrMask = reader.readUInt16(); + for (size_t i=0; i<9; i++) + { + if (((terrMask >> i) & 1 ) != 0) + allowedTerrains.insert(ETerrainType(i)); + } + + id = Obj(reader.readUInt32()); + subid = reader.readUInt32(); + int type = reader.readUInt8(); + printPriority = reader.readUInt8() * 100; // to have some space in future + + if (isVisitableFromTop(id, type)) + visitDir = 0xff; + else + visitDir = (8|16|32|64|128); + + reader.skip(16); + readMsk(); + + if (id == Obj::EVENT) + { + setSize(1,1); + usedTiles[0][0] = VISITABLE; + } +} + +void ObjectTemplate::readJson(const JsonNode &node) +{ + //id = Obj(node["basebase"].Float()); // temporary, should be removed and determined indirectly via object type parent (e.g. base->base) + //subid = node["base"].Float(); + animationFile = node["animation"].String(); + + const JsonVector & visitDirs = node["visitableFrom"].Vector(); + if (!visitDirs.empty()) + { + if (visitDirs[0].String()[0] == '+') visitDir |= 1; + if (visitDirs[0].String()[1] == '+') visitDir |= 2; + if (visitDirs[0].String()[2] == '+') visitDir |= 4; + if (visitDirs[1].String()[2] == '+') visitDir |= 8; + if (visitDirs[2].String()[2] == '+') visitDir |= 16; + if (visitDirs[2].String()[1] == '+') visitDir |= 32; + if (visitDirs[2].String()[0] == '+') visitDir |= 64; + if (visitDirs[1].String()[0] == '+') visitDir |= 128; + } + else + visitDir = 0x00; + + if (!node["allowedTerrains"].isNull()) + { + for (auto & entry : node["allowedTerrains"].Vector()) + allowedTerrains.insert(ETerrainType(vstd::find_pos(GameConstants::TERRAIN_NAMES, entry.String()))); + } + else + { + for (size_t i=0; i< GameConstants::TERRAIN_TYPES; i++) + allowedTerrains.insert(ETerrainType(i)); + } + + auto charToTile = [&](const char & ch) -> ui8 + { + switch (ch) + { + case ' ' : return 0; + case '0' : return 0; + case 'V' : return VISIBLE; + case 'B' : return VISIBLE | BLOCKED; + case 'H' : return BLOCKED; + case 'A' : return VISIBLE | BLOCKED | VISITABLE; + case 'T' : return BLOCKED | VISITABLE; + default: + logGlobal->errorStream() << "Unrecognized char " << ch << " in template mask"; + return 0; + } + }; + + const JsonVector & mask = node["mask"].Vector(); + + size_t height = mask.size(); + size_t width = 0; + for (auto & line : mask) + vstd::amax(width, line.String().size()); + + setSize(width, height); + + for (size_t i=0; i= getWidth() || Y >= getHeight()) + return false; + return true; +} + +bool ObjectTemplate::isVisitableAt(si32 X, si32 Y) const +{ + if (isWithin(X, Y)) + return usedTiles[Y][X] & VISITABLE; + return false; +} + +bool ObjectTemplate::isVisibleAt(si32 X, si32 Y) const +{ + if (isWithin(X, Y)) + return usedTiles[Y][X] & VISIBLE; + return false; +} + +bool ObjectTemplate::isBlockedAt(si32 X, si32 Y) const +{ + if (isWithin(X, Y)) + return usedTiles[Y][X] & BLOCKED; + return false; +} + +bool ObjectTemplate::isVisitableFrom(si8 X, si8 Y) const +{ + // visitDir uses format + // 1 2 3 + // 8 4 + // 7 6 5 + int dirMap[3][3] = + { + { visitDir & 1, visitDir & 2, visitDir & 4 }, + { visitDir & 128, 1 , visitDir & 8 }, + { visitDir & 64, visitDir & 32, visitDir & 16 } + }; + // map input values to range 0..2 + int dx = X < 0 ? 0 : X == 0 ? 1 : 2; + int dy = Y < 0 ? 0 : Y == 0 ? 1 : 2; + + return dirMap[dy][dx] != 0; +} + +bool ObjectTemplate::canBePlacedAt(ETerrainType terrain) const +{ + return allowedTerrains.count(terrain) != 0; +} + diff --git a/lib/mapObjects/ObjectTemplate.h b/lib/mapObjects/ObjectTemplate.h new file mode 100644 index 000000000..2c9230fc2 --- /dev/null +++ b/lib/mapObjects/ObjectTemplate.h @@ -0,0 +1,79 @@ +#pragma once + +#include "GameConstants.h" + +/* + * ObjectTemplate.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 CBinaryReader; +class CLegacyConfigParser; +class JsonNode; + +class DLL_LINKAGE ObjectTemplate +{ + enum EBlockMapBits + { + VISIBLE = 1, + VISITABLE = 2, + BLOCKED = 4 + }; + + /// tiles that are covered by this object, uses EBlockMapBits enum as flags + std::vector> usedTiles; + /// directions from which object can be entered, format same as for moveDir in CGHeroInstance(but 0 - 7) + ui8 visitDir; + /// list of terrains on which this object can be placed + std::set allowedTerrains; + +public: + /// H3 ID/subID of this object + Obj id; + si32 subid; + /// print priority, objects with higher priority will be print first, below everything else + si32 printPriority; + /// animation file that should be used to display object + std::string animationFile; + + /// string ID, equals to def base name for h3m files (lower case, no extension) or specified in mod data + std::string stringID; + + ui32 getWidth() const; + ui32 getHeight() const; + void setSize(ui32 width, ui32 height); + + bool isVisitable() const; + + // Checks object used tiles + // Position is relative to bottom-right corner of the object, can not be negative + bool isWithin(si32 X, si32 Y) const; + bool isVisitableAt(si32 X, si32 Y) const; + bool isVisibleAt(si32 X, si32 Y) const; + bool isBlockedAt(si32 X, si32 Y) const; + + // Checks if object is visitable from certain direction. X and Y must be between -1..+1 + bool isVisitableFrom(si8 X, si8 Y) const; + + // Checks if object can be placed on specific terrain + bool canBePlacedAt(ETerrainType terrain) const; + + ObjectTemplate(); + + void readTxt(CLegacyConfigParser & parser); + void readMsk(); + void readMap(CBinaryReader & reader); + void readJson(const JsonNode & node); + + template void serialize(Handler &h, const int version) + { + h & usedTiles & allowedTerrains & animationFile & stringID; + h & id & subid & printPriority & visitDir; + } +}; + diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 864609c36..455def111 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -10,7 +10,7 @@ #include "../CGeneralTextHandler.h" #include "../StartInfo.h" #include "../CArtHandler.h" //for hero crossover -#include "../CObjectHandler.h" //for hero crossover +#include "../mapObjects/CObjectHandler.h" //for hero crossover #include "../CHeroHandler.h" #include "CMapService.h" #include "CMap.h" diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 68f3b1caa..8991beef3 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -6,7 +6,7 @@ #include "../CCreatureHandler.h" #include "../CTownHandler.h" #include "../CHeroHandler.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" #include "../CGeneralTextHandler.h" #include "../CSpellHandler.h" #include "CMapEditManager.h" diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index f71c4df06..e14a29a99 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -12,7 +12,7 @@ #pragma once #include "../ConstTransitivePtr.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../ResourceSet.h" #include "../int3.h" #include "../GameConstants.h" diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index 75c490abb..a6885ee7e 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -3,7 +3,7 @@ #include "../JsonNode.h" #include "../filesystem/Filesystem.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" #include "../VCMI_Lib.h" MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 48c9c9a18..7066e8e4b 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -20,9 +20,9 @@ #include "../CCreatureHandler.h" #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" -#include "../CObjectHandler.h" -#include "../CObjectWithReward.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectHandler.h" +#include "../mapObjects/CRewardableObject.h" +#include "../mapObjects/CObjectClassesHandler.h" #include "../VCMI_Lib.h" #include "../NetPacksBase.h" diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index 0c61400a2..b4213ba85 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -14,7 +14,7 @@ #include "CMapService.h" #include "../GameConstants.h" #include "../ResourceSet.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" #include "../int3.h" diff --git a/lib/registerTypes/RegisterTypes.cpp b/lib/registerTypes/RegisterTypes.cpp index e3ffbdfd3..123089b45 100644 --- a/lib/registerTypes/RegisterTypes.cpp +++ b/lib/registerTypes/RegisterTypes.cpp @@ -8,7 +8,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -17,7 +17,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" // For reference: peak memory usage by gcc during compilation of register type templates // registerTypesMapObjects: 1.9 Gb diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 79e6a7ccb..184a3d01e 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -4,14 +4,14 @@ #include "../NetPacks.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" -#include "../CObjectHandler.h" -#include "../CObjectWithReward.h" +#include "../mapObjects/CObjectHandler.h" +#include "../mapObjects/CRewardableObject.h" #include "../CGameState.h" #include "../CHeroHandler.h" #include "../CTownHandler.h" #include "../CModHandler.h" //needed? -#include "../CObjectClassesHandler.h" -#include "../CObjectConstructor.h" +#include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/CRewardableConstructor.h" /* * RegisterTypes.h, part of VCMI engine @@ -77,7 +77,7 @@ void registerTypesMapObjects1(Serializer &s) template void registerTypesMapObjectTypes(Serializer &s) { - s.template registerType(); + s.template registerType(); #define REGISTER_GENERIC_HANDLER(TYPENAME) s.template registerType >() @@ -137,11 +137,11 @@ void registerTypesMapObjects2(Serializer &s) s.template registerType(); s.template registerType(); - s.template registerType(); - s.template registerType(); - s.template registerType(); - s.template registerType(); - s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); s.template registerType(); s.template registerType(); diff --git a/lib/registerTypes/TypesClientPacks1.cpp b/lib/registerTypes/TypesClientPacks1.cpp index 44101ad82..d7c25e297 100644 --- a/lib/registerTypes/TypesClientPacks1.cpp +++ b/lib/registerTypes/TypesClientPacks1.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesClientPacks1>(CISer& s); diff --git a/lib/registerTypes/TypesClientPacks2.cpp b/lib/registerTypes/TypesClientPacks2.cpp index 37e137992..9773e7787 100644 --- a/lib/registerTypes/TypesClientPacks2.cpp +++ b/lib/registerTypes/TypesClientPacks2.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesClientPacks2>(CISer& s); diff --git a/lib/registerTypes/TypesMapObjects1.cpp b/lib/registerTypes/TypesMapObjects1.cpp index 310c647e0..0a12c1fd8 100644 --- a/lib/registerTypes/TypesMapObjects1.cpp +++ b/lib/registerTypes/TypesMapObjects1.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesMapObjectTypes>(CISer& s); template void registerTypesMapObjectTypes>(COSer& s); diff --git a/lib/registerTypes/TypesMapObjects2.cpp b/lib/registerTypes/TypesMapObjects2.cpp index 197e2f5fe..ab49b6698 100644 --- a/lib/registerTypes/TypesMapObjects2.cpp +++ b/lib/registerTypes/TypesMapObjects2.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesMapObjects2>(CISer& s); diff --git a/lib/registerTypes/TypesPregamePacks.cpp b/lib/registerTypes/TypesPregamePacks.cpp index b4d73bf4f..08ead1777 100644 --- a/lib/registerTypes/TypesPregamePacks.cpp +++ b/lib/registerTypes/TypesPregamePacks.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesPregamePacks>(CISer& s); template void registerTypesPregamePacks>(COSer& s); diff --git a/lib/registerTypes/TypesServerPacks.cpp b/lib/registerTypes/TypesServerPacks.cpp index eff72a34e..306078e92 100644 --- a/lib/registerTypes/TypesServerPacks.cpp +++ b/lib/registerTypes/TypesServerPacks.cpp @@ -7,7 +7,7 @@ #include "../CGameState.h" #include "../mapping/CMap.h" #include "../CModHandler.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../CCreatureHandler.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" @@ -16,7 +16,7 @@ #include "../CTownHandler.h" #include "../mapping/CCampaignHandler.h" #include "../NetPacks.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" template void registerTypesServerPacks>(CISer& s); template void registerTypesServerPacks>(COSer& s); diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 0b204678c..a0ae6b39c 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -5,8 +5,8 @@ #include "../VCMI_Lib.h" #include "../CGeneralTextHandler.h" #include "../mapping/CMapEditManager.h" -#include "../CObjectHandler.h" -#include "../CObjectClassesHandler.h" +#include "../mapObjects/CObjectHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" #include "../CTownHandler.h" #include "../StringConstants.h" #include "../filesystem/Filesystem.h" diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index d802e00ad..0d32b783b 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -15,7 +15,7 @@ #include "../CRandomGenerator.h" #include "CMapGenOptions.h" #include "CRmgTemplateZone.h" -#include "../CObjectHandler.h" +#include "../mapObjects/CObjectHandler.h" #include "../int3.h" class CMap; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 1b64683fb..42f5d7a6b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -8,9 +8,9 @@ #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CBuildingHandler.h" -#include "../lib/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 5c26fbff0..1ee49b599 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -8,7 +8,7 @@ #include "../lib/Connection.h" #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" -#include "../lib/CObjectClassesHandler.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" @@ -26,7 +26,7 @@ #include "../lib/VCMIDirs.h" #include "CGameHandler.h" #include "../lib/mapping/CMapInfo.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/GameConstants.h" #include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/CConfigHandler.h" diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 12c76ca94..6a3dbb614 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -2,7 +2,7 @@ #include "../lib/NetPacks.h" #include "CGameHandler.h" -#include "../lib/CObjectHandler.h" +#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/IGameCallback.h" #include "../lib/mapping/CMap.h" #include "../lib/CGameState.h" From 652ceb2bde9e584f4b08d616be490ac51166f1d5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jun 2014 19:52:14 +0300 Subject: [PATCH 24/33] Finally shattered CObjectHandler.cpp into tiny bits - This file is now split into multiple smaller files in mapObjects directory - CObjectHandler itself now contains only core classes (Handler itself, CGObject and interfaces) - Cleaned up excessive #include's through whole project --- AI/VCAI/AIUtility.cpp | 2 +- AI/VCAI/AIUtility.h | 4 +- AI/VCAI/Fuzzy.cpp | 2 +- AI/VCAI/Goals.h | 2 +- AI/VCAI/VCAI.cpp | 3 +- AI/VCAI/VCAI.h | 4 +- CCallback.cpp | 2 +- client/AdventureMapClasses.cpp | 2 +- client/CAdvmapInterface.cpp | 2 +- client/CCastleInterface.cpp | 2 +- client/CHeroWindow.cpp | 2 +- client/CKingdomInterface.cpp | 4 +- client/CMT.cpp | 4 +- client/CPlayerInterface.cpp | 4 +- client/CPreGame.cpp | 4 +- client/CQuestLog.cpp | 2 +- client/CSpellWindow.cpp | 2 +- client/Client.cpp | 4 +- client/GUIClasses.cpp | 2 +- client/Graphics.cpp | 6 +- client/NetPacksClient.cpp | 4 +- client/battle/CBattleInterface.cpp | 2 +- client/battle/CBattleInterfaceClasses.cpp | 2 +- client/mapHandler.cpp | 2 +- lib/BattleState.h | 4 +- lib/CArtHandler.cpp | 4 +- lib/CCreatureHandler.cpp | 2 + lib/CCreatureSet.cpp | 2 +- lib/CGameState.h | 2 +- lib/CHeroHandler.cpp | 2 + lib/CMakeLists.txt | 37 +- lib/Connection.h | 3 +- lib/IGameCallback.cpp | 1 + lib/NetPacks.h | 3 +- lib/mapObjects/CGArmedInstance.cpp | 128 + lib/mapObjects/CGArmedInstance.h | 40 + lib/mapObjects/CGBank.cpp | 443 ++ lib/mapObjects/CGBank.h | 59 + lib/mapObjects/CGHeroInstance.cpp | 1368 +++++ lib/mapObjects/CGHeroInstance.h | 227 + lib/mapObjects/CGMarket.cpp | 334 ++ lib/mapObjects/CGMarket.h | 88 + lib/mapObjects/CGPandoraBox.cpp | 364 ++ lib/mapObjects/CGPandoraBox.h | 73 + lib/mapObjects/CGTownInstance.cpp | 1201 ++++ lib/mapObjects/CGTownInstance.h | 248 + lib/mapObjects/CObjectClassesHandler.cpp | 2 +- lib/mapObjects/CObjectClassesHandler.h | 7 +- lib/mapObjects/CObjectHandler.cpp | 6067 +-------------------- lib/mapObjects/CObjectHandler.h | 1226 +---- lib/mapObjects/CQuest.cpp | 861 +++ lib/mapObjects/CQuest.h | 198 + lib/mapObjects/CRewardableConstructor.h | 5 +- lib/mapObjects/CRewardableObject.cpp | 2 + lib/mapObjects/CRewardableObject.h | 4 +- lib/mapObjects/MapObjects.h | 25 + lib/mapObjects/MiscObjects.cpp | 1574 ++++++ lib/mapObjects/MiscObjects.h | 377 ++ lib/mapping/CCampaignHandler.cpp | 2 +- lib/mapping/CMap.cpp | 1 + lib/mapping/CMap.h | 4 +- lib/mapping/CMapEditManager.cpp | 1 + lib/mapping/MapFormatH3M.cpp | 3 +- lib/mapping/MapFormatH3M.h | 3 +- lib/registerTypes/RegisterTypes.h | 3 +- lib/rmg/CMapGenerator.cpp | 4 +- lib/rmg/CMapGenerator.h | 1 - lib/rmg/CRmgTemplateZone.cpp | 2 + server/CGameHandler.cpp | 4 +- server/CVCMIServer.cpp | 4 +- server/NetPacksServer.cpp | 2 +- 71 files changed, 7730 insertions(+), 7354 deletions(-) create mode 100644 lib/mapObjects/CGArmedInstance.cpp create mode 100644 lib/mapObjects/CGArmedInstance.h create mode 100644 lib/mapObjects/CGBank.cpp create mode 100644 lib/mapObjects/CGBank.h create mode 100644 lib/mapObjects/CGHeroInstance.cpp create mode 100644 lib/mapObjects/CGHeroInstance.h create mode 100644 lib/mapObjects/CGMarket.cpp create mode 100644 lib/mapObjects/CGMarket.h create mode 100644 lib/mapObjects/CGPandoraBox.cpp create mode 100644 lib/mapObjects/CGPandoraBox.h create mode 100644 lib/mapObjects/CGTownInstance.cpp create mode 100644 lib/mapObjects/CGTownInstance.h create mode 100644 lib/mapObjects/CQuest.cpp create mode 100644 lib/mapObjects/CQuest.h create mode 100644 lib/mapObjects/MapObjects.h create mode 100644 lib/mapObjects/MiscObjects.cpp create mode 100644 lib/mapObjects/MiscObjects.h diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index 3e449fed6..783b2266e 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -3,7 +3,7 @@ #include "VCAI.h" #include "../../lib/UnlockGuard.h" -#include "../../lib/mapObjects/CObjectHandler.h" +//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" diff --git a/AI/VCAI/AIUtility.h b/AI/VCAI/AIUtility.h index d4fab2843..cfa678a55 100644 --- a/AI/VCAI/AIUtility.h +++ b/AI/VCAI/AIUtility.h @@ -5,7 +5,7 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" #include "../../lib/CSpellHandler.h" -#include "../../lib/mapObjects/CObjectHandler.h" +//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/Connection.h" #include "../../lib/CGameState.h" #include "../../lib/mapping/CMap.h" @@ -200,4 +200,4 @@ bool compareMovement(HeroPtr lhs, HeroPtr rhs); bool compareHeroStrength(HeroPtr h1, HeroPtr h2); bool compareArmyStrength(const CArmedInstance *a1, const CArmedInstance *a2); ui64 howManyReinforcementsCanGet(HeroPtr h, const CGTownInstance *t); -int3 whereToExplore(HeroPtr h); \ No newline at end of file +int3 whereToExplore(HeroPtr h); diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 11dc47969..63547cd72 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -2,7 +2,7 @@ #include "Fuzzy.h" #include -#include "../../lib/mapObjects/CObjectHandler.h" +#include "../../lib/mapObjects/MapObjects.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/VCMI_Lib.h" #include "../../CCallback.h" diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index 5cdb1ebbf..2c459c817 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -1,7 +1,7 @@ #pragma once #include "../../lib/VCMI_Lib.h" -#include "../../lib/mapObjects/CObjectHandler.h" +//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/CBuildingHandler.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" diff --git a/AI/VCAI/VCAI.cpp b/AI/VCAI/VCAI.cpp index 5f5ec993f..7ab42c222 100644 --- a/AI/VCAI/VCAI.cpp +++ b/AI/VCAI/VCAI.cpp @@ -2,8 +2,7 @@ #include "VCAI.h" #include "Goals.h" #include "../../lib/UnlockGuard.h" -#include "../../lib/mapObjects/CObjectHandler.h" -#include "../../lib/mapObjects/CRewardableObject.h" +#include "../../lib/mapObjects/MapObjects.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 33d664f65..116913daf 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -4,7 +4,7 @@ #include "Goals.h" #include "../../lib/AI_Base.h" #include "../../CCallback.h" -#include "../../lib/mapObjects/CObjectClassesHandler.h" +//#include "../../lib/mapObjects/CObjectClassesHandler.h" #include "../../lib/CThreadHelper.h" @@ -14,7 +14,7 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" #include "../../lib/CSpellHandler.h" -#include "../../lib/mapObjects/CObjectHandler.h" +//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/Connection.h" #include "../../lib/CGameState.h" #include "../../lib/mapping/CMap.h" diff --git a/CCallback.cpp b/CCallback.cpp index 73b0f7d90..38392c512 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -12,7 +12,7 @@ #include "lib/mapObjects/CObjectClassesHandler.h" #include "lib/CGeneralTextHandler.h" #include "lib/CHeroHandler.h" -#include "lib/mapObjects/CObjectHandler.h" +//#include "lib/mapObjects/CObjectHandler.h" #include "lib/Connection.h" #include "lib/NetPacks.h" #include "client/mapHandler.h" diff --git a/client/AdventureMapClasses.cpp b/client/AdventureMapClasses.cpp index b7774244c..31960805f 100644 --- a/client/AdventureMapClasses.cpp +++ b/client/AdventureMapClasses.cpp @@ -6,7 +6,7 @@ #include "../lib/filesystem/Filesystem.h" #include "../lib/mapping/CMap.h" #include "../lib/CModHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/CGameState.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" diff --git a/client/CAdvmapInterface.cpp b/client/CAdvmapInterface.cpp index d717cb67c..09feb557f 100644 --- a/client/CAdvmapInterface.cpp +++ b/client/CAdvmapInterface.cpp @@ -17,7 +17,7 @@ #include "CDefHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/CTownHandler.h" #include "../lib/mapping/CMap.h" #include "../lib/JsonNode.h" diff --git a/client/CCastleInterface.cpp b/client/CCastleInterface.cpp index 679feccc5..394479ca1 100644 --- a/client/CCastleInterface.cpp +++ b/client/CCastleInterface.cpp @@ -7,7 +7,7 @@ #include "../lib/CCreatureHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CModHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" #include "CAdvmapInterface.h" diff --git a/client/CHeroWindow.cpp b/client/CHeroWindow.cpp index dbd5187df..3d04917d9 100644 --- a/client/CHeroWindow.cpp +++ b/client/CHeroWindow.cpp @@ -20,7 +20,7 @@ #include "CDefHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/NetPacksBase.h" #include "gui/CGuiHandler.h" diff --git a/client/CKingdomInterface.cpp b/client/CKingdomInterface.cpp index 9ee5b9912..c604eab85 100644 --- a/client/CKingdomInterface.cpp +++ b/client/CKingdomInterface.cpp @@ -5,7 +5,7 @@ #include "../lib/CCreatureHandler.h" //creatures name for objects list #include "../lib/CGeneralTextHandler.h" #include "../lib/CModHandler.h" //for buildings per turn -#include "../lib/mapObjects/CObjectHandler.h" //Hero/Town objects +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/CHeroHandler.h" // only for calculating required xp? worth it? #include "../lib/CTownHandler.h" #include "CAnimation.h" //CAnimImage @@ -953,4 +953,4 @@ void CHeroItem::onArtChange(int tabIndex) //redraw item after background change if (active) redraw(); -} \ No newline at end of file +} diff --git a/client/CMT.cpp b/client/CMT.cpp index d22c4a7d6..22e2f9d33 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -34,7 +34,7 @@ #include "CMessage.h" #include "../lib/CModHandler.h" #include "../lib/CTownHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CScriptingModule.h" #include "../lib/GameConstants.h" @@ -44,7 +44,7 @@ #ifdef _WIN32 #include "SDL_syswm.h" #endif -#include "../lib/mapObjects/CObjectClassesHandler.h" +//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/UnlockGuard.h" #include "CMT.h" diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 82a72b37f..00ff6e880 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1,5 +1,5 @@ #include "StdInc.h" -#include "../lib/mapObjects/CObjectClassesHandler.h" +//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "CAdvmapInterface.h" #include "battle/CBattleInterface.h" #include "battle/CBattleInterfaceClasses.h" @@ -21,7 +21,7 @@ #include "../lib/CArtHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/Connection.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index d8ff91dd5..d4ba8fd91 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -11,11 +11,11 @@ #include "gui/CCursorHandler.h" #include "CAnimation.h" #include "CDefHandler.h" -#include "../lib/mapObjects/CObjectClassesHandler.h" +//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/mapping/CCampaignHandler.h" #include "../lib/CCreatureHandler.h" #include "../lib/JsonNode.h" diff --git a/client/CQuestLog.cpp b/client/CQuestLog.cpp index 94c8edb75..a85758b04 100644 --- a/client/CQuestLog.cpp +++ b/client/CQuestLog.cpp @@ -16,7 +16,7 @@ #include "../lib/CGameState.h" #include "../lib/CArtHandler.h" #include "../lib/NetPacksBase.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "gui/CGuiHandler.h" #include "gui/CIntObjectClasses.h" diff --git a/client/CSpellWindow.cpp b/client/CSpellWindow.cpp index a43df7c10..34168743a 100644 --- a/client/CSpellWindow.cpp +++ b/client/CSpellWindow.cpp @@ -3,7 +3,7 @@ #include "Graphics.h" #include "CDefHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CGeneralTextHandler.h" #include "CVideoHandler.h" diff --git a/client/Client.cpp b/client/Client.cpp index bd3658487..9d0c27d42 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -11,11 +11,11 @@ #include "../lib/BattleState.h" #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" -#include "../lib/mapObjects/CObjectClassesHandler.h" +//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CBuildingHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/Connection.h" diff --git a/client/GUIClasses.cpp b/client/GUIClasses.cpp index da38becdb..f183a39c5 100644 --- a/client/GUIClasses.cpp +++ b/client/GUIClasses.cpp @@ -24,7 +24,7 @@ #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CModHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CondSh.h" diff --git a/client/Graphics.cpp b/client/Graphics.cpp index c11fe0669..d1025adc4 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -12,13 +12,13 @@ #include "../CCallback.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CCreatureHandler.h" #include "CBitmapHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" -#include "../lib/mapObjects/CObjectClassesHandler.h" +//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGameState.h" #include "../lib/JsonNode.h" #include "../lib/vcmi_endian.h" diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 4a701e148..051b72d20 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -9,9 +9,9 @@ #include "CGameInfo.h" #include "../lib/Connection.h" #include "../lib/CGeneralTextHandler.h" -#include "../lib/mapObjects/CObjectClassesHandler.h" +//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/VCMI_Lib.h" #include "../lib/mapping/CMap.h" #include "../lib/VCMIDirs.h" diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 52c773112..6219f692d 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -6,7 +6,7 @@ #include "../CAdvmapInterface.h" #include "../CAnimation.h" #include "../CBitmapHandler.h" -#include "../../lib/mapObjects/CObjectHandler.h" +//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/CHeroHandler.h" # include "../CDefHandler.h" #include "../../lib/CSpellHandler.h" diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index b9fb761cd..81ef34d0e 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -16,7 +16,7 @@ #include "../../lib/CGeneralTextHandler.h" #include "../../lib/NetPacks.h" #include "../../lib/CCreatureHandler.h" -#include "../../lib/mapObjects/CObjectHandler.h" +//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/BattleState.h" #include "../CMusicHandler.h" #include "../CVideoHandler.h" diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index 80265dad1..ade34616e 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -14,12 +14,12 @@ #include "CBitmapHandler.h" #include "gui/SDL_Extensions.h" #include "CGameInfo.h" +#include "../lib/mapObjects/CGHeroInstance.h" #include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGameState.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" #include "Graphics.h" -#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/mapping/CMap.h" #include "CDefHandler.h" #include "../lib/CConfigHandler.h" diff --git a/lib/BattleState.h b/lib/BattleState.h index 27a993c84..3d8ac4587 100644 --- a/lib/BattleState.h +++ b/lib/BattleState.h @@ -4,12 +4,14 @@ #include "BattleHex.h" #include "HeroBonus.h" #include "CCreatureSet.h" -#include "mapObjects/CObjectHandler.h" +#include "mapObjects/CGTownInstance.h" +#include "mapObjects/CGHeroInstance.h" #include "CCreatureHandler.h" #include "CObstacleInstance.h" #include "ConstTransitivePtr.h" #include "GameConstants.h" #include "CBattleCallback.h" +#include "int3.h" /* * BattleState.h, part of VCMI engine diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 0fb855e68..7be90644e 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -16,11 +16,13 @@ #include "VCMI_Lib.h" #include "CModHandler.h" #include "CSpellHandler.h" -#include "mapObjects/CObjectHandler.h" +#include "mapObjects/MapObjects.h" #include "NetPacksBase.h" #include "GameConstants.h" #include "CRandomGenerator.h" +#include "mapObjects/CObjectClassesHandler.h" + using namespace boost::assign; // Note: list must match entries in ArtTraits.txt diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index a3f7ae46d..a7e890cdd 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -9,6 +9,8 @@ #include "CModHandler.h" #include "StringConstants.h" +#include "mapObjects/CObjectClassesHandler.h" + using namespace boost::assign; /* diff --git a/lib/CCreatureSet.cpp b/lib/CCreatureSet.cpp index e1813b01b..f33019a81 100644 --- a/lib/CCreatureSet.cpp +++ b/lib/CCreatureSet.cpp @@ -4,7 +4,7 @@ #include "CCreatureHandler.h" #include "VCMI_Lib.h" #include "CModHandler.h" -#include "mapObjects/CObjectHandler.h" +#include "mapObjects/CGHeroInstance.h" #include "IGameCallback.h" #include "CGameState.h" #include "CGeneralTextHandler.h" diff --git a/lib/CGameState.h b/lib/CGameState.h index b4197492a..51178bf77 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -14,7 +14,7 @@ #include "IGameCallback.h" #include "ResourceSet.h" #include "int3.h" -#include "mapObjects/CObjectHandler.h" +//#include "mapObjects/CObjectHandler.h" #include "CRandomGenerator.h" /* diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 9938b468b..de6f318fb 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -13,6 +13,8 @@ #include "mapObjects/CObjectHandler.h" //for hero specialty #include +#include "mapObjects/CObjectClassesHandler.h" + /* * CHeroHandler.cpp, part of VCMI engine * diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 75b43edc7..5900f6053 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -6,19 +6,6 @@ include_directories(${Boost_INCLUDE_DIRS} ${SDL_INCLUDE_DIR} ${ZLIB_INCLUDE_DIR} set(lib_SRCS StdInc.cpp - IGameCallback.cpp - CGameInfoCallback.cpp - CGameState.cpp - Connection.cpp - NetPacksLib.cpp - - registerTypes/RegisterTypes.cpp - registerTypes/TypesClientPacks1.cpp - registerTypes/TypesClientPacks2.cpp - registerTypes/TypesMapObjects1.cpp - registerTypes/TypesMapObjects2.cpp - registerTypes/TypesPregamePacks.cpp - registerTypes/TypesServerPacks.cpp filesystem/AdapterLoaders.cpp filesystem/CCompressedStream.cpp @@ -32,10 +19,18 @@ set(lib_SRCS filesystem/Filesystem.cpp filesystem/ResourceID.cpp + mapObjects/CGArmedInstance.cpp + mapObjects/CGBank.cpp + mapObjects/CGHeroInstance.cpp + mapObjects/CGMarket.cpp + mapObjects/CGPandoraBox.cpp + mapObjects/CGTownInstance.cpp mapObjects/CObjectClassesHandler.cpp mapObjects/CObjectHandler.cpp + mapObjects/CQuest.cpp mapObjects/CRewardableConstructor.cpp mapObjects/CRewardableObject.cpp + mapObjects/MiscObjects.cpp mapObjects/ObjectTemplate.cpp logging/CBasicLogConfigurator.cpp @@ -85,6 +80,20 @@ set(lib_SRCS ResourceSet.cpp VCMI_Lib.cpp VCMIDirs.cpp + + IGameCallback.cpp + CGameInfoCallback.cpp + CGameState.cpp + Connection.cpp + NetPacksLib.cpp + + registerTypes/RegisterTypes.cpp + registerTypes/TypesClientPacks1.cpp + registerTypes/TypesClientPacks2.cpp + registerTypes/TypesMapObjects1.cpp + registerTypes/TypesMapObjects2.cpp + registerTypes/TypesPregamePacks.cpp + registerTypes/TypesServerPacks.cpp ) set(lib_HEADERS @@ -93,6 +102,8 @@ set(lib_HEADERS filesystem/CInputStream.h filesystem/ISimpleResourceLoader.h + mapObjects/MapObjects.h + AI_Base.h CondSh.h ConstTransitivePtr.h diff --git a/lib/Connection.h b/lib/Connection.h index 01a960a12..cfb51deb8 100644 --- a/lib/Connection.h +++ b/lib/Connection.h @@ -14,7 +14,6 @@ #include //XXX this is in namespace std if you want w/o use typeinfo.h? #include -#include #include #include #include @@ -24,7 +23,7 @@ #include "ConstTransitivePtr.h" #include "CCreatureSet.h" //for CStackInstance -#include "mapObjects/CObjectHandler.h" //for CArmedInstance +#include "mapObjects/CGHeroInstance.h" #include "mapping/CCampaignHandler.h" //for CCampaignState #include "rmg/CMapGenerator.h" // for CMapGenOptions diff --git a/lib/IGameCallback.cpp b/lib/IGameCallback.cpp index 00eeb6534..98bcf2c4a 100644 --- a/lib/IGameCallback.cpp +++ b/lib/IGameCallback.cpp @@ -17,6 +17,7 @@ #include "CBonusTypeHandler.h" #include "Connection.h" // for SAVEGAME_MAGIC +#include "mapObjects/CObjectClassesHandler.h" void CPrivilagedInfoCallback::getFreeTiles (std::vector &tiles) const { diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 01f0336b3..62ff3ce37 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1,11 +1,10 @@ #pragma once -#include - #include "NetPacksBase.h" #include "BattleAction.h" #include "HeroBonus.h" +#include "mapObjects/CGHeroInstance.h" #include "CCreatureSet.h" #include "mapping/CMapInfo.h" #include "StartInfo.h" diff --git a/lib/mapObjects/CGArmedInstance.cpp b/lib/mapObjects/CGArmedInstance.cpp new file mode 100644 index 000000000..1cfd17f70 --- /dev/null +++ b/lib/mapObjects/CGArmedInstance.cpp @@ -0,0 +1,128 @@ +/* + * CObjectHandler.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 "CGArmedInstance.h" + +#include "../CTownHandler.h" +#include "../CCreatureHandler.h" +#include "../CGeneralTextHandler.h" +#include "../CGameState.h" + +using namespace boost::assign; + +void CArmedInstance::randomizeArmy(int type) +{ + for (auto & elem : stacks) + { + int & randID = elem.second->idRand; + if(randID >= 0) + { + int level = randID / 2; + bool upgrade = randID % 2; + elem.second->setType(VLC->townh->factions[type]->town->creatures[level][upgrade]); + + randID = -1; + } + assert(elem.second->valid(false)); + assert(elem.second->armyObj == this); + } + return; +} + +CArmedInstance::CArmedInstance() +{ + battle = nullptr; +} + +void CArmedInstance::updateMoraleBonusFromArmy() +{ + if(!validTypes(false)) //object not randomized, don't bother + return; + + Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE))); + if(!b) + { + b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1); + addNewBonus(b); + } + + //number of alignments and presence of undead + std::set factions; + bool hasUndead = false; + + for(auto slot : Slots()) + { + const CStackInstance * inst = slot.second; + const CCreature * creature = VLC->creh->creatures[inst->getCreatureID()]; + + factions.insert(creature->faction); + // Check for undead flag instead of faction (undead mummies are neutral) + hasUndead |= inst->hasBonusOfType(Bonus::UNDEAD); + } + + size_t factionsInArmy = factions.size(); //town garrison seems to take both sets into account + + // Take Angelic Alliance troop-mixing freedom of non-evil units into account. + if (hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX)) + { + size_t mixableFactions = 0; + + for(TFaction f : factions) + { + if (VLC->townh->factions[f]->alignment != EAlignment::EVIL) + mixableFactions++; + } + if (mixableFactions > 0) + factionsInArmy -= mixableFactions - 1; + } + + if(factionsInArmy == 1) + { + b->val = +1; + b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1 + } + else if (!factions.empty()) // no bonus from empty garrison + { + b->val = 2 - factionsInArmy; + b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d + } + boost::algorithm::trim(b->description); + + //-1 modifier for any Undead unit in army + const ui8 UNDEAD_MODIFIER_ID = -2; + Bonus *undeadModifier = getBonusList().getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID)); + if(hasUndead) + { + if(!undeadModifier) + addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116])); + } + else if(undeadModifier) + removeBonus(undeadModifier); + +} + +void CArmedInstance::armyChanged() +{ + updateMoraleBonusFromArmy(); +} + +CBonusSystemNode * CArmedInstance::whereShouldBeAttached(CGameState *gs) +{ + if(tempOwner < PlayerColor::PLAYER_LIMIT) + return gs->getPlayer(tempOwner); + else + return &gs->globalEffects; +} + +CBonusSystemNode * CArmedInstance::whatShouldBeAttached() +{ + return this; +} diff --git a/lib/mapObjects/CGArmedInstance.h b/lib/mapObjects/CGArmedInstance.h new file mode 100644 index 000000000..ecc024acc --- /dev/null +++ b/lib/mapObjects/CGArmedInstance.h @@ -0,0 +1,40 @@ +#pragma once + +#include "CObjectHandler.h" +#include "../CCreatureSet.h" + +/* + * CObjectHandler.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 CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet +{ +public: + BattleInfo *battle; //set to the current battle, if engaged + + void randomizeArmy(int type); + virtual void updateMoraleBonusFromArmy(); + + void armyChanged() override; + + ////////////////////////////////////////////////////////////////////////// +// int valOfGlobalBonuses(CSelector selector) const; //used only for castle interface ??? + virtual CBonusSystemNode *whereShouldBeAttached(CGameState *gs); + virtual CBonusSystemNode *whatShouldBeAttached(); + ////////////////////////////////////////////////////////////////////////// + + CArmedInstance(); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + h & static_cast(*this); + } +}; diff --git a/lib/mapObjects/CGBank.cpp b/lib/mapObjects/CGBank.cpp new file mode 100644 index 000000000..48e5a2c5c --- /dev/null +++ b/lib/mapObjects/CGBank.cpp @@ -0,0 +1,443 @@ +/* + * CObjectHandler.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 "CGBank.h" + +#include "NetPacks.h" +#include "CGeneralTextHandler.h" +#include "../client/CSoundBase.h" + +using namespace boost::assign; + +///helpers +static std::string & visitedTxt(const bool visited) +{ + int id = visited ? 352 : 353; + return VLC->generaltexth->allTexts[id]; +} + +void CBank::initObj() +{ + index = VLC->objh->bankObjToIndex(this); + bc = nullptr; + daycounter = 0; + multiplier = 1; +} +const std::string & CBank::getHoverText() const +{ + bool visited = (bc == nullptr); + hoverName = VLC->objh->creBanksNames[index] + " " + visitedTxt(visited); + return hoverName; +} +void CBank::reset(ui16 var1) //prevents desync +{ + ui8 chance = 0; + for (auto & elem : VLC->objh->banksInfo[index]) + { + if (var1 < (chance += elem->chance)) + { + bc = elem; + break; + } + } + artifacts.clear(); +} + +void CBank::initialize() const +{ + cb->setObjProperty(id, ObjProperty::BANK_RESET, cb->gameState()->getRandomGenerator().nextInt()); //synchronous reset + + for (ui8 i = 0; i <= 3; i++) + { + for (ui8 n = 0; n < bc->artifacts[i]; n++) + { + CArtifact::EartClass artClass; + switch(i) + { + case 0: artClass = CArtifact::ART_TREASURE; break; + case 1: artClass = CArtifact::ART_MINOR; break; + case 2: artClass = CArtifact::ART_MAJOR; break; + case 3: artClass = CArtifact::ART_RELIC; break; + default: assert(0); continue; + } + + int artID = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), artClass); + cb->setObjProperty(id, ObjProperty::BANK_ADD_ARTIFACT, artID); + } + } + + cb->setObjProperty(id, ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //get army +} +void CBank::setPropertyDer (ui8 what, ui32 val) +/// random values are passed as arguments and processed identically on all clients +{ + switch (what) + { + case ObjProperty::BANK_DAYCOUNTER: //daycounter + if (val == 0) + daycounter = 1; //yes, 1 + else + daycounter++; + break; + case ObjProperty::BANK_MULTIPLIER: //multiplier, in percent + multiplier = val / 100.0; + break; + case 13: //bank preset + bc = VLC->objh->banksInfo[index][val]; + break; + case ObjProperty::BANK_RESET: + reset (val%100); + break; + case ObjProperty::BANK_CLEAR_CONFIG: + bc = nullptr; + break; + case ObjProperty::BANK_CLEAR_ARTIFACTS: //remove rewards from Derelict Ship + artifacts.clear(); + break; + case ObjProperty::BANK_INIT_ARMY: //set ArmedInstance army + { + int upgraded = 0; + if (val%100 < bc->upgradeChance) //once again anti-desync + upgraded = 1; + switch (bc->guards.size()) + { + case 1: + for (int i = 0; i < 4; ++i) + setCreature (SlotID(i), bc->guards[0].first, bc->guards[0].second / 5 ); + setCreature (SlotID(4), CreatureID(bc->guards[0].first + upgraded), bc->guards[0].second / 5 ); + break; + case 4: + { + if (bc->guards.back().second) //all stacks are present + { + for (auto & elem : bc->guards) + { + setCreature (SlotID(stacksCount()), elem.first, elem.second); + } + } + else if (bc->guards[2].second)//Wraiths are present, split two stacks in Crypt + { + setCreature (SlotID(0), bc->guards[0].first, bc->guards[0].second / 2 ); + setCreature (SlotID(1), bc->guards[1].first, bc->guards[1].second / 2); + setCreature (SlotID(2), CreatureID(bc->guards[2].first + upgraded), bc->guards[2].second); + setCreature (SlotID(3), bc->guards[1].first, bc->guards[1].second / 2 ); + setCreature (SlotID(4), bc->guards[0].first, bc->guards[0].second - (bc->guards[0].second / 2) ); + + } + else //split both stacks + { + for (int i = 0; i < 3; ++i) //skellies + setCreature (SlotID(2*i), bc->guards[0].first, bc->guards[0].second / 3); + for (int i = 0; i < 2; ++i) //zombies + setCreature (SlotID(2*i+1), bc->guards[1].first, bc->guards[1].second / 2); + } + } + break; + default: + logGlobal->warnStream() << "Error: Unexpected army data: " << bc->guards.size() <<" items found"; + return; + } + } + break; + case ObjProperty::BANK_ADD_ARTIFACT: //add Artifact + { + artifacts.push_back (val); + break; + } + } +} + +void CBank::newTurn() const +{ + if (bc == nullptr) + { + if (cb->getDate() == 1) + initialize(); //initialize on first day + else if (daycounter >= 28 && (subID < 13 || subID > 16)) //no reset for Emissaries + { + initialize(); + cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 0); //daycounter 0 + if (ID == Obj::DERELICT_SHIP && cb->getDate() > 1) + { + cb->setObjProperty (id, ObjProperty::BANK_MULTIPLIER, 0);//ugly hack to make derelict ships usable only once + cb->setObjProperty (id, ObjProperty::BANK_CLEAR_ARTIFACTS, 0); + } + } + else + cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++ + } +} +bool CBank::wasVisited (PlayerColor player) const +{ + return !bc; +} + +void CBank::onHeroVisit (const CGHeroInstance * h) const +{ + if (bc) + { + int banktext = 0; + switch (ID) + { + case Obj::CREATURE_BANK: + banktext = 32; + break; + case Obj::DERELICT_SHIP: + banktext = 41; + break; + case Obj::DRAGON_UTOPIA: + banktext = 47; + break; + case Obj::CRYPT: + banktext = 119; + break; + case Obj::SHIPWRECK: + banktext = 122; + break; + } + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundBase::ROGUE; + bd.text.addTxt(MetaString::ADVOB_TXT,banktext); + if (ID == Obj::CREATURE_BANK) + bd.text.addReplacement(VLC->objh->creBanksNames[index]); + cb->showBlockingDialog (&bd); + } + else + { + InfoWindow iw; + iw.soundID = soundBase::GRAVEYARD; + iw.player = h->getOwner(); + if (ID == Obj::CRYPT) //morale penalty for empty Crypt + { + GiveBonus gbonus; + gbonus.id = h->id.getNum(); + gbonus.bonus.duration = Bonus::ONE_BATTLE; + gbonus.bonus.source = Bonus::OBJECT; + gbonus.bonus.sid = ID; + gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[98]; + gbonus.bonus.type = Bonus::MORALE; + gbonus.bonus.val = -1; + cb->giveHeroBonus(&gbonus); + iw.text << VLC->generaltexth->advobtxt[120]; + iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); + } + else + { + iw.text << VLC->generaltexth->advobtxt[33]; + iw.text.addReplacement(VLC->objh->creBanksNames[index]); + } + cb->showInfoDialog(&iw); + } +} + +void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if (result.winner == 0) + { + int textID = -1; + InfoWindow iw; + iw.player = hero->getOwner(); + MetaString loot; + + switch (ID) + { + case Obj::CREATURE_BANK: case Obj::DRAGON_UTOPIA: + textID = 34; + break; + case Obj::DERELICT_SHIP: + if (multiplier) + textID = 43; + else + { + GiveBonus gbonus; + gbonus.id = hero->id.getNum(); + gbonus.bonus.duration = Bonus::ONE_BATTLE; + gbonus.bonus.source = Bonus::OBJECT; + gbonus.bonus.sid = ID; + gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[101]; + gbonus.bonus.type = Bonus::MORALE; + gbonus.bonus.val = -1; + cb->giveHeroBonus(&gbonus); + textID = 42; + iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); + } + break; + case Obj::CRYPT: + if (bc->resources.size() != 0) + textID = 121; + else + { + iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); + GiveBonus gbonus; + gbonus.id = hero->id.getNum(); + gbonus.bonus.duration = Bonus::ONE_BATTLE; + gbonus.bonus.source = Bonus::OBJECT; + gbonus.bonus.sid = ID; + gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[ID]; + gbonus.bonus.type = Bonus::MORALE; + gbonus.bonus.val = -1; + cb->giveHeroBonus(&gbonus); + textID = 120; + iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); + } + break; + case Obj::SHIPWRECK: + if (bc->resources.size()) + textID = 124; + else + textID = 123; + break; + } + + //grant resources + if (textID != 42) //empty derelict ship gives no cash + { + for (int it = 0; it < bc->resources.size(); it++) + { + if (bc->resources[it] != 0) + { + iw.components.push_back (Component (Component::RESOURCE, it, bc->resources[it], 0)); + loot << "%d %s"; + loot.addReplacement(iw.components.back().val); + loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype); + cb->giveResource (hero->getOwner(), static_cast(it), bc->resources[it]); + } + } + } + //grant artifacts + for (auto & elem : artifacts) + { + iw.components.push_back (Component (Component::ARTIFACT, elem, 0, 0)); + loot << "%s"; + loot.addReplacement(MetaString::ART_NAMES, elem); + cb->giveHeroNewArtifact (hero, VLC->arth->artifacts[elem], ArtifactPosition::FIRST_AVAILABLE); + } + //display loot + if (!iw.components.empty()) + { + iw.text.addTxt (MetaString::ADVOB_TXT, textID); + if (textID == 34) + { + iw.text.addReplacement(MetaString::CRE_PL_NAMES, result.casualties[1].begin()->first); + iw.text.addReplacement(loot.buildList()); + } + cb->showInfoDialog(&iw); + } + loot.clear(); + iw.components.clear(); + iw.text.clear(); + + //grant creatures + CCreatureSet ourArmy; + for (auto it = bc->creatures.cbegin(); it != bc->creatures.cend(); it++) + { + SlotID slot = ourArmy.getSlotFor(it->first); + ourArmy.addToSlot(slot, it->first, it->second); + } + for (auto & elem : ourArmy.Slots()) + { + iw.components.push_back(Component(*elem.second)); + loot << "%s"; + loot.addReplacement(*elem.second); + } + + if (ourArmy.Slots().size()) + { + if (ourArmy.Slots().size() == 1 && ourArmy.Slots().begin()->second->count == 1) + iw.text.addTxt (MetaString::ADVOB_TXT, 185); + else + iw.text.addTxt (MetaString::ADVOB_TXT, 186); + + iw.text.addReplacement(loot.buildList()); + iw.text.addReplacement(hero->name); + cb->showInfoDialog(&iw); + cb->giveCreatures(this, hero, ourArmy, false); + } + cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); //bc = nullptr + } +} + +void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer) + { + cb->startBattleI(hero, this, true); + } +} + +void CGPyramid::initObj() +{ + std::vector available; + cb->getAllowedSpells (available, 5); + if (available.size()) + { + bc = VLC->objh->banksInfo[21].front(); //TODO: remove hardcoded value? + spell = *RandomGeneratorUtil::nextItem(available, cb->gameState()->getRandomGenerator()); + } + else + { + logGlobal->errorStream() <<"No spells available for Pyramid! Object set to empty."; + } + setPropertyDer(ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //set guards at game start +} +const std::string & CGPyramid::getHoverText() const +{ + hoverName = VLC->objh->creBanksNames[21]+ " " + visitedTxt((bc==nullptr)); + return hoverName; +} +void CGPyramid::onHeroVisit (const CGHeroInstance * h) const +{ + if (bc) + { + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundBase::MYSTERY; + bd.text << VLC->generaltexth->advobtxt[105]; + cb->showBlockingDialog(&bd); + } + else + { + InfoWindow iw; + iw.player = h->getOwner(); + iw.text << VLC->generaltexth->advobtxt[107]; + iw.components.push_back (Component (Component::LUCK, 0 , -2, 0)); + GiveBonus gb; + gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,-2,id.getNum(),VLC->generaltexth->arraytxt[70]); + gb.id = h->id.getNum(); + cb->giveHeroBonus(&gb); + cb->showInfoDialog(&iw); + } +} + +void CGPyramid::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if (result.winner == 0) + { + InfoWindow iw; + iw.player = hero->getOwner(); + iw.text.addTxt (MetaString::ADVOB_TXT, 106); + iw.text.addTxt (MetaString::SPELL_NAME, spell); + if (!hero->getArt(ArtifactPosition::SPELLBOOK)) + iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook + else if (hero->getSecSkillLevel(SecondarySkill::WISDOM) < 3) + iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom + else + { + std::set spells; + spells.insert (SpellID(spell)); + cb->changeSpells (hero, true, spells); + iw.components.push_back(Component (Component::SPELL, spell, 0, 0)); + } + cb->showInfoDialog(&iw); + cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); + } +} diff --git a/lib/mapObjects/CGBank.h b/lib/mapObjects/CGBank.h new file mode 100644 index 000000000..14eff6f31 --- /dev/null +++ b/lib/mapObjects/CGBank.h @@ -0,0 +1,59 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CGArmedInstance.h" + +/* + * CObjectHandler.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 CBank : public CArmedInstance +{ + public: + int index; //banks have unusal numbering - see ZCRBANK.txt and initObj() + BankConfig *bc; + double multiplier; //for improved banks script + std::vector artifacts; //fixed and deterministic + ui32 daycounter; + + void initObj() override; + const std::string & getHoverText() const override; + void initialize() const; + void reset(ui16 var1); + void newTurn() const override; + bool wasVisited (PlayerColor player) const override; + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & index & multiplier & artifacts & daycounter & bc; + } +protected: + void setPropertyDer(ui8 what, ui32 val) override; +}; +class DLL_LINKAGE CGPyramid : public CBank +{ +public: + ui16 spell; + + void initObj() override; + const std::string & getHoverText() const override; + void newTurn() const override {}; //empty, no reset + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & spell; + } +}; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp new file mode 100644 index 000000000..22a365e7a --- /dev/null +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -0,0 +1,1368 @@ +/* + * CObjectHandler.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 "CGHeroInstance.h" + +#include "NetPacks.h" +#include "CGeneralTextHandler.h" +#include "CHeroHandler.h" +#include "../client/CSoundBase.h" +#include "CSpellHandler.h" +#include "CObjectClassesHandler.h" + + +using namespace boost::assign; + +///helpers +static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) +{ + InfoWindow iw; + iw.soundID = soundID; + iw.player = playerID; + iw.text.addTxt(MetaString::ADVOB_TXT,txtID); + IObjectInterface::cb->sendAndApply(&iw); +} + +static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = h->getOwner(); + showInfoDialog(playerID,txtID,soundID); +} + +static int lowestSpeed(const CGHeroInstance * chi) +{ + if(!chi->Slots().size()) + { + logGlobal->errorStream() << "Error! Hero " << chi->id.getNum() << " ("<name<<") has no army!"; + return 20; + } + auto i = chi->Slots().begin(); + //TODO? should speed modifiers (eg from artifacts) affect hero movement? + int ret = (i++)->second->valOfBonuses(Bonus::STACKS_SPEED); + for (;i!=chi->Slots().end();i++) + { + ret = std::min(ret, i->second->valOfBonuses(Bonus::STACKS_SPEED)); + } + return ret; +} + +ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const +{ + //base move cost + unsigned ret = 100; + + //if there is road both on dest and src tiles - use road movement cost + if(dest.roadType != ERoadType::NO_ROAD && from.roadType != ERoadType::NO_ROAD) + { + int road = std::min(dest.roadType,from.roadType); //used road ID + switch(road) + { + case ERoadType::DIRT_ROAD: + ret = 75; + break; + case ERoadType::GRAVEL_ROAD: + ret = 65; + break; + case ERoadType::COBBLESTONE_ROAD: + ret = 50; + break; + default: + logGlobal->errorStream() << "Unknown road type: " << road << "... Something wrong!"; + break; + } + } + else + { + //FIXME: in H3 presence of Nomad in army will remove terrain penalty for sand. Bonus not implemented in VCMI + + // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. + // This is clearly bug in H3 however intended behaviour is not clear. + // Current VCMI behaviour will ignore neutrals in calculations so army in VCMI + // will always have best penalty without any influence from player-defined stacks order + + bool nativeArmy = true; + for(auto stack : stacks) + { + int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain; + + if (nativeTerrain != -1 && nativeTerrain != from.terType) + { + nativeArmy = false; + break; + } + } + if (!nativeArmy) + ret = VLC->heroh->terrCosts[from.terType]; + } + return ret; +} + +int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest +{ + if (toh3m) + { + src.x+=1; + return src; + } + else + { + src.x-=1; + return src; + } +} +int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' +{ + if (h3m) + { + return pos; + } + else + { + return convertPosition(pos,false); + } +} + +bool CGHeroInstance::canWalkOnSea() const +{ + return hasBonusOfType(Bonus::FLYING_MOVEMENT) || hasBonusOfType(Bonus::WATER_WALKING); +} + +ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const +{ + for(auto & elem : secSkills) + if(elem.first == skill) + return elem.second; + return 0; +} + +void CGHeroInstance::setSecSkillLevel(SecondarySkill which, int val, bool abs) +{ + if(getSecSkillLevel(which) == 0) + { + secSkills.push_back(std::pair(which, val)); + updateSkill(which, val); + } + else + { + for (auto & elem : secSkills) + { + if(elem.first == which) + { + if(abs) + elem.second = val; + else + elem.second += val; + + if(elem.second > 3) //workaround to avoid crashes when same sec skill is given more than once + { + logGlobal->warnStream() << "Warning: Skill " << which << " increased over limit! Decreasing to Expert."; + elem.second = 3; + } + updateSkill(which, elem.second); //when we know final value + } + } + } +} + +bool CGHeroInstance::canLearnSkill() const +{ + return secSkills.size() < GameConstants::SKILL_PER_HERO; +} + +int CGHeroInstance::maxMovePoints(bool onLand) const +{ + int base; + + if(onLand) + { + // used function is f(x) = 66.6x + 1300, rounded to second digit, where x is lowest speed in army + static const int baseSpeed = 1300; // base speed from creature with 0 speed + + int armySpeed = lowestSpeed(this) * 20 / 3; + + base = armySpeed * 10 + baseSpeed; // separate *10 is intentional to receive same rounding as in h3 + vstd::abetween(base, 1500, 2000); // base speed is limited by these values + } + else + { + base = 1500; //on water base movement is always 1500 (speed of army doesn't matter) + } + + const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT; + const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt); + + const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION; + const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0; + + return int(base* (1+modifier)) + bonus; +} + +CGHeroInstance::CGHeroInstance() + : IBoatGenerator(this) +{ + setNodeType(HERO); + ID = Obj::HERO; + tacticFormationEnabled = inTownGarrison = false; + mana = movement = portrait = level = -1; + isStanding = true; + moveDir = 4; + exp = 0xffffffff; + visitedTown = nullptr; + type = nullptr; + boat = nullptr; + commander = nullptr; + sex = 0xff; + secSkills.push_back(std::make_pair(SecondarySkill::DEFAULT, -1)); +} + +void CGHeroInstance::initHero(HeroTypeID SUBID) +{ + subID = SUBID.getNum(); + initHero(); +} + +void CGHeroInstance::setType(si32 ID, si32 subID) +{ + assert(ID == Obj::HERO); // just in case + CGObjectInstance::setType(ID, subID); + type = VLC->heroh->heroes[subID]; + portrait = type->imageIndex; + randomizeArmy(type->heroClass->faction); +} + +void CGHeroInstance::initHero() +{ + assert(validTypes(true)); + if(!type) + type = VLC->heroh->heroes[subID]; + + if (ID == Obj::HERO) + appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->id)->getTemplates().front(); + + if(!vstd::contains(spells, SpellID::PRESET)) //hero starts with a spell + { + for(auto spellID : type->spells) + spells.insert(spellID); + } + else //remove placeholder + spells -= SpellID::PRESET; + + if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) //no catapult means we haven't read pre-existent set -> use default rules for spellbook + putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0)); + + if(!getArt(ArtifactPosition::MACH4)) + putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createNewArtifactInstance(3)); //everyone has a catapult + + if(portrait < 0 || portrait == 255) + portrait = type->imageIndex; + if(!hasBonus(Selector::sourceType(Bonus::HERO_BASE_SKILL))) + { + for(int g=0; g(g), type->heroClass->primarySkillInitial[g]); + } + } + if(secSkills.size() == 1 && secSkills[0] == std::pair(SecondarySkill::DEFAULT, -1)) //set secondary skills to default + secSkills = type->secSkillsInit; + if (!name.length()) + name = type->name; + + if (sex == 0xFF)//sex is default + sex = type->sex; + + setFormation(false); + if (!stacksCount()) //standard army//initial army + { + initArmy(); + } + assert(validTypes()); + + level = 1; + if(exp == 0xffffffff) + { + initExp(); + } + else + { + levelUpAutomatically(); + } + + if (VLC->modh->modules.COMMANDERS && !commander) + { + commander = new CCommanderInstance(type->heroClass->commander->idNumber); + commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders + commander->giveStackExp (exp); //after our exp is set + } + + if (mana < 0) + mana = manaLimit(); +} + +void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/) +{ + if(!dst) + dst = this; + + int howManyStacks = 0; //how many stacks will hero receives <1 - 3> + int pom = cb->gameState()->getRandomGenerator().nextInt(99); + int warMachinesGiven = 0; + + if(pom < 9) + howManyStacks = 1; + else if(pom < 79) + howManyStacks = 2; + else + howManyStacks = 3; + + vstd::amin(howManyStacks, type->initialArmy.size()); + + for(int stackNo=0; stackNo < howManyStacks; stackNo++) + { + auto & stack = type->initialArmy[stackNo]; + + int count = cb->gameState()->getRandomGenerator().nextInt(stack.minAmount, stack.maxAmount); + + if(stack.creature >= CreatureID::CATAPULT && + stack.creature <= CreatureID::ARROW_TOWERS) //war machine + { + warMachinesGiven++; + if(dst != this) + continue; + + int slot = -1; + ArtifactID aid = ArtifactID::NONE; + switch (stack.creature) + { + case CreatureID::CATAPULT: + slot = ArtifactPosition::MACH4; + aid = ArtifactID::CATAPULT; + break; + default: + aid = CArtHandler::creatureToMachineID(stack.creature); + slot = 9 + aid; + break; + } + auto convSlot = ArtifactPosition(slot); + if(!getArt(convSlot)) + putArtifact(convSlot, CArtifactInstance::createNewArtifactInstance(aid)); + else + logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid; + } + else + dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count); + } +} + +CGHeroInstance::~CGHeroInstance() +{ + commander.dellNull(); +} + +bool CGHeroInstance::needsLastStack() const +{ + return true; +} + +void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const +{ + if(h == this) return; //exclude potential self-visiting + + if (ID == Obj::HERO) + { + if( cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner)) //our or ally hero + { + //exchange + cb->heroExchange(h->id, id); + } + else //battle + { + if(visitedTown) //we're in town + visitedTown->onHeroVisit(h); //town will handle attacking + else + cb->startBattleI(h, this); + } + } + else if(ID == Obj::PRISON) + { + int txt_id; + + if (cb->getHeroCount(h->tempOwner, false) < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)//GameConstants::MAX_HEROES_PER_PLAYER) //free hero slot + { + cb->changeObjPos(id,pos+int3(1,0,0),0); + //update hero parameters + SetMovePoints smp; + smp.hid = id; + smp.val = maxMovePoints (true); //TODO: hota prison on water? + cb->setMovePoints (&smp); + cb->setManaPoints (id, manaLimit()); + + cb->setObjProperty(id, ObjProperty::ID, Obj::HERO); //set ID to 34 + cb->giveHero(id,h->tempOwner); //recreates def and adds hero to player + + txt_id = 102; + } + else //already 8 wandering heroes + { + txt_id = 103; + } + + showInfoDialog(h,txt_id,soundBase::ROGUE); + } +} + +const std::string & CGHeroInstance::getHoverText() const +{ + if(ID != Obj::PRISON) + { + hoverName = VLC->generaltexth->allTexts[15]; + boost::algorithm::replace_first(hoverName,"%s",name); + boost::algorithm::replace_first(hoverName,"%s", type->heroClass->name); + return hoverName; + } + else + hoverName = VLC->objtypeh->getObjectName(ID); + + return hoverName; +} + +const std::string & CGHeroInstance::getBiography() const +{ + if (biography.length()) + return biography; + return type->biography; +} + +ui8 CGHeroInstance::maxlevelsToMagicSchool() const +{ + return type->heroClass->isMagicHero() ? 3 : 4; +} +ui8 CGHeroInstance::maxlevelsToWisdom() const +{ + return type->heroClass->isMagicHero() ? 3 : 6; +} + +void CGHeroInstance::SecondarySkillsInfo::resetMagicSchoolCounter() +{ + magicSchoolCounter = 1; +} +void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter() +{ + wisdomCounter = 1; +} + +void CGHeroInstance::initObj() +{ + blockVisit = true; + auto hs = new HeroSpecial(); + hs->setNodeType(CBonusSystemNode::SPECIALTY); + attachTo(hs); //do we ever need to detach it? + + if(!type) + initHero(); //TODO: set up everything for prison before specialties are configured + + skillsInfo.rand.setSeed(cb->gameState()->getRandomGenerator().nextInt()); + skillsInfo.resetMagicSchoolCounter(); + skillsInfo.resetWisdomCounter(); + + for(const auto &spec : type->spec) //TODO: unfity with bonus system + { + auto bonus = new Bonus(); + bonus->val = spec.val; + bonus->sid = id.getNum(); //from the hero, specialty has no unique id + bonus->duration = Bonus::PERMANENT; + bonus->source = Bonus::HERO_SPECIAL; + switch (spec.type) + { + case 1:// creature specialty + { + hs->growsWithLevel = true; + + const CCreature &specCreature = *VLC->creh->creatures[spec.additionalinfo]; //creature in which we have specialty + + //int creLevel = specCreature.level; + //if(!creLevel) + //{ + // if(spec.additionalinfo == 146) + // creLevel = 5; //treat ballista as 5-level + // else + // { + // logGlobal->warnStream() << "Warning: unknown level of " << specCreature.namePl; + // continue; + // } + //} + + //bonus->additionalInfo = spec.additionalinfo; //creature id, should not be used again - this works only with limiter + bonus->limiter.reset(new CCreatureTypeLimiter (specCreature, true)); //with upgrades + bonus->type = Bonus::PRIMARY_SKILL; + bonus->valType = Bonus::ADDITIVE_VALUE; + + bonus->subtype = PrimarySkill::ATTACK; + hs->addNewBonus(bonus); + + bonus = new Bonus(*bonus); + bonus->subtype = PrimarySkill::DEFENSE; + hs->addNewBonus(bonus); + //values will be calculated later + + bonus = new Bonus(*bonus); + bonus->type = Bonus::STACKS_SPEED; + bonus->val = 1; //+1 speed + hs->addNewBonus(bonus); + } + break; + case 2://secondary skill + hs->growsWithLevel = true; + bonus->type = Bonus::SPECIAL_SECONDARY_SKILL; //needs to be recalculated with level, based on this value + bonus->valType = Bonus::BASE_NUMBER; // to receive nonzero value + bonus->subtype = spec.subtype; //skill id + bonus->val = spec.val; //value per level, in percent + hs->addNewBonus(bonus); + bonus = new Bonus(*bonus); + + switch (spec.additionalinfo) + { + case 0: //normal + bonus->valType = Bonus::PERCENT_TO_BASE; + break; + case 1: //when it's navigation or there's no 'base' at all + bonus->valType = Bonus::PERCENT_TO_ALL; + break; + } + bonus->type = Bonus::SECONDARY_SKILL_PREMY; //value will be calculated later + hs->addNewBonus(bonus); + break; + case 3://spell damage bonus, level dependent but calculated elsewhere + bonus->type = Bonus::SPECIAL_SPELL_LEV; + bonus->subtype = spec.subtype; + hs->addNewBonus(bonus); + break; + case 4://creature stat boost + switch (spec.subtype) + { + case 1://attack + bonus->type = Bonus::PRIMARY_SKILL; + bonus->subtype = PrimarySkill::ATTACK; + break; + case 2://defense + bonus->type = Bonus::PRIMARY_SKILL; + bonus->subtype = PrimarySkill::DEFENSE; + break; + case 3: + bonus->type = Bonus::CREATURE_DAMAGE; + bonus->subtype = 0; //both min and max + break; + case 4://hp + bonus->type = Bonus::STACK_HEALTH; + break; + case 5: + bonus->type = Bonus::STACKS_SPEED; + break; + default: + continue; + } + bonus->additionalInfo = spec.additionalinfo; //creature id + bonus->valType = Bonus::ADDITIVE_VALUE; + bonus->limiter.reset(new CCreatureTypeLimiter (*VLC->creh->creatures[spec.additionalinfo], true)); + hs->addNewBonus(bonus); + break; + case 5://spell damage bonus in percent + bonus->type = Bonus::SPECIFIC_SPELL_DAMAGE; + bonus->valType = Bonus::BASE_NUMBER; // current spell system is screwed + bonus->subtype = spec.subtype; //spell id + hs->addNewBonus(bonus); + break; + case 6://damage bonus for bless (Adela) + bonus->type = Bonus::SPECIAL_BLESS_DAMAGE; + bonus->subtype = spec.subtype; //spell id if you ever wanted to use it otherwise + bonus->additionalInfo = spec.additionalinfo; //damage factor + hs->addNewBonus(bonus); + break; + case 7://maxed mastery for spell + bonus->type = Bonus::MAXED_SPELL; + bonus->subtype = spec.subtype; //spell i + hs->addNewBonus(bonus); + break; + case 8://peculiar spells - enchantments + bonus->type = Bonus::SPECIAL_PECULIAR_ENCHANT; + bonus->subtype = spec.subtype; //spell id + bonus->additionalInfo = spec.additionalinfo;//0, 1 for Coronius + hs->addNewBonus(bonus); + break; + case 9://upgrade creatures + { + const auto &creatures = VLC->creh->creatures; + bonus->type = Bonus::SPECIAL_UPGRADE; + bonus->subtype = spec.subtype; //base id + bonus->additionalInfo = spec.additionalinfo; //target id + hs->addNewBonus(bonus); + bonus = new Bonus(*bonus); + + for(auto cre_id : creatures[spec.subtype]->upgrades) + { + bonus->subtype = cre_id; //propagate for regular upgrades of base creature + hs->addNewBonus(bonus); + bonus = new Bonus(*bonus); + } + vstd::clear_pointer(bonus); + break; + } + case 10://resource generation + bonus->type = Bonus::GENERATE_RESOURCE; + bonus->subtype = spec.subtype; + hs->addNewBonus(bonus); + break; + case 11://starting skill with mastery (Adrienne) + setSecSkillLevel(SecondarySkill(spec.val), spec.additionalinfo, true); + break; + case 12://army speed + bonus->type = Bonus::STACKS_SPEED; + hs->addNewBonus(bonus); + break; + case 13://Dragon bonuses (Mutare) + bonus->type = Bonus::PRIMARY_SKILL; + bonus->valType = Bonus::ADDITIVE_VALUE; + switch (spec.subtype) + { + case 1: + bonus->subtype = PrimarySkill::ATTACK; + break; + case 2: + bonus->subtype = PrimarySkill::DEFENSE; + break; + } + bonus->limiter.reset(new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE)); + hs->addNewBonus(bonus); + break; + default: + logGlobal->warnStream() << "Unexpected hero specialty " << type; + } + } + specialty.push_back(hs); //will it work? + + for (auto hs2 : type->specialty) //copy active (probably growing) bonuses from hero prootype to hero object + { + auto hs = new HeroSpecial(); + attachTo(hs); //do we ever need to detach it? + + hs->setNodeType(CBonusSystemNode::SPECIALTY); + for (auto bonus : hs2.bonuses) + { + hs->addNewBonus (bonus); + } + hs->growsWithLevel = hs2.growsWithLevel; + + specialty.push_back(hs); //will it work? + } + + //initialize bonuses + recreateSecondarySkillsBonuses(); + Updatespecialty(); + + mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one + type->name = name; +} +void CGHeroInstance::Updatespecialty() //TODO: calculate special value of bonuses on-the-fly? +{ + for (auto hs : specialty) + { + if (hs->growsWithLevel) + { + //const auto &creatures = VLC->creh->creatures; + + for(Bonus * b : hs->getBonusList()) + { + switch (b->type) + { + case Bonus::SECONDARY_SKILL_PREMY: + b->val = (hs->valOfBonuses(Bonus::SPECIAL_SECONDARY_SKILL, b->subtype) * level); + break; //use only hero skills as bonuses to avoid feedback loop + case Bonus::PRIMARY_SKILL: //for creatures, that is + { + const CCreature * cre = nullptr; + int creLevel = 0; + if (auto creatureLimiter = std::dynamic_pointer_cast(b->limiter)) //TODO: more general eveluation of bonuses? + { + cre = creatureLimiter->creature; + creLevel = cre->level; + if (!creLevel) + { + creLevel = 5; //treat ballista as tier 5 + } + } + else //no creature found, can't calculate value + { + logGlobal->warnStream() << "Primary skill specialty growth supported only with creature type limiters"; + break; + } + + double primSkillModifier = (int)(level / creLevel) / 20.0; + int param; + switch (b->subtype) + { + case PrimarySkill::ATTACK: + param = cre->Attack(); + break; + case PrimarySkill::DEFENSE: + param = cre->Defense(); + break; + default: + continue; + } + b->val = ceil(param * (1 + primSkillModifier)) - param; //yep, overcomplicated but matches original + break; + } + } + } + } + } +} + +void CGHeroInstance::recreateSecondarySkillsBonuses() +{ + auto secondarySkillsBonuses = getBonuses(Selector::sourceType(Bonus::SECONDARY_SKILL)); + for(auto bonus : *secondarySkillsBonuses) + removeBonus(bonus); + + for(auto skill_info : secSkills) + updateSkill(SecondarySkill(skill_info.first), skill_info.second); +} + +void CGHeroInstance::updateSkill(SecondarySkill which, int val) +{ + if(which == SecondarySkill::LEADERSHIP || which == SecondarySkill::LUCK) + { //luck-> VLC->generaltexth->arraytxt[73+luckSkill]; VLC->generaltexth->arraytxt[104+moraleSkill] + bool luck = which == SecondarySkill::LUCK; + Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK}; + + Bonus *b = getBonusLocalFirst(Selector::type(type[luck]).And(Selector::sourceType(Bonus::SECONDARY_SKILL))); + if(!b) + { + b = new Bonus(Bonus::PERMANENT, type[luck], Bonus::SECONDARY_SKILL, +val, which, which, Bonus::BASE_NUMBER); + addNewBonus(b); + } + else + b->val = +val; + } + else if(which == SecondarySkill::DIPLOMACY) //surrender discount: 20% per level + { + + if(Bonus *b = getBonusLocalFirst(Selector::type(Bonus::SURRENDER_DISCOUNT).And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) + b->val = +val; + else + addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::SURRENDER_DISCOUNT, Bonus::SECONDARY_SKILL, val * 20, which)); + } + + int skillVal = 0; + switch (which) + { + case SecondarySkill::ARCHERY: + switch (val) + { + case 1: + skillVal = 10; break; + case 2: + skillVal = 25; break; + case 3: + skillVal = 50; break; + } + break; + case SecondarySkill::LOGISTICS: + skillVal = 10 * val; break; + case SecondarySkill::NAVIGATION: + skillVal = 50 * val; break; + case SecondarySkill::MYSTICISM: + skillVal = val; break; + case SecondarySkill::EAGLE_EYE: + skillVal = 30 + 10 * val; break; + case SecondarySkill::NECROMANCY: + skillVal = 10 * val; break; + case SecondarySkill::LEARNING: + skillVal = 5 * val; break; + case SecondarySkill::OFFENCE: + skillVal = 10 * val; break; + case SecondarySkill::ARMORER: + skillVal = 5 * val; break; + case SecondarySkill::INTELLIGENCE: + skillVal = 25 << (val-1); break; + case SecondarySkill::SORCERY: + skillVal = 5 * val; break; + case SecondarySkill::RESISTANCE: + skillVal = 5 << (val-1); break; + case SecondarySkill::FIRST_AID: + skillVal = 25 + 25*val; break; + case SecondarySkill::ESTATES: + skillVal = 125 << (val-1); break; + } + + + Bonus::ValueType skillValType = skillVal ? Bonus::BASE_NUMBER : Bonus::INDEPENDENT_MIN; + if(Bonus * b = getBonusList().getFirst(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, which) + .And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) //only local hero bonus + { + b->val = skillVal; + b->valType = skillValType; + } + else + { + auto bonus = new Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, Bonus::SECONDARY_SKILL, skillVal, id.getNum(), which, skillValType); + bonus->source = Bonus::SECONDARY_SKILL; + addNewBonus(bonus); + } + +} +void CGHeroInstance::setPropertyDer( ui8 what, ui32 val ) +{ + if(what == ObjProperty::PRIMARY_STACK_COUNT) + setStackCount(SlotID(0), val); +} + +double CGHeroInstance::getFightingStrength() const +{ + return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE))); +} + +double CGHeroInstance::getMagicStrength() const +{ + return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER))); +} + +double CGHeroInstance::getHeroStrength() const +{ + return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrength(), 2.0)); +} + +ui64 CGHeroInstance::getTotalStrength() const +{ + double ret = getFightingStrength() * getArmyStrength(); + return (ui64) ret; +} + +TExpType CGHeroInstance::calculateXp(TExpType exp) const +{ + return exp * (100 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::LEARNING))/100.0; +} + +ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool) const +{ + si16 skill = -1; //skill level + +#define TRY_SCHOOL(schoolName, schoolMechanicsId, schoolOutId) \ + if(spell-> schoolName) \ + { \ + int thisSchool = std::max(getSecSkillLevel( \ + SecondarySkill(14 + (schoolMechanicsId))), \ + valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << (schoolMechanicsId))); \ + if(thisSchool > skill) \ + { \ + skill = thisSchool; \ + if(outSelectedSchool) \ + *outSelectedSchool = schoolOutId; \ + } \ + } + TRY_SCHOOL(fire, 0, 1) + TRY_SCHOOL(air, 1, 0) + TRY_SCHOOL(water, 2, 2) + TRY_SCHOOL(earth, 3, 3) +#undef TRY_SCHOOL + + + + vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus + vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect + if (hasBonusOfType(Bonus::MAXED_SPELL, spell->id))//hero specialty (Daremyth, Melodia) + skill = 3; + assert(skill >= 0 && skill <= 3); + return skill; +} + +bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const +{ + if(!getArt(ArtifactPosition::SPELLBOOK)) //if hero has no spellbook + return false; + + if (spell->isSpecialSpell()) + { + if (vstd::contains(spells, spell->id)) + {//hero has this spell in spellbook + logGlobal->errorStream() << "Special spell in spellbook "<name; + } + + if (hasBonusOfType(Bonus::SPELL, spell->id)) + return true; + + return false; + } + else + { + if(vstd::contains(spells, spell->id) //hero has this spell in spellbook + || (spell->air && hasBonusOfType(Bonus::AIR_SPELLS)) // this is air spell and hero can cast all air spells + || (spell->fire && hasBonusOfType(Bonus::FIRE_SPELLS)) // this is fire spell and hero can cast all fire spells + || (spell->water && hasBonusOfType(Bonus::WATER_SPELLS)) // this is water spell and hero can cast all water spells + || (spell->earth && hasBonusOfType(Bonus::EARTH_SPELLS)) // this is earth spell and hero can cast all earth spells + || hasBonusOfType(Bonus::SPELL, spell->id) + || hasBonusOfType(Bonus::SPELLS_OF_LEVEL, spell->level) + ) + return true; + + return false; + } +} + +/** + * Calculates what creatures and how many to be raised from a battle. + * @param battleResult The results of the battle. + * @return Returns a pair with the first value indicating the ID of the creature + * type and second value the amount. Both values are returned as -1 if necromancy + * could not be applied. + */ +CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &battleResult) const +{ + const ui8 necromancyLevel = getSecSkillLevel(SecondarySkill::NECROMANCY); + + // Hero knows necromancy or has Necromancer Cloak + if (necromancyLevel > 0 || hasBonusOfType(Bonus::IMPROVED_NECROMANCY)) + { + double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::NECROMANCY)/100.0; + vstd::amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all... + const std::map &casualties = battleResult.casualties[!battleResult.winner]; + ui32 raisedUnits = 0; + + // Figure out what to raise and how many. + const CreatureID creatureTypes[] = {CreatureID::SKELETON, CreatureID::WALKING_DEAD, CreatureID::WIGHTS, CreatureID::LICHES}; + const bool improvedNecromancy = hasBonusOfType(Bonus::IMPROVED_NECROMANCY); + const CCreature *raisedUnitType = VLC->creh->creatures[creatureTypes[improvedNecromancy ? necromancyLevel : 0]]; + const ui32 raisedUnitHP = raisedUnitType->valOfBonuses(Bonus::STACK_HEALTH); + + //calculate creatures raised from each defeated stack + for (auto & casualtie : casualties) + { + // Get lost enemy hit points convertible to units. + CCreature * c = VLC->creh->creatures[casualtie.first]; + + const ui32 raisedHP = c->valOfBonuses(Bonus::STACK_HEALTH) * casualtie.second * necromancySkill; + raisedUnits += std::min(raisedHP / raisedUnitHP, casualtie.second * necromancySkill); //limit to % of HP and % of original stack count + } + + // Make room for new units. + SlotID slot = getSlotFor(raisedUnitType->idNumber); + if (slot == SlotID()) + { + // If there's no room for unit, try it's upgraded version 2/3rds the size. + raisedUnitType = VLC->creh->creatures[*raisedUnitType->upgrades.begin()]; + raisedUnits = (raisedUnits*2)/3; + + slot = getSlotFor(raisedUnitType->idNumber); + } + if (raisedUnits <= 0) + raisedUnits = 1; + + return CStackBasicDescriptor(raisedUnitType->idNumber, raisedUnits); + } + + return CStackBasicDescriptor(); +} + +/** + * Show the necromancy dialog with information about units raised. + * @param raisedStack Pair where the first element represents ID of the raised creature + * and the second element the amount. + */ +void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const +{ + InfoWindow iw; + iw.soundID = soundBase::pickup01 + cb->gameState()->getRandomGenerator().nextInt(6); + iw.player = tempOwner; + iw.components.push_back(Component(raisedStack)); + + if (raisedStack.count > 1) // Practicing the dark arts of necromancy, ... (plural) + { + iw.text.addTxt(MetaString::GENERAL_TXT, 145); + iw.text.addReplacement(raisedStack.count); + } + else // Practicing the dark arts of necromancy, ... (singular) + { + iw.text.addTxt(MetaString::GENERAL_TXT, 146); + } + iw.text.addReplacement(raisedStack); + + cb->showInfoDialog(&iw); +} + +int3 CGHeroInstance::getSightCenter() const +{ + return getPosition(false); +} + +int CGHeroInstance::getSightRadious() const +{ + return 5 + getSecSkillLevel(SecondarySkill::SCOUTING) + valOfBonuses(Bonus::SIGHT_RADIOUS); //default + scouting +} + +si32 CGHeroInstance::manaRegain() const +{ + if (hasBonusOfType(Bonus::FULL_MANA_REGENERATION)) + return manaLimit(); + + return 1 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 8) + valOfBonuses(Bonus::MANA_REGENERATION); //1 + Mysticism level +} + +// /** +// * Places an artifact in hero's backpack. If it's a big artifact equips it +// * or discards it if it cannot be equipped. +// */ +// void CGHeroInstance::giveArtifact (ui32 aid) //use only for fixed artifacts +// { +// CArtifact * const artifact = VLC->arth->artifacts[aid]; //pointer to constant object +// CArtifactInstance *ai = CArtifactInstance::createNewArtifactInstance(artifact); +// ai->putAt(this, ai->firstAvailableSlot(this)); +// } + +int CGHeroInstance::getBoatType() const +{ + switch(type->heroClass->getAlignment()) + { + case EAlignment::GOOD: + return 1; + case EAlignment::EVIL: + return 0; + case EAlignment::NEUTRAL: + return 2; + default: + throw std::runtime_error("Wrong alignment!"); + } +} + +void CGHeroInstance::getOutOffsets(std::vector &offsets) const +{ + static 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) }; + for (auto & dir : dirs) + offsets += dir; +} + +int CGHeroInstance::getSpellCost(const CSpell *sp) const +{ + return sp->getCost(getSpellSchoolLevel(sp)); +} + +void CGHeroInstance::pushPrimSkill( PrimarySkill::PrimarySkill which, int val ) +{ + assert(!hasBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, which) + .And(Selector::sourceType(Bonus::HERO_BASE_SKILL)))); + addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::HERO_BASE_SKILL, val, id.getNum(), which)); +} + +EAlignment::EAlignment CGHeroInstance::getAlignment() const +{ + return type->heroClass->getAlignment(); +} + +void CGHeroInstance::initExp() +{ + exp = cb->gameState()->getRandomGenerator().nextInt(40, 89); +} + +std::string CGHeroInstance::nodeName() const +{ + return "Hero " + name; +} + +void CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance *art) +{ + assert(!getArt(pos)); + art->putAt(ArtifactLocation(this, pos)); +} + +void CGHeroInstance::putInBackpack(CArtifactInstance *art) +{ + putArtifact(art->firstBackpackSlot(this), art); +} + +bool CGHeroInstance::hasSpellbook() const +{ + return getArt(ArtifactPosition::SPELLBOOK); +} + +void CGHeroInstance::deserializationFix() +{ + artDeserializationFix(this); + + for (auto hs : specialty) + { + attachTo (hs); + } +} + +CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs) +{ + if(visitedTown) + { + if(inTownGarrison) + return visitedTown; + else + return &visitedTown->townAndVis; + } + else + return CArmedInstance::whereShouldBeAttached(gs); +} + +int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const +{ + if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) + return (MPsBefore - basicCost) * static_cast(maxMovePoints(disembark)) / maxMovePoints(!disembark); + + return 0; //take all MPs otherwise +} + +CGHeroInstance::ECanDig CGHeroInstance::diggingStatus() const +{ + if(movement < maxMovePoints(true)) + return LACK_OF_MOVEMENT; + else if(cb->getTile(getPosition(false))->terType == ETerrainType::WATER) + return WRONG_TERRAIN; + else + { + const TerrainTile *t = cb->getTile(getPosition()); + //TODO look for hole + //CGI->mh->getTerrainDescr(h->getPosition(false), hlp, false); + if(/*hlp.length() || */t->blockingObjects.size() > 1) + return TILE_OCCUPIED; + else + return CAN_DIG; + } +} + +ArtBearer::ArtBearer CGHeroInstance::bearerType() const +{ + return ArtBearer::HERO; +} + +std::vector CGHeroInstance::getLevelUpProposedSecondarySkills() const +{ + std::vector obligatorySkills; //hero is offered magic school or wisdom if possible + if (!skillsInfo.wisdomCounter) + { + if (cb->isAllowed(2, SecondarySkill::WISDOM) && !getSecSkillLevel(SecondarySkill::WISDOM)) + obligatorySkills.push_back(SecondarySkill::WISDOM); + } + if (!skillsInfo.magicSchoolCounter) + { + std::vector ss; + ss += SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC; + + std::shuffle(ss.begin(), ss.end(), skillsInfo.rand.getStdGenerator()); + + for (auto skill : ss) + { + if (cb->isAllowed(2, skill) && !getSecSkillLevel(skill)) //only schools hero doesn't know yet + { + obligatorySkills.push_back(skill); + break; //only one + } + } + } + + std::vector skills; + //picking sec. skills for choice + std::set basicAndAdv, expert, none; + for(int i=0;iisAllowed(2,i)) + none.insert(SecondarySkill(i)); + + for(auto & elem : secSkills) + { + if(elem.second < SecSkillLevel::EXPERT) + basicAndAdv.insert(elem.first); + else + expert.insert(elem.first); + none.erase(elem.first); + } + for (auto s : obligatorySkills) //don't duplicate them + { + none.erase (s); + basicAndAdv.erase (s); + expert.erase (s); + } + + //first offered skill: + // 1) give obligatory skill + // 2) give any other new skill + // 3) upgrade existing + if (canLearnSkill() && obligatorySkills.size() > 0) + { + skills.push_back (obligatorySkills[0]); + } + else if(none.size() && canLearnSkill()) //hero have free skill slot + { + skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //new skill + none.erase(skills.back()); + } + else if(!basicAndAdv.empty()) + { + skills.push_back(type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand)); //upgrade existing + basicAndAdv.erase(skills.back()); + } + + //second offered skill: + //1) upgrade existing + //2) give obligatory skill + //3) give any other new skill + if(!basicAndAdv.empty()) + { + SecondarySkill s = type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand);//upgrade existing + skills.push_back(s); + basicAndAdv.erase(s); + } + else if (canLearnSkill() && obligatorySkills.size() > 1) + { + skills.push_back (obligatorySkills[1]); + } + else if(none.size() && canLearnSkill()) + { + skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //give new skill + none.erase(skills.back()); + } + + return skills; +} + +PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill() const +{ + assert(gainsLevel()); + int randomValue = cb->gameState()->getRandomGenerator().nextInt(99), pom = 0, primarySkill = 0; + const auto & skillChances = (level > 9) ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel; + + for(; primarySkill < GameConstants::PRIMARY_SKILLS; ++primarySkill) + { + pom += skillChances[primarySkill]; + if(randomValue < pom) + { + break; + } + } + + logGlobal->traceStream() << "The hero gets the primary skill " << primarySkill << " with a probability of " << randomValue << "%."; + return static_cast(primarySkill); +} + +boost::optional CGHeroInstance::nextSecondarySkill() const +{ + assert(gainsLevel()); + + boost::optional chosenSecondarySkill; + const auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(); + if(!proposedSecondarySkills.empty()) + { + std::vector learnedSecondarySkills; + for(auto secondarySkill : proposedSecondarySkills) + { + if(getSecSkillLevel(secondarySkill) > 0) + { + learnedSecondarySkills.push_back(secondarySkill); + } + } + + auto & rand = cb->gameState()->getRandomGenerator(); + if(learnedSecondarySkills.empty()) + { + // there are only new skills to learn, so choose anyone of them + chosenSecondarySkill = *RandomGeneratorUtil::nextItem(proposedSecondarySkills, rand); + } + else + { + // preferably upgrade a already learned secondary skill + chosenSecondarySkill = *RandomGeneratorUtil::nextItem(learnedSecondarySkills, rand); + } + } + return chosenSecondarySkill; +} + +void CGHeroInstance::setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs) +{ + if(primarySkill < PrimarySkill::EXPERIENCE) + { + Bonus * skill = getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL) + .And(Selector::subtype(primarySkill)) + .And(Selector::sourceType(Bonus::HERO_BASE_SKILL))); + assert(skill); + + if(abs) + { + skill->val = value; + } + else + { + skill->val += value; + } + } + else if(primarySkill == PrimarySkill::EXPERIENCE) + { + if(abs) + { + exp = value; + } + else + { + exp += value; + } + } +} + +bool CGHeroInstance::gainsLevel() const +{ + return exp >= VLC->heroh->reqExp(level+1); +} + +void CGHeroInstance::levelUp(std::vector skills) +{ + ++level; + + //deterministic secondary skills + skillsInfo.magicSchoolCounter = (skillsInfo.magicSchoolCounter + 1) % maxlevelsToMagicSchool(); + skillsInfo.wisdomCounter = (skillsInfo.wisdomCounter + 1) % maxlevelsToWisdom(); + if(vstd::contains(skills, SecondarySkill::WISDOM)) + { + skillsInfo.resetWisdomCounter(); + } + + SecondarySkill spellSchools[] = { + SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC}; + for(auto skill : spellSchools) + { + if(vstd::contains(skills, skill)) + { + skillsInfo.resetMagicSchoolCounter(); + break; + } + } + + //specialty + Updatespecialty(); +} + +void CGHeroInstance::levelUpAutomatically() +{ + while(gainsLevel()) + { + const auto primarySkill = nextPrimarySkill(); + setPrimarySkill(primarySkill, 1, false); + + auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(); + + const auto secondarySkill = nextSecondarySkill(); + if(secondarySkill) + { + setSecSkillLevel(*secondarySkill, 1, false); + } + + //TODO why has the secondary skills to be passed to the method? + levelUp(proposedSecondarySkills); + } +} diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h new file mode 100644 index 000000000..75da7346e --- /dev/null +++ b/lib/mapObjects/CGHeroInstance.h @@ -0,0 +1,227 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CGArmedInstance.h" + +#include "../CArtHandler.h" // For CArtifactSet +#include "../CRandomGenerator.h" + +/* + * CObjectHandler.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 CHero; +class CGBoat; + +class CGHeroPlaceholder : public CGObjectInstance +{ +public: + //subID stores id of hero type. If it's 0xff then following field is used + ui8 power; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & power; + } +}; + + +class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet +{ +public: + enum ECanDig + { + CAN_DIG, LACK_OF_MOVEMENT, WRONG_TERRAIN, TILE_OCCUPIED + }; + ////////////////////////////////////////////////////////////////////////// + + ui8 moveDir; //format: 123 + // 8 4 + // 765 + mutable ui8 isStanding, tacticFormationEnabled; + + ////////////////////////////////////////////////////////////////////////// + + ConstTransitivePtr type; + TExpType exp; //experience points + ui32 level; //current level of hero + std::string name; //may be custom + std::string biography; //if custom + si32 portrait; //may be custom + si32 mana; // remaining spell points + std::vector > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities + ui32 movement; //remaining movement points + ui8 sex; + bool inTownGarrison; // if hero is in town garrison + ConstTransitivePtr visitedTown; //set if hero is visiting town or in the town garrison + ConstTransitivePtr commander; + const CGBoat *boat; //set to CGBoat when sailing + + + //std::vector artifacts; //hero's artifacts from bag + //std::map artifWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 + std::set spells; //known spells (spell IDs) + std::set visitedObjects; + + struct DLL_LINKAGE Patrol + { + Patrol(){patrolling=false;patrolRadious=-1;}; + bool patrolling; + ui32 patrolRadious; + template void serialize(Handler &h, const int version) + { + h & patrolling & patrolRadious; + } + } patrol; + + struct DLL_LINKAGE HeroSpecial : CBonusSystemNode + { + bool growsWithLevel; + + HeroSpecial(){growsWithLevel = false;}; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & growsWithLevel; + } + }; + + std::vector specialty; + + struct DLL_LINKAGE SecondarySkillsInfo + { + //skills are determined, initialized at map start + //FIXME remove mutable + mutable CRandomGenerator rand; + ui8 magicSchoolCounter; + ui8 wisdomCounter; + + void resetMagicSchoolCounter(); + void resetWisdomCounter(); + + template void serialize(Handler &h, const int version) + { + h & magicSchoolCounter & wisdomCounter & rand; + } + } skillsInfo; + + int3 getSightCenter() const; //"center" tile from which the sight distance is calculated + int getSightRadious() const; //sight distance (should be used if player-owned structure) + ////////////////////////////////////////////////////////////////////////// + + int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral + void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed + + ////////////////////////////////////////////////////////////////////////// + + bool hasSpellbook() const; + EAlignment::EAlignment getAlignment() const; + const std::string &getBiography() const; + bool needsLastStack()const; + ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling + ui32 getLowestCreatureSpeed() const; + int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' + si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day + bool canWalkOnSea() const; + int getCurrentLuck(int stack=-1, bool town=false) const; + int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored + + // ----- primary and secondary skill, experience, level handling ----- + + /// Returns true if hero has lower level than should upon his experience. + bool gainsLevel() const; + + /// Returns the next primary skill on level up. Can only be called if hero can gain a level up. + PrimarySkill::PrimarySkill nextPrimarySkill() const; + + /// Returns the next secondary skill randomly on level up. Can only be called if hero can gain a level up. + boost::optional nextSecondarySkill() const; + + /// Gets 0, 1 or 2 secondary skills which are proposed on hero level up. + std::vector getLevelUpProposedSecondarySkills() const; + + ui8 getSecSkillLevel(SecondarySkill skill) const; //0 - no skill + + /// Returns true if hero has free secondary skill slot. + bool canLearnSkill() const; + + void setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs); + void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value + void levelUp(std::vector skills); + + int maxMovePoints(bool onLand) const; + int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const; + + //int getSpellSecLevel(int spell) const; //returns level of secondary ability (fire, water, earth, air magic) known to this hero and applicable to given spell; -1 if error + static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest + double getFightingStrength() const; // takes attack / defense skill into account + double getMagicStrength() const; // takes knowledge / spell power skill into account + double getHeroStrength() const; // includes fighting and magic strength + ui64 getTotalStrength() const; // includes fighting strength and army strength + TExpType calculateXp(TExpType exp) const; //apply learning skill + ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const; //returns level on which given spell would be cast by this hero (0 - none, 1 - basic etc); optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic, + bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses + CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const; + void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const; + ECanDig diggingStatus() const; //0 - can dig; 1 - lack of movement; 2 - + + ////////////////////////////////////////////////////////////////////////// + + void setType(si32 ID, si32 subID); + + void initHero(); + void initHero(HeroTypeID SUBID); + + void putArtifact(ArtifactPosition pos, CArtifactInstance *art); + void putInBackpack(CArtifactInstance *art); + void initExp(); + void initArmy(IArmyDescriptor *dst = nullptr); + //void giveArtifact (ui32 aid); + void pushPrimSkill(PrimarySkill::PrimarySkill which, int val); + ui8 maxlevelsToMagicSchool() const; + ui8 maxlevelsToWisdom() const; + void Updatespecialty(); + void recreateSecondarySkillsBonuses(); + void updateSkill(SecondarySkill which, int val); + + CGHeroInstance(); + virtual ~CGHeroInstance(); + ////////////////////////////////////////////////////////////////////////// + // + ArtBearer::ArtBearer bearerType() const override; + ////////////////////////////////////////////////////////////////////////// + + CBonusSystemNode *whereShouldBeAttached(CGameState *gs) override; + std::string nodeName() const override; + void deserializationFix(); + + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + const std::string & getHoverText() const override; +protected: + void setPropertyDer(ui8 what, ui32 val) override;//synchr + +private: + void levelUpAutomatically(); + +public: + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + h & exp & level & name & biography & portrait & mana & secSkills & movement + & sex & inTownGarrison & spells & patrol & moveDir & skillsInfo; + h & visitedTown & boat; + h & type & specialty & commander; + BONUS_TREE_DESERIALIZATION_FIX + //visitied town pointer will be restored by map serialization method + } +}; diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp new file mode 100644 index 000000000..887c02128 --- /dev/null +++ b/lib/mapObjects/CGMarket.cpp @@ -0,0 +1,334 @@ +/* + * CObjectHandler.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 "CGMarket.h" + +#include "NetPacks.h" +#include "CGeneralTextHandler.h" + +using namespace boost::assign; + +///helpers +static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1) +{ + OpenWindow ow; + ow.window = type; + ow.id1 = id1; + ow.id2 = id2; + IObjectInterface::cb->sendAndApply(&ow); +} + +bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + { + double effectiveness = std::min((getMarketEfficiency() + 1.0) / 20.0, 0.5); + + double r = VLC->objh->resVals[id1], //value of given resource + g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource + + if(r>g) //if given resource is more expensive than wanted + { + val2 = ceil(r / g); + val1 = 1; + } + else //if wanted resource is more expensive + { + val1 = (g / r) + 0.5; + val2 = 1; + } + } + break; + case EMarketMode::CREATURE_RESOURCE: + { + const double effectivenessArray[] = {0.0, 0.3, 0.45, 0.50, 0.65, 0.7, 0.85, 0.9, 1.0}; + double effectiveness = effectivenessArray[std::min(getMarketEfficiency(), 8)]; + + double r = VLC->creh->creatures[id1]->cost[6], //value of given creature in gold + g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource + + if(r>g) //if given resource is more expensive than wanted + { + val2 = ceil(r / g); + val1 = 1; + } + else //if wanted resource is more expensive + { + val1 = (g / r) + 0.5; + val2 = 1; + } + } + break; + case EMarketMode::RESOURCE_PLAYER: + val1 = 1; + val2 = 1; + break; + case EMarketMode::RESOURCE_ARTIFACT: + { + double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6); + double r = VLC->objh->resVals[id1], //value of offered resource + g = VLC->arth->artifacts[id2]->price / effectiveness; //value of bought artifact in gold + + if(id1 != 6) //non-gold prices are doubled + r /= 2; + + val1 = std::max(1, (int)((g / r) + 0.5)); //don't sell arts for less than 1 resource + val2 = 1; + } + break; + case EMarketMode::ARTIFACT_RESOURCE: + { + double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6); + double r = VLC->arth->artifacts[id1]->price * effectiveness, + g = VLC->objh->resVals[id2]; + +// if(id2 != 6) //non-gold prices are doubled +// r /= 2; + + val1 = 1; + val2 = std::max(1, (int)((r / g) + 0.5)); //at least one resource is given in return + } + break; + case EMarketMode::CREATURE_EXP: + { + val1 = 1; + val2 = (VLC->creh->creatures[id1]->AIValue / 40) * 5; + } + break; + case EMarketMode::ARTIFACT_EXP: + { + val1 = 1; + + int givenClass = VLC->arth->artifacts[id1]->getArtClassSerial(); + if(givenClass < 0 || givenClass > 3) + { + val2 = 0; + return false; + } + + static const int expPerClass[] = {1000, 1500, 3000, 6000}; + val2 = expPerClass[givenClass]; + } + break; + default: + assert(0); + return false; + } + + return true; +} + +bool IMarket::allowsTrade(EMarketMode::EMarketMode mode) const +{ + return false; +} + +int IMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::ARTIFACT_RESOURCE: + case EMarketMode::CREATURE_RESOURCE: + return -1; + default: + return 1; + } +} + +std::vector IMarket::availableItemsIds(EMarketMode::EMarketMode mode) const +{ + std::vector ret; + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::ARTIFACT_RESOURCE: + case EMarketMode::CREATURE_RESOURCE: + for (int i = 0; i < 7; i++) + ret.push_back(i); + } + return ret; +} + +const IMarket * IMarket::castFrom(const CGObjectInstance *obj, bool verbose /*= true*/) +{ + switch(obj->ID) + { + case Obj::TOWN: + return static_cast(obj); + case Obj::ALTAR_OF_SACRIFICE: + case Obj::BLACK_MARKET: + case Obj::TRADING_POST: + case Obj::TRADING_POST_SNOW: + case Obj::FREELANCERS_GUILD: + return static_cast(obj); + case Obj::UNIVERSITY: + return static_cast(obj); + default: + if(verbose) + logGlobal->errorStream() << "Cannot cast to IMarket object with ID " << obj->ID; + return nullptr; + } +} + +IMarket::IMarket(const CGObjectInstance *O) + :o(O) +{ + +} + +std::vector IMarket::availableModes() const +{ + std::vector ret; + for (int i = 0; i < EMarketMode::MARTKET_AFTER_LAST_PLACEHOLDER; i++) + if(allowsTrade((EMarketMode::EMarketMode)i)) + ret.push_back((EMarketMode::EMarketMode)i); + + return ret; +} + +void CGMarket::onHeroVisit(const CGHeroInstance * h) const +{ + openWindow(OpenWindow::MARKET_WINDOW,id.getNum(),h->id.getNum()); +} + +int CGMarket::getMarketEfficiency() const +{ + return 5; +} + +bool CGMarket::allowsTrade(EMarketMode::EMarketMode mode) const +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::RESOURCE_PLAYER: + switch(ID) + { + case Obj::TRADING_POST: + case Obj::TRADING_POST_SNOW: + return true; + default: + return false; + } + case EMarketMode::CREATURE_RESOURCE: + return ID == Obj::FREELANCERS_GUILD; + //case ARTIFACT_RESOURCE: + case EMarketMode::RESOURCE_ARTIFACT: + return ID == Obj::BLACK_MARKET; + case EMarketMode::ARTIFACT_EXP: + case EMarketMode::CREATURE_EXP: + return ID == Obj::ALTAR_OF_SACRIFICE; //TODO? check here for alignment of visiting hero? - would not be coherent with other checks here + case EMarketMode::RESOURCE_SKILL: + return ID == Obj::UNIVERSITY; + default: + return false; + } +} + +int CGMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const +{ + return -1; +} + +std::vector CGMarket::availableItemsIds(EMarketMode::EMarketMode mode) const +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::RESOURCE_PLAYER: + return IMarket::availableItemsIds(mode); + default: + return std::vector(); + } +} + +CGMarket::CGMarket() + :IMarket(this) +{ +} + +std::vector CGBlackMarket::availableItemsIds(EMarketMode::EMarketMode mode) const +{ + switch(mode) + { + case EMarketMode::ARTIFACT_RESOURCE: + return IMarket::availableItemsIds(mode); + case EMarketMode::RESOURCE_ARTIFACT: + { + std::vector ret; + for(const CArtifact *a : artifacts) + if(a) + ret.push_back(a->id); + else + ret.push_back(-1); + return ret; + } + default: + return std::vector(); + } +} + +void CGBlackMarket::newTurn() const +{ + if(cb->getDate(Date::DAY_OF_MONTH) != 1) //new month + return; + + SetAvailableArtifacts saa; + saa.id = id.getNum(); + cb->pickAllowedArtsSet(saa.arts); + cb->sendAndApply(&saa); +} + +void CGUniversity::initObj() +{ + std::vector toChoose; + for(int i = 0; i < GameConstants::SKILL_QUANTITY; ++i) + { + if(cb->isAllowed(2, i)) + { + toChoose.push_back(i); + } + } + if(toChoose.size() < 4) + { + logGlobal->warnStream()<<"Warning: less then 4 available skills was found by University initializer!"; + return; + } + + // get 4 skills + for(int i = 0; i < 4; ++i) + { + // move randomly one skill to selected and remove from list + auto it = RandomGeneratorUtil::nextItem(toChoose, cb->gameState()->getRandomGenerator()); + skills.push_back(*it); + toChoose.erase(it); + } +} + +std::vector CGUniversity::availableItemsIds(EMarketMode::EMarketMode mode) const +{ + switch (mode) + { + case EMarketMode::RESOURCE_SKILL: + return skills; + + default: + return std::vector (); + } +} + +void CGUniversity::onHeroVisit(const CGHeroInstance * h) const +{ + openWindow(OpenWindow::UNIVERSITY_WINDOW,id.getNum(),h->id.getNum()); +} diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h new file mode 100644 index 000000000..b28fde8cf --- /dev/null +++ b/lib/mapObjects/CGMarket.h @@ -0,0 +1,88 @@ +#pragma once + +#include "CObjectHandler.h" + +/* + * CObjectHandler.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 IMarket +{ +public: + const CGObjectInstance *o; + + IMarket(const CGObjectInstance *O); + virtual ~IMarket() {} + + virtual int getMarketEfficiency() const =0; + virtual bool allowsTrade(EMarketMode::EMarketMode mode) const; + virtual int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const; //-1 if unlimited + virtual std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; + + bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units + std::vector availableModes() const; + + static const IMarket *castFrom(const CGObjectInstance *obj, bool verbose = true); + + template void serialize(Handler &h, const int version) + { + h & o; + } +}; + +class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket +{ +public: + CGMarket(); + ///IObjectIntercae + void onHeroVisit(const CGHeroInstance * h) const override; //open trading window + + ///IMarket + int getMarketEfficiency() const override; + bool allowsTrade(EMarketMode::EMarketMode mode) const override; + int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited + std::vector availableItemsIds(EMarketMode::EMarketMode mode) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGBlackMarket : public CGMarket +{ +public: + std::vector artifacts; //available artifacts + + void newTurn() const override; //reset artifacts for black market every month + std::vector availableItemsIds(EMarketMode::EMarketMode mode) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & artifacts; + } +}; + +class DLL_LINKAGE CGUniversity : public CGMarket +{ +public: + std::vector skills; //available skills + + std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; + void initObj() override;//set skills for trade + void onHeroVisit(const CGHeroInstance * h) const override; //open window + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & skills; + } +}; diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp new file mode 100644 index 000000000..ea55c25be --- /dev/null +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -0,0 +1,364 @@ +/* + * CObjectHandler.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 "CGPandoraBox.h" + +#include "NetPacks.h" +#include "../client/CSoundBase.h" + +#include "../CSpellHandler.h" + +using namespace boost::assign; + +///helpers +static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) +{ + InfoWindow iw; + iw.soundID = soundID; + iw.player = playerID; + iw.text.addTxt(MetaString::ADVOB_TXT,txtID); + IObjectInterface::cb->sendAndApply(&iw); +} + +static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = h->getOwner(); + showInfoDialog(playerID,txtID,soundID); +} + +void CGPandoraBox::initObj() +{ + blockVisit = (ID==Obj::PANDORAS_BOX); //block only if it's really pandora's box (events also derive from that class) + hasGuardians = stacks.size(); +} + +void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const +{ + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundBase::QUEST; + bd.text.addTxt (MetaString::ADVOB_TXT, 14); + cb->showBlockingDialog (&bd); +} + +void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const +{ + cb->removeAfterVisit(this); + + InfoWindow iw; + iw.player = h->getOwner(); + + bool changesPrimSkill = false; + for (auto & elem : primskills) + { + if(elem) + { + changesPrimSkill = true; + break; + } + } + + if(gainedExp || changesPrimSkill || abilities.size()) + { + TExpType expVal = h->calculateXp(gainedExp); + //getText(iw,afterBattle,175,h); //wtf? + iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something + iw.text.addReplacement(h->name); + + if(expVal) + iw.components.push_back(Component(Component::EXPERIENCE,0,expVal,0)); + for(int i=0; ishowInfoDialog(&iw); + + //give sec skills + for(int i=0; igetSecSkillLevel(abilities[i]); + + if( (curLev && curLev < abilityLevels[i]) || (h->canLearnSkill() )) + { + cb->changeSecSkill(h,abilities[i],abilityLevels[i],true); + } + } + + //give prim skills + for(int i=0; ichangePrimSkill(h,static_cast(i),primskills[i],false); + + assert(!cb->isVisitCoveredByAnotherQuery(this, h)); + + //give exp + if(expVal) + cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); + } + + if(!cb->isVisitCoveredByAnotherQuery(this, h)) + giveContentsAfterExp(h); + //Otherwise continuation occurs via post-level-up callback. +} + +void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const +{ + bool hadGuardians = hasGuardians; //copy, because flag will be emptied after issuing first post-battle message + + std::string msg = message; //in case box is removed in the meantime + InfoWindow iw; + iw.player = h->getOwner(); + + if(spells.size()) + { + std::set spellsToGive; + iw.components.clear(); + if (spells.size() > 1) + { + iw.text.addTxt(MetaString::ADVOB_TXT, 188); //%s learns spells + } + else + { + iw.text.addTxt(MetaString::ADVOB_TXT, 184); //%s learns a spell + } + iw.text.addReplacement(h->name); + std::vector > * sp = &VLC->spellh->objects; + for(auto i=spells.cbegin(); i != spells.cend(); i++) + { + if ((*sp)[*i]->level <= h->getSecSkillLevel(SecondarySkill::WISDOM) + 2) //enough wisdom + { + iw.components.push_back(Component(Component::SPELL,*i,0,0)); + spellsToGive.insert(*i); + } + } + if(!spellsToGive.empty()) + { + cb->changeSpells(h,true,spellsToGive); + cb->showInfoDialog(&iw); + } + } + + if(manaDiff) + { + getText(iw,hadGuardians,manaDiff,176,177,h); + iw.components.push_back(Component(Component::PRIM_SKILL,5,manaDiff,0)); + cb->showInfoDialog(&iw); + cb->setManaPoints(h->id, h->mana + manaDiff); + } + + if(moraleDiff) + { + getText(iw,hadGuardians,moraleDiff,178,179,h); + iw.components.push_back(Component(Component::MORALE,0,moraleDiff,0)); + cb->showInfoDialog(&iw); + GiveBonus gb; + gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,moraleDiff,id.getNum(),""); + gb.id = h->id.getNum(); + cb->giveHeroBonus(&gb); + } + + if(luckDiff) + { + getText(iw,hadGuardians,luckDiff,180,181,h); + iw.components.push_back(Component(Component::LUCK,0,luckDiff,0)); + cb->showInfoDialog(&iw); + GiveBonus gb; + gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,luckDiff,id.getNum(),""); + gb.id = h->id.getNum(); + cb->giveHeroBonus(&gb); + } + + iw.components.clear(); + iw.text.clear(); + for(int i=0; ishowInfoDialog(&iw); + } + + iw.components.clear(); + iw.text.clear(); + for(int i=0; i 0) + iw.components.push_back(Component(Component::RESOURCE,i,resources[i],0)); + } + if(iw.components.size()) + { + getText(iw,hadGuardians,183,h); + cb->showInfoDialog(&iw); + } + + iw.components.clear(); + // getText(iw,afterBattle,183,h); + iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure + iw.text.addReplacement(h->name); + for(auto & elem : artifacts) + { + iw.components.push_back(Component(Component::ARTIFACT,elem,0,0)); + if(iw.components.size() >= 14) + { + cb->showInfoDialog(&iw); + iw.components.clear(); + iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - once more? + iw.text.addReplacement(h->name); + } + } + if(iw.components.size()) + { + cb->showInfoDialog(&iw); + } + + for(int i=0; igiveResource(h->getOwner(),static_cast(i),resources[i]); + + for(auto & elem : artifacts) + cb->giveHeroNewArtifact(h, VLC->arth->artifacts[elem],ArtifactPosition::FIRST_AVAILABLE); + + iw.components.clear(); + iw.text.clear(); + + if (creatures.Slots().size()) + { //this part is taken straight from creature bank + MetaString loot; + for(auto & elem : creatures.Slots()) + { //build list of joined creatures + iw.components.push_back(Component(*elem.second)); + loot << "%s"; + loot.addReplacement(*elem.second); + } + + if (creatures.Slots().size() == 1 && creatures.Slots().begin()->second->count == 1) + iw.text.addTxt(MetaString::ADVOB_TXT, 185); + else + iw.text.addTxt(MetaString::ADVOB_TXT, 186); + + iw.text.addReplacement(loot.buildList()); + iw.text.addReplacement(h->name); + + cb->showInfoDialog(&iw); + cb->giveCreatures(this, h, creatures, true); + } + if(!hasGuardians && msg.size()) + { + iw.text << msg; + cb->showInfoDialog(&iw); + } +} + +void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const +{ + if(afterBattle || !message.size()) + { + iw.text.addTxt(MetaString::ADVOB_TXT,text);//%s has lost treasure. + iw.text.addReplacement(h->name); + } + else + { + iw.text << message; + afterBattle = true; + } +} + +void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const +{ + iw.components.clear(); + iw.text.clear(); + if(afterBattle || !message.size()) + { + iw.text.addTxt(MetaString::ADVOB_TXT,val < 0 ? negative : positive); //%s's luck takes a turn for the worse / %s's luck increases + iw.text.addReplacement(h->name); + } + else + { + iw.text << message; + afterBattle = true; + } +} + +void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner) + return; + + giveContentsUpToExp(hero); +} + +void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer) + { + if (stacksCount() > 0) //if pandora's box is protected by army + { + showInfoDialog(hero,16,0); + cb->startBattleI(hero, this); //grants things after battle + } + else if (message.size() == 0 && resources.size() == 0 + && primskills.size() == 0 && abilities.size() == 0 + && abilityLevels.size() == 0 && artifacts.size() == 0 + && spells.size() == 0 && creatures.Slots().size() > 0 + && gainedExp == 0 && manaDiff == 0 && moraleDiff == 0 && luckDiff == 0) //if it gives nothing without battle + { + showInfoDialog(hero,15,0); + cb->removeObject(this); + } + else //if it gives something without battle + { + giveContentsUpToExp(hero); + } + } +} + +void CGPandoraBox::heroLevelUpDone(const CGHeroInstance *hero) const +{ + giveContentsAfterExp(hero); +} + +void CGEvent::onHeroVisit( const CGHeroInstance * h ) const +{ + if(!(availableFor & (1 << h->tempOwner.getNum()))) + return; + if(cb->getPlayerSettings(h->tempOwner)->playerID) + { + if(humanActivate) + activated(h); + } + else if(computerActivate) + activated(h); +} + +void CGEvent::activated( const CGHeroInstance * h ) const +{ + if(stacksCount() > 0) + { + InfoWindow iw; + iw.player = h->tempOwner; + if(message.size()) + iw.text << message; + else + iw.text.addTxt(MetaString::ADVOB_TXT, 16); + cb->showInfoDialog(&iw); + cb->startBattleI(h, this); + } + else + { + giveContentsUpToExp(h); + } +} diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h new file mode 100644 index 000000000..72508cdb9 --- /dev/null +++ b/lib/mapObjects/CGPandoraBox.h @@ -0,0 +1,73 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CGArmedInstance.h" + + +/* + * CObjectHandler.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 CGPandoraBox : public CArmedInstance +{ +public: + std::string message; + bool hasGuardians; //helper - after battle even though we have no stacks, allows us to know that there was battle + + //gained things: + ui32 gainedExp; + si32 manaDiff; //amount of gained / lost mana + si32 moraleDiff; //morale modifier + si32 luckDiff; //luck modifier + TResources resources;//gained / lost resources + std::vector primskills;//gained / lost prim skills + std::vector abilities; //gained abilities + std::vector abilityLevels; //levels of gained abilities + std::vector artifacts; //gained artifacts + std::vector spells; //gained spells + CCreatureSet creatures; //gained creatures + + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + void heroLevelUpDone(const CGHeroInstance *hero) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & message & hasGuardians & gainedExp & manaDiff & moraleDiff & luckDiff & resources & primskills + & abilities & abilityLevels & artifacts & spells & creatures; + } +protected: + void giveContentsUpToExp(const CGHeroInstance *h) const; + void giveContentsAfterExp(const CGHeroInstance *h) const; +private: + void getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const; + void getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const; +}; + +class DLL_LINKAGE CGEvent : public CGPandoraBox //event objects +{ +public: + bool removeAfterVisit; //true if event is removed after occurring + ui8 availableFor; //players whom this event is available for + bool computerActivate; //true if computer player can activate this event + bool humanActivate; //true if human player can activate this event + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & removeAfterVisit & availableFor & computerActivate & humanActivate; + } + + void onHeroVisit(const CGHeroInstance * h) const override; +private: + void activated(const CGHeroInstance * h) const; +}; diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp new file mode 100644 index 000000000..53efa35b6 --- /dev/null +++ b/lib/mapObjects/CGTownInstance.cpp @@ -0,0 +1,1201 @@ +/* + * CObjectHandler.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 "CGTownInstance.h" + +#include "NetPacks.h" +#include "../CGeneralTextHandler.h" + +using namespace boost::assign; + +std::vector CGTownInstance::merchantArtifacts; +std::vector CGTownInstance::universitySkills; + +void CGDwelling::initObj() +{ + switch(ID) + { + case Obj::CREATURE_GENERATOR1: + { + CreatureID crid = VLC->objh->cregens[subID]; + const CCreature *crs = VLC->creh->creatures[crid]; + + creatures.resize(1); + creatures[0].second.push_back(crid); + if (subID >= VLC->generaltexth->creGens.size()) //very messy workaround + { + auto & dwellingNames = VLC->townh->factions[crs->faction]->town->dwellingNames; + assert (dwellingNames.size() > crs->level - 1); + hoverName = dwellingNames[crs->level - 1]; + } + else + hoverName = VLC->generaltexth->creGens[subID]; + if(crs->level > 4) + putStack(SlotID(0), new CStackInstance(crs, (crs->growth) * 3)); + if (getOwner() != PlayerColor::NEUTRAL) + cb->gameState()->players[getOwner()].dwellings.push_back (this); + } + break; + + case Obj::CREATURE_GENERATOR4: + creatures.resize(4); + if(subID == 1) //Golem Factory + { + creatures[0].second.push_back(CreatureID::STONE_GOLEM); + creatures[1].second.push_back(CreatureID::IRON_GOLEM); + creatures[2].second.push_back(CreatureID::GOLD_GOLEM); + creatures[3].second.push_back(CreatureID::DIAMOND_GOLEM); + //guards + putStack(SlotID(0), new CStackInstance(CreatureID::GOLD_GOLEM, 9)); + putStack(SlotID(1), new CStackInstance(CreatureID::DIAMOND_GOLEM, 6)); + } + else if(subID == 0) // Elemental Conflux + { + creatures[0].second.push_back(CreatureID::AIR_ELEMENTAL); + creatures[1].second.push_back(CreatureID::FIRE_ELEMENTAL); + creatures[2].second.push_back(CreatureID::EARTH_ELEMENTAL); + creatures[3].second.push_back(CreatureID::WATER_ELEMENTAL); + //guards + putStack(SlotID(0), new CStackInstance(CreatureID::EARTH_ELEMENTAL, 12)); + } + else + { + assert(0); + } + hoverName = VLC->generaltexth->creGens4[subID]; + break; + + case Obj::REFUGEE_CAMP: + //is handled within newturn func + break; + + case Obj::WAR_MACHINE_FACTORY: + creatures.resize(3); + creatures[0].second.push_back(CreatureID::BALLISTA); + creatures[1].second.push_back(CreatureID::FIRST_AID_TENT); + creatures[2].second.push_back(CreatureID::AMMO_CART); + break; + + default: + assert(0); + break; + } +} + +void CGDwelling::setProperty(ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::OWNER: //change owner + if (ID == Obj::CREATURE_GENERATOR1) //single generators + { + if (tempOwner != PlayerColor::NEUTRAL) + { + std::vector >* dwellings = &cb->gameState()->players[tempOwner].dwellings; + dwellings->erase (std::find(dwellings->begin(), dwellings->end(), this)); + } + if (PlayerColor(val) != PlayerColor::NEUTRAL) //can new owner be neutral? + cb->gameState()->players[PlayerColor(val)].dwellings.push_back (this); + } + break; + case ObjProperty::AVAILABLE_CREATURE: + creatures.resize(1); + creatures[0].second.resize(1); + creatures[0].second[0] = CreatureID(val); + break; + } + CGObjectInstance::setProperty(what,val); +} +void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const +{ + if(ID == Obj::REFUGEE_CAMP && !creatures[0].first) //Refugee Camp, no available cres + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.text.addTxt(MetaString::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted. Perhaps you should try next week. + iw.text.addReplacement(MetaString::OBJ_NAMES, ID); + cb->sendAndApply(&iw); + return; + } + + PlayerRelations::PlayerRelations relations = cb->gameState()->getPlayerRelations( h->tempOwner, tempOwner ); + + if ( relations == PlayerRelations::ALLIES ) + return;//do not allow recruiting or capturing + + if( !relations && stacksCount() > 0) //object is guarded, owned by enemy + { + BlockingDialog bd(true,false); + bd.player = h->tempOwner; + bd.text.addTxt(MetaString::GENERAL_TXT, 421); //Much to your dismay, the %s is guarded by %s %s. Do you wish to fight the guards? + bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); + bd.text.addReplacement(MetaString::ARRAY_TXT, 176 + Slots().begin()->second->getQuantityID()*3); + bd.text.addReplacement(*Slots().begin()->second); + cb->showBlockingDialog(&bd); + return; + } + + if(!relations && ID != Obj::WAR_MACHINE_FACTORY) + { + cb->setOwner(this, h->tempOwner); + } + + BlockingDialog bd (true,false); + bd.player = h->tempOwner; + if(ID == Obj::CREATURE_GENERATOR1 || ID == Obj::CREATURE_GENERATOR4) + { + bd.text.addTxt(MetaString::ADVOB_TXT, ID == Obj::CREATURE_GENERATOR1 ? 35 : 36); //{%s} Would you like to recruit %s? / {%s} Would you like to recruit %s, %s, %s, or %s? + bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); + for(auto & elem : creatures) + bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); + } + else if(ID == Obj::REFUGEE_CAMP) + { + bd.text.addTxt(MetaString::ADVOB_TXT, 35); //{%s} Would you like to recruit %s? + bd.text.addReplacement(MetaString::OBJ_NAMES, ID); + for(auto & elem : creatures) + bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); + } + else if(ID == Obj::WAR_MACHINE_FACTORY) + bd.text.addTxt(MetaString::ADVOB_TXT, 157); //{War Machine Factory} Would you like to purchase War Machines? + else + throw std::runtime_error("Illegal dwelling!"); + + cb->showBlockingDialog(&bd); +} + +void CGDwelling::newTurn() const +{ + if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week + return; + + //town growths and War Machines Factories are handled separately + if(ID == Obj::TOWN || ID == Obj::WAR_MACHINE_FACTORY) + return; + + if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature + { + cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(cb->gameState()->getRandomGenerator())); + } + + bool change = false; + + SetAvailableCreatures sac; + sac.creatures = creatures; + sac.tid = id; + for (size_t i = 0; i < creatures.size(); i++) + { + if(creatures[i].second.size()) + { + CCreature *cre = VLC->creh->creatures[creatures[i].second[0]]; + TQuantity amount = cre->growth * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH); + if (VLC->modh->settings.DWELLINGS_ACCUMULATE_CREATURES && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures + sac.creatures[i].first += amount; + else + sac.creatures[i].first = amount; + change = true; + } + } + + if(change) + cb->sendAndApply(&sac); +} + +void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const +{ + CreatureID crid = creatures[0].second[0]; + CCreature *crs = VLC->creh->creatures[crid]; + TQuantity count = creatures[0].first; + + if(crs->level == 1 && ID != Obj::REFUGEE_CAMP) //first level - creatures are for free + { + if(count) //there are available creatures + { + SlotID slot = h->getSlotFor(crid); + if(!slot.validSlot()) //no available slot + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.text.addTxt(MetaString::GENERAL_TXT, 425);//The %s would join your hero, but there aren't enough provisions to support them. + iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); + cb->showInfoDialog(&iw); + } + else //give creatures + { + SetAvailableCreatures sac; + sac.tid = id; + sac.creatures = creatures; + sac.creatures[0].first = 0; + + + InfoWindow iw; + iw.player = h->tempOwner; + iw.text.addTxt(MetaString::GENERAL_TXT, 423); //%d %s join your army. + iw.text.addReplacement(count); + iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); + + cb->showInfoDialog(&iw); + cb->sendAndApply(&sac); + cb->addToSlot(StackLocation(h, slot), crs, count); + } + } + else //there no creatures + { + InfoWindow iw; + iw.text.addTxt(MetaString::GENERAL_TXT, 422); //There are no %s here to recruit. + iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); + iw.player = h->tempOwner; + cb->sendAndApply(&iw); + } + } + else + { + if(ID == Obj::WAR_MACHINE_FACTORY) //pick available War Machines + { + //there is 1 war machine available to recruit if hero doesn't have one + SetAvailableCreatures sac; + sac.tid = id; + sac.creatures = creatures; + sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista + sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent + sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart + cb->sendAndApply(&sac); + } + + OpenWindow ow; + ow.id1 = id.getNum(); + ow.id2 = h->id.getNum(); + ow.window = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP) + ? OpenWindow::RECRUITMENT_FIRST + : OpenWindow::RECRUITMENT_ALL; + cb->sendAndApply(&ow); + } +} + +void CGDwelling::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if (result.winner == 0) + { + onHeroVisit(hero); + } +} + +void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + auto relations = cb->getPlayerRelations(getOwner(), hero->getOwner()); + if(stacksCount() > 0 && relations == PlayerRelations::ENEMIES) //guards present + { + if(answer) + cb->startBattleI(hero, this); + } + else if(answer) + { + heroAcceptsCreatures(hero); + } +} + +int CGTownInstance::getSightRadious() const //returns sight distance +{ + if (subID == ETownType::TOWER) + { + if (hasBuilt(BuildingID::GRAIL)) //skyship + return -1; //entire map + if (hasBuilt(BuildingID::LOOKOUT_TOWER)) //lookout tower + return 20; + } + return 5; +} + +void CGTownInstance::setPropertyDer(ui8 what, ui32 val) +{ +///this is freakin' overcomplicated solution + switch (what) + { + case ObjProperty::STRUCTURE_ADD_VISITING_HERO: + bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, visitingHero->id.getNum()); + break; + case ObjProperty::STRUCTURE_CLEAR_VISITORS: + bonusingBuildings[val]->setProperty (ObjProperty::STRUCTURE_CLEAR_VISITORS, 0); + break; + case ObjProperty::STRUCTURE_ADD_GARRISONED_HERO: //add garrisoned hero to visitors + bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, garrisonHero->id.getNum()); + break; + case ObjProperty::BONUS_VALUE_FIRST: + bonusValue.first = val; + break; + case ObjProperty::BONUS_VALUE_SECOND: + bonusValue.second = val; + break; + } +} +CGTownInstance::EFortLevel CGTownInstance::fortLevel() const //0 - none, 1 - fort, 2 - citadel, 3 - castle +{ + if (hasBuilt(BuildingID::CASTLE)) + return CASTLE; + if (hasBuilt(BuildingID::CITADEL)) + return CITADEL; + if (hasBuilt(BuildingID::FORT)) + return FORT; + return NONE; +} + +int CGTownInstance::hallLevel() const // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol +{ + + if (hasBuilt(BuildingID::CAPITOL)) + return 3; + if (hasBuilt(BuildingID::CITY_HALL)) + return 2; + if (hasBuilt(BuildingID::TOWN_HALL)) + return 1; + if (hasBuilt(BuildingID::VILLAGE_HALL)) + return 0; + return -1; +} +int CGTownInstance::mageGuildLevel() const +{ + if (hasBuilt(BuildingID::MAGES_GUILD_5)) + return 5; + if (hasBuilt(BuildingID::MAGES_GUILD_4)) + return 4; + if (hasBuilt(BuildingID::MAGES_GUILD_3)) + return 3; + if (hasBuilt(BuildingID::MAGES_GUILD_2)) + return 2; + if (hasBuilt(BuildingID::MAGES_GUILD_1)) + return 1; + return 0; +} + +int CGTownInstance::getHordeLevel(const int & HID) const//HID - 0 or 1; returns creature level or -1 if that horde structure is not present +{ + return town->hordeLvl.at(HID); +} + +int CGTownInstance::creatureGrowth(const int & level) const +{ + return getGrowthInfo(level).totalGrowth(); +} + +GrowthInfo CGTownInstance::getGrowthInfo(int level) const +{ + GrowthInfo ret; + + if (level<0 || level >=GameConstants::CREATURES_PER_TOWN) + return ret; + if (creatures[level].second.empty()) + return ret; //no dwelling + + const CCreature *creature = VLC->creh->creatures[creatures[level].second.back()]; + const int base = creature->growth; + int castleBonus = 0; + + ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[590], base));// \n\nBasic growth %d" + + if (hasBuilt(BuildingID::CASTLE)) + ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CASTLE, castleBonus = base)); + else if (hasBuilt(BuildingID::CITADEL)) + ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CITADEL, castleBonus = base / 2)); + + if(town->hordeLvl.at(0) == level)//horde 1 + if(hasBuilt(BuildingID::HORDE_1)) + ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_1, creature->hordeGrowth)); + + if(town->hordeLvl.at(1) == level)//horde 2 + if(hasBuilt(BuildingID::HORDE_2)) + ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_2, creature->hordeGrowth)); + + int dwellingBonus = 0; + if(const PlayerState *p = cb->getPlayer(tempOwner, false)) + { + for(const CGDwelling *dwelling : p->dwellings) + if(vstd::contains(creatures[level].second, dwelling->creatures[0].second[0])) + dwellingBonus++; + } + + if(dwellingBonus) + ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[591], dwellingBonus));// \nExternal dwellings %+d + + //other *-of-legion-like bonuses (%d to growth cumulative with grail) + TBonusListPtr bonuses = getBonuses(Selector::type(Bonus::CREATURE_GROWTH).And(Selector::subtype(level))); + for(const Bonus *b : *bonuses) + ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val)); + + //statue-of-legion-like bonus: % to base+castle + TBonusListPtr bonuses2 = getBonuses(Selector::type(Bonus::CREATURE_GROWTH_PERCENT)); + for(const Bonus *b : *bonuses2) + ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val * (base + castleBonus) / 100)); + + if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth + ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::GRAIL, ret.totalGrowth() / 2)); + + return ret; +} + +TResources CGTownInstance::dailyIncome() const +{ + TResources ret; + + for (auto & p : town->buildings) + { + BuildingID buildingUpgrade; + + for (auto & p2 : town->buildings) + { + if (p2.second->upgrade == p.first) + { + buildingUpgrade = p2.first; + } + } + + if (!hasBuilt(buildingUpgrade)&&(hasBuilt(p.first))) + { + ret += p.second->produce; + } + + } + + return ret; +} +bool CGTownInstance::hasFort() const +{ + return hasBuilt(BuildingID::FORT); +} +bool CGTownInstance::hasCapitol() const +{ + return hasBuilt(BuildingID::CAPITOL); +} +CGTownInstance::CGTownInstance() + :IShipyard(this), IMarket(this), town(nullptr), builded(0), destroyed(0), identifier(0), alignment(0xff) +{ + +} + +CGTownInstance::~CGTownInstance() +{ + for (auto & elem : bonusingBuildings) + delete elem; +} + +int CGTownInstance::spellsAtLevel(int level, bool checkGuild) const +{ + if(checkGuild && mageGuildLevel() < level) + return 0; + int ret = 6 - level; //how many spells are available at this level + + if (hasBuilt(BuildingID::LIBRARY, ETownType::TOWER)) + ret++; + + return ret; +} + +bool CGTownInstance::needsLastStack() const +{ + if(garrisonHero) + return true; + else return false; +} + +void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const +{ + if( !cb->gameState()->getPlayerRelations( getOwner(), h->getOwner() ))//if this is enemy + { + if(armedGarrison() || visitingHero) + { + const CGHeroInstance *defendingHero = nullptr; + const CArmedInstance *defendingArmy = this; + + if(visitingHero) + defendingHero = visitingHero; + else if(garrisonHero) + defendingHero = garrisonHero; + + if(defendingHero) + defendingArmy = defendingHero; + + bool outsideTown = (defendingHero == visitingHero && garrisonHero); + + //TODO + //"borrowing" army from garrison to visiting hero + + cb->startBattlePrimary(h, defendingArmy, getSightCenter(), h, defendingHero, false, (outsideTown ? nullptr : this)); + } + else + { + cb->setOwner(this, h->tempOwner); + removeCapitols(h->getOwner()); + cb->heroVisitCastle(this, h); + } + } + else if(h->visitablePos() == visitablePos()) + { + if (h->commander && !h->commander->alive) //rise commander. TODO: interactive script + { + SetCommanderProperty scp; + scp.heroid = h->id; + scp.which = SetCommanderProperty::ALIVE; + scp.amount = 1; + cb->sendAndApply (&scp); + } + cb->heroVisitCastle(this, h); + } + else + { + logGlobal->errorStream() << h->name << " visits allied town of " << name << " from different pos?"; + } +} + +void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const +{ + cb->stopHeroVisitCastle(this, h); +} + +void CGTownInstance::initObj() +///initialize town structures +{ + blockVisit = true; + hoverName = name + ", " + town->faction->name; + + if (subID == ETownType::DUNGEON) + creatures.resize(GameConstants::CREATURES_PER_TOWN+1);//extra dwelling for Dungeon + else + creatures.resize(GameConstants::CREATURES_PER_TOWN); + for (int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++) + { + BuildingID buildID = BuildingID(BuildingID::DWELL_FIRST).advance(level); + int upgradeNum = 0; + + for (; town->buildings.count(buildID); upgradeNum++, buildID.advance(GameConstants::CREATURES_PER_TOWN)) + { + if (hasBuilt(buildID) && town->creatures.at(level).size() > upgradeNum) + creatures[level].second.push_back(town->creatures[level][upgradeNum]); + } + } + + switch (subID) + { //add new visitable objects + case 0: + bonusingBuildings.push_back (new COPWBonus(BuildingID::STABLES, this)); + break; + case 5: + bonusingBuildings.push_back (new COPWBonus(BuildingID::MANA_VORTEX, this)); + //fallthrough + case 2: case 3: case 6: + bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_4, this)); + break; + case 7: + bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_1, this)); + break; + } + //add special bonuses from buildings + + recreateBuildingsBonuses(); +} + +void CGTownInstance::newTurn() const +{ + if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week + { + auto & rand = cb->gameState()->getRandomGenerator(); + + //give resources for Rampart, Mystic Pond + if (hasBuilt(BuildingID::MYSTIC_POND, ETownType::RAMPART) + && cb->getDate(Date::DAY) != 1 && (tempOwner < PlayerColor::PLAYER_LIMIT)) + { + int resID = rand.nextInt(2, 5); //bonus to random rare resource + resID = (resID==2)?1:resID; + int resVal = rand.nextInt(1, 4);//with size 1..4 + cb->giveResource(tempOwner, static_cast(resID), resVal); + cb->setObjProperty (id, ObjProperty::BONUS_VALUE_FIRST, resID); + cb->setObjProperty (id, ObjProperty::BONUS_VALUE_SECOND, resVal); + } + + if ( subID == ETownType::DUNGEON ) + for (auto & elem : bonusingBuildings) + { + if ((elem)->ID == BuildingID::MANA_VORTEX) + cb->setObjProperty (id, ObjProperty::STRUCTURE_CLEAR_VISITORS, (elem)->id); //reset visitors for Mana Vortex + } + + if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns + { + std::vector nativeCrits; //slots + for (auto & elem : Slots()) + { + if (elem.second->type->faction == subID) //native + { + nativeCrits.push_back(elem.first); //collect matching slots + } + } + if (nativeCrits.size()) + { + SlotID pos = *RandomGeneratorUtil::nextItem(nativeCrits, rand); + StackLocation sl(this, pos); + + const CCreature *c = getCreature(pos); + if (rand.nextInt(99) < 90 || c->upgrades.empty()) //increase number if no upgrade available + { + cb->changeStackCount(sl, c->growth); + } + else //upgrade + { + cb->changeStackType(sl, VLC->creh->creatures[*c->upgrades.begin()]); + } + } + if ((stacksCount() < GameConstants::ARMY_SIZE && rand.nextInt(99) < 25) || Slots().empty()) //add new stack + { + int i = rand.nextInt(std::min(GameConstants::CREATURES_PER_TOWN, cb->getDate(Date::MONTH) << 1) - 1); + if (!town->creatures[i].empty()) + { + CreatureID c = town->creatures[i][0]; + SlotID n; + + TQuantity count = creatureGrowth(i); + if (!count) // no dwelling + count = VLC->creh->creatures[c]->growth; + + {//no lower tiers or above current month + + if ((n = getSlotFor(c)).validSlot()) + { + StackLocation sl(this, n); + if (slotEmpty(n)) + cb->insertNewStack(sl, VLC->creh->creatures[c], count); + else //add to existing + cb->changeStackCount(sl, count); + } + } + } + } + } + } +} + +int3 CGTownInstance::getSightCenter() const +{ + return pos - int3(2,0,0); +} + +ui8 CGTownInstance::getPassableness() const +{ + if (!armedGarrison())//empty castle - anyone can visit + return GameConstants::ALL_PLAYERS; + if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit + return 0; + + ui8 mask = 0; + TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner); + for(PlayerColor it : ts->players) + mask |= 1< &offsets ) const +{ + offsets += int3(-1,2,0), int3(-3,2,0); +} + +void CGTownInstance::removeCapitols (PlayerColor owner) const +{ + if (hasCapitol()) // search if there's an older capitol + { + PlayerState* state = cb->gameState()->getPlayer (owner); //get all towns owned by player + for (auto i = state->towns.cbegin(); i < state->towns.cend(); ++i) + { + if (*i != this && (*i)->hasCapitol()) + { + RazeStructures rs; + rs.tid = id; + rs.bid.insert(BuildingID::CAPITOL); + rs.destroyed = destroyed; + cb->sendAndApply(&rs); + return; + } + } + } +} + +int CGTownInstance::getBoatType() const +{ + switch (town->faction->alignment) + { + case EAlignment::EVIL : return 0; + case EAlignment::GOOD : return 1; + case EAlignment::NEUTRAL : return 2; + } + assert(0); + return -1; +} + +int CGTownInstance::getMarketEfficiency() const +{ + if (!hasBuilt(BuildingID::MARKETPLACE)) + return 0; + + const PlayerState *p = cb->getPlayer(tempOwner); + assert(p); + + int marketCount = 0; + for(const CGTownInstance *t : p->towns) + if(t->hasBuilt(BuildingID::MARKETPLACE)) + marketCount++; + + return marketCount; +} + +bool CGTownInstance::allowsTrade(EMarketMode::EMarketMode mode) const +{ + switch(mode) + { + case EMarketMode::RESOURCE_RESOURCE: + case EMarketMode::RESOURCE_PLAYER: + return hasBuilt(BuildingID::MARKETPLACE); + + case EMarketMode::ARTIFACT_RESOURCE: + case EMarketMode::RESOURCE_ARTIFACT: + return hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::TOWER) + || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::DUNGEON) + || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::CONFLUX); + + case EMarketMode::CREATURE_RESOURCE: + return hasBuilt(BuildingID::FREELANCERS_GUILD, ETownType::STRONGHOLD); + + case EMarketMode::CREATURE_UNDEAD: + return hasBuilt(BuildingID::SKELETON_TRANSFORMER, ETownType::NECROPOLIS); + + case EMarketMode::RESOURCE_SKILL: + return hasBuilt(BuildingID::MAGIC_UNIVERSITY, ETownType::CONFLUX); + default: + assert(0); + return false; + } +} + +std::vector CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode) const +{ + if(mode == EMarketMode::RESOURCE_ARTIFACT) + { + std::vector ret; + for(const CArtifact *a : merchantArtifacts) + if(a) + ret.push_back(a->id); + else + ret.push_back(-1); + return ret; + } + else if ( mode == EMarketMode::RESOURCE_SKILL ) + { + return universitySkills; + } + else + return IMarket::availableItemsIds(mode); +} + +void CGTownInstance::setType(si32 ID, si32 subID) +{ + assert(ID == Obj::TOWN); // just in case + CGObjectInstance::setType(ID, subID); + town = VLC->townh->factions[subID]->town; + randomizeArmy(subID); + updateAppearance(); +} + +void CGTownInstance::updateAppearance() +{ + if (!hasFort()) + appearance.animationFile = town->clientInfo.advMapVillage; + else if(hasCapitol()) + appearance.animationFile = town->clientInfo.advMapCapitol; + else + appearance.animationFile = town->clientInfo.advMapCastle; +} + +std::string CGTownInstance::nodeName() const +{ + return "Town (" + (town ? town->faction->name : "unknown") + ") of " + name; +} + +void CGTownInstance::deserializationFix() +{ + attachTo(&townAndVis); + + //Hero is already handled by CGameState::attachArmedObjects + +// if(visitingHero) +// visitingHero->attachTo(&townAndVis); +// if(garrisonHero) +// garrisonHero->attachTo(this); +} + +void CGTownInstance::updateMoraleBonusFromArmy() +{ + Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE))); + if(!b) + { + b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1); + addNewBonus(b); + } + + if (garrisonHero) + b->val = 0; + else + CArmedInstance::updateMoraleBonusFromArmy(); +} + +void CGTownInstance::recreateBuildingsBonuses() +{ + static TPropagatorPtr playerProp(new CPropagatorNodeType(PLAYER)); + + BonusList bl; + getExportedBonusList().getBonuses(bl, Selector::sourceType(Bonus::TOWN_STRUCTURE)); + for(Bonus *b : bl) + removeBonus(b); + + //tricky! -> checks tavern only if no bratherhood of sword or not a castle + if(subID != ETownType::CASTLE || !addBonusIfBuilt(BuildingID::BROTHERHOOD, Bonus::MORALE, +2)) + addBonusIfBuilt(BuildingID::TAVERN, Bonus::MORALE, +1); + + if(subID == ETownType::CASTLE) //castle + { + addBonusIfBuilt(BuildingID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp); + addBonusIfBuilt(BuildingID::GRAIL, Bonus::MORALE, +2, playerProp); //colossus + } + else if(subID == ETownType::RAMPART) //rampart + { + addBonusIfBuilt(BuildingID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2); //fountain of fortune + addBonusIfBuilt(BuildingID::GRAIL, Bonus::LUCK, +2, playerProp); //guardian spirit + } + else if(subID == ETownType::TOWER) //tower + { + addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +15, PrimarySkill::KNOWLEDGE); //grail + } + else if(subID == ETownType::INFERNO) //Inferno + { + addBonusIfBuilt(BuildingID::STORMCLOUDS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER); //Brimstone Clouds + } + else if(subID == ETownType::NECROPOLIS) //necropolis + { + addBonusIfBuilt(BuildingID::COVER_OF_DARKNESS, Bonus::DARKNESS, +20); + addBonusIfBuilt(BuildingID::NECROMANCY_AMPLIFIER, Bonus::SECONDARY_SKILL_PREMY, +10, playerProp, SecondarySkill::NECROMANCY); //necromancy amplifier + addBonusIfBuilt(BuildingID::GRAIL, Bonus::SECONDARY_SKILL_PREMY, +20, playerProp, SecondarySkill::NECROMANCY); //Soul prison + } + else if(subID == ETownType::DUNGEON) //Dungeon + { + addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +12, PrimarySkill::SPELL_POWER); //grail + } + else if(subID == ETownType::STRONGHOLD) //Stronghold + { + addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +20, PrimarySkill::ATTACK); //grail + } + else if(subID == ETownType::FORTRESS) //Fortress + { + addBonusIfBuilt(BuildingID::GLYPHS_OF_FEAR, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE); //Glyphs of Fear + addBonusIfBuilt(BuildingID::BLOOD_OBELISK, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK); //Blood Obelisk + addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail + addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail + } + else if(subID == ETownType::CONFLUX) + { + + } +} + +bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype /*= -1*/) +{ + static auto emptyPropagator = TPropagatorPtr(); + return addBonusIfBuilt(building, type, val, emptyPropagator, subtype); +} + +bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype /*= -1*/) +{ + if(hasBuilt(building)) + { + std::ostringstream descr; + descr << town->buildings.at(building)->Name() << " "; + if(val > 0) + descr << "+"; + else if(val < 0) + descr << "-"; + descr << val; + + Bonus *b = new Bonus(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, descr.str(), subtype); + if(prop) + b->addPropagator(prop); + addNewBonus(b); + return true; + } + + return false; +} + +void CGTownInstance::setVisitingHero(CGHeroInstance *h) +{ + assert(!!visitingHero == !h); + if(h) + { + PlayerState *p = cb->gameState()->getPlayer(h->tempOwner); + assert(p); + h->detachFrom(p); + h->attachTo(&townAndVis); + visitingHero = h; + h->visitedTown = this; + h->inTownGarrison = false; + } + else + { + PlayerState *p = cb->gameState()->getPlayer(visitingHero->tempOwner); + visitingHero->visitedTown = nullptr; + visitingHero->detachFrom(&townAndVis); + visitingHero->attachTo(p); + visitingHero = nullptr; + } +} + +void CGTownInstance::setGarrisonedHero(CGHeroInstance *h) +{ + assert(!!garrisonHero == !h); + if(h) + { + PlayerState *p = cb->gameState()->getPlayer(h->tempOwner); + assert(p); + h->detachFrom(p); + h->attachTo(this); + garrisonHero = h; + h->visitedTown = this; + h->inTownGarrison = true; + } + else + { + PlayerState *p = cb->gameState()->getPlayer(garrisonHero->tempOwner); + garrisonHero->visitedTown = nullptr; + garrisonHero->inTownGarrison = false; + garrisonHero->detachFrom(this); + garrisonHero->attachTo(p); + garrisonHero = nullptr; + } + updateMoraleBonusFromArmy(); //avoid giving morale bonus for same army twice +} + +bool CGTownInstance::armedGarrison() const +{ + return stacksCount() || garrisonHero; +} + +int CGTownInstance::getTownLevel() const +{ + // count all buildings that are not upgrades + return boost::range::count_if(builtBuildings, [&](const BuildingID & build) + { + return town->buildings.at(build) && town->buildings.at(build)->upgrade == -1; + }); +} + +CBonusSystemNode * CGTownInstance::whatShouldBeAttached() +{ + return &townAndVis; +} + +const CArmedInstance * CGTownInstance::getUpperArmy() const +{ + if(garrisonHero) + return garrisonHero; + return this; +} + +bool CGTownInstance::hasBuilt(BuildingID buildingID, int townID) const +{ + if (townID == town->faction->index || townID == ETownType::ANY) + return hasBuilt(buildingID); + return false; +} + +bool CGTownInstance::hasBuilt(BuildingID buildingID) const +{ + return vstd::contains(builtBuildings, buildingID); +} + +void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si32 structureInstanceID ) const +{ + if(visitingHero == h) + cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_VISITING_HERO, structureInstanceID); //add to visitors + else if(garrisonHero == h) + cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_GARRISONED_HERO, structureInstanceID); //then it must be garrisoned hero + else + { + //should never ever happen + logGlobal->errorStream() << "Cannot add hero " << h->name << " to visitors of structure #" << structureInstanceID; + assert(0); + } +} + +void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == 0) + { + removeCapitols(hero->getOwner()); + cb->setOwner (this, hero->tempOwner); //give control after checkout is done + FoWChange fw; + fw.player = hero->tempOwner; + fw.mode = 1; + getSightTiles (fw.tiles); //update visibility for castle structures + cb->sendAndApply (&fw); + } +} + +COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN) +{ + ID = index; + town = TOWN; + id = town->bonusingBuildings.size(); +} +void COPWBonus::setProperty(ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::VISITORS: + visitors.insert(val); + break; + case ObjProperty::STRUCTURE_CLEAR_VISITORS: + visitors.clear(); + break; + } +} +void COPWBonus::onHeroVisit (const CGHeroInstance * h) const +{ + ObjectInstanceID heroID = h->id; + if (town->hasBuilt(ID)) + { + InfoWindow iw; + iw.player = h->tempOwner; + switch (town->subID) + { + case ETownType::CASTLE: //Stables + if (!h->hasBonusFrom(Bonus::OBJECT, Obj::STABLES)) //does not stack with advMap Stables + { + GiveBonus gb; + gb.bonus = Bonus(Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, Bonus::OBJECT, 600, 94, VLC->generaltexth->arraytxt[100]); + gb.id = heroID.getNum(); + cb->giveHeroBonus(&gb); + iw.text << VLC->generaltexth->allTexts[580]; + cb->showInfoDialog(&iw); + } + break; + case ETownType::DUNGEON: //Mana Vortex + if (visitors.empty() && h->mana <= h->manaLimit() * 2) + { + cb->setManaPoints (heroID, 2 * h->manaLimit()); + //TODO: investigate line below + //cb->setObjProperty (town->id, ObjProperty::VISITED, true); + iw.text << VLC->generaltexth->allTexts[579]; + cb->showInfoDialog(&iw); + town->addHeroToStructureVisitors(h, id); + } + break; + } + } +} +CTownBonus::CTownBonus (BuildingID index, CGTownInstance *TOWN) +{ + ID = index; + town = TOWN; + id = town->bonusingBuildings.size(); +} +void CTownBonus::setProperty (ui8 what, ui32 val) +{ + if(what == ObjProperty::VISITORS) + visitors.insert(ObjectInstanceID(val)); +} +void CTownBonus::onHeroVisit (const CGHeroInstance * h) const +{ + ObjectInstanceID heroID = h->id; + if (town->hasBuilt(ID) && visitors.find(heroID) == visitors.end()) + { + InfoWindow iw; + PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK; + int val=0, mid=0; + switch (ID) + { + case BuildingID::SPECIAL_4: + switch(town->subID) + { + case ETownType::TOWER: //wall + what = PrimarySkill::KNOWLEDGE; + val = 1; + mid = 581; + iw.components.push_back (Component(Component::PRIM_SKILL, 3, 1, 0)); + break; + case ETownType::INFERNO: //order of fire + what = PrimarySkill::SPELL_POWER; + val = 1; + mid = 582; + iw.components.push_back (Component(Component::PRIM_SKILL, 2, 1, 0)); + break; + case ETownType::STRONGHOLD://hall of Valhalla + what = PrimarySkill::ATTACK; + val = 1; + mid = 584; + iw.components.push_back (Component(Component::PRIM_SKILL, 0, 1, 0)); + break; + case ETownType::DUNGEON://academy of battle scholars + what = PrimarySkill::EXPERIENCE; + val = h->calculateXp(1000); + mid = 583; + iw.components.push_back (Component(Component::EXPERIENCE, 0, val, 0)); + break; + } + break; + case BuildingID::SPECIAL_1: + switch(town->subID) + { + case ETownType::FORTRESS: //cage of warlords + what = PrimarySkill::DEFENSE; + val = 1; + mid = 585; + iw.components.push_back (Component(Component::PRIM_SKILL, 1, 1, 0)); + break; + } + break; + } + assert(mid); + iw.player = cb->getOwner(heroID); + iw.text << VLC->generaltexth->allTexts[mid]; + cb->showInfoDialog(&iw); + cb->changePrimSkill (cb->getHero(heroID), what, val); + town->addHeroToStructureVisitors(h, id); + } +} + +GrowthInfo::Entry::Entry(const std::string &format, int _count) + : count(_count) +{ + description = boost::str(boost::format(format) % count); +} + +GrowthInfo::Entry::Entry(int subID, BuildingID building, int _count) + : count(_count) +{ + description = boost::str(boost::format("%s %+d") % VLC->townh->factions[subID]->town->buildings.at(building)->Name() % count); +} + +CTownAndVisitingHero::CTownAndVisitingHero() +{ + setNodeType(TOWN_AND_VISITOR); +} + +int GrowthInfo::totalGrowth() const +{ + int ret = 0; + for(const Entry &entry : entries) + ret += entry.count; + + return ret; +} diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h new file mode 100644 index 000000000..6ad935bc3 --- /dev/null +++ b/lib/mapObjects/CGTownInstance.h @@ -0,0 +1,248 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CGMarket.h" // For IMarket interface +#include "CGArmedInstance.h" + +#include "../CTownHandler.h" // For CTown + +/* + * CObjectHandler.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 CCastleEvent; + +class DLL_LINKAGE CSpecObjInfo +{ +public: + virtual ~CSpecObjInfo(){}; + PlayerColor player; //owner +}; + +class DLL_LINKAGE CCreGenAsCastleInfo : public virtual CSpecObjInfo +{ +public: + bool asCastle; + ui32 identifier; + ui8 castles[2]; //allowed castles +}; + +class DLL_LINKAGE CCreGenLeveledInfo : public virtual CSpecObjInfo +{ +public: + ui8 minLevel, maxLevel; //minimal and maximal level of creature in dwelling: <0, 6> +}; + +class DLL_LINKAGE CCreGenLeveledCastleInfo : public CCreGenAsCastleInfo, public CCreGenLeveledInfo +{ +}; + +class DLL_LINKAGE CGDwelling : public CArmedInstance +{ +public: + typedef std::vector > > TCreaturesSet; + + CSpecObjInfo * info; //h3m info about dewlling + TCreaturesSet creatures; //creatures[level] -> + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this) & creatures; + } + + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + void newTurn() const override; + void setProperty(ui8 what, ui32 val) override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + +private: + void heroAcceptsCreatures(const CGHeroInstance *h) const; +}; + +class DLL_LINKAGE CGTownBuilding : public IObjectInterface +{ +///basic class for town structures handled as map objects +public: + BuildingID ID; //from buildig list + si32 id; //identifies its index on towns vector + CGTownInstance *town; + + template void serialize(Handler &h, const int version) + { + h & ID & id; + } +}; +class DLL_LINKAGE COPWBonus : public CGTownBuilding +{///used for OPW bonusing structures +public: + std::set visitors; + void setProperty(ui8 what, ui32 val) override; + void onHeroVisit (const CGHeroInstance * h) const override; + + COPWBonus (BuildingID index, CGTownInstance *TOWN); + COPWBonus (){ID = BuildingID::NONE; town = nullptr;}; + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & visitors; + } +}; + +class DLL_LINKAGE CTownBonus : public CGTownBuilding +{ +///used for one-time bonusing structures +///feel free to merge inheritance tree +public: + std::set visitors; + void setProperty(ui8 what, ui32 val) override; + void onHeroVisit (const CGHeroInstance * h) const override; + + CTownBonus (BuildingID index, CGTownInstance *TOWN); + CTownBonus (){ID = BuildingID::NONE; town = nullptr;}; + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & visitors; + } +}; + +class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode +{ +public: + CTownAndVisitingHero(); +}; + +struct DLL_LINKAGE GrowthInfo +{ + struct Entry + { + int count; + std::string description; + Entry(const std::string &format, int _count); + Entry(int subID, BuildingID building, int _count); + }; + + std::vector entries; + int totalGrowth() const; +}; + +class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket +{ +public: + enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3}; + + CTownAndVisitingHero townAndVis; + const CTown * town; + std::string name; // name of town + si32 builded; //how many buildings has been built this turn + si32 destroyed; //how many buildings has been destroyed this turn + ConstTransitivePtr garrisonHero, visitingHero; + ui32 identifier; //special identifier from h3m (only > RoE maps) + si32 alignment; + std::set forbiddenBuildings, builtBuildings; + std::vector bonusingBuildings; + std::vector possibleSpells, obligatorySpells; + std::vector > spells; //spells[level] -> vector of spells, first will be available in guild + std::list events; + std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond); + + ////////////////////////////////////////////////////////////////////////// + static std::vector merchantArtifacts; //vector of artifacts available at Artifact merchant, NULLs possible (for making empty space when artifact is bought) + static std::vector universitySkills;//skills for university of magic + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + h & static_cast(*this); + h & name & builded & destroyed & identifier; + h & garrisonHero & visitingHero; + h & alignment & forbiddenBuildings & builtBuildings & bonusValue + & possibleSpells & obligatorySpells & spells & /*strInfo & */events & bonusingBuildings; + + for (std::vector::iterator i = bonusingBuildings.begin(); i!=bonusingBuildings.end(); i++) + (*i)->town = this; + + h & town & townAndVis; + BONUS_TREE_DESERIALIZATION_FIX + + vstd::erase_if(builtBuildings, [this](BuildingID building) -> bool + { + if(!town->buildings.count(building) || !town->buildings.at(building)) + { + logGlobal->errorStream() << boost::format("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s") + % name % pos % building; + return true; + } + return false; + }); + } + ////////////////////////////////////////////////////////////////////////// + + CBonusSystemNode *whatShouldBeAttached() override; + std::string nodeName() const override; + void updateMoraleBonusFromArmy() override; + void deserializationFix(); + void recreateBuildingsBonuses(); + bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr &prop, int subtype = -1); //returns true if building is built and bonus has been added + bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype = -1); //convienence version of above + void setVisitingHero(CGHeroInstance *h); + void setGarrisonedHero(CGHeroInstance *h); + const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself + + ////////////////////////////////////////////////////////////////////////// + + ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used + int3 getSightCenter() const override; //"center" tile from which the sight distance is calculated + int getSightRadious() const override; //returns sight distance + int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral + void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed + int getMarketEfficiency() const override; //=market count + bool allowsTrade(EMarketMode::EMarketMode mode) const; + std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; + + void setType(si32 ID, si32 subID); + void updateAppearance(); + + ////////////////////////////////////////////////////////////////////////// + + bool needsLastStack() const; + CGTownInstance::EFortLevel fortLevel() const; + int hallLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol + int mageGuildLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol + int getHordeLevel(const int & HID) const; //HID - 0 or 1; returns creature level or -1 if that horde structure is not present + int creatureGrowth(const int & level) const; + GrowthInfo getGrowthInfo(int level) const; + bool hasFort() const; + bool hasCapitol() const; + //checks if building is constructed and town has same subID + bool hasBuilt(BuildingID buildingID) const; + bool hasBuilt(BuildingID buildingID, int townID) const; + TResources dailyIncome() const; //calculates daily income of this town + int spellsAtLevel(int level, bool checkGuild) const; //levels are counted from 1 (1 - 5) + bool armedGarrison() const; //true if town has creatures in garrison or garrisoned hero + int getTownLevel() const; + + void removeCapitols (PlayerColor owner) const; + void addHeroToStructureVisitors(const CGHeroInstance *h, si32 structureInstanceID) const; //hero must be visiting or garrisoned in town + + CGTownInstance(); + virtual ~CGTownInstance(); + + ///IObjectInterface overrides + void newTurn() const override; + void onHeroVisit(const CGHeroInstance * h) const override; + void onHeroLeave(const CGHeroInstance * h) const override; + void initObj() override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; +protected: + void setPropertyDer(ui8 what, ui32 val) override; +}; diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 7077c4974..86d20d519 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -7,11 +7,11 @@ #include "GameConstants.h" #include "StringConstants.h" #include "CGeneralTextHandler.h" -#include "CObjectHandler.h" #include "CModHandler.h" #include "JsonNode.h" #include "CRewardableConstructor.h" +#include "MapObjects.h" /* * CObjectClassesHandler.cpp, part of VCMI engine diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index eea4bae4a..17b072321 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -1,10 +1,11 @@ #pragma once -#include "GameConstants.h" -#include "../lib/ConstTransitivePtr.h" -#include "IHandlerBase.h" #include "ObjectTemplate.h" +#include "../GameConstants.h" +#include "../ConstTransitivePtr.h" +#include "../IHandlerBase.h" + /* * CObjectClassesHandler.h, part of VCMI engine * diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 72d288fd0..bd05c0bda 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -11,38 +11,16 @@ #include "StdInc.h" #include "CObjectHandler.h" -#include "CObjectClassesHandler.h" -#include "CGeneralTextHandler.h" -#include "CObjectClassesHandler.h" -#include "CHeroHandler.h" -#include "CSpellHandler.h" -#include "CModHandler.h" -#include "../client/CSoundBase.h" -#include "CTownHandler.h" -#include "CCreatureHandler.h" -#include "VCMI_Lib.h" -#include "IGameCallback.h" -#include "CGameState.h" #include "NetPacks.h" -#include "StartInfo.h" -#include "mapping/CMap.h" -#include -#include "CBuildingHandler.h" -#include "JsonNode.h" -#include "filesystem/Filesystem.h" +#include "CGeneralTextHandler.h" +#include "CHeroHandler.h" +#include "../client/CSoundBase.h" + +#include "CObjectClassesHandler.h" using namespace boost::assign; -std::map > > CGTeleport::objs; -std::vector > CGTeleport::gates; IGameCallback * IObjectInterface::cb = nullptr; -std::map > CGKeys::playerKeyMap; -std::map > CGMagi::eyelist; -ui8 CGObelisk::obeliskCount; //how many obelisks are on map -std::map CGObelisk::visited; //map: team_id => how many obelisks has been visited - -std::vector CGTownInstance::merchantArtifacts; -std::vector CGTownInstance::universitySkills; ///helpers static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1) @@ -119,45 +97,16 @@ void IObjectInterface::preInit() {} void IObjectInterface::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - -} +{} void IObjectInterface::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - -} +{} void IObjectInterface::garrisonDialogClosed(const CGHeroInstance *hero) const -{ - -} +{} void IObjectInterface::heroLevelUpDone(const CGHeroInstance *hero) const -{ - -} - -void CPlayersVisited::setPropertyDer( ui8 what, ui32 val ) -{ - if(what == 10) - players.insert(PlayerColor(val)); -} - -bool CPlayersVisited::wasVisited( PlayerColor player ) const -{ - return vstd::contains(players,player); -} - -bool CPlayersVisited::wasVisited( TeamID team ) const -{ - for(auto i : players) - { - if(cb->getPlayer(i)->team == team) - return true; - } - return false; -} +{} // Bank helper. Find the creature ID and their number, and store the // result in storage (either guards or reward creatures). @@ -556,5351 +505,6 @@ bool CGObjectInstanceBySubIdFinder::operator()(CGObjectInstance * obj) const return this->obj->subID == obj->subID; } -static int lowestSpeed(const CGHeroInstance * chi) -{ - if(!chi->Slots().size()) - { - logGlobal->errorStream() << "Error! Hero " << chi->id.getNum() << " ("<name<<") has no army!"; - return 20; - } - auto i = chi->Slots().begin(); - //TODO? should speed modifiers (eg from artifacts) affect hero movement? - int ret = (i++)->second->valOfBonuses(Bonus::STACKS_SPEED); - for (;i!=chi->Slots().end();i++) - { - ret = std::min(ret, i->second->valOfBonuses(Bonus::STACKS_SPEED)); - } - return ret; -} - -ui32 CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const -{ - //base move cost - unsigned ret = 100; - - //if there is road both on dest and src tiles - use road movement cost - if(dest.roadType != ERoadType::NO_ROAD && from.roadType != ERoadType::NO_ROAD) - { - int road = std::min(dest.roadType,from.roadType); //used road ID - switch(road) - { - case ERoadType::DIRT_ROAD: - ret = 75; - break; - case ERoadType::GRAVEL_ROAD: - ret = 65; - break; - case ERoadType::COBBLESTONE_ROAD: - ret = 50; - break; - default: - logGlobal->errorStream() << "Unknown road type: " << road << "... Something wrong!"; - break; - } - } - else - { - //FIXME: in H3 presence of Nomad in army will remove terrain penalty for sand. Bonus not implemented in VCMI - - // NOTE: in H3 neutral stacks will ignore terrain penalty only if placed as topmost stack(s) in hero army. - // This is clearly bug in H3 however intended behaviour is not clear. - // Current VCMI behaviour will ignore neutrals in calculations so army in VCMI - // will always have best penalty without any influence from player-defined stacks order - - bool nativeArmy = true; - for(auto stack : stacks) - { - int nativeTerrain = VLC->townh->factions[stack.second->type->faction]->nativeTerrain; - - if (nativeTerrain != -1 && nativeTerrain != from.terType) - { - nativeArmy = false; - break; - } - } - if (!nativeArmy) - ret = VLC->heroh->terrCosts[from.terType]; - } - return ret; -} - -int3 CGHeroInstance::convertPosition(int3 src, bool toh3m) //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest -{ - if (toh3m) - { - src.x+=1; - return src; - } - else - { - src.x-=1; - return src; - } -} -int3 CGHeroInstance::getPosition(bool h3m) const //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' -{ - if (h3m) - { - return pos; - } - else - { - return convertPosition(pos,false); - } -} - -bool CGHeroInstance::canWalkOnSea() const -{ - return hasBonusOfType(Bonus::FLYING_MOVEMENT) || hasBonusOfType(Bonus::WATER_WALKING); -} - -ui8 CGHeroInstance::getSecSkillLevel(SecondarySkill skill) const -{ - for(auto & elem : secSkills) - if(elem.first == skill) - return elem.second; - return 0; -} - -void CGHeroInstance::setSecSkillLevel(SecondarySkill which, int val, bool abs) -{ - if(getSecSkillLevel(which) == 0) - { - secSkills.push_back(std::pair(which, val)); - updateSkill(which, val); - } - else - { - for (auto & elem : secSkills) - { - if(elem.first == which) - { - if(abs) - elem.second = val; - else - elem.second += val; - - if(elem.second > 3) //workaround to avoid crashes when same sec skill is given more than once - { - logGlobal->warnStream() << "Warning: Skill " << which << " increased over limit! Decreasing to Expert."; - elem.second = 3; - } - updateSkill(which, elem.second); //when we know final value - } - } - } -} - -bool CGHeroInstance::canLearnSkill() const -{ - return secSkills.size() < GameConstants::SKILL_PER_HERO; -} - -int CGHeroInstance::maxMovePoints(bool onLand) const -{ - int base; - - if(onLand) - { - // used function is f(x) = 66.6x + 1300, rounded to second digit, where x is lowest speed in army - static const int baseSpeed = 1300; // base speed from creature with 0 speed - - int armySpeed = lowestSpeed(this) * 20 / 3; - - base = armySpeed * 10 + baseSpeed; // separate *10 is intentional to receive same rounding as in h3 - vstd::abetween(base, 1500, 2000); // base speed is limited by these values - } - else - { - base = 1500; //on water base movement is always 1500 (speed of army doesn't matter) - } - - const Bonus::BonusType bt = onLand ? Bonus::LAND_MOVEMENT : Bonus::SEA_MOVEMENT; - const int bonus = valOfBonuses(Bonus::MOVEMENT) + valOfBonuses(bt); - - const int subtype = onLand ? SecondarySkill::LOGISTICS : SecondarySkill::NAVIGATION; - const double modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, subtype) / 100.0; - - return int(base* (1+modifier)) + bonus; -} - -CGHeroInstance::CGHeroInstance() - : IBoatGenerator(this) -{ - setNodeType(HERO); - ID = Obj::HERO; - tacticFormationEnabled = inTownGarrison = false; - mana = movement = portrait = level = -1; - isStanding = true; - moveDir = 4; - exp = 0xffffffff; - visitedTown = nullptr; - type = nullptr; - boat = nullptr; - commander = nullptr; - sex = 0xff; - secSkills.push_back(std::make_pair(SecondarySkill::DEFAULT, -1)); -} - -void CGHeroInstance::initHero(HeroTypeID SUBID) -{ - subID = SUBID.getNum(); - initHero(); -} - -void CGHeroInstance::setType(si32 ID, si32 subID) -{ - assert(ID == Obj::HERO); // just in case - CGObjectInstance::setType(ID, subID); - type = VLC->heroh->heroes[subID]; - portrait = type->imageIndex; - randomizeArmy(type->heroClass->faction); -} - -void CGHeroInstance::initHero() -{ - assert(validTypes(true)); - if(!type) - type = VLC->heroh->heroes[subID]; - - if (ID == Obj::HERO) - appearance = VLC->objtypeh->getHandlerFor(Obj::HERO, type->heroClass->id)->getTemplates().front(); - - if(!vstd::contains(spells, SpellID::PRESET)) //hero starts with a spell - { - for(auto spellID : type->spells) - spells.insert(spellID); - } - else //remove placeholder - spells -= SpellID::PRESET; - - if(!getArt(ArtifactPosition::MACH4) && !getArt(ArtifactPosition::SPELLBOOK) && type->haveSpellBook) //no catapult means we haven't read pre-existent set -> use default rules for spellbook - putArtifact(ArtifactPosition::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0)); - - if(!getArt(ArtifactPosition::MACH4)) - putArtifact(ArtifactPosition::MACH4, CArtifactInstance::createNewArtifactInstance(3)); //everyone has a catapult - - if(portrait < 0 || portrait == 255) - portrait = type->imageIndex; - if(!hasBonus(Selector::sourceType(Bonus::HERO_BASE_SKILL))) - { - for(int g=0; g(g), type->heroClass->primarySkillInitial[g]); - } - } - if(secSkills.size() == 1 && secSkills[0] == std::pair(SecondarySkill::DEFAULT, -1)) //set secondary skills to default - secSkills = type->secSkillsInit; - if (!name.length()) - name = type->name; - - if (sex == 0xFF)//sex is default - sex = type->sex; - - setFormation(false); - if (!stacksCount()) //standard army//initial army - { - initArmy(); - } - assert(validTypes()); - - level = 1; - if(exp == 0xffffffff) - { - initExp(); - } - else - { - levelUpAutomatically(); - } - - if (VLC->modh->modules.COMMANDERS && !commander) - { - commander = new CCommanderInstance(type->heroClass->commander->idNumber); - commander->setArmyObj (castToArmyObj()); //TODO: separate function for setting commanders - commander->giveStackExp (exp); //after our exp is set - } - - if (mana < 0) - mana = manaLimit(); -} - -void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= nullptr*/) -{ - if(!dst) - dst = this; - - int howManyStacks = 0; //how many stacks will hero receives <1 - 3> - int pom = cb->gameState()->getRandomGenerator().nextInt(99); - int warMachinesGiven = 0; - - if(pom < 9) - howManyStacks = 1; - else if(pom < 79) - howManyStacks = 2; - else - howManyStacks = 3; - - vstd::amin(howManyStacks, type->initialArmy.size()); - - for(int stackNo=0; stackNo < howManyStacks; stackNo++) - { - auto & stack = type->initialArmy[stackNo]; - - int count = cb->gameState()->getRandomGenerator().nextInt(stack.minAmount, stack.maxAmount); - - if(stack.creature >= CreatureID::CATAPULT && - stack.creature <= CreatureID::ARROW_TOWERS) //war machine - { - warMachinesGiven++; - if(dst != this) - continue; - - int slot = -1; - ArtifactID aid = ArtifactID::NONE; - switch (stack.creature) - { - case CreatureID::CATAPULT: - slot = ArtifactPosition::MACH4; - aid = ArtifactID::CATAPULT; - break; - default: - aid = CArtHandler::creatureToMachineID(stack.creature); - slot = 9 + aid; - break; - } - auto convSlot = ArtifactPosition(slot); - if(!getArt(convSlot)) - putArtifact(convSlot, CArtifactInstance::createNewArtifactInstance(aid)); - else - logGlobal->warnStream() << "Hero " << name << " already has artifact at " << slot << ", omitting giving " << aid; - } - else - dst->setCreature(SlotID(stackNo-warMachinesGiven), stack.creature, count); - } -} - -CGHeroInstance::~CGHeroInstance() -{ - commander.dellNull(); -} - -bool CGHeroInstance::needsLastStack() const -{ - return true; -} - -void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const -{ - if(h == this) return; //exclude potential self-visiting - - if (ID == Obj::HERO) - { - if( cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner)) //our or ally hero - { - //exchange - cb->heroExchange(h->id, id); - } - else //battle - { - if(visitedTown) //we're in town - visitedTown->onHeroVisit(h); //town will handle attacking - else - cb->startBattleI(h, this); - } - } - else if(ID == Obj::PRISON) - { - int txt_id; - - if (cb->getHeroCount(h->tempOwner, false) < VLC->modh->settings.MAX_HEROES_ON_MAP_PER_PLAYER)//GameConstants::MAX_HEROES_PER_PLAYER) //free hero slot - { - cb->changeObjPos(id,pos+int3(1,0,0),0); - //update hero parameters - SetMovePoints smp; - smp.hid = id; - smp.val = maxMovePoints (true); //TODO: hota prison on water? - cb->setMovePoints (&smp); - cb->setManaPoints (id, manaLimit()); - - cb->setObjProperty(id, ObjProperty::ID, Obj::HERO); //set ID to 34 - cb->giveHero(id,h->tempOwner); //recreates def and adds hero to player - - txt_id = 102; - } - else //already 8 wandering heroes - { - txt_id = 103; - } - - showInfoDialog(h,txt_id,soundBase::ROGUE); - } -} - -const std::string & CGHeroInstance::getHoverText() const -{ - if(ID != Obj::PRISON) - { - hoverName = VLC->generaltexth->allTexts[15]; - boost::algorithm::replace_first(hoverName,"%s",name); - boost::algorithm::replace_first(hoverName,"%s", type->heroClass->name); - return hoverName; - } - else - hoverName = VLC->objtypeh->getObjectName(ID); - - return hoverName; -} - -const std::string & CGHeroInstance::getBiography() const -{ - if (biography.length()) - return biography; - return type->biography; -} - -ui8 CGHeroInstance::maxlevelsToMagicSchool() const -{ - return type->heroClass->isMagicHero() ? 3 : 4; -} -ui8 CGHeroInstance::maxlevelsToWisdom() const -{ - return type->heroClass->isMagicHero() ? 3 : 6; -} - -void CGHeroInstance::SecondarySkillsInfo::resetMagicSchoolCounter() -{ - magicSchoolCounter = 1; -} -void CGHeroInstance::SecondarySkillsInfo::resetWisdomCounter() -{ - wisdomCounter = 1; -} - -void CGHeroInstance::initObj() -{ - blockVisit = true; - auto hs = new HeroSpecial(); - hs->setNodeType(CBonusSystemNode::SPECIALTY); - attachTo(hs); //do we ever need to detach it? - - if(!type) - initHero(); //TODO: set up everything for prison before specialties are configured - - skillsInfo.rand.setSeed(cb->gameState()->getRandomGenerator().nextInt()); - skillsInfo.resetMagicSchoolCounter(); - skillsInfo.resetWisdomCounter(); - - for(const auto &spec : type->spec) //TODO: unfity with bonus system - { - auto bonus = new Bonus(); - bonus->val = spec.val; - bonus->sid = id.getNum(); //from the hero, specialty has no unique id - bonus->duration = Bonus::PERMANENT; - bonus->source = Bonus::HERO_SPECIAL; - switch (spec.type) - { - case 1:// creature specialty - { - hs->growsWithLevel = true; - - const CCreature &specCreature = *VLC->creh->creatures[spec.additionalinfo]; //creature in which we have specialty - - //int creLevel = specCreature.level; - //if(!creLevel) - //{ - // if(spec.additionalinfo == 146) - // creLevel = 5; //treat ballista as 5-level - // else - // { - // logGlobal->warnStream() << "Warning: unknown level of " << specCreature.namePl; - // continue; - // } - //} - - //bonus->additionalInfo = spec.additionalinfo; //creature id, should not be used again - this works only with limiter - bonus->limiter.reset(new CCreatureTypeLimiter (specCreature, true)); //with upgrades - bonus->type = Bonus::PRIMARY_SKILL; - bonus->valType = Bonus::ADDITIVE_VALUE; - - bonus->subtype = PrimarySkill::ATTACK; - hs->addNewBonus(bonus); - - bonus = new Bonus(*bonus); - bonus->subtype = PrimarySkill::DEFENSE; - hs->addNewBonus(bonus); - //values will be calculated later - - bonus = new Bonus(*bonus); - bonus->type = Bonus::STACKS_SPEED; - bonus->val = 1; //+1 speed - hs->addNewBonus(bonus); - } - break; - case 2://secondary skill - hs->growsWithLevel = true; - bonus->type = Bonus::SPECIAL_SECONDARY_SKILL; //needs to be recalculated with level, based on this value - bonus->valType = Bonus::BASE_NUMBER; // to receive nonzero value - bonus->subtype = spec.subtype; //skill id - bonus->val = spec.val; //value per level, in percent - hs->addNewBonus(bonus); - bonus = new Bonus(*bonus); - - switch (spec.additionalinfo) - { - case 0: //normal - bonus->valType = Bonus::PERCENT_TO_BASE; - break; - case 1: //when it's navigation or there's no 'base' at all - bonus->valType = Bonus::PERCENT_TO_ALL; - break; - } - bonus->type = Bonus::SECONDARY_SKILL_PREMY; //value will be calculated later - hs->addNewBonus(bonus); - break; - case 3://spell damage bonus, level dependent but calculated elsewhere - bonus->type = Bonus::SPECIAL_SPELL_LEV; - bonus->subtype = spec.subtype; - hs->addNewBonus(bonus); - break; - case 4://creature stat boost - switch (spec.subtype) - { - case 1://attack - bonus->type = Bonus::PRIMARY_SKILL; - bonus->subtype = PrimarySkill::ATTACK; - break; - case 2://defense - bonus->type = Bonus::PRIMARY_SKILL; - bonus->subtype = PrimarySkill::DEFENSE; - break; - case 3: - bonus->type = Bonus::CREATURE_DAMAGE; - bonus->subtype = 0; //both min and max - break; - case 4://hp - bonus->type = Bonus::STACK_HEALTH; - break; - case 5: - bonus->type = Bonus::STACKS_SPEED; - break; - default: - continue; - } - bonus->additionalInfo = spec.additionalinfo; //creature id - bonus->valType = Bonus::ADDITIVE_VALUE; - bonus->limiter.reset(new CCreatureTypeLimiter (*VLC->creh->creatures[spec.additionalinfo], true)); - hs->addNewBonus(bonus); - break; - case 5://spell damage bonus in percent - bonus->type = Bonus::SPECIFIC_SPELL_DAMAGE; - bonus->valType = Bonus::BASE_NUMBER; // current spell system is screwed - bonus->subtype = spec.subtype; //spell id - hs->addNewBonus(bonus); - break; - case 6://damage bonus for bless (Adela) - bonus->type = Bonus::SPECIAL_BLESS_DAMAGE; - bonus->subtype = spec.subtype; //spell id if you ever wanted to use it otherwise - bonus->additionalInfo = spec.additionalinfo; //damage factor - hs->addNewBonus(bonus); - break; - case 7://maxed mastery for spell - bonus->type = Bonus::MAXED_SPELL; - bonus->subtype = spec.subtype; //spell i - hs->addNewBonus(bonus); - break; - case 8://peculiar spells - enchantments - bonus->type = Bonus::SPECIAL_PECULIAR_ENCHANT; - bonus->subtype = spec.subtype; //spell id - bonus->additionalInfo = spec.additionalinfo;//0, 1 for Coronius - hs->addNewBonus(bonus); - break; - case 9://upgrade creatures - { - const auto &creatures = VLC->creh->creatures; - bonus->type = Bonus::SPECIAL_UPGRADE; - bonus->subtype = spec.subtype; //base id - bonus->additionalInfo = spec.additionalinfo; //target id - hs->addNewBonus(bonus); - bonus = new Bonus(*bonus); - - for(auto cre_id : creatures[spec.subtype]->upgrades) - { - bonus->subtype = cre_id; //propagate for regular upgrades of base creature - hs->addNewBonus(bonus); - bonus = new Bonus(*bonus); - } - vstd::clear_pointer(bonus); - break; - } - case 10://resource generation - bonus->type = Bonus::GENERATE_RESOURCE; - bonus->subtype = spec.subtype; - hs->addNewBonus(bonus); - break; - case 11://starting skill with mastery (Adrienne) - setSecSkillLevel(SecondarySkill(spec.val), spec.additionalinfo, true); - break; - case 12://army speed - bonus->type = Bonus::STACKS_SPEED; - hs->addNewBonus(bonus); - break; - case 13://Dragon bonuses (Mutare) - bonus->type = Bonus::PRIMARY_SKILL; - bonus->valType = Bonus::ADDITIVE_VALUE; - switch (spec.subtype) - { - case 1: - bonus->subtype = PrimarySkill::ATTACK; - break; - case 2: - bonus->subtype = PrimarySkill::DEFENSE; - break; - } - bonus->limiter.reset(new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE)); - hs->addNewBonus(bonus); - break; - default: - logGlobal->warnStream() << "Unexpected hero specialty " << type; - } - } - specialty.push_back(hs); //will it work? - - for (auto hs2 : type->specialty) //copy active (probably growing) bonuses from hero prootype to hero object - { - auto hs = new HeroSpecial(); - attachTo(hs); //do we ever need to detach it? - - hs->setNodeType(CBonusSystemNode::SPECIALTY); - for (auto bonus : hs2.bonuses) - { - hs->addNewBonus (bonus); - } - hs->growsWithLevel = hs2.growsWithLevel; - - specialty.push_back(hs); //will it work? - } - - //initialize bonuses - recreateSecondarySkillsBonuses(); - Updatespecialty(); - - mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one - type->name = name; -} -void CGHeroInstance::Updatespecialty() //TODO: calculate special value of bonuses on-the-fly? -{ - for (auto hs : specialty) - { - if (hs->growsWithLevel) - { - //const auto &creatures = VLC->creh->creatures; - - for(Bonus * b : hs->getBonusList()) - { - switch (b->type) - { - case Bonus::SECONDARY_SKILL_PREMY: - b->val = (hs->valOfBonuses(Bonus::SPECIAL_SECONDARY_SKILL, b->subtype) * level); - break; //use only hero skills as bonuses to avoid feedback loop - case Bonus::PRIMARY_SKILL: //for creatures, that is - { - const CCreature * cre = nullptr; - int creLevel = 0; - if (auto creatureLimiter = std::dynamic_pointer_cast(b->limiter)) //TODO: more general eveluation of bonuses? - { - cre = creatureLimiter->creature; - creLevel = cre->level; - if (!creLevel) - { - creLevel = 5; //treat ballista as tier 5 - } - } - else //no creature found, can't calculate value - { - logGlobal->warnStream() << "Primary skill specialty growth supported only with creature type limiters"; - break; - } - - double primSkillModifier = (int)(level / creLevel) / 20.0; - int param; - switch (b->subtype) - { - case PrimarySkill::ATTACK: - param = cre->Attack(); - break; - case PrimarySkill::DEFENSE: - param = cre->Defense(); - break; - default: - continue; - } - b->val = ceil(param * (1 + primSkillModifier)) - param; //yep, overcomplicated but matches original - break; - } - } - } - } - } -} - -void CGHeroInstance::recreateSecondarySkillsBonuses() -{ - auto secondarySkillsBonuses = getBonuses(Selector::sourceType(Bonus::SECONDARY_SKILL)); - for(auto bonus : *secondarySkillsBonuses) - removeBonus(bonus); - - for(auto skill_info : secSkills) - updateSkill(SecondarySkill(skill_info.first), skill_info.second); -} - -void CGHeroInstance::updateSkill(SecondarySkill which, int val) -{ - if(which == SecondarySkill::LEADERSHIP || which == SecondarySkill::LUCK) - { //luck-> VLC->generaltexth->arraytxt[73+luckSkill]; VLC->generaltexth->arraytxt[104+moraleSkill] - bool luck = which == SecondarySkill::LUCK; - Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK}; - - Bonus *b = getBonusLocalFirst(Selector::type(type[luck]).And(Selector::sourceType(Bonus::SECONDARY_SKILL))); - if(!b) - { - b = new Bonus(Bonus::PERMANENT, type[luck], Bonus::SECONDARY_SKILL, +val, which, which, Bonus::BASE_NUMBER); - addNewBonus(b); - } - else - b->val = +val; - } - else if(which == SecondarySkill::DIPLOMACY) //surrender discount: 20% per level - { - - if(Bonus *b = getBonusLocalFirst(Selector::type(Bonus::SURRENDER_DISCOUNT).And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) - b->val = +val; - else - addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::SURRENDER_DISCOUNT, Bonus::SECONDARY_SKILL, val * 20, which)); - } - - int skillVal = 0; - switch (which) - { - case SecondarySkill::ARCHERY: - switch (val) - { - case 1: - skillVal = 10; break; - case 2: - skillVal = 25; break; - case 3: - skillVal = 50; break; - } - break; - case SecondarySkill::LOGISTICS: - skillVal = 10 * val; break; - case SecondarySkill::NAVIGATION: - skillVal = 50 * val; break; - case SecondarySkill::MYSTICISM: - skillVal = val; break; - case SecondarySkill::EAGLE_EYE: - skillVal = 30 + 10 * val; break; - case SecondarySkill::NECROMANCY: - skillVal = 10 * val; break; - case SecondarySkill::LEARNING: - skillVal = 5 * val; break; - case SecondarySkill::OFFENCE: - skillVal = 10 * val; break; - case SecondarySkill::ARMORER: - skillVal = 5 * val; break; - case SecondarySkill::INTELLIGENCE: - skillVal = 25 << (val-1); break; - case SecondarySkill::SORCERY: - skillVal = 5 * val; break; - case SecondarySkill::RESISTANCE: - skillVal = 5 << (val-1); break; - case SecondarySkill::FIRST_AID: - skillVal = 25 + 25*val; break; - case SecondarySkill::ESTATES: - skillVal = 125 << (val-1); break; - } - - - Bonus::ValueType skillValType = skillVal ? Bonus::BASE_NUMBER : Bonus::INDEPENDENT_MIN; - if(Bonus * b = getBonusList().getFirst(Selector::typeSubtype(Bonus::SECONDARY_SKILL_PREMY, which) - .And(Selector::sourceType(Bonus::SECONDARY_SKILL)))) //only local hero bonus - { - b->val = skillVal; - b->valType = skillValType; - } - else - { - auto bonus = new Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, Bonus::SECONDARY_SKILL, skillVal, id.getNum(), which, skillValType); - bonus->source = Bonus::SECONDARY_SKILL; - addNewBonus(bonus); - } - -} -void CGHeroInstance::setPropertyDer( ui8 what, ui32 val ) -{ - if(what == ObjProperty::PRIMARY_STACK_COUNT) - setStackCount(SlotID(0), val); -} - -double CGHeroInstance::getFightingStrength() const -{ - return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::ATTACK)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::DEFENSE))); -} - -double CGHeroInstance::getMagicStrength() const -{ - return sqrt((1.0 + 0.05*getPrimSkillLevel(PrimarySkill::KNOWLEDGE)) * (1.0 + 0.05*getPrimSkillLevel(PrimarySkill::SPELL_POWER))); -} - -double CGHeroInstance::getHeroStrength() const -{ - return sqrt(pow(getFightingStrength(), 2.0) * pow(getMagicStrength(), 2.0)); -} - -ui64 CGHeroInstance::getTotalStrength() const -{ - double ret = getFightingStrength() * getArmyStrength(); - return (ui64) ret; -} - -TExpType CGHeroInstance::calculateXp(TExpType exp) const -{ - return exp * (100 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::LEARNING))/100.0; -} - -ui8 CGHeroInstance::getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool) const -{ - si16 skill = -1; //skill level - -#define TRY_SCHOOL(schoolName, schoolMechanicsId, schoolOutId) \ - if(spell-> schoolName) \ - { \ - int thisSchool = std::max(getSecSkillLevel( \ - SecondarySkill(14 + (schoolMechanicsId))), \ - valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 1 << (schoolMechanicsId))); \ - if(thisSchool > skill) \ - { \ - skill = thisSchool; \ - if(outSelectedSchool) \ - *outSelectedSchool = schoolOutId; \ - } \ - } - TRY_SCHOOL(fire, 0, 1) - TRY_SCHOOL(air, 1, 0) - TRY_SCHOOL(water, 2, 2) - TRY_SCHOOL(earth, 3, 3) -#undef TRY_SCHOOL - - - - vstd::amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus - vstd::amax(skill, valOfBonuses(Bonus::SPELL, spell->id.toEnum())); //given by artifact or other effect - if (hasBonusOfType(Bonus::MAXED_SPELL, spell->id))//hero specialty (Daremyth, Melodia) - skill = 3; - assert(skill >= 0 && skill <= 3); - return skill; -} - -bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const -{ - if(!getArt(ArtifactPosition::SPELLBOOK)) //if hero has no spellbook - return false; - - if (spell->isSpecialSpell()) - { - if (vstd::contains(spells, spell->id)) - {//hero has this spell in spellbook - logGlobal->errorStream() << "Special spell in spellbook "<name; - } - - if (hasBonusOfType(Bonus::SPELL, spell->id)) - return true; - - return false; - } - else - { - if(vstd::contains(spells, spell->id) //hero has this spell in spellbook - || (spell->air && hasBonusOfType(Bonus::AIR_SPELLS)) // this is air spell and hero can cast all air spells - || (spell->fire && hasBonusOfType(Bonus::FIRE_SPELLS)) // this is fire spell and hero can cast all fire spells - || (spell->water && hasBonusOfType(Bonus::WATER_SPELLS)) // this is water spell and hero can cast all water spells - || (spell->earth && hasBonusOfType(Bonus::EARTH_SPELLS)) // this is earth spell and hero can cast all earth spells - || hasBonusOfType(Bonus::SPELL, spell->id) - || hasBonusOfType(Bonus::SPELLS_OF_LEVEL, spell->level) - ) - return true; - - return false; - } -} - -/** - * Calculates what creatures and how many to be raised from a battle. - * @param battleResult The results of the battle. - * @return Returns a pair with the first value indicating the ID of the creature - * type and second value the amount. Both values are returned as -1 if necromancy - * could not be applied. - */ -CStackBasicDescriptor CGHeroInstance::calculateNecromancy (const BattleResult &battleResult) const -{ - const ui8 necromancyLevel = getSecSkillLevel(SecondarySkill::NECROMANCY); - - // Hero knows necromancy or has Necromancer Cloak - if (necromancyLevel > 0 || hasBonusOfType(Bonus::IMPROVED_NECROMANCY)) - { - double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, SecondarySkill::NECROMANCY)/100.0; - vstd::amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all... - const std::map &casualties = battleResult.casualties[!battleResult.winner]; - ui32 raisedUnits = 0; - - // Figure out what to raise and how many. - const CreatureID creatureTypes[] = {CreatureID::SKELETON, CreatureID::WALKING_DEAD, CreatureID::WIGHTS, CreatureID::LICHES}; - const bool improvedNecromancy = hasBonusOfType(Bonus::IMPROVED_NECROMANCY); - const CCreature *raisedUnitType = VLC->creh->creatures[creatureTypes[improvedNecromancy ? necromancyLevel : 0]]; - const ui32 raisedUnitHP = raisedUnitType->valOfBonuses(Bonus::STACK_HEALTH); - - //calculate creatures raised from each defeated stack - for (auto & casualtie : casualties) - { - // Get lost enemy hit points convertible to units. - CCreature * c = VLC->creh->creatures[casualtie.first]; - - const ui32 raisedHP = c->valOfBonuses(Bonus::STACK_HEALTH) * casualtie.second * necromancySkill; - raisedUnits += std::min(raisedHP / raisedUnitHP, casualtie.second * necromancySkill); //limit to % of HP and % of original stack count - } - - // Make room for new units. - SlotID slot = getSlotFor(raisedUnitType->idNumber); - if (slot == SlotID()) - { - // If there's no room for unit, try it's upgraded version 2/3rds the size. - raisedUnitType = VLC->creh->creatures[*raisedUnitType->upgrades.begin()]; - raisedUnits = (raisedUnits*2)/3; - - slot = getSlotFor(raisedUnitType->idNumber); - } - if (raisedUnits <= 0) - raisedUnits = 1; - - return CStackBasicDescriptor(raisedUnitType->idNumber, raisedUnits); - } - - return CStackBasicDescriptor(); -} - -/** - * Show the necromancy dialog with information about units raised. - * @param raisedStack Pair where the first element represents ID of the raised creature - * and the second element the amount. - */ -void CGHeroInstance::showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const -{ - InfoWindow iw; - iw.soundID = soundBase::pickup01 + cb->gameState()->getRandomGenerator().nextInt(6); - iw.player = tempOwner; - iw.components.push_back(Component(raisedStack)); - - if (raisedStack.count > 1) // Practicing the dark arts of necromancy, ... (plural) - { - iw.text.addTxt(MetaString::GENERAL_TXT, 145); - iw.text.addReplacement(raisedStack.count); - } - else // Practicing the dark arts of necromancy, ... (singular) - { - iw.text.addTxt(MetaString::GENERAL_TXT, 146); - } - iw.text.addReplacement(raisedStack); - - cb->showInfoDialog(&iw); -} - -int3 CGHeroInstance::getSightCenter() const -{ - return getPosition(false); -} - -int CGHeroInstance::getSightRadious() const -{ - return 5 + getSecSkillLevel(SecondarySkill::SCOUTING) + valOfBonuses(Bonus::SIGHT_RADIOUS); //default + scouting -} - -si32 CGHeroInstance::manaRegain() const -{ - if (hasBonusOfType(Bonus::FULL_MANA_REGENERATION)) - return manaLimit(); - - return 1 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, 8) + valOfBonuses(Bonus::MANA_REGENERATION); //1 + Mysticism level -} - -// /** -// * Places an artifact in hero's backpack. If it's a big artifact equips it -// * or discards it if it cannot be equipped. -// */ -// void CGHeroInstance::giveArtifact (ui32 aid) //use only for fixed artifacts -// { -// CArtifact * const artifact = VLC->arth->artifacts[aid]; //pointer to constant object -// CArtifactInstance *ai = CArtifactInstance::createNewArtifactInstance(artifact); -// ai->putAt(this, ai->firstAvailableSlot(this)); -// } - -int CGHeroInstance::getBoatType() const -{ - switch(type->heroClass->getAlignment()) - { - case EAlignment::GOOD: - return 1; - case EAlignment::EVIL: - return 0; - case EAlignment::NEUTRAL: - return 2; - default: - throw std::runtime_error("Wrong alignment!"); - } -} - -void CGHeroInstance::getOutOffsets(std::vector &offsets) const -{ - static 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) }; - for (auto & dir : dirs) - offsets += dir; -} - -int CGHeroInstance::getSpellCost(const CSpell *sp) const -{ - return sp->getCost(getSpellSchoolLevel(sp)); -} - -void CGHeroInstance::pushPrimSkill( PrimarySkill::PrimarySkill which, int val ) -{ - assert(!hasBonus(Selector::typeSubtype(Bonus::PRIMARY_SKILL, which) - .And(Selector::sourceType(Bonus::HERO_BASE_SKILL)))); - addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::HERO_BASE_SKILL, val, id.getNum(), which)); -} - -EAlignment::EAlignment CGHeroInstance::getAlignment() const -{ - return type->heroClass->getAlignment(); -} - -void CGHeroInstance::initExp() -{ - exp = cb->gameState()->getRandomGenerator().nextInt(40, 89); -} - -std::string CGHeroInstance::nodeName() const -{ - return "Hero " + name; -} - -void CGHeroInstance::putArtifact(ArtifactPosition pos, CArtifactInstance *art) -{ - assert(!getArt(pos)); - art->putAt(ArtifactLocation(this, pos)); -} - -void CGHeroInstance::putInBackpack(CArtifactInstance *art) -{ - putArtifact(art->firstBackpackSlot(this), art); -} - -bool CGHeroInstance::hasSpellbook() const -{ - return getArt(ArtifactPosition::SPELLBOOK); -} - -void CGHeroInstance::deserializationFix() -{ - artDeserializationFix(this); - - for (auto hs : specialty) - { - attachTo (hs); - } -} - -CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs) -{ - if(visitedTown) - { - if(inTownGarrison) - return visitedTown; - else - return &visitedTown->townAndVis; - } - else - return CArmedInstance::whereShouldBeAttached(gs); -} - -int CGHeroInstance::movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark /*= false*/) const -{ - if(hasBonusOfType(Bonus::FREE_SHIP_BOARDING)) - return (MPsBefore - basicCost) * static_cast(maxMovePoints(disembark)) / maxMovePoints(!disembark); - - return 0; //take all MPs otherwise -} - -CGHeroInstance::ECanDig CGHeroInstance::diggingStatus() const -{ - if(movement < maxMovePoints(true)) - return LACK_OF_MOVEMENT; - else if(cb->getTile(getPosition(false))->terType == ETerrainType::WATER) - return WRONG_TERRAIN; - else - { - const TerrainTile *t = cb->getTile(getPosition()); - //TODO look for hole - //CGI->mh->getTerrainDescr(h->getPosition(false), hlp, false); - if(/*hlp.length() || */t->blockingObjects.size() > 1) - return TILE_OCCUPIED; - else - return CAN_DIG; - } -} - -ArtBearer::ArtBearer CGHeroInstance::bearerType() const -{ - return ArtBearer::HERO; -} - -std::vector CGHeroInstance::getLevelUpProposedSecondarySkills() const -{ - std::vector obligatorySkills; //hero is offered magic school or wisdom if possible - if (!skillsInfo.wisdomCounter) - { - if (cb->isAllowed(2, SecondarySkill::WISDOM) && !getSecSkillLevel(SecondarySkill::WISDOM)) - obligatorySkills.push_back(SecondarySkill::WISDOM); - } - if (!skillsInfo.magicSchoolCounter) - { - std::vector ss; - ss += SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC; - - std::shuffle(ss.begin(), ss.end(), skillsInfo.rand.getStdGenerator()); - - for (auto skill : ss) - { - if (cb->isAllowed(2, skill) && !getSecSkillLevel(skill)) //only schools hero doesn't know yet - { - obligatorySkills.push_back(skill); - break; //only one - } - } - } - - std::vector skills; - //picking sec. skills for choice - std::set basicAndAdv, expert, none; - for(int i=0;iisAllowed(2,i)) - none.insert(SecondarySkill(i)); - - for(auto & elem : secSkills) - { - if(elem.second < SecSkillLevel::EXPERT) - basicAndAdv.insert(elem.first); - else - expert.insert(elem.first); - none.erase(elem.first); - } - for (auto s : obligatorySkills) //don't duplicate them - { - none.erase (s); - basicAndAdv.erase (s); - expert.erase (s); - } - - //first offered skill: - // 1) give obligatory skill - // 2) give any other new skill - // 3) upgrade existing - if (canLearnSkill() && obligatorySkills.size() > 0) - { - skills.push_back (obligatorySkills[0]); - } - else if(none.size() && canLearnSkill()) //hero have free skill slot - { - skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //new skill - none.erase(skills.back()); - } - else if(!basicAndAdv.empty()) - { - skills.push_back(type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand)); //upgrade existing - basicAndAdv.erase(skills.back()); - } - - //second offered skill: - //1) upgrade existing - //2) give obligatory skill - //3) give any other new skill - if(!basicAndAdv.empty()) - { - SecondarySkill s = type->heroClass->chooseSecSkill(basicAndAdv, skillsInfo.rand);//upgrade existing - skills.push_back(s); - basicAndAdv.erase(s); - } - else if (canLearnSkill() && obligatorySkills.size() > 1) - { - skills.push_back (obligatorySkills[1]); - } - else if(none.size() && canLearnSkill()) - { - skills.push_back(type->heroClass->chooseSecSkill(none, skillsInfo.rand)); //give new skill - none.erase(skills.back()); - } - - return skills; -} - -PrimarySkill::PrimarySkill CGHeroInstance::nextPrimarySkill() const -{ - assert(gainsLevel()); - int randomValue = cb->gameState()->getRandomGenerator().nextInt(99), pom = 0, primarySkill = 0; - const auto & skillChances = (level > 9) ? type->heroClass->primarySkillLowLevel : type->heroClass->primarySkillHighLevel; - - for(; primarySkill < GameConstants::PRIMARY_SKILLS; ++primarySkill) - { - pom += skillChances[primarySkill]; - if(randomValue < pom) - { - break; - } - } - - logGlobal->traceStream() << "The hero gets the primary skill " << primarySkill << " with a probability of " << randomValue << "%."; - return static_cast(primarySkill); -} - -boost::optional CGHeroInstance::nextSecondarySkill() const -{ - assert(gainsLevel()); - - boost::optional chosenSecondarySkill; - const auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(); - if(!proposedSecondarySkills.empty()) - { - std::vector learnedSecondarySkills; - for(auto secondarySkill : proposedSecondarySkills) - { - if(getSecSkillLevel(secondarySkill) > 0) - { - learnedSecondarySkills.push_back(secondarySkill); - } - } - - auto & rand = cb->gameState()->getRandomGenerator(); - if(learnedSecondarySkills.empty()) - { - // there are only new skills to learn, so choose anyone of them - chosenSecondarySkill = *RandomGeneratorUtil::nextItem(proposedSecondarySkills, rand); - } - else - { - // preferably upgrade a already learned secondary skill - chosenSecondarySkill = *RandomGeneratorUtil::nextItem(learnedSecondarySkills, rand); - } - } - return chosenSecondarySkill; -} - -void CGHeroInstance::setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs) -{ - if(primarySkill < PrimarySkill::EXPERIENCE) - { - Bonus * skill = getBonusLocalFirst(Selector::type(Bonus::PRIMARY_SKILL) - .And(Selector::subtype(primarySkill)) - .And(Selector::sourceType(Bonus::HERO_BASE_SKILL))); - assert(skill); - - if(abs) - { - skill->val = value; - } - else - { - skill->val += value; - } - } - else if(primarySkill == PrimarySkill::EXPERIENCE) - { - if(abs) - { - exp = value; - } - else - { - exp += value; - } - } -} - -bool CGHeroInstance::gainsLevel() const -{ - return exp >= VLC->heroh->reqExp(level+1); -} - -void CGHeroInstance::levelUp(std::vector skills) -{ - ++level; - - //deterministic secondary skills - skillsInfo.magicSchoolCounter = (skillsInfo.magicSchoolCounter + 1) % maxlevelsToMagicSchool(); - skillsInfo.wisdomCounter = (skillsInfo.wisdomCounter + 1) % maxlevelsToWisdom(); - if(vstd::contains(skills, SecondarySkill::WISDOM)) - { - skillsInfo.resetWisdomCounter(); - } - - SecondarySkill spellSchools[] = { - SecondarySkill::FIRE_MAGIC, SecondarySkill::AIR_MAGIC, SecondarySkill::WATER_MAGIC, SecondarySkill::EARTH_MAGIC}; - for(auto skill : spellSchools) - { - if(vstd::contains(skills, skill)) - { - skillsInfo.resetMagicSchoolCounter(); - break; - } - } - - //specialty - Updatespecialty(); -} - -void CGHeroInstance::levelUpAutomatically() -{ - while(gainsLevel()) - { - const auto primarySkill = nextPrimarySkill(); - setPrimarySkill(primarySkill, 1, false); - - auto proposedSecondarySkills = getLevelUpProposedSecondarySkills(); - - const auto secondarySkill = nextSecondarySkill(); - if(secondarySkill) - { - setSecSkillLevel(*secondarySkill, 1, false); - } - - //TODO why has the secondary skills to be passed to the method? - levelUp(proposedSecondarySkills); - } -} - -void CGDwelling::initObj() -{ - switch(ID) - { - case Obj::CREATURE_GENERATOR1: - { - CreatureID crid = VLC->objh->cregens[subID]; - const CCreature *crs = VLC->creh->creatures[crid]; - - creatures.resize(1); - creatures[0].second.push_back(crid); - if (subID >= VLC->generaltexth->creGens.size()) //very messy workaround - { - auto & dwellingNames = VLC->townh->factions[crs->faction]->town->dwellingNames; - assert (dwellingNames.size() > crs->level - 1); - hoverName = dwellingNames[crs->level - 1]; - } - else - hoverName = VLC->generaltexth->creGens[subID]; - if(crs->level > 4) - putStack(SlotID(0), new CStackInstance(crs, (crs->growth) * 3)); - if (getOwner() != PlayerColor::NEUTRAL) - cb->gameState()->players[getOwner()].dwellings.push_back (this); - } - break; - - case Obj::CREATURE_GENERATOR4: - creatures.resize(4); - if(subID == 1) //Golem Factory - { - creatures[0].second.push_back(CreatureID::STONE_GOLEM); - creatures[1].second.push_back(CreatureID::IRON_GOLEM); - creatures[2].second.push_back(CreatureID::GOLD_GOLEM); - creatures[3].second.push_back(CreatureID::DIAMOND_GOLEM); - //guards - putStack(SlotID(0), new CStackInstance(CreatureID::GOLD_GOLEM, 9)); - putStack(SlotID(1), new CStackInstance(CreatureID::DIAMOND_GOLEM, 6)); - } - else if(subID == 0) // Elemental Conflux - { - creatures[0].second.push_back(CreatureID::AIR_ELEMENTAL); - creatures[1].second.push_back(CreatureID::FIRE_ELEMENTAL); - creatures[2].second.push_back(CreatureID::EARTH_ELEMENTAL); - creatures[3].second.push_back(CreatureID::WATER_ELEMENTAL); - //guards - putStack(SlotID(0), new CStackInstance(CreatureID::EARTH_ELEMENTAL, 12)); - } - else - { - assert(0); - } - hoverName = VLC->generaltexth->creGens4[subID]; - break; - - case Obj::REFUGEE_CAMP: - //is handled within newturn func - break; - - case Obj::WAR_MACHINE_FACTORY: - creatures.resize(3); - creatures[0].second.push_back(CreatureID::BALLISTA); - creatures[1].second.push_back(CreatureID::FIRST_AID_TENT); - creatures[2].second.push_back(CreatureID::AMMO_CART); - break; - - default: - assert(0); - break; - } -} - -void CGDwelling::setProperty(ui8 what, ui32 val) -{ - switch (what) - { - case ObjProperty::OWNER: //change owner - if (ID == Obj::CREATURE_GENERATOR1) //single generators - { - if (tempOwner != PlayerColor::NEUTRAL) - { - std::vector >* dwellings = &cb->gameState()->players[tempOwner].dwellings; - dwellings->erase (std::find(dwellings->begin(), dwellings->end(), this)); - } - if (PlayerColor(val) != PlayerColor::NEUTRAL) //can new owner be neutral? - cb->gameState()->players[PlayerColor(val)].dwellings.push_back (this); - } - break; - case ObjProperty::AVAILABLE_CREATURE: - creatures.resize(1); - creatures[0].second.resize(1); - creatures[0].second[0] = CreatureID(val); - break; - } - CGObjectInstance::setProperty(what,val); -} -void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const -{ - if(ID == Obj::REFUGEE_CAMP && !creatures[0].first) //Refugee Camp, no available cres - { - InfoWindow iw; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::ADVOB_TXT, 44); //{%s} \n\n The camp is deserted. Perhaps you should try next week. - iw.text.addReplacement(MetaString::OBJ_NAMES, ID); - cb->sendAndApply(&iw); - return; - } - - PlayerRelations::PlayerRelations relations = cb->gameState()->getPlayerRelations( h->tempOwner, tempOwner ); - - if ( relations == PlayerRelations::ALLIES ) - return;//do not allow recruiting or capturing - - if( !relations && stacksCount() > 0) //object is guarded, owned by enemy - { - BlockingDialog bd(true,false); - bd.player = h->tempOwner; - bd.text.addTxt(MetaString::GENERAL_TXT, 421); //Much to your dismay, the %s is guarded by %s %s. Do you wish to fight the guards? - bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); - bd.text.addReplacement(MetaString::ARRAY_TXT, 176 + Slots().begin()->second->getQuantityID()*3); - bd.text.addReplacement(*Slots().begin()->second); - cb->showBlockingDialog(&bd); - return; - } - - if(!relations && ID != Obj::WAR_MACHINE_FACTORY) - { - cb->setOwner(this, h->tempOwner); - } - - BlockingDialog bd (true,false); - bd.player = h->tempOwner; - if(ID == Obj::CREATURE_GENERATOR1 || ID == Obj::CREATURE_GENERATOR4) - { - bd.text.addTxt(MetaString::ADVOB_TXT, ID == Obj::CREATURE_GENERATOR1 ? 35 : 36); //{%s} Would you like to recruit %s? / {%s} Would you like to recruit %s, %s, %s, or %s? - bd.text.addReplacement(ID == Obj::CREATURE_GENERATOR1 ? MetaString::CREGENS : MetaString::CREGENS4, subID); - for(auto & elem : creatures) - bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); - } - else if(ID == Obj::REFUGEE_CAMP) - { - bd.text.addTxt(MetaString::ADVOB_TXT, 35); //{%s} Would you like to recruit %s? - bd.text.addReplacement(MetaString::OBJ_NAMES, ID); - for(auto & elem : creatures) - bd.text.addReplacement(MetaString::CRE_PL_NAMES, elem.second[0]); - } - else if(ID == Obj::WAR_MACHINE_FACTORY) - bd.text.addTxt(MetaString::ADVOB_TXT, 157); //{War Machine Factory} Would you like to purchase War Machines? - else - throw std::runtime_error("Illegal dwelling!"); - - cb->showBlockingDialog(&bd); -} - -void CGDwelling::newTurn() const -{ - if(cb->getDate(Date::DAY_OF_WEEK) != 1) //not first day of week - return; - - //town growths and War Machines Factories are handled separately - if(ID == Obj::TOWN || ID == Obj::WAR_MACHINE_FACTORY) - return; - - if(ID == Obj::REFUGEE_CAMP) //if it's a refugee camp, we need to pick an available creature - { - cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster(cb->gameState()->getRandomGenerator())); - } - - bool change = false; - - SetAvailableCreatures sac; - sac.creatures = creatures; - sac.tid = id; - for (size_t i = 0; i < creatures.size(); i++) - { - if(creatures[i].second.size()) - { - CCreature *cre = VLC->creh->creatures[creatures[i].second[0]]; - TQuantity amount = cre->growth * (1 + cre->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100) + cre->valOfBonuses(Bonus::CREATURE_GROWTH); - if (VLC->modh->settings.DWELLINGS_ACCUMULATE_CREATURES && ID != Obj::REFUGEE_CAMP) //camp should not try to accumulate different kinds of creatures - sac.creatures[i].first += amount; - else - sac.creatures[i].first = amount; - change = true; - } - } - - if(change) - cb->sendAndApply(&sac); -} - -void CGDwelling::heroAcceptsCreatures( const CGHeroInstance *h) const -{ - CreatureID crid = creatures[0].second[0]; - CCreature *crs = VLC->creh->creatures[crid]; - TQuantity count = creatures[0].first; - - if(crs->level == 1 && ID != Obj::REFUGEE_CAMP) //first level - creatures are for free - { - if(count) //there are available creatures - { - SlotID slot = h->getSlotFor(crid); - if(!slot.validSlot()) //no available slot - { - InfoWindow iw; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::GENERAL_TXT, 425);//The %s would join your hero, but there aren't enough provisions to support them. - iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); - cb->showInfoDialog(&iw); - } - else //give creatures - { - SetAvailableCreatures sac; - sac.tid = id; - sac.creatures = creatures; - sac.creatures[0].first = 0; - - - InfoWindow iw; - iw.player = h->tempOwner; - iw.text.addTxt(MetaString::GENERAL_TXT, 423); //%d %s join your army. - iw.text.addReplacement(count); - iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); - - cb->showInfoDialog(&iw); - cb->sendAndApply(&sac); - cb->addToSlot(StackLocation(h, slot), crs, count); - } - } - else //there no creatures - { - InfoWindow iw; - iw.text.addTxt(MetaString::GENERAL_TXT, 422); //There are no %s here to recruit. - iw.text.addReplacement(MetaString::CRE_PL_NAMES, crid); - iw.player = h->tempOwner; - cb->sendAndApply(&iw); - } - } - else - { - if(ID == Obj::WAR_MACHINE_FACTORY) //pick available War Machines - { - //there is 1 war machine available to recruit if hero doesn't have one - SetAvailableCreatures sac; - sac.tid = id; - sac.creatures = creatures; - sac.creatures[0].first = !h->getArt(ArtifactPosition::MACH1); //ballista - sac.creatures[1].first = !h->getArt(ArtifactPosition::MACH3); //first aid tent - sac.creatures[2].first = !h->getArt(ArtifactPosition::MACH2); //ammo cart - cb->sendAndApply(&sac); - } - - OpenWindow ow; - ow.id1 = id.getNum(); - ow.id2 = h->id.getNum(); - ow.window = (ID == Obj::CREATURE_GENERATOR1 || ID == Obj::REFUGEE_CAMP) - ? OpenWindow::RECRUITMENT_FIRST - : OpenWindow::RECRUITMENT_ALL; - cb->sendAndApply(&ow); - } -} - -void CGDwelling::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if (result.winner == 0) - { - onHeroVisit(hero); - } -} - -void CGDwelling::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - auto relations = cb->getPlayerRelations(getOwner(), hero->getOwner()); - if(stacksCount() > 0 && relations == PlayerRelations::ENEMIES) //guards present - { - if(answer) - cb->startBattleI(hero, this); - } - else if(answer) - { - heroAcceptsCreatures(hero); - } -} - -int CGTownInstance::getSightRadious() const //returns sight distance -{ - if (subID == ETownType::TOWER) - { - if (hasBuilt(BuildingID::GRAIL)) //skyship - return -1; //entire map - if (hasBuilt(BuildingID::LOOKOUT_TOWER)) //lookout tower - return 20; - } - return 5; -} - -void CGTownInstance::setPropertyDer(ui8 what, ui32 val) -{ -///this is freakin' overcomplicated solution - switch (what) - { - case ObjProperty::STRUCTURE_ADD_VISITING_HERO: - bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, visitingHero->id.getNum()); - break; - case ObjProperty::STRUCTURE_CLEAR_VISITORS: - bonusingBuildings[val]->setProperty (ObjProperty::STRUCTURE_CLEAR_VISITORS, 0); - break; - case ObjProperty::STRUCTURE_ADD_GARRISONED_HERO: //add garrisoned hero to visitors - bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, garrisonHero->id.getNum()); - break; - case ObjProperty::BONUS_VALUE_FIRST: - bonusValue.first = val; - break; - case ObjProperty::BONUS_VALUE_SECOND: - bonusValue.second = val; - break; - } -} -CGTownInstance::EFortLevel CGTownInstance::fortLevel() const //0 - none, 1 - fort, 2 - citadel, 3 - castle -{ - if (hasBuilt(BuildingID::CASTLE)) - return CASTLE; - if (hasBuilt(BuildingID::CITADEL)) - return CITADEL; - if (hasBuilt(BuildingID::FORT)) - return FORT; - return NONE; -} - -int CGTownInstance::hallLevel() const // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol -{ - - if (hasBuilt(BuildingID::CAPITOL)) - return 3; - if (hasBuilt(BuildingID::CITY_HALL)) - return 2; - if (hasBuilt(BuildingID::TOWN_HALL)) - return 1; - if (hasBuilt(BuildingID::VILLAGE_HALL)) - return 0; - return -1; -} -int CGTownInstance::mageGuildLevel() const -{ - if (hasBuilt(BuildingID::MAGES_GUILD_5)) - return 5; - if (hasBuilt(BuildingID::MAGES_GUILD_4)) - return 4; - if (hasBuilt(BuildingID::MAGES_GUILD_3)) - return 3; - if (hasBuilt(BuildingID::MAGES_GUILD_2)) - return 2; - if (hasBuilt(BuildingID::MAGES_GUILD_1)) - return 1; - return 0; -} - -int CGTownInstance::getHordeLevel(const int & HID) const//HID - 0 or 1; returns creature level or -1 if that horde structure is not present -{ - return town->hordeLvl.at(HID); -} - -int CGTownInstance::creatureGrowth(const int & level) const -{ - return getGrowthInfo(level).totalGrowth(); -} - -GrowthInfo CGTownInstance::getGrowthInfo(int level) const -{ - GrowthInfo ret; - - if (level<0 || level >=GameConstants::CREATURES_PER_TOWN) - return ret; - if (creatures[level].second.empty()) - return ret; //no dwelling - - const CCreature *creature = VLC->creh->creatures[creatures[level].second.back()]; - const int base = creature->growth; - int castleBonus = 0; - - ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[590], base));// \n\nBasic growth %d" - - if (hasBuilt(BuildingID::CASTLE)) - ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CASTLE, castleBonus = base)); - else if (hasBuilt(BuildingID::CITADEL)) - ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::CITADEL, castleBonus = base / 2)); - - if(town->hordeLvl.at(0) == level)//horde 1 - if(hasBuilt(BuildingID::HORDE_1)) - ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_1, creature->hordeGrowth)); - - if(town->hordeLvl.at(1) == level)//horde 2 - if(hasBuilt(BuildingID::HORDE_2)) - ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::HORDE_2, creature->hordeGrowth)); - - int dwellingBonus = 0; - if(const PlayerState *p = cb->getPlayer(tempOwner, false)) - { - for(const CGDwelling *dwelling : p->dwellings) - if(vstd::contains(creatures[level].second, dwelling->creatures[0].second[0])) - dwellingBonus++; - } - - if(dwellingBonus) - ret.entries.push_back(GrowthInfo::Entry(VLC->generaltexth->allTexts[591], dwellingBonus));// \nExternal dwellings %+d - - //other *-of-legion-like bonuses (%d to growth cumulative with grail) - TBonusListPtr bonuses = getBonuses(Selector::type(Bonus::CREATURE_GROWTH).And(Selector::subtype(level))); - for(const Bonus *b : *bonuses) - ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val)); - - //statue-of-legion-like bonus: % to base+castle - TBonusListPtr bonuses2 = getBonuses(Selector::type(Bonus::CREATURE_GROWTH_PERCENT)); - for(const Bonus *b : *bonuses2) - ret.entries.push_back(GrowthInfo::Entry(b->Description() + " %+d", b->val * (base + castleBonus) / 100)); - - if(hasBuilt(BuildingID::GRAIL)) //grail - +50% to ALL (so far added) growth - ret.entries.push_back(GrowthInfo::Entry(subID, BuildingID::GRAIL, ret.totalGrowth() / 2)); - - return ret; -} - -TResources CGTownInstance::dailyIncome() const -{ - TResources ret; - - for (auto & p : town->buildings) - { - BuildingID buildingUpgrade; - - for (auto & p2 : town->buildings) - { - if (p2.second->upgrade == p.first) - { - buildingUpgrade = p2.first; - } - } - - if (!hasBuilt(buildingUpgrade)&&(hasBuilt(p.first))) - { - ret += p.second->produce; - } - - } - - return ret; -} -bool CGTownInstance::hasFort() const -{ - return hasBuilt(BuildingID::FORT); -} -bool CGTownInstance::hasCapitol() const -{ - return hasBuilt(BuildingID::CAPITOL); -} -CGTownInstance::CGTownInstance() - :IShipyard(this), IMarket(this), town(nullptr), builded(0), destroyed(0), identifier(0), alignment(0xff) -{ - -} - -CGTownInstance::~CGTownInstance() -{ - for (auto & elem : bonusingBuildings) - delete elem; -} - -int CGTownInstance::spellsAtLevel(int level, bool checkGuild) const -{ - if(checkGuild && mageGuildLevel() < level) - return 0; - int ret = 6 - level; //how many spells are available at this level - - if (hasBuilt(BuildingID::LIBRARY, ETownType::TOWER)) - ret++; - - return ret; -} - -bool CGTownInstance::needsLastStack() const -{ - if(garrisonHero) - return true; - else return false; -} - -void CGTownInstance::onHeroVisit(const CGHeroInstance * h) const -{ - if( !cb->gameState()->getPlayerRelations( getOwner(), h->getOwner() ))//if this is enemy - { - if(armedGarrison() || visitingHero) - { - const CGHeroInstance *defendingHero = nullptr; - const CArmedInstance *defendingArmy = this; - - if(visitingHero) - defendingHero = visitingHero; - else if(garrisonHero) - defendingHero = garrisonHero; - - if(defendingHero) - defendingArmy = defendingHero; - - bool outsideTown = (defendingHero == visitingHero && garrisonHero); - - //TODO - //"borrowing" army from garrison to visiting hero - - cb->startBattlePrimary(h, defendingArmy, getSightCenter(), h, defendingHero, false, (outsideTown ? nullptr : this)); - } - else - { - cb->setOwner(this, h->tempOwner); - removeCapitols(h->getOwner()); - cb->heroVisitCastle(this, h); - } - } - else if(h->visitablePos() == visitablePos()) - { - if (h->commander && !h->commander->alive) //rise commander. TODO: interactive script - { - SetCommanderProperty scp; - scp.heroid = h->id; - scp.which = SetCommanderProperty::ALIVE; - scp.amount = 1; - cb->sendAndApply (&scp); - } - cb->heroVisitCastle(this, h); - } - else - { - logGlobal->errorStream() << h->name << " visits allied town of " << name << " from different pos?"; - } -} - -void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const -{ - cb->stopHeroVisitCastle(this, h); -} - -void CGTownInstance::initObj() -///initialize town structures -{ - blockVisit = true; - hoverName = name + ", " + town->faction->name; - - if (subID == ETownType::DUNGEON) - creatures.resize(GameConstants::CREATURES_PER_TOWN+1);//extra dwelling for Dungeon - else - creatures.resize(GameConstants::CREATURES_PER_TOWN); - for (int level = 0; level < GameConstants::CREATURES_PER_TOWN; level++) - { - BuildingID buildID = BuildingID(BuildingID::DWELL_FIRST).advance(level); - int upgradeNum = 0; - - for (; town->buildings.count(buildID); upgradeNum++, buildID.advance(GameConstants::CREATURES_PER_TOWN)) - { - if (hasBuilt(buildID) && town->creatures.at(level).size() > upgradeNum) - creatures[level].second.push_back(town->creatures[level][upgradeNum]); - } - } - - switch (subID) - { //add new visitable objects - case 0: - bonusingBuildings.push_back (new COPWBonus(BuildingID::STABLES, this)); - break; - case 5: - bonusingBuildings.push_back (new COPWBonus(BuildingID::MANA_VORTEX, this)); - //fallthrough - case 2: case 3: case 6: - bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_4, this)); - break; - case 7: - bonusingBuildings.push_back (new CTownBonus(BuildingID::SPECIAL_1, this)); - break; - } - //add special bonuses from buildings - - recreateBuildingsBonuses(); -} - -void CGTownInstance::newTurn() const -{ - if (cb->getDate(Date::DAY_OF_WEEK) == 1) //reset on new week - { - auto & rand = cb->gameState()->getRandomGenerator(); - - //give resources for Rampart, Mystic Pond - if (hasBuilt(BuildingID::MYSTIC_POND, ETownType::RAMPART) - && cb->getDate(Date::DAY) != 1 && (tempOwner < PlayerColor::PLAYER_LIMIT)) - { - int resID = rand.nextInt(2, 5); //bonus to random rare resource - resID = (resID==2)?1:resID; - int resVal = rand.nextInt(1, 4);//with size 1..4 - cb->giveResource(tempOwner, static_cast(resID), resVal); - cb->setObjProperty (id, ObjProperty::BONUS_VALUE_FIRST, resID); - cb->setObjProperty (id, ObjProperty::BONUS_VALUE_SECOND, resVal); - } - - if ( subID == ETownType::DUNGEON ) - for (auto & elem : bonusingBuildings) - { - if ((elem)->ID == BuildingID::MANA_VORTEX) - cb->setObjProperty (id, ObjProperty::STRUCTURE_CLEAR_VISITORS, (elem)->id); //reset visitors for Mana Vortex - } - - if (tempOwner == PlayerColor::NEUTRAL) //garrison growth for neutral towns - { - std::vector nativeCrits; //slots - for (auto & elem : Slots()) - { - if (elem.second->type->faction == subID) //native - { - nativeCrits.push_back(elem.first); //collect matching slots - } - } - if (nativeCrits.size()) - { - SlotID pos = *RandomGeneratorUtil::nextItem(nativeCrits, rand); - StackLocation sl(this, pos); - - const CCreature *c = getCreature(pos); - if (rand.nextInt(99) < 90 || c->upgrades.empty()) //increase number if no upgrade available - { - cb->changeStackCount(sl, c->growth); - } - else //upgrade - { - cb->changeStackType(sl, VLC->creh->creatures[*c->upgrades.begin()]); - } - } - if ((stacksCount() < GameConstants::ARMY_SIZE && rand.nextInt(99) < 25) || Slots().empty()) //add new stack - { - int i = rand.nextInt(std::min(GameConstants::CREATURES_PER_TOWN, cb->getDate(Date::MONTH) << 1) - 1); - if (!town->creatures[i].empty()) - { - CreatureID c = town->creatures[i][0]; - SlotID n; - - TQuantity count = creatureGrowth(i); - if (!count) // no dwelling - count = VLC->creh->creatures[c]->growth; - - {//no lower tiers or above current month - - if ((n = getSlotFor(c)).validSlot()) - { - StackLocation sl(this, n); - if (slotEmpty(n)) - cb->insertNewStack(sl, VLC->creh->creatures[c], count); - else //add to existing - cb->changeStackCount(sl, count); - } - } - } - } - } - } -} - -int3 CGTownInstance::getSightCenter() const -{ - return pos - int3(2,0,0); -} - -ui8 CGTownInstance::getPassableness() const -{ - if (!armedGarrison())//empty castle - anyone can visit - return GameConstants::ALL_PLAYERS; - if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit - return 0; - - ui8 mask = 0; - TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner); - for(PlayerColor it : ts->players) - mask |= 1< &offsets ) const -{ - offsets += int3(-1,2,0), int3(-3,2,0); -} - -void CGTownInstance::removeCapitols (PlayerColor owner) const -{ - if (hasCapitol()) // search if there's an older capitol - { - PlayerState* state = cb->gameState()->getPlayer (owner); //get all towns owned by player - for (auto i = state->towns.cbegin(); i < state->towns.cend(); ++i) - { - if (*i != this && (*i)->hasCapitol()) - { - RazeStructures rs; - rs.tid = id; - rs.bid.insert(BuildingID::CAPITOL); - rs.destroyed = destroyed; - cb->sendAndApply(&rs); - return; - } - } - } -} - -int CGTownInstance::getBoatType() const -{ - switch (town->faction->alignment) - { - case EAlignment::EVIL : return 0; - case EAlignment::GOOD : return 1; - case EAlignment::NEUTRAL : return 2; - } - assert(0); - return -1; -} - -int CGTownInstance::getMarketEfficiency() const -{ - if (!hasBuilt(BuildingID::MARKETPLACE)) - return 0; - - const PlayerState *p = cb->getPlayer(tempOwner); - assert(p); - - int marketCount = 0; - for(const CGTownInstance *t : p->towns) - if(t->hasBuilt(BuildingID::MARKETPLACE)) - marketCount++; - - return marketCount; -} - -bool CGTownInstance::allowsTrade(EMarketMode::EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::RESOURCE_PLAYER: - return hasBuilt(BuildingID::MARKETPLACE); - - case EMarketMode::ARTIFACT_RESOURCE: - case EMarketMode::RESOURCE_ARTIFACT: - return hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::TOWER) - || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::DUNGEON) - || hasBuilt(BuildingID::ARTIFACT_MERCHANT, ETownType::CONFLUX); - - case EMarketMode::CREATURE_RESOURCE: - return hasBuilt(BuildingID::FREELANCERS_GUILD, ETownType::STRONGHOLD); - - case EMarketMode::CREATURE_UNDEAD: - return hasBuilt(BuildingID::SKELETON_TRANSFORMER, ETownType::NECROPOLIS); - - case EMarketMode::RESOURCE_SKILL: - return hasBuilt(BuildingID::MAGIC_UNIVERSITY, ETownType::CONFLUX); - default: - assert(0); - return false; - } -} - -std::vector CGTownInstance::availableItemsIds(EMarketMode::EMarketMode mode) const -{ - if(mode == EMarketMode::RESOURCE_ARTIFACT) - { - std::vector ret; - for(const CArtifact *a : merchantArtifacts) - if(a) - ret.push_back(a->id); - else - ret.push_back(-1); - return ret; - } - else if ( mode == EMarketMode::RESOURCE_SKILL ) - { - return universitySkills; - } - else - return IMarket::availableItemsIds(mode); -} - -void CGTownInstance::setType(si32 ID, si32 subID) -{ - assert(ID == Obj::TOWN); // just in case - CGObjectInstance::setType(ID, subID); - town = VLC->townh->factions[subID]->town; - randomizeArmy(subID); - updateAppearance(); -} - -void CGTownInstance::updateAppearance() -{ - if (!hasFort()) - appearance.animationFile = town->clientInfo.advMapVillage; - else if(hasCapitol()) - appearance.animationFile = town->clientInfo.advMapCapitol; - else - appearance.animationFile = town->clientInfo.advMapCastle; -} - -std::string CGTownInstance::nodeName() const -{ - return "Town (" + (town ? town->faction->name : "unknown") + ") of " + name; -} - -void CGTownInstance::deserializationFix() -{ - attachTo(&townAndVis); - - //Hero is already handled by CGameState::attachArmedObjects - -// if(visitingHero) -// visitingHero->attachTo(&townAndVis); -// if(garrisonHero) -// garrisonHero->attachTo(this); -} - -void CGTownInstance::updateMoraleBonusFromArmy() -{ - Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE))); - if(!b) - { - b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1); - addNewBonus(b); - } - - if (garrisonHero) - b->val = 0; - else - CArmedInstance::updateMoraleBonusFromArmy(); -} - -void CGTownInstance::recreateBuildingsBonuses() -{ - static TPropagatorPtr playerProp(new CPropagatorNodeType(PLAYER)); - - BonusList bl; - getExportedBonusList().getBonuses(bl, Selector::sourceType(Bonus::TOWN_STRUCTURE)); - for(Bonus *b : bl) - removeBonus(b); - - //tricky! -> checks tavern only if no bratherhood of sword or not a castle - if(subID != ETownType::CASTLE || !addBonusIfBuilt(BuildingID::BROTHERHOOD, Bonus::MORALE, +2)) - addBonusIfBuilt(BuildingID::TAVERN, Bonus::MORALE, +1); - - if(subID == ETownType::CASTLE) //castle - { - addBonusIfBuilt(BuildingID::LIGHTHOUSE, Bonus::SEA_MOVEMENT, +500, playerProp); - addBonusIfBuilt(BuildingID::GRAIL, Bonus::MORALE, +2, playerProp); //colossus - } - else if(subID == ETownType::RAMPART) //rampart - { - addBonusIfBuilt(BuildingID::FOUNTAIN_OF_FORTUNE, Bonus::LUCK, +2); //fountain of fortune - addBonusIfBuilt(BuildingID::GRAIL, Bonus::LUCK, +2, playerProp); //guardian spirit - } - else if(subID == ETownType::TOWER) //tower - { - addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +15, PrimarySkill::KNOWLEDGE); //grail - } - else if(subID == ETownType::INFERNO) //Inferno - { - addBonusIfBuilt(BuildingID::STORMCLOUDS, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER); //Brimstone Clouds - } - else if(subID == ETownType::NECROPOLIS) //necropolis - { - addBonusIfBuilt(BuildingID::COVER_OF_DARKNESS, Bonus::DARKNESS, +20); - addBonusIfBuilt(BuildingID::NECROMANCY_AMPLIFIER, Bonus::SECONDARY_SKILL_PREMY, +10, playerProp, SecondarySkill::NECROMANCY); //necromancy amplifier - addBonusIfBuilt(BuildingID::GRAIL, Bonus::SECONDARY_SKILL_PREMY, +20, playerProp, SecondarySkill::NECROMANCY); //Soul prison - } - else if(subID == ETownType::DUNGEON) //Dungeon - { - addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +12, PrimarySkill::SPELL_POWER); //grail - } - else if(subID == ETownType::STRONGHOLD) //Stronghold - { - addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +20, PrimarySkill::ATTACK); //grail - } - else if(subID == ETownType::FORTRESS) //Fortress - { - addBonusIfBuilt(BuildingID::GLYPHS_OF_FEAR, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE); //Glyphs of Fear - addBonusIfBuilt(BuildingID::BLOOD_OBELISK, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK); //Blood Obelisk - addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail - addBonusIfBuilt(BuildingID::GRAIL, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail - } - else if(subID == ETownType::CONFLUX) - { - - } -} - -bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype /*= -1*/) -{ - static auto emptyPropagator = TPropagatorPtr(); - return addBonusIfBuilt(building, type, val, emptyPropagator, subtype); -} - -bool CGTownInstance::addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr & prop, int subtype /*= -1*/) -{ - if(hasBuilt(building)) - { - std::ostringstream descr; - descr << town->buildings.at(building)->Name() << " "; - if(val > 0) - descr << "+"; - else if(val < 0) - descr << "-"; - descr << val; - - Bonus *b = new Bonus(Bonus::PERMANENT, type, Bonus::TOWN_STRUCTURE, val, building, descr.str(), subtype); - if(prop) - b->addPropagator(prop); - addNewBonus(b); - return true; - } - - return false; -} - -void CGTownInstance::setVisitingHero(CGHeroInstance *h) -{ - assert(!!visitingHero == !h); - if(h) - { - PlayerState *p = cb->gameState()->getPlayer(h->tempOwner); - assert(p); - h->detachFrom(p); - h->attachTo(&townAndVis); - visitingHero = h; - h->visitedTown = this; - h->inTownGarrison = false; - } - else - { - PlayerState *p = cb->gameState()->getPlayer(visitingHero->tempOwner); - visitingHero->visitedTown = nullptr; - visitingHero->detachFrom(&townAndVis); - visitingHero->attachTo(p); - visitingHero = nullptr; - } -} - -void CGTownInstance::setGarrisonedHero(CGHeroInstance *h) -{ - assert(!!garrisonHero == !h); - if(h) - { - PlayerState *p = cb->gameState()->getPlayer(h->tempOwner); - assert(p); - h->detachFrom(p); - h->attachTo(this); - garrisonHero = h; - h->visitedTown = this; - h->inTownGarrison = true; - } - else - { - PlayerState *p = cb->gameState()->getPlayer(garrisonHero->tempOwner); - garrisonHero->visitedTown = nullptr; - garrisonHero->inTownGarrison = false; - garrisonHero->detachFrom(this); - garrisonHero->attachTo(p); - garrisonHero = nullptr; - } - updateMoraleBonusFromArmy(); //avoid giving morale bonus for same army twice -} - -bool CGTownInstance::armedGarrison() const -{ - return stacksCount() || garrisonHero; -} - -int CGTownInstance::getTownLevel() const -{ - // count all buildings that are not upgrades - return boost::range::count_if(builtBuildings, [&](const BuildingID & build) - { - return town->buildings.at(build) && town->buildings.at(build)->upgrade == -1; - }); -} - -CBonusSystemNode * CGTownInstance::whatShouldBeAttached() -{ - return &townAndVis; -} - -const CArmedInstance * CGTownInstance::getUpperArmy() const -{ - if(garrisonHero) - return garrisonHero; - return this; -} - -bool CGTownInstance::hasBuilt(BuildingID buildingID, int townID) const -{ - if (townID == town->faction->index || townID == ETownType::ANY) - return hasBuilt(buildingID); - return false; -} - -bool CGTownInstance::hasBuilt(BuildingID buildingID) const -{ - return vstd::contains(builtBuildings, buildingID); -} - -void CGTownInstance::addHeroToStructureVisitors( const CGHeroInstance *h, si32 structureInstanceID ) const -{ - if(visitingHero == h) - cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_VISITING_HERO, structureInstanceID); //add to visitors - else if(garrisonHero == h) - cb->setObjProperty(id, ObjProperty::STRUCTURE_ADD_GARRISONED_HERO, structureInstanceID); //then it must be garrisoned hero - else - { - //should never ever happen - logGlobal->errorStream() << "Cannot add hero " << h->name << " to visitors of structure #" << structureInstanceID; - assert(0); - } -} - -void CGTownInstance::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if(result.winner == 0) - { - removeCapitols(hero->getOwner()); - cb->setOwner (this, hero->tempOwner); //give control after checkout is done - FoWChange fw; - fw.player = hero->tempOwner; - fw.mode = 1; - getSightTiles (fw.tiles); //update visibility for castle structures - cb->sendAndApply (&fw); - } -} - -COPWBonus::COPWBonus (BuildingID index, CGTownInstance *TOWN) -{ - ID = index; - town = TOWN; - id = town->bonusingBuildings.size(); -} -void COPWBonus::setProperty(ui8 what, ui32 val) -{ - switch (what) - { - case ObjProperty::VISITORS: - visitors.insert(val); - break; - case ObjProperty::STRUCTURE_CLEAR_VISITORS: - visitors.clear(); - break; - } -} -void COPWBonus::onHeroVisit (const CGHeroInstance * h) const -{ - ObjectInstanceID heroID = h->id; - if (town->hasBuilt(ID)) - { - InfoWindow iw; - iw.player = h->tempOwner; - switch (town->subID) - { - case ETownType::CASTLE: //Stables - if (!h->hasBonusFrom(Bonus::OBJECT, Obj::STABLES)) //does not stack with advMap Stables - { - GiveBonus gb; - gb.bonus = Bonus(Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, Bonus::OBJECT, 600, 94, VLC->generaltexth->arraytxt[100]); - gb.id = heroID.getNum(); - cb->giveHeroBonus(&gb); - iw.text << VLC->generaltexth->allTexts[580]; - cb->showInfoDialog(&iw); - } - break; - case ETownType::DUNGEON: //Mana Vortex - if (visitors.empty() && h->mana <= h->manaLimit() * 2) - { - cb->setManaPoints (heroID, 2 * h->manaLimit()); - //TODO: investigate line below - //cb->setObjProperty (town->id, ObjProperty::VISITED, true); - iw.text << VLC->generaltexth->allTexts[579]; - cb->showInfoDialog(&iw); - town->addHeroToStructureVisitors(h, id); - } - break; - } - } -} -CTownBonus::CTownBonus (BuildingID index, CGTownInstance *TOWN) -{ - ID = index; - town = TOWN; - id = town->bonusingBuildings.size(); -} -void CTownBonus::setProperty (ui8 what, ui32 val) -{ - if(what == ObjProperty::VISITORS) - visitors.insert(ObjectInstanceID(val)); -} -void CTownBonus::onHeroVisit (const CGHeroInstance * h) const -{ - ObjectInstanceID heroID = h->id; - if (town->hasBuilt(ID) && visitors.find(heroID) == visitors.end()) - { - InfoWindow iw; - PrimarySkill::PrimarySkill what = PrimarySkill::ATTACK; - int val=0, mid=0; - switch (ID) - { - case BuildingID::SPECIAL_4: - switch(town->subID) - { - case ETownType::TOWER: //wall - what = PrimarySkill::KNOWLEDGE; - val = 1; - mid = 581; - iw.components.push_back (Component(Component::PRIM_SKILL, 3, 1, 0)); - break; - case ETownType::INFERNO: //order of fire - what = PrimarySkill::SPELL_POWER; - val = 1; - mid = 582; - iw.components.push_back (Component(Component::PRIM_SKILL, 2, 1, 0)); - break; - case ETownType::STRONGHOLD://hall of Valhalla - what = PrimarySkill::ATTACK; - val = 1; - mid = 584; - iw.components.push_back (Component(Component::PRIM_SKILL, 0, 1, 0)); - break; - case ETownType::DUNGEON://academy of battle scholars - what = PrimarySkill::EXPERIENCE; - val = h->calculateXp(1000); - mid = 583; - iw.components.push_back (Component(Component::EXPERIENCE, 0, val, 0)); - break; - } - break; - case BuildingID::SPECIAL_1: - switch(town->subID) - { - case ETownType::FORTRESS: //cage of warlords - what = PrimarySkill::DEFENSE; - val = 1; - mid = 585; - iw.components.push_back (Component(Component::PRIM_SKILL, 1, 1, 0)); - break; - } - break; - } - assert(mid); - iw.player = cb->getOwner(heroID); - iw.text << VLC->generaltexth->allTexts[mid]; - cb->showInfoDialog(&iw); - cb->changePrimSkill (cb->getHero(heroID), what, val); - town->addHeroToStructureVisitors(h, id); - } -} -const std::string & CGCreature::getHoverText() const -{ - if(stacks.empty()) - { - static const std::string errorValue("!!!INVALID_STACK!!!"); - - //should not happen... - logGlobal->errorStream() << "Invalid stack at tile " << pos << ": subID=" << subID << "; id=" << id; - return errorValue; // references to temporary are illegal - use pre-constructed string - } - - MetaString ms; - int pom = stacks.begin()->second->getQuantityID(); - pom = 172 + 3*pom; - ms.addTxt(MetaString::ARRAY_TXT,pom); - ms << " " ; - ms.addTxt(MetaString::CRE_PL_NAMES,subID); - ms.toString(hoverName); - - if(const CGHeroInstance *selHero = cb->getSelectedHero(cb->getCurrentPlayer())) - { - const JsonNode & texts = VLC->generaltexth->localizedTexts["adventureMap"]["monsterThreat"]; - - hoverName += texts["title"].String(); - int choice; - double ratio = ((double)getArmyStrength() / selHero->getTotalStrength()); - if (ratio < 0.1) choice = 0; - else if (ratio < 0.25) choice = 1; - else if (ratio < 0.6) choice = 2; - else if (ratio < 0.9) choice = 3; - else if (ratio < 1.1) choice = 4; - else if (ratio < 1.3) choice = 5; - else if (ratio < 1.8) choice = 6; - else if (ratio < 2.5) choice = 7; - else if (ratio < 4) choice = 8; - else if (ratio < 8) choice = 9; - else if (ratio < 20) choice = 10; - else choice = 11; - hoverName += texts["levels"].Vector()[choice].String(); - } - return hoverName; -} -void CGCreature::onHeroVisit( const CGHeroInstance * h ) const -{ - int action = takenAction(h); - switch( action ) //decide what we do... - { - case FIGHT: - fight(h); - break; - case FLEE: //flee - { - flee(h); - break; - } - case JOIN_FOR_FREE: //join for free - { - BlockingDialog ynd(true,false); - ynd.player = h->tempOwner; - ynd.text.addTxt(MetaString::ADVOB_TXT, 86); - ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID); - cb->showBlockingDialog(&ynd); - break; - } - default: //join for gold - { - assert(action > 0); - - //ask if player agrees to pay gold - BlockingDialog ynd(true,false); - ynd.player = h->tempOwner; - std::string tmp = VLC->generaltexth->advobtxt[90]; - boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(getStackCount(SlotID(0)))); - boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(action)); - boost::algorithm::replace_first(tmp,"%s",VLC->creh->creatures[subID]->namePl); - ynd.text << tmp; - cb->showBlockingDialog(&ynd); - break; - } - } - -} - -void CGCreature::initObj() -{ - blockVisit = true; - switch(character) - { - case 0: - character = -4; - break; - case 1: - character = cb->gameState()->getRandomGenerator().nextInt(1, 7); - break; - case 2: - character = cb->gameState()->getRandomGenerator().nextInt(1, 10); - break; - case 3: - character = cb->gameState()->getRandomGenerator().nextInt(4, 10); - break; - case 4: - character = 10; - break; - } - - stacks[SlotID(0)]->setType(CreatureID(subID)); - TQuantity &amount = stacks[SlotID(0)]->count; - CCreature &c = *VLC->creh->creatures[subID]; - if(amount == 0) - { - amount = cb->gameState()->getRandomGenerator().nextInt(c.ammMin, c.ammMax); - - if(amount == 0) //armies with 0 creatures are illegal - { - logGlobal->warnStream() << "Problem: stack " << nodeName() << " cannot have 0 creatures. Check properties of " << c.nodeName(); - amount = 1; - } - } - formation.randomFormation = cb->gameState()->getRandomGenerator().nextInt(); - - temppower = stacks[SlotID(0)]->count * 1000; - refusedJoining = false; -} - -void CGCreature::newTurn() const -{//Works only for stacks of single type of size up to 2 millions - if (stacks.begin()->second->count < VLC->modh->settings.CREEP_SIZE && cb->getDate(Date::DAY_OF_WEEK) == 1 && cb->getDate(Date::DAY) > 1) - { - ui32 power = temppower * (100 + VLC->modh->settings.WEEKLY_GROWTH)/100; - cb->setObjProperty(id, ObjProperty::MONSTER_COUNT, std::min (power/1000 , (ui32)VLC->modh->settings.CREEP_SIZE)); //set new amount - cb->setObjProperty(id, ObjProperty::MONSTER_POWER, power); //increase temppower - } - if (VLC->modh->modules.STACK_EXP) - cb->setObjProperty(id, ObjProperty::MONSTER_EXP, VLC->modh->settings.NEUTRAL_STACK_EXP); //for testing purpose -} -void CGCreature::setPropertyDer(ui8 what, ui32 val) -{ - switch (what) - { - case ObjProperty::MONSTER_COUNT: - stacks[SlotID(0)]->count = val; - break; - case ObjProperty::MONSTER_POWER: - temppower = val; - break; - case ObjProperty::MONSTER_EXP: - giveStackExp(val); - break; - case ObjProperty::MONSTER_RESTORE_TYPE: - formation.basicType = val; - break; - case ObjProperty::MONSTER_REFUSED_JOIN: - refusedJoining = val; - break; - } -} - -int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const -{ - //calculate relative strength of hero and creatures armies - double relStrength = double(h->getTotalStrength()) / getArmyStrength(); - - int powerFactor; - if(relStrength >= 7) - powerFactor = 11; - - else if(relStrength >= 1) - powerFactor = (int)(2*(relStrength-1)); - - else if(relStrength >= 0.5) - powerFactor = -1; - - else if(relStrength >= 0.333) - powerFactor = -2; - else - powerFactor = -3; - - std::set myKindCres; //what creatures are the same kind as we - const CCreature * myCreature = VLC->creh->creatures[subID]; - myKindCres.insert(myCreature->idNumber); //we - myKindCres.insert(myCreature->upgrades.begin(), myCreature->upgrades.end()); //our upgrades - - for(ConstTransitivePtr &crea : VLC->creh->creatures) - { - if(vstd::contains(crea->upgrades, myCreature->idNumber)) //it's our base creatures - myKindCres.insert(crea->idNumber); - } - - int count = 0, //how many creatures of similar kind has hero - totalCount = 0; - - for (auto & elem : h->Slots()) - { - if(vstd::contains(myKindCres,elem.second->type->idNumber)) - count += elem.second->count; - totalCount += elem.second->count; - } - - int sympathy = 0; // 0 if hero have no similar creatures - if(count) - sympathy++; // 1 - if hero have at least 1 similar creature - if(count*2 > totalCount) - sympathy++; // 2 - hero have similar creatures more that 50% - - int charisma = powerFactor + h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy; - - if(charisma < character) //creatures will fight - return -2; - - if (allowJoin) - { - if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy + 1 >= character) - return 0; //join for free - - else if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) * 2 + sympathy + 1 >= character) - return VLC->creh->creatures[subID]->cost[6] * getStackCount(SlotID(0)); //join for gold - } - - //we are still here - creatures have not joined hero, flee or fight - - if (charisma > character) - return -1; //flee - else - return -2; //fight -} - -void CGCreature::fleeDecision(const CGHeroInstance *h, ui32 pursue) const -{ - if(refusedJoining) - cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, false); - - if(pursue) - { - fight(h); - } - else - { - cb->removeObject(this); - } -} - -void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const -{ - if(!accept) - { - if(takenAction(h,false) == -1) //they flee - { - cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, true); - flee(h); - } - else //they fight - { - showInfoDialog(h,87,0);//Insulted by your refusal of their offer, the monsters attack! - fight(h); - } - } - else //accepted - { - if (cb->getResource(h->tempOwner, Res::GOLD) < cost) //player don't have enough gold! - { - InfoWindow iw; - iw.player = h->tempOwner; - iw.text << std::pair(1,29); //You don't have enough gold - cb->showInfoDialog(&iw); - - //act as if player refused - joinDecision(h,cost,false); - return; - } - - //take gold - if(cost) - cb->giveResource(h->tempOwner,Res::GOLD,-cost); - - cb->tryJoiningArmy(this, h, true, true); - } -} - -void CGCreature::fight( const CGHeroInstance *h ) const -{ - //split stacks - //TODO: multiple creature types in a stack? - int basicType = stacks.begin()->second->type->idNumber; - cb->setObjProperty(id, ObjProperty::MONSTER_RESTORE_TYPE, basicType); //store info about creature stack - - double relativePower = static_cast(h->getTotalStrength()) / getArmyStrength(); - int stacksCount; - //TODO: number depends on tile type - if (relativePower < 0.5) - { - stacksCount = 7; - } - else if (relativePower < 0.67) - { - stacksCount = 7; - } - else if (relativePower < 1) - { - stacksCount = 6; - } - else if (relativePower < 1.5) - { - stacksCount = 5; - } - else if (relativePower < 2) - { - stacksCount = 4; - } - else - { - stacksCount = 3; - } - SlotID sourceSlot = stacks.begin()->first; - SlotID destSlot; - for (int stacksLeft = stacksCount; stacksLeft > 1; --stacksLeft) - { - int stackSize = stacks.begin()->second->count / stacksLeft; - if (stackSize) - { - if ((destSlot = getFreeSlot()).validSlot()) - cb->moveStack(StackLocation(this, sourceSlot), StackLocation(this, destSlot), stackSize); - else - { - logGlobal->warnStream() <<"Warning! Not enough empty slots to split stack!"; - break; - } - } - else break; - } - if (stacksCount > 1) - { - if (formation.randomFormation % 100 < 50) //upgrade - { - SlotID slotId = SlotID(stacks.size() / 2); - const auto & upgrades = getStack(slotId).type->upgrades; - if(!upgrades.empty()) - { - auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator()); - cb->changeStackType(StackLocation(this, slotId), VLC->creh->creatures[*it]); - } - } - } - - cb->startBattleI(h, this); - -} - -void CGCreature::flee( const CGHeroInstance * h ) const -{ - BlockingDialog ynd(true,false); - ynd.player = h->tempOwner; - ynd.text.addTxt(MetaString::ADVOB_TXT,91); - ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID); - cb->showBlockingDialog(&ynd); -} - -void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - - if(result.winner==0) - { - cb->removeObject(this); - } - else - { - //int killedAmount=0; - //for(std::set >::iterator i=result->casualties[1].begin(); i!=result->casualties[1].end(); i++) - // if(i->first == subID) - // killedAmount += i->second; - //cb->setAmount(id, slots.find(0)->second.second - killedAmount); - - /* - MetaString ms; - int pom = slots.find(0)->second.getQuantityID(); - pom = 174 + 3*pom + 1; - ms << std::pair(6,pom) << " " << std::pair(7,subID); - cb->setHoverName(id,&ms); - cb->setObjProperty(id, 11, slots.begin()->second.count * 1000); - */ - - //merge stacks into one - TSlots::const_iterator i; - CCreature * cre = VLC->creh->creatures[formation.basicType]; - for (i = stacks.begin(); i != stacks.end(); i++) - { - if (cre->isMyUpgrade(i->second->type)) - { - cb->changeStackType (StackLocation(this, i->first), cre); //un-upgrade creatures - } - } - - //first stack has to be at slot 0 -> if original one got killed, move there first remaining stack - if(!hasStackAtSlot(SlotID(0))) - cb->moveStack(StackLocation(this, stacks.begin()->first), StackLocation(this, SlotID(0)), stacks.begin()->second->count); - - while (stacks.size() > 1) //hopefully that's enough - { - // TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere) - i = stacks.end(); - i--; - SlotID slot = getSlotFor(i->second->type); - if (slot == i->first) //no reason to move stack to its own slot - break; - else - cb->moveStack (StackLocation(this, i->first), StackLocation(this, slot), i->second->count); - } - - cb->setObjProperty(id, ObjProperty::MONSTER_POWER, stacks.begin()->second->count * 1000); //remember casualties - } -} - -void CGCreature::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - auto action = takenAction(hero); - if(!refusedJoining && action >= JOIN_FOR_FREE) //higher means price - joinDecision(hero, action, answer); - else if(action != FIGHT) - fleeDecision(hero, answer); - else - assert(0); -} - -void CGMine::onHeroVisit( const CGHeroInstance * h ) const -{ - int relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); - - if(relations == 2) //we're visiting our mine - { - cb->showGarrisonDialog(id,h->id,true); - return; - } - else if (relations == 1)//ally - return; - - if(stacksCount()) //Mine is guarded - { - BlockingDialog ynd(true,false); - ynd.player = h->tempOwner; - ynd.text.addTxt(MetaString::ADVOB_TXT, subID == 7 ? 84 : 187); - cb->showBlockingDialog(&ynd); - return; - } - - flagMine(h->tempOwner); - -} - -void CGMine::newTurn() const -{ - if(cb->getDate() == 1) - return; - - if (tempOwner == PlayerColor::NEUTRAL) - return; - - cb->giveResource(tempOwner, producedResource, producedQuantity); -} - -void CGMine::initObj() -{ - if(subID >= 7) //Abandoned Mine - { - //set guardians - int howManyTroglodytes = cb->gameState()->getRandomGenerator().nextInt(100, 199); - auto troglodytes = new CStackInstance(CreatureID::TROGLODYTES, howManyTroglodytes); - putStack(SlotID(0), troglodytes); - - //after map reading tempOwner placeholds bitmask for allowed resources - std::vector possibleResources; - for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) - if(tempOwner.getNum() & 1<(i)); - - assert(!possibleResources.empty()); - producedResource = *RandomGeneratorUtil::nextItem(possibleResources, cb->gameState()->getRandomGenerator()); - tempOwner = PlayerColor::NEUTRAL; - hoverName = VLC->generaltexth->mines[7].first + "\n" + VLC->generaltexth->allTexts[202] + " " + troglodytes->getQuantityTXT(false) + " " + troglodytes->type->namePl; - } - else - { - producedResource = static_cast(subID); - - MetaString ms; - ms << std::pair(9,producedResource); - if(tempOwner >= PlayerColor::PLAYER_LIMIT) - tempOwner = PlayerColor::NEUTRAL; - else - ms << " (" << std::pair(6,23+tempOwner.getNum()) << ")"; - ms.toString(hoverName); - } - - producedQuantity = defaultResProduction(); -} - -void CGMine::flagMine(PlayerColor player) const -{ - assert(tempOwner != player); - cb->setOwner(this, player); //not ours? flag it! - - MetaString ms; - ms << std::pair(9,subID) << "\n(" << std::pair(6,23+player.getNum()) << ")"; - if(subID == 7) - { - ms << "(%s)"; - ms.addReplacement(MetaString::RES_NAMES, producedResource); - } - cb->setHoverName(this,&ms); - - InfoWindow iw; - iw.soundID = soundBase::FLAGMINE; - iw.text.addTxt(MetaString::MINE_EVNTS,producedResource); //not use subID, abandoned mines uses default mine texts - iw.player = player; - iw.components.push_back(Component(Component::RESOURCE,producedResource,producedQuantity,-1)); - cb->showInfoDialog(&iw); -} - -ui32 CGMine::defaultResProduction() -{ - switch(producedResource) - { - case Res::WOOD: - case Res::ORE: - return 2; - case Res::GOLD: - return 1000; - default: - return 1; - } -} - -void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if(result.winner == 0) //attacker won - { - if(subID == 7) - { - showInfoDialog(hero->tempOwner, 85, 0); - } - flagMine(hero->tempOwner); - } -} - -void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if(answer) - cb->startBattleI(hero, this); -} - -void CGResource::initObj() -{ - blockVisit = true; - hoverName = VLC->generaltexth->restypes[subID]; - - if(!amount) - { - switch(subID) - { - case 6: - amount = cb->gameState()->getRandomGenerator().nextInt(500, 1000); - break; - case 0: case 2: - amount = cb->gameState()->getRandomGenerator().nextInt(6, 10); - break; - default: - amount = cb->gameState()->getRandomGenerator().nextInt(3, 5); - break; - } - } -} - -void CGResource::onHeroVisit( const CGHeroInstance * h ) const -{ - if(stacksCount()) - { - if(message.size()) - { - BlockingDialog ynd(true,false); - ynd.player = h->getOwner(); - ynd.text << message; - cb->showBlockingDialog(&ynd); - } - else - { - blockingDialogAnswered(h, true); //behave as if player accepted battle - } - } - else - { - if(message.length()) - { - InfoWindow iw; - iw.player = h->tempOwner; - iw.text << message; - cb->showInfoDialog(&iw); - } - collectRes(h->getOwner()); - } -} - -void CGResource::collectRes( PlayerColor player ) const -{ - cb->giveResource(player, static_cast(subID), amount); - ShowInInfobox sii; - sii.player = player; - sii.c = Component(Component::RESOURCE,subID,amount,0); - sii.text.addTxt(MetaString::ADVOB_TXT,113); - sii.text.addReplacement(MetaString::RES_NAMES, subID); - cb->showCompInfo(&sii); - cb->removeObject(this); -} - -void CGResource::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if(result.winner == 0) //attacker won - collectRes(hero->getOwner()); -} - -void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if(answer) - cb->startBattleI(hero, this); -} - -void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const -{ - ObjectInstanceID destinationid; - switch(ID) - { - case Obj::MONOLITH_ONE_WAY_ENTRANCE: //one way - find corresponding exit monolith - { - if(vstd::contains(objs,Obj::MONOLITH_ONE_WAY_EXIT) && vstd::contains(objs[Obj::MONOLITH_ONE_WAY_EXIT],subID) && objs[Obj::MONOLITH_ONE_WAY_EXIT][subID].size()) - { - destinationid = *RandomGeneratorUtil::nextItem(objs[Obj::MONOLITH_ONE_WAY_EXIT][subID], cb->gameState()->getRandomGenerator()); - } - else - { - logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; - } - break; - } - case Obj::MONOLITH_TWO_WAY://two way monolith - pick any other one - case Obj::WHIRLPOOL: //Whirlpool - if(vstd::contains(objs,ID) && vstd::contains(objs[ID],subID) && objs[ID][subID].size()>1) - { - //choose another exit - do - { - destinationid = *RandomGeneratorUtil::nextItem(objs[ID][subID], cb->gameState()->getRandomGenerator()); - } while(destinationid == id); - - if (ID == Obj::WHIRLPOOL) - { - if (!h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION)) - { - if (h->Slots().size() > 1 || h->Slots().begin()->second->count > 1) - { //we can't remove last unit - SlotID targetstack = h->Slots().begin()->first; //slot numbers may vary - for(auto i = h->Slots().rbegin(); i != h->Slots().rend(); i++) - { - if (h->getPower(targetstack) > h->getPower(i->first)) - { - targetstack = (i->first); - } - } - - TQuantity countToTake = h->getStackCount(targetstack) * 0.5; - vstd::amax(countToTake, 1); - - - InfoWindow iw; - iw.player = h->tempOwner; - iw.text.addTxt (MetaString::ADVOB_TXT, 168); - iw.components.push_back (Component(CStackBasicDescriptor(h->getCreature(targetstack), countToTake))); - cb->showInfoDialog(&iw); - cb->changeStackCount(StackLocation(h, targetstack), -countToTake); - } - } - } - } - else - logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; - break; - case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level - { - destinationid = getMatchingGate(id); - if(destinationid == ObjectInstanceID()) //no exit - { - showInfoDialog(h,153,0);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged. - } - break; - } - } - if(destinationid == ObjectInstanceID()) - { - logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( "; - return; - } - if (ID == Obj::WHIRLPOOL) - { - std::set tiles = cb->getObj(destinationid)->getBlockedPos(); - auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); - cb->moveHero(h->id, tile + int3(1,0,0), true); - } - else - cb->moveHero (h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(), true); -} - -void CGTeleport::initObj() -{ - int si = subID; - switch (ID) - { - case Obj::SUBTERRANEAN_GATE://ignore subterranean gates subid - case Obj::WHIRLPOOL: - { - si = 0; - break; - } - default: - break; - } - objs[ID][si].push_back(id); -} - -void CGTeleport::postInit() //matches subterranean gates into pairs -{ - //split on underground and surface gates - std::vector gatesSplit[2]; //surface and underground gates - for(auto & elem : objs[Obj::SUBTERRANEAN_GATE][0]) - { - const CGObjectInstance *hlp = cb->getObj(elem); - gatesSplit[hlp->pos.z].push_back(hlp); - } - - //sort by position - std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), [](const CGObjectInstance * a, const CGObjectInstance * b) - { - return a->pos < b->pos; - }); - - for(size_t i = 0; i < gatesSplit[0].size(); i++) - { - const CGObjectInstance *cur = gatesSplit[0][i]; - - //find nearest underground exit - std::pair best(-1, std::numeric_limits::max()); //pair - for(int j = 0; j < gatesSplit[1].size(); j++) - { - const CGObjectInstance *checked = gatesSplit[1][j]; - if(!checked) - continue; - si32 hlp = checked->pos.dist2dSQ(cur->pos); - if(hlp < best.second) - { - best.first = j; - best.second = hlp; - } - } - - if(best.first >= 0) //found pair - { - gates.push_back(std::make_pair(cur->id, gatesSplit[1][best.first]->id)); - gatesSplit[1][best.first] = nullptr; - } - else - gates.push_back(std::make_pair(cur->id, ObjectInstanceID())); - } - objs.erase(Obj::SUBTERRANEAN_GATE); -} - -ObjectInstanceID CGTeleport::getMatchingGate(ObjectInstanceID id) -{ - for(auto & gate : gates) - { - if(gate.first == id) - return gate.second; - if(gate.second == id) - return gate.first; - } - - return ObjectInstanceID(); -} - -void CGArtifact::initObj() -{ - blockVisit = true; - if(ID == Obj::ARTIFACT) - { - hoverName = VLC->arth->artifacts[subID]->Name(); - if(!storedArtifact->artType) - storedArtifact->setType(VLC->arth->artifacts[subID]); - } - if(ID == Obj::SPELL_SCROLL) - subID = 1; - - assert(storedArtifact->artType); - assert(storedArtifact->getParentNodes().size()); - - //assert(storedArtifact->artType->id == subID); //this does not stop desync -} - -void CGArtifact::onHeroVisit( const CGHeroInstance * h ) const -{ - if(!stacksCount()) - { - InfoWindow iw; - iw.player = h->tempOwner; - switch(ID) - { - case Obj::ARTIFACT: - { - iw.soundID = soundBase::treasure; //play sound only for non-scroll arts - iw.components.push_back(Component(Component::ARTIFACT,subID,0,0)); - if(message.length()) - iw.text << message; - else - { - if (VLC->arth->artifacts[subID]->EventText().size()) - iw.text << std::pair (MetaString::ART_EVNTS, subID); - else //fix for mod artifacts with no event text - { - iw.text.addTxt (MetaString::ADVOB_TXT, 183); //% has found treasure - iw.text.addReplacement (h->name); - } - - } - } - break; - case Obj::SPELL_SCROLL: - { - int spellID = storedArtifact->getGivenSpellID(); - iw.components.push_back (Component(Component::SPELL, spellID,0,0)); - iw.text.addTxt (MetaString::ADVOB_TXT,135); - iw.text.addReplacement(MetaString::SPELL_NAME, spellID); - } - break; - } - cb->showInfoDialog(&iw); - pick(h); - } - else - { - if(message.size()) - { - BlockingDialog ynd(true,false); - ynd.player = h->getOwner(); - ynd.text << message; - cb->showBlockingDialog(&ynd); - } - else - { - blockingDialogAnswered(h, true); - } - } -} - -void CGArtifact::pick(const CGHeroInstance * h) const -{ - cb->giveHeroArtifact(h, storedArtifact, ArtifactPosition::FIRST_AVAILABLE); - cb->removeObject(this); -} - -void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if(result.winner == 0) //attacker won - pick(hero); -} - -void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if(answer) - cb->startBattleI(hero, this); -} - -bool CQuest::checkQuest (const CGHeroInstance * h) const -{ - switch (missionType) - { - case MISSION_NONE: - return true; - case MISSION_LEVEL: - if (m13489val <= h->level) - return true; - return false; - case MISSION_PRIMARY_STAT: - for (int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) - { - if (h->getPrimSkillLevel(static_cast(i)) < m2stats[i]) - return false; - } - return true; - case MISSION_KILL_HERO: - case MISSION_KILL_CREATURE: - if (!h->cb->getObjByQuestIdentifier(m13489val)) - return true; - return false; - case MISSION_ART: - for (auto & elem : m5arts) - { - if (h->hasArt(elem)) - continue; - return false; //if the artifact was not found - } - return true; - case MISSION_ARMY: - { - std::vector::const_iterator cre; - TSlots::const_iterator it; - ui32 count; - for (cre = m6creatures.begin(); cre != m6creatures.end(); ++cre) - { - for (count = 0, it = h->Slots().begin(); it != h->Slots().end(); ++it) - { - if (it->second->type == cre->type) - count += it->second->count; - } - if (count < cre->count) //not enough creatures of this kind - return false; - } - } - return true; - case MISSION_RESOURCES: - for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, +1)) //including Mithril ? - { //Quest has no direct access to callback - if (h->cb->getResource (h->tempOwner, i) < m7resources[i]) - return false; - } - return true; - case MISSION_HERO: - if (m13489val == h->type->ID.getNum()) - return true; - return false; - case MISSION_PLAYER: - if (m13489val == h->getOwner().getNum()) - return true; - return false; - default: - return false; - } -} -void CQuest::getVisitText (MetaString &iwText, std::vector &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const -{ - std::string text; - bool failRequirements = (h ? !checkQuest(h) : true); - - if (firstVisit) - { - isCustom = isCustomFirst; - iwText << (text = firstVisitText); - } - else if (failRequirements) - { - isCustom = isCustomNext; - iwText << (text = nextVisitText); - } - switch (missionType) - { - case MISSION_LEVEL: - components.push_back(Component (Component::EXPERIENCE, 0, m13489val, 0)); - if (!isCustom) - iwText.addReplacement(m13489val); - break; - case MISSION_PRIMARY_STAT: - { - MetaString loot; - for (int i = 0; i < 4; ++i) - { - if (m2stats[i]) - { - components.push_back(Component (Component::PRIM_SKILL, i, m2stats[i], 0)); - loot << "%d %s"; - loot.addReplacement(m2stats[i]); - loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); - } - } - if (!isCustom) - iwText.addReplacement(loot.buildList()); - } - break; - case MISSION_KILL_HERO: - components.push_back(Component(Component::HERO_PORTRAIT, heroPortrait, 0, 0)); - if (!isCustom) - addReplacements(iwText, text); - break; - case MISSION_HERO: - //FIXME: portrait may not match hero, if custom portrait was set in map editor - components.push_back(Component (Component::HERO_PORTRAIT, VLC->heroh->heroes[m13489val]->imageIndex, 0, 0)); - if (!isCustom) - iwText.addReplacement(VLC->heroh->heroes[m13489val]->name); - break; - case MISSION_KILL_CREATURE: - { - components.push_back(Component(stackToKill)); - if (!isCustom) - { - addReplacements(iwText, text); - } - } - break; - case MISSION_ART: - { - MetaString loot; - for (auto & elem : m5arts) - { - components.push_back(Component (Component::ARTIFACT, elem, 0, 0)); - loot << "%s"; - loot.addReplacement(MetaString::ART_NAMES, elem); - } - if (!isCustom) - iwText.addReplacement(loot.buildList()); - } - break; - case MISSION_ARMY: - { - MetaString loot; - for (auto & elem : m6creatures) - { - components.push_back(Component(elem)); - loot << "%s"; - loot.addReplacement(elem); - } - if (!isCustom) - iwText.addReplacement(loot.buildList()); - } - break; - case MISSION_RESOURCES: - { - MetaString loot; - for (int i = 0; i < 7; ++i) - { - if (m7resources[i]) - { - components.push_back(Component (Component::RESOURCE, i, m7resources[i], 0)); - loot << "%d %s"; - loot.addReplacement(m7resources[i]); - loot.addReplacement(MetaString::RES_NAMES, i); - } - } - if (!isCustom) - iwText.addReplacement(loot.buildList()); - } - break; - case MISSION_PLAYER: - components.push_back(Component (Component::FLAG, m13489val, 0, 0)); - if (!isCustom) - iwText.addReplacement(VLC->generaltexth->colors[m13489val]); - break; - } -} -void CQuest::getRolloverText (MetaString &ms, bool onHover) const -{ - if (onHover) - ms << "\n\n"; - - ms << VLC->generaltexth->quests[missionType-1][onHover ? 3 : 4][textOption]; - - switch (missionType) - { - case MISSION_LEVEL: - ms.addReplacement(m13489val); - break; - case MISSION_PRIMARY_STAT: - { - MetaString loot; - for (int i = 0; i < 4; ++i) - { - if (m2stats[i]) - { - loot << "%d %s"; - loot.addReplacement(m2stats[i]); - loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); - } - } - ms.addReplacement(loot.buildList()); - } - break; - case MISSION_KILL_HERO: - ms.addReplacement(heroName); - break; - case MISSION_KILL_CREATURE: - ms.addReplacement(stackToKill); - break; - case MISSION_ART: - { - MetaString loot; - for (auto & elem : m5arts) - { - loot << "%s"; - loot.addReplacement(MetaString::ART_NAMES, elem); - } - ms.addReplacement(loot.buildList()); - } - break; - case MISSION_ARMY: - { - MetaString loot; - for (auto & elem : m6creatures) - { - loot << "%s"; - loot.addReplacement(elem); - } - ms.addReplacement(loot.buildList()); - } - break; - case MISSION_RESOURCES: - { - MetaString loot; - for (int i = 0; i < 7; ++i) - { - if (m7resources[i]) - { - loot << "%d %s"; - loot.addReplacement(m7resources[i]); - loot.addReplacement(MetaString::RES_NAMES, i); - } - } - ms.addReplacement(loot.buildList()); - } - break; - case MISSION_HERO: - ms.addReplacement(VLC->heroh->heroes[m13489val]->name); - break; - case MISSION_PLAYER: - ms.addReplacement(VLC->generaltexth->colors[m13489val]); - break; - default: - break; - } -} - -void CQuest::getCompletionText (MetaString &iwText, std::vector &components, bool isCustom, const CGHeroInstance * h) const -{ - iwText << completedText; - switch (missionType) - { - case CQuest::MISSION_LEVEL: - if (!isCustomComplete) - iwText.addReplacement(m13489val); - break; - case CQuest::MISSION_PRIMARY_STAT: - if (vstd::contains (completedText,'%')) //there's one case when there's nothing to replace - { - MetaString loot; - for (int i = 0; i < 4; ++i) - { - if (m2stats[i]) - { - loot << "%d %s"; - loot.addReplacement(m2stats[i]); - loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); - } - } - if (!isCustomComplete) - iwText.addReplacement(loot.buildList()); - } - break; - case CQuest::MISSION_ART: - { - MetaString loot; - for (auto & elem : m5arts) - { - loot << "%s"; - loot.addReplacement(MetaString::ART_NAMES, elem); - } - if (!isCustomComplete) - iwText.addReplacement(loot.buildList()); - } - break; - case CQuest::MISSION_ARMY: - { - MetaString loot; - for (auto & elem : m6creatures) - { - loot << "%s"; - loot.addReplacement(elem); - } - if (!isCustomComplete) - iwText.addReplacement(loot.buildList()); - } - break; - case CQuest::MISSION_RESOURCES: - { - MetaString loot; - for (int i = 0; i < 7; ++i) - { - if (m7resources[i]) - { - loot << "%d %s"; - loot.addReplacement(m7resources[i]); - loot.addReplacement(MetaString::RES_NAMES, i); - } - } - if (!isCustomComplete) - iwText.addReplacement(loot.buildList()); - } - break; - case MISSION_KILL_HERO: - case MISSION_KILL_CREATURE: - if (!isCustomComplete) - addReplacements(iwText, completedText); - break; - case MISSION_HERO: - if (!isCustomComplete) - iwText.addReplacement(VLC->heroh->heroes[m13489val]->name); - break; - case MISSION_PLAYER: - if (!isCustomComplete) - iwText.addReplacement(VLC->generaltexth->colors[m13489val]); - break; - } -} -void CGSeerHut::setObjToKill() -{ - if (quest->missionType == CQuest::MISSION_KILL_CREATURE) - { - quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :? - assert(quest->stackToKill.type); - quest->stackToKill.count = 0; //no count in info window - quest->stackDirection = checkDirection(); - } - else if (quest->missionType == CQuest::MISSION_KILL_HERO) - { - quest->heroName = getHeroToKill(false)->name; - quest->heroPortrait = getHeroToKill(false)->portrait; - } -} - -void CGSeerHut::init() -{ - seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, cb->gameState()->getRandomGenerator()); - quest->textOption = cb->gameState()->getRandomGenerator().nextInt(2); -} - -void CGSeerHut::initObj() -{ - init(); - - quest->progress = CQuest::NOT_ACTIVE; - if (quest->missionType) - { - if (!quest->isCustomFirst) - quest->firstVisitText = VLC->generaltexth->quests[quest->missionType-1][0][quest->textOption]; - if (!quest->isCustomNext) - quest->nextVisitText = VLC->generaltexth->quests[quest->missionType-1][1][quest->textOption]; - if (!quest->isCustomComplete) - quest->completedText = VLC->generaltexth->quests[quest->missionType-1][2][quest->textOption]; - } - else - { - quest->progress = CQuest::COMPLETE; - quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->textOption]; - } - -} - -void CGSeerHut::getRolloverText (MetaString &text, bool onHover) const -{ - quest->getRolloverText (text, onHover);//TODO: simplify? - if (!onHover) - text.addReplacement(seerName); -} - -const std::string & CGSeerHut::getHoverText() const -{ - switch (ID) - { - case Obj::SEER_HUT: - if (quest->progress != CQuest::NOT_ACTIVE) - { - hoverName = VLC->generaltexth->allTexts[347]; - boost::algorithm::replace_first(hoverName,"%s", seerName); - } - else //just seer hut - hoverName = VLC->objtypeh->getObjectName(ID); - break; - case Obj::QUEST_GUARD: - hoverName = VLC->objtypeh->getObjectName(ID); - break; - default: - logGlobal->debugStream() << "unrecognized quest object"; - } - if (quest->progress & quest->missionType) //rollover when the quest is active - { - MetaString ms; - getRolloverText (ms, true); - hoverName += ms.toString(); - } - return hoverName; -} - -void CQuest::addReplacements(MetaString &out, const std::string &base) const -{ - switch(missionType) - { - case MISSION_KILL_CREATURE: - out.addReplacement(stackToKill); - if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster - { - out.addReplacement(VLC->generaltexth->arraytxt[147+stackDirection]); - } - break; - case MISSION_KILL_HERO: - out.addReplacement(heroName); - break; - } -} - -bool IQuestObject::checkQuest(const CGHeroInstance* h) const -{ - return quest->checkQuest(h); -} - -void IQuestObject::getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const -{ - quest->getVisitText (text,components, isCustom, FirstVisit, h); -} - -void CGSeerHut::getCompletionText(MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h) const -{ - quest->getCompletionText (text, components, isCustom, h); - switch (rewardType) - { - case EXPERIENCE: components.push_back(Component (Component::EXPERIENCE, 0, h->calculateXp(rVal), 0)); - break; - case MANA_POINTS: components.push_back(Component (Component::PRIM_SKILL, 5, rVal, 0)); - break; - case MORALE_BONUS: components.push_back(Component (Component::MORALE, 0, rVal, 0)); - break; - case LUCK_BONUS: components.push_back(Component (Component::LUCK, 0, rVal, 0)); - break; - case RESOURCES: components.push_back(Component (Component::RESOURCE, rID, rVal, 0)); - break; - case PRIMARY_SKILL: components.push_back(Component (Component::PRIM_SKILL, rID, rVal, 0)); - break; - case SECONDARY_SKILL: components.push_back(Component (Component::SEC_SKILL, rID, rVal, 0)); - break; - case ARTIFACT: components.push_back(Component (Component::ARTIFACT, rID, 0, 0)); - break; - case SPELL: components.push_back(Component (Component::SPELL, rID, 0, 0)); - break; - case CREATURE: components.push_back(Component (Component::CREATURE, rID, rVal, 0)); - break; - } -} - -void CGSeerHut::setPropertyDer (ui8 what, ui32 val) -{ - switch (what) - { - case 10: - quest->progress = static_cast(val); - break; - } -} -void CGSeerHut::newTurn() const -{ - if (quest->lastDay >= 0 && quest->lastDay < cb->getDate()-1) //time is up - { - cb->setObjProperty (id, 10, CQuest::COMPLETE); - } - -} -void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.player = h->getOwner(); - if (quest->progress < CQuest::COMPLETE) - { - bool firstVisit = !quest->progress; - bool failRequirements = !checkQuest(h); - bool isCustom=false; - - if (firstVisit) - { - isCustom = quest->isCustomFirst; - cb->setObjProperty (id, 10, CQuest::IN_PROGRESS); - - AddQuest aq; - aq.quest = QuestInfo (quest, this, visitablePos()); - aq.player = h->tempOwner; - cb->sendAndApply (&aq); //TODO: merge with setObjProperty? - } - else if (failRequirements) - { - isCustom = quest->isCustomNext; - } - - if (firstVisit || failRequirements) - { - getVisitText (iw.text, iw.components, isCustom, firstVisit, h); - - cb->showInfoDialog(&iw); - } - if (!failRequirements) // propose completion, also on first visit - { - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::QUEST; - - getCompletionText (bd.text, bd.components, isCustom, h); - - cb->showBlockingDialog (&bd); - return; - } - } - else - { - iw.text << VLC->generaltexth->seerEmpty[quest->textOption]; - if (ID == Obj::SEER_HUT) - iw.text.addReplacement(seerName); - cb->showInfoDialog(&iw); - } -} -int CGSeerHut::checkDirection() const -{ - int3 cord = getCreatureToKill()->pos; - if ((double)cord.x/(double)cb->getMapSize().x < 0.34) //north - { - if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //northwest - return 8; - else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //north - return 1; - else //northeast - return 2; - } - else if ((double)cord.x/(double)cb->getMapSize().x < 0.67) //horizontal - { - if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //west - return 7; - else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //central - return 9; - else //east - return 3; - } - else //south - { - if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //southwest - return 6; - else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //south - return 5; - else //southeast - return 4; - } -} -void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const -{ - if (accept) - { - switch (quest->missionType) - { - case CQuest::MISSION_ART: - for (auto & elem : quest->m5arts) - { - cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false))); - } - break; - case CQuest::MISSION_ARMY: - cb->takeCreatures(h->id, quest->m6creatures); - break; - case CQuest::MISSION_RESOURCES: - for (int i = 0; i < 7; ++i) - { - cb->giveResource(h->getOwner(), static_cast(i), -quest->m7resources[i]); - } - break; - default: - break; - } - cb->setObjProperty (id, 10, CQuest::COMPLETE); //mission complete - completeQuest(h); //make sure to remove QuestGuard at the very end - } -} -void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward -{ - switch (rewardType) - { - case EXPERIENCE: - { - TExpType expVal = h->calculateXp(rVal); - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); - break; - } - case MANA_POINTS: - { - cb->setManaPoints(h->id, h->mana+rVal); - break; - } - case MORALE_BONUS: case LUCK_BONUS: - { - Bonus hb(Bonus::ONE_WEEK, (rewardType == 3 ? Bonus::MORALE : Bonus::LUCK), - Bonus::OBJECT, rVal, h->id.getNum(), "", -1); - GiveBonus gb; - gb.id = h->id.getNum(); - gb.bonus = hb; - cb->giveHeroBonus(&gb); - } - break; - case RESOURCES: - cb->giveResource(h->getOwner(), static_cast(rID), rVal); - break; - case PRIMARY_SKILL: - cb->changePrimSkill(h, static_cast(rID), rVal, false); - break; - case SECONDARY_SKILL: - cb->changeSecSkill(h, SecondarySkill(rID), rVal, false); - break; - case ARTIFACT: - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[rID],ArtifactPosition::FIRST_AVAILABLE); - break; - case SPELL: - { - std::set spell; - spell.insert (SpellID(rID)); - cb->changeSpells(h, true, spell); - } - break; - case CREATURE: - { - CCreatureSet creatures; - creatures.setCreature(SlotID(0), CreatureID(rID), rVal); - cb->giveCreatures(this, h, creatures, false); - } - break; - default: - break; - } -} - -const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const -{ - const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val); - if(allowNull && !o) - return nullptr; - assert(o && (o->ID == Obj::HERO || o->ID == Obj::PRISON)); - return static_cast(o); -} - -const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const -{ - const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val); - if(allowNull && !o) - return nullptr; - assert(o && o->ID == Obj::MONSTER); - return static_cast(o); -} - -void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - finishQuest(hero, answer); -} - -void CGQuestGuard::init() -{ - blockVisit = true; - quest->textOption = cb->gameState()->getRandomGenerator().nextInt(3, 5); -} -void CGQuestGuard::completeQuest(const CGHeroInstance *h) const -{ - cb->removeObject(this); -} -void CGWitchHut::initObj() -{ - ability = *RandomGeneratorUtil::nextItem(allowedAbilities, cb->gameState()->getRandomGenerator()); -} - -void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.soundID = soundBase::gazebo; - iw.player = h->getOwner(); - if(!wasVisited(h->tempOwner)) - cb->setObjProperty(id, 10, h->tempOwner.getNum()); - ui32 txt_id; - if(h->getSecSkillLevel(SecondarySkill(ability))) //you already know this skill - { - txt_id =172; - } - else if(!h->canLearnSkill()) //already all skills slots used - { - txt_id = 173; - } - else //give sec skill - { - iw.components.push_back(Component(Component::SEC_SKILL, ability, 1, 0)); - txt_id = 171; - cb->changeSecSkill(h, SecondarySkill(ability), 1, true); - } - - iw.text.addTxt(MetaString::ADVOB_TXT,txt_id); - iw.text.addReplacement(MetaString::SEC_SKILL_NAME, ability); - cb->showInfoDialog(&iw); -} - -const std::string & CGWitchHut::getHoverText() const -{ - hoverName = VLC->objtypeh->getObjectName(ID); - if(wasVisited(cb->getLocalPlayer())) - { - hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s) - boost::algorithm::replace_first(hoverName,"%s",VLC->generaltexth->skillName[ability]); - const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); - if(h && h->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability - hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned) - } - return hoverName; -} - -void CGMagicWell::onHeroVisit( const CGHeroInstance * h ) const -{ - int message; - - if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Well today - { - message = 78;//"A second drink at the well in one day will not help you." - } - else if(h->mana < h->manaLimit()) - { - giveDummyBonus(h->id); - cb->setManaPoints(h->id,h->manaLimit()); - message = 77; - } - else - { - message = 79; - } - showInfoDialog(h,message,soundBase::faerie); -} - -const std::string & CGMagicWell::getHoverText() const -{ - getNameVis(hoverName); - return hoverName; -} - -void CGPandoraBox::initObj() -{ - blockVisit = (ID==Obj::PANDORAS_BOX); //block only if it's really pandora's box (events also derive from that class) - hasGuardians = stacks.size(); -} - -void CGPandoraBox::onHeroVisit(const CGHeroInstance * h) const -{ - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::QUEST; - bd.text.addTxt (MetaString::ADVOB_TXT, 14); - cb->showBlockingDialog (&bd); -} - -void CGPandoraBox::giveContentsUpToExp(const CGHeroInstance *h) const -{ - cb->removeAfterVisit(this); - - InfoWindow iw; - iw.player = h->getOwner(); - - bool changesPrimSkill = false; - for (auto & elem : primskills) - { - if(elem) - { - changesPrimSkill = true; - break; - } - } - - if(gainedExp || changesPrimSkill || abilities.size()) - { - TExpType expVal = h->calculateXp(gainedExp); - //getText(iw,afterBattle,175,h); //wtf? - iw.text.addTxt(MetaString::ADVOB_TXT, 175); //%s learns something - iw.text.addReplacement(h->name); - - if(expVal) - iw.components.push_back(Component(Component::EXPERIENCE,0,expVal,0)); - for(int i=0; ishowInfoDialog(&iw); - - //give sec skills - for(int i=0; igetSecSkillLevel(abilities[i]); - - if( (curLev && curLev < abilityLevels[i]) || (h->canLearnSkill() )) - { - cb->changeSecSkill(h,abilities[i],abilityLevels[i],true); - } - } - - //give prim skills - for(int i=0; ichangePrimSkill(h,static_cast(i),primskills[i],false); - - assert(!cb->isVisitCoveredByAnotherQuery(this, h)); - - //give exp - if(expVal) - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); - } - - if(!cb->isVisitCoveredByAnotherQuery(this, h)) - giveContentsAfterExp(h); - //Otherwise continuation occurs via post-level-up callback. -} - -void CGPandoraBox::giveContentsAfterExp(const CGHeroInstance *h) const -{ - bool hadGuardians = hasGuardians; //copy, because flag will be emptied after issuing first post-battle message - - std::string msg = message; //in case box is removed in the meantime - InfoWindow iw; - iw.player = h->getOwner(); - - if(spells.size()) - { - std::set spellsToGive; - iw.components.clear(); - if (spells.size() > 1) - { - iw.text.addTxt(MetaString::ADVOB_TXT, 188); //%s learns spells - } - else - { - iw.text.addTxt(MetaString::ADVOB_TXT, 184); //%s learns a spell - } - iw.text.addReplacement(h->name); - std::vector > * sp = &VLC->spellh->objects; - for(auto i=spells.cbegin(); i != spells.cend(); i++) - { - if ((*sp)[*i]->level <= h->getSecSkillLevel(SecondarySkill::WISDOM) + 2) //enough wisdom - { - iw.components.push_back(Component(Component::SPELL,*i,0,0)); - spellsToGive.insert(*i); - } - } - if(!spellsToGive.empty()) - { - cb->changeSpells(h,true,spellsToGive); - cb->showInfoDialog(&iw); - } - } - - if(manaDiff) - { - getText(iw,hadGuardians,manaDiff,176,177,h); - iw.components.push_back(Component(Component::PRIM_SKILL,5,manaDiff,0)); - cb->showInfoDialog(&iw); - cb->setManaPoints(h->id, h->mana + manaDiff); - } - - if(moraleDiff) - { - getText(iw,hadGuardians,moraleDiff,178,179,h); - iw.components.push_back(Component(Component::MORALE,0,moraleDiff,0)); - cb->showInfoDialog(&iw); - GiveBonus gb; - gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,moraleDiff,id.getNum(),""); - gb.id = h->id.getNum(); - cb->giveHeroBonus(&gb); - } - - if(luckDiff) - { - getText(iw,hadGuardians,luckDiff,180,181,h); - iw.components.push_back(Component(Component::LUCK,0,luckDiff,0)); - cb->showInfoDialog(&iw); - GiveBonus gb; - gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,luckDiff,id.getNum(),""); - gb.id = h->id.getNum(); - cb->giveHeroBonus(&gb); - } - - iw.components.clear(); - iw.text.clear(); - for(int i=0; ishowInfoDialog(&iw); - } - - iw.components.clear(); - iw.text.clear(); - for(int i=0; i 0) - iw.components.push_back(Component(Component::RESOURCE,i,resources[i],0)); - } - if(iw.components.size()) - { - getText(iw,hadGuardians,183,h); - cb->showInfoDialog(&iw); - } - - iw.components.clear(); - // getText(iw,afterBattle,183,h); - iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - iw.text.addReplacement(h->name); - for(auto & elem : artifacts) - { - iw.components.push_back(Component(Component::ARTIFACT,elem,0,0)); - if(iw.components.size() >= 14) - { - cb->showInfoDialog(&iw); - iw.components.clear(); - iw.text.addTxt(MetaString::ADVOB_TXT, 183); //% has found treasure - once more? - iw.text.addReplacement(h->name); - } - } - if(iw.components.size()) - { - cb->showInfoDialog(&iw); - } - - for(int i=0; igiveResource(h->getOwner(),static_cast(i),resources[i]); - - for(auto & elem : artifacts) - cb->giveHeroNewArtifact(h, VLC->arth->artifacts[elem],ArtifactPosition::FIRST_AVAILABLE); - - iw.components.clear(); - iw.text.clear(); - - if (creatures.Slots().size()) - { //this part is taken straight from creature bank - MetaString loot; - for(auto & elem : creatures.Slots()) - { //build list of joined creatures - iw.components.push_back(Component(*elem.second)); - loot << "%s"; - loot.addReplacement(*elem.second); - } - - if (creatures.Slots().size() == 1 && creatures.Slots().begin()->second->count == 1) - iw.text.addTxt(MetaString::ADVOB_TXT, 185); - else - iw.text.addTxt(MetaString::ADVOB_TXT, 186); - - iw.text.addReplacement(loot.buildList()); - iw.text.addReplacement(h->name); - - cb->showInfoDialog(&iw); - cb->giveCreatures(this, h, creatures, true); - } - if(!hasGuardians && msg.size()) - { - iw.text << msg; - cb->showInfoDialog(&iw); - } -} - -void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const -{ - if(afterBattle || !message.size()) - { - iw.text.addTxt(MetaString::ADVOB_TXT,text);//%s has lost treasure. - iw.text.addReplacement(h->name); - } - else - { - iw.text << message; - afterBattle = true; - } -} - -void CGPandoraBox::getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const -{ - iw.components.clear(); - iw.text.clear(); - if(afterBattle || !message.size()) - { - iw.text.addTxt(MetaString::ADVOB_TXT,val < 0 ? negative : positive); //%s's luck takes a turn for the worse / %s's luck increases - iw.text.addReplacement(h->name); - } - else - { - iw.text << message; - afterBattle = true; - } -} - -void CGPandoraBox::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if(result.winner) - return; - - giveContentsUpToExp(hero); -} - -void CGPandoraBox::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if (answer) - { - if (stacksCount() > 0) //if pandora's box is protected by army - { - showInfoDialog(hero,16,0); - cb->startBattleI(hero, this); //grants things after battle - } - else if (message.size() == 0 && resources.size() == 0 - && primskills.size() == 0 && abilities.size() == 0 - && abilityLevels.size() == 0 && artifacts.size() == 0 - && spells.size() == 0 && creatures.Slots().size() > 0 - && gainedExp == 0 && manaDiff == 0 && moraleDiff == 0 && luckDiff == 0) //if it gives nothing without battle - { - showInfoDialog(hero,15,0); - cb->removeObject(this); - } - else //if it gives something without battle - { - giveContentsUpToExp(hero); - } - } -} - -void CGPandoraBox::heroLevelUpDone(const CGHeroInstance *hero) const -{ - giveContentsAfterExp(hero); -} - -void CGEvent::onHeroVisit( const CGHeroInstance * h ) const -{ - if(!(availableFor & (1 << h->tempOwner.getNum()))) - return; - if(cb->getPlayerSettings(h->tempOwner)->playerID) - { - if(humanActivate) - activated(h); - } - else if(computerActivate) - activated(h); -} - -void CGEvent::activated( const CGHeroInstance * h ) const -{ - if(stacksCount() > 0) - { - InfoWindow iw; - iw.player = h->tempOwner; - if(message.size()) - iw.text << message; - else - iw.text.addTxt(MetaString::ADVOB_TXT, 16); - cb->showInfoDialog(&iw); - cb->startBattleI(h, this); - } - else - { - giveContentsUpToExp(h); - } -} - -void CGObservatory::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.player = h->tempOwner; - switch (ID) - { - case Obj::REDWOOD_OBSERVATORY: - case Obj::PILLAR_OF_FIRE: - { - iw.soundID = soundBase::LIGHTHOUSE; - iw.text.addTxt(MetaString::ADVOB_TXT,98 + (ID==Obj::PILLAR_OF_FIRE)); - - FoWChange fw; - fw.player = h->tempOwner; - fw.mode = 1; - cb->getTilesInRange (fw.tiles, pos, 20, h->tempOwner, 1); - cb->sendAndApply (&fw); - break; - } - case Obj::COVER_OF_DARKNESS: - { - iw.text.addTxt (MetaString::ADVOB_TXT, 31); - hideTiles(h->tempOwner, 20); - break; - } - } - cb->showInfoDialog(&iw); -} - -void CGShrine::onHeroVisit( const CGHeroInstance * h ) const -{ - if(spell == SpellID::NONE) - { - logGlobal->errorStream() << "Not initialized shrine visited!"; - return; - } - - if(!wasVisited(h->tempOwner)) - cb->setObjProperty(id, 10, h->tempOwner.getNum()); - - InfoWindow iw; - iw.soundID = soundBase::temple; - iw.player = h->getOwner(); - iw.text.addTxt(MetaString::ADVOB_TXT,127 + ID - 88); - iw.text.addTxt(MetaString::SPELL_NAME,spell); - iw.text << "."; - - if(!h->getArt(ArtifactPosition::SPELLBOOK)) - { - iw.text.addTxt(MetaString::ADVOB_TXT,131); - } - else if(ID == Obj::SHRINE_OF_MAGIC_THOUGHT && !h->getSecSkillLevel(SecondarySkill::WISDOM)) //it's third level spell and hero doesn't have wisdom - { - iw.text.addTxt(MetaString::ADVOB_TXT,130); - } - else if(vstd::contains(h->spells,spell))//hero already knows the spell - { - iw.text.addTxt(MetaString::ADVOB_TXT,174); - } - else //give spell - { - std::set spells; - spells.insert(spell); - cb->changeSpells(h, true, spells); - - iw.components.push_back(Component(Component::SPELL,spell,0,0)); - } - - cb->showInfoDialog(&iw); -} - -void CGShrine::initObj() -{ - if(spell == SpellID::NONE) //spell not set - { - int level = ID-87; - std::vector possibilities; - cb->getAllowedSpells (possibilities, level); - - if(possibilities.empty()) - { - logGlobal->errorStream() << "Error: cannot init shrine, no allowed spells!"; - return; - } - - spell = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator()); - } -} - -const std::string & CGShrine::getHoverText() const -{ - hoverName = VLC->objtypeh->getObjectName(ID); - if(wasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current - { - hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s) - boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->name); - const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); - if(h && vstd::contains(h->spells,spell)) //hero knows that ability - hoverName += "\n\n" + VLC->generaltexth->allTexts[354]; // (Already learned) - } - return hoverName; -} - -void CGSignBottle::initObj() -{ - //if no text is set than we pick random from the predefined ones - if(message.empty()) - { - message = *RandomGeneratorUtil::nextItem(VLC->generaltexth->randsign, cb->gameState()->getRandomGenerator()); - } - - if(ID == Obj::OCEAN_BOTTLE) - { - blockVisit = true; - } -} - -void CGSignBottle::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.soundID = soundBase::STORE; - iw.player = h->getOwner(); - iw.text << message; - cb->showInfoDialog(&iw); - - if(ID == Obj::OCEAN_BOTTLE) - cb->removeObject(this); -} - -//TODO: remove -//void CGScholar::giveAnyBonus( const CGHeroInstance * h ) const -//{ -// -//} - -void CGScholar::onHeroVisit( const CGHeroInstance * h ) const -{ - - EBonusType type = bonusType; - int bid = bonusID; - //check if the bonus if applicable, if not - give primary skill (always possible) - int ssl = h->getSecSkillLevel(SecondarySkill(bid)); //current sec skill level, used if bonusType == 1 - if((type == SECONDARY_SKILL - && ((ssl == 3) || (!ssl && !h->canLearnSkill()))) ////hero already has expert level in the skill or (don't know skill and doesn't have free slot) - || (type == SPELL && (!h->getArt(ArtifactPosition::SPELLBOOK) || vstd::contains(h->spells, (ui32) bid) - || ( SpellID(bid).toSpell()->level > h->getSecSkillLevel(SecondarySkill::WISDOM) + 2) - ))) //hero doesn't have a spellbook or already knows the spell or doesn't have Wisdom - { - type = PRIM_SKILL; - bid = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS - 1); - } - - InfoWindow iw; - iw.soundID = soundBase::gazebo; - iw.player = h->getOwner(); - iw.text.addTxt(MetaString::ADVOB_TXT,115); - - switch (type) - { - case PRIM_SKILL: - cb->changePrimSkill(h,static_cast(bid),+1); - iw.components.push_back(Component(Component::PRIM_SKILL,bid,+1,0)); - break; - case SECONDARY_SKILL: - cb->changeSecSkill(h,SecondarySkill(bid),+1); - iw.components.push_back(Component(Component::SEC_SKILL,bid,ssl+1,0)); - break; - case SPELL: - { - std::set hlp; - hlp.insert(SpellID(bid)); - cb->changeSpells(h,true,hlp); - iw.components.push_back(Component(Component::SPELL,bid,0,0)); - } - break; - default: - logGlobal->errorStream() << "Error: wrong bonus type (" << (int)type << ") for Scholar!\n"; - return; - } - - cb->showInfoDialog(&iw); - cb->removeObject(this); -} - -void CGScholar::initObj() -{ - blockVisit = true; - if(bonusType == RANDOM) - { - bonusType = static_cast(cb->gameState()->getRandomGenerator().nextInt(2)); - switch(bonusType) - { - case PRIM_SKILL: - bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS -1); - break; - case SECONDARY_SKILL: - bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::SKILL_QUANTITY -1); - break; - case SPELL: - std::vector possibilities; - for (int i = 1; i < 6; ++i) - cb->getAllowedSpells (possibilities, i); - bonusID = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator()); - break; - } - } -} - -void CGGarrison::onHeroVisit (const CGHeroInstance *h) const -{ - int ally = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); - if (!ally && stacksCount() > 0) { - //TODO: Find a way to apply magic garrison effects in battle. - cb->startBattleI(h, this); - return; - } - - //New owner. - if (!ally) - cb->setOwner(this, h->tempOwner); - - cb->showGarrisonDialog(id, h->id, removableUnits); -} - -ui8 CGGarrison::getPassableness() const -{ - if ( !stacksCount() )//empty - anyone can visit - return GameConstants::ALL_PLAYERS; - if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit - return 0; - - ui8 mask = 0; - TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner); - for(PlayerColor it : ts->players) - mask |= 1<objh->bankObjToIndex(this); - bc = nullptr; - daycounter = 0; - multiplier = 1; -} -const std::string & CBank::getHoverText() const -{ - bool visited = (bc == nullptr); - hoverName = VLC->objh->creBanksNames[index] + " " + visitedTxt(visited); - return hoverName; -} -void CBank::reset(ui16 var1) //prevents desync -{ - ui8 chance = 0; - for (auto & elem : VLC->objh->banksInfo[index]) - { - if (var1 < (chance += elem->chance)) - { - bc = elem; - break; - } - } - artifacts.clear(); -} - -void CBank::initialize() const -{ - cb->setObjProperty(id, ObjProperty::BANK_RESET, cb->gameState()->getRandomGenerator().nextInt()); //synchronous reset - - for (ui8 i = 0; i <= 3; i++) - { - for (ui8 n = 0; n < bc->artifacts[i]; n++) - { - CArtifact::EartClass artClass; - switch(i) - { - case 0: artClass = CArtifact::ART_TREASURE; break; - case 1: artClass = CArtifact::ART_MINOR; break; - case 2: artClass = CArtifact::ART_MAJOR; break; - case 3: artClass = CArtifact::ART_RELIC; break; - default: assert(0); continue; - } - - int artID = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), artClass); - cb->setObjProperty(id, ObjProperty::BANK_ADD_ARTIFACT, artID); - } - } - - cb->setObjProperty(id, ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //get army -} -void CBank::setPropertyDer (ui8 what, ui32 val) -/// random values are passed as arguments and processed identically on all clients -{ - switch (what) - { - case ObjProperty::BANK_DAYCOUNTER: //daycounter - if (val == 0) - daycounter = 1; //yes, 1 - else - daycounter++; - break; - case ObjProperty::BANK_MULTIPLIER: //multiplier, in percent - multiplier = val / 100.0; - break; - case 13: //bank preset - bc = VLC->objh->banksInfo[index][val]; - break; - case ObjProperty::BANK_RESET: - reset (val%100); - break; - case ObjProperty::BANK_CLEAR_CONFIG: - bc = nullptr; - break; - case ObjProperty::BANK_CLEAR_ARTIFACTS: //remove rewards from Derelict Ship - artifacts.clear(); - break; - case ObjProperty::BANK_INIT_ARMY: //set ArmedInstance army - { - int upgraded = 0; - if (val%100 < bc->upgradeChance) //once again anti-desync - upgraded = 1; - switch (bc->guards.size()) - { - case 1: - for (int i = 0; i < 4; ++i) - setCreature (SlotID(i), bc->guards[0].first, bc->guards[0].second / 5 ); - setCreature (SlotID(4), CreatureID(bc->guards[0].first + upgraded), bc->guards[0].second / 5 ); - break; - case 4: - { - if (bc->guards.back().second) //all stacks are present - { - for (auto & elem : bc->guards) - { - setCreature (SlotID(stacksCount()), elem.first, elem.second); - } - } - else if (bc->guards[2].second)//Wraiths are present, split two stacks in Crypt - { - setCreature (SlotID(0), bc->guards[0].first, bc->guards[0].second / 2 ); - setCreature (SlotID(1), bc->guards[1].first, bc->guards[1].second / 2); - setCreature (SlotID(2), CreatureID(bc->guards[2].first + upgraded), bc->guards[2].second); - setCreature (SlotID(3), bc->guards[1].first, bc->guards[1].second / 2 ); - setCreature (SlotID(4), bc->guards[0].first, bc->guards[0].second - (bc->guards[0].second / 2) ); - - } - else //split both stacks - { - for (int i = 0; i < 3; ++i) //skellies - setCreature (SlotID(2*i), bc->guards[0].first, bc->guards[0].second / 3); - for (int i = 0; i < 2; ++i) //zombies - setCreature (SlotID(2*i+1), bc->guards[1].first, bc->guards[1].second / 2); - } - } - break; - default: - logGlobal->warnStream() << "Error: Unexpected army data: " << bc->guards.size() <<" items found"; - return; - } - } - break; - case ObjProperty::BANK_ADD_ARTIFACT: //add Artifact - { - artifacts.push_back (val); - break; - } - } -} - -void CBank::newTurn() const -{ - if (bc == nullptr) - { - if (cb->getDate() == 1) - initialize(); //initialize on first day - else if (daycounter >= 28 && (subID < 13 || subID > 16)) //no reset for Emissaries - { - initialize(); - cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 0); //daycounter 0 - if (ID == Obj::DERELICT_SHIP && cb->getDate() > 1) - { - cb->setObjProperty (id, ObjProperty::BANK_MULTIPLIER, 0);//ugly hack to make derelict ships usable only once - cb->setObjProperty (id, ObjProperty::BANK_CLEAR_ARTIFACTS, 0); - } - } - else - cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++ - } -} -bool CBank::wasVisited (PlayerColor player) const -{ - return !bc; -} - -void CBank::onHeroVisit (const CGHeroInstance * h) const -{ - if (bc) - { - int banktext = 0; - switch (ID) - { - case Obj::CREATURE_BANK: - banktext = 32; - break; - case Obj::DERELICT_SHIP: - banktext = 41; - break; - case Obj::DRAGON_UTOPIA: - banktext = 47; - break; - case Obj::CRYPT: - banktext = 119; - break; - case Obj::SHIPWRECK: - banktext = 122; - break; - } - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::ROGUE; - bd.text.addTxt(MetaString::ADVOB_TXT,banktext); - if (ID == Obj::CREATURE_BANK) - bd.text.addReplacement(VLC->objh->creBanksNames[index]); - cb->showBlockingDialog (&bd); - } - else - { - InfoWindow iw; - iw.soundID = soundBase::GRAVEYARD; - iw.player = h->getOwner(); - if (ID == Obj::CRYPT) //morale penalty for empty Crypt - { - GiveBonus gbonus; - gbonus.id = h->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[98]; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = -1; - cb->giveHeroBonus(&gbonus); - iw.text << VLC->generaltexth->advobtxt[120]; - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - } - else - { - iw.text << VLC->generaltexth->advobtxt[33]; - iw.text.addReplacement(VLC->objh->creBanksNames[index]); - } - cb->showInfoDialog(&iw); - } -} - -void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if (result.winner == 0) - { - int textID = -1; - InfoWindow iw; - iw.player = hero->getOwner(); - MetaString loot; - - switch (ID) - { - case Obj::CREATURE_BANK: case Obj::DRAGON_UTOPIA: - textID = 34; - break; - case Obj::DERELICT_SHIP: - if (multiplier) - textID = 43; - else - { - GiveBonus gbonus; - gbonus.id = hero->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[101]; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = -1; - cb->giveHeroBonus(&gbonus); - textID = 42; - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - } - break; - case Obj::CRYPT: - if (bc->resources.size() != 0) - textID = 121; - else - { - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - GiveBonus gbonus; - gbonus.id = hero->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[ID]; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = -1; - cb->giveHeroBonus(&gbonus); - textID = 120; - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - } - break; - case Obj::SHIPWRECK: - if (bc->resources.size()) - textID = 124; - else - textID = 123; - break; - } - - //grant resources - if (textID != 42) //empty derelict ship gives no cash - { - for (int it = 0; it < bc->resources.size(); it++) - { - if (bc->resources[it] != 0) - { - iw.components.push_back (Component (Component::RESOURCE, it, bc->resources[it], 0)); - loot << "%d %s"; - loot.addReplacement(iw.components.back().val); - loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype); - cb->giveResource (hero->getOwner(), static_cast(it), bc->resources[it]); - } - } - } - //grant artifacts - for (auto & elem : artifacts) - { - iw.components.push_back (Component (Component::ARTIFACT, elem, 0, 0)); - loot << "%s"; - loot.addReplacement(MetaString::ART_NAMES, elem); - cb->giveHeroNewArtifact (hero, VLC->arth->artifacts[elem], ArtifactPosition::FIRST_AVAILABLE); - } - //display loot - if (!iw.components.empty()) - { - iw.text.addTxt (MetaString::ADVOB_TXT, textID); - if (textID == 34) - { - iw.text.addReplacement(MetaString::CRE_PL_NAMES, result.casualties[1].begin()->first); - iw.text.addReplacement(loot.buildList()); - } - cb->showInfoDialog(&iw); - } - loot.clear(); - iw.components.clear(); - iw.text.clear(); - - //grant creatures - CCreatureSet ourArmy; - for (auto it = bc->creatures.cbegin(); it != bc->creatures.cend(); it++) - { - SlotID slot = ourArmy.getSlotFor(it->first); - ourArmy.addToSlot(slot, it->first, it->second); - } - for (auto & elem : ourArmy.Slots()) - { - iw.components.push_back(Component(*elem.second)); - loot << "%s"; - loot.addReplacement(*elem.second); - } - - if (ourArmy.Slots().size()) - { - if (ourArmy.Slots().size() == 1 && ourArmy.Slots().begin()->second->count == 1) - iw.text.addTxt (MetaString::ADVOB_TXT, 185); - else - iw.text.addTxt (MetaString::ADVOB_TXT, 186); - - iw.text.addReplacement(loot.buildList()); - iw.text.addReplacement(hero->name); - cb->showInfoDialog(&iw); - cb->giveCreatures(this, hero, ourArmy, false); - } - cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); //bc = nullptr - } -} - -void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if (answer) - { - cb->startBattleI(hero, this, true); - } -} - -void CGPyramid::initObj() -{ - std::vector available; - cb->getAllowedSpells (available, 5); - if (available.size()) - { - bc = VLC->objh->banksInfo[21].front(); //TODO: remove hardcoded value? - spell = *RandomGeneratorUtil::nextItem(available, cb->gameState()->getRandomGenerator()); - } - else - { - logGlobal->errorStream() <<"No spells available for Pyramid! Object set to empty."; - } - setPropertyDer(ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //set guards at game start -} -const std::string & CGPyramid::getHoverText() const -{ - hoverName = VLC->objh->creBanksNames[21]+ " " + visitedTxt((bc==nullptr)); - return hoverName; -} -void CGPyramid::onHeroVisit (const CGHeroInstance * h) const -{ - if (bc) - { - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::MYSTERY; - bd.text << VLC->generaltexth->advobtxt[105]; - cb->showBlockingDialog(&bd); - } - else - { - InfoWindow iw; - iw.player = h->getOwner(); - iw.text << VLC->generaltexth->advobtxt[107]; - iw.components.push_back (Component (Component::LUCK, 0 , -2, 0)); - GiveBonus gb; - gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,-2,id.getNum(),VLC->generaltexth->arraytxt[70]); - gb.id = h->id.getNum(); - cb->giveHeroBonus(&gb); - cb->showInfoDialog(&iw); - } -} - -void CGPyramid::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if (result.winner == 0) - { - InfoWindow iw; - iw.player = hero->getOwner(); - iw.text.addTxt (MetaString::ADVOB_TXT, 106); - iw.text.addTxt (MetaString::SPELL_NAME, spell); - if (!hero->getArt(ArtifactPosition::SPELLBOOK)) - iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook - else if (hero->getSecSkillLevel(SecondarySkill::WISDOM) < 3) - iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom - else - { - std::set spells; - spells.insert (SpellID(spell)); - cb->changeSpells (hero, true, spells); - iw.components.push_back(Component (Component::SPELL, spell, 0, 0)); - } - cb->showInfoDialog(&iw); - cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); - } -} - -void CGKeys::setPropertyDer (ui8 what, ui32 val) //101-108 - enable key for player 1-8 -{ - if (what >= 101 && what <= (100 + PlayerColor::PLAYER_LIMIT_I)) - { - PlayerColor player(what-101); - playerKeyMap[player].insert((ui8)val); - } - else - logGlobal->errorStream() << boost::format("Unexpected properties requested to set: what=%d, val=%d") % (int)what % val; -} - -bool CGKeys::wasMyColorVisited (PlayerColor player) const -{ - if (vstd::contains(playerKeyMap[player], subID)) //creates set if it's not there - return true; - else - return false; -} - -const std::string& CGKeys::getHoverText() const -{ - bool visited = wasMyColorVisited (cb->getLocalPlayer()); - hoverName = getName() + "\n" + visitedTxt(visited); - return hoverName; -} - - -const std::string CGKeys::getName() const -{ - std::string name; - name = VLC->generaltexth->tentColors[subID] + " " + VLC->objtypeh->getObjectName(ID); - return name; -} - -bool CGKeymasterTent::wasVisited (PlayerColor player) const -{ - return wasMyColorVisited (player); -} - -void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const -{ - int txt_id; - if (!wasMyColorVisited (h->getOwner()) ) - { - cb->setObjProperty(id, h->tempOwner.getNum()+101, subID); - txt_id=19; - } - else - txt_id=20; - showInfoDialog(h,txt_id,soundBase::CAVEHEAD); -} - -void CGBorderGuard::initObj() -{ - //ui32 m13489val = subID; //store color as quest info - blockVisit = true; -} - -void CGBorderGuard::getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const -{ - text << std::pair(11,18); -} - -void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const -{ - if (!onHover) - text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER); -} - -bool CGBorderGuard::checkQuest (const CGHeroInstance * h) const -{ - return wasMyColorVisited (h->tempOwner); -} - -void CGBorderGuard::onHeroVisit( const CGHeroInstance * h ) const -{ - if (wasMyColorVisited (h->getOwner()) ) - { - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::QUEST; - bd.text.addTxt (MetaString::ADVOB_TXT, 17); - cb->showBlockingDialog (&bd); - } - else - { - showInfoDialog(h,18,soundBase::CAVEHEAD); - - AddQuest aq; - aq.quest = QuestInfo (quest, this, visitablePos()); - aq.player = h->tempOwner; - cb->sendAndApply (&aq); - //TODO: add this quest only once OR check for multiple instances later - } -} - -void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if (answer) - cb->removeObject(this); -} - -void CGBorderGate::onHeroVisit( const CGHeroInstance * h ) const //TODO: passability -{ - if (!wasMyColorVisited (h->getOwner()) ) - { - showInfoDialog(h,18,0); - - AddQuest aq; - aq.quest = QuestInfo (quest, this, visitablePos()); - aq.player = h->tempOwner; - cb->sendAndApply (&aq); - } -} - -ui8 CGBorderGate::getPassableness() const -{ - ui8 ret = 0; - for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) - ret |= wasMyColorVisited(PlayerColor(i))<tempOwner; - cv.focusTime = 2000; - - FoWChange fw; - fw.player = h->tempOwner; - fw.mode = 1; - - for(auto it : eyelist[subID]) - { - const CGObjectInstance *eye = cb->getObj(it); - - cb->getTilesInRange (fw.tiles, eye->pos, 10, h->tempOwner, 1); - cb->sendAndApply(&fw); - cv.pos = eye->pos; - - cb->sendAndApply(&cv); - } - cv.pos = h->getPosition(false); - cb->sendAndApply(&cv); - } - } - else if (ID == Obj::EYE_OF_MAGI) - { - showInfoDialog(h,48,soundBase::invalid); - } - -} -void CGBoat::initObj() -{ - hero = nullptr; -} - -void CGSirens::initObj() -{ - blockVisit = true; -} - -const std::string & CGSirens::getHoverText() const -{ - getNameVis(hoverName); - return hoverName; -} - -void CGSirens::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.soundID = soundBase::DANGER; - iw.player = h->tempOwner; - if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Sirens - { - iw.text.addTxt(MetaString::ADVOB_TXT,133); - } - else - { - giveDummyBonus(h->id, Bonus::ONE_BATTLE); - TExpType xp = 0; - - for (auto i = h->Slots().begin(); i != h->Slots().end(); i++) - { - TQuantity drown = i->second->count * 0.3; - if(drown) - { - cb->changeStackCount(StackLocation(h, i->first), -drown); - xp += drown * i->second->type->valOfBonuses(Bonus::STACK_HEALTH); - } - } - - if(xp) - { - xp = h->calculateXp(xp); - iw.text.addTxt(MetaString::ADVOB_TXT,132); - iw.text.addReplacement(xp); - cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, xp, false); - } - else - { - iw.text.addTxt(MetaString::ADVOB_TXT,134); - } - } - cb->showInfoDialog(&iw); - -} - -//bool IShipyard::validLocation() const -//{ -// std::vector offsets; -// getOutOffsets(offsets); -// -// TerrainTile *tile; -// for(int i = 0; i < offsets.size(); i++) -// if((tile = IObjectInterface::cb->getTile(o->pos + offsets[i])) && tile->terType == TerrainTile::water) //tile is in the map and is water -// return true; -// return false; -//} - int3 IBoatGenerator::bestLocation() const { std::vector offsets; @@ -5910,7 +514,7 @@ int3 IBoatGenerator::bestLocation() const { if (const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map { - if (tile->terType == ETerrainType::WATER && (!tile->blocked || tile->blockingObjects.front()->ID == 8)) //and is water and is not blocked or is blocked by boat + if (tile->terType == ETerrainType::WATER && (!tile->blocked || tile->blockingObjects.front()->ID == 8)) //and is water and is not blocked or is blocked by boat return o->pos + offset; } } @@ -5960,7 +564,7 @@ void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visit out.addTxt(MetaString::ADVOB_TXT, 189); break; case NO_WATER: - logGlobal->errorStream() << "Shipyard without water!!! " << o->pos << "\t" << o->id; + logGlobal->errorStream() << "Shipyard without water!!! " << o->pos << "\t" << o->id; return; } } @@ -6000,652 +604,3 @@ const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj ) { return castFrom(const_cast(obj)); } - -CGShipyard::CGShipyard() - :IShipyard(this) -{ -} - -void CGShipyard::getOutOffsets( std::vector &offsets ) const -{ - // H J L K I - // A x S x B - // C E G F D - offsets += int3(-3,0,0), int3(1,0,0), //AB - int3(-3,1,0), int3(1,1,0), int3(-2,1,0), int3(0,1,0), int3(-1,1,0), //CDEFG - int3(-3,-1,0), int3(1,-1,0), int3(-2,-1,0), int3(0,-1,0), int3(-1,-1,0); //HIJKL -} - -void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const -{ - if(!cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner)) - cb->setOwner(this, h->tempOwner); - - auto s = shipyardStatus(); - if(s != IBoatGenerator::GOOD) - { - InfoWindow iw; - iw.player = tempOwner; - getProblemText(iw.text, h); - cb->showInfoDialog(&iw); - } - else - { - openWindow(OpenWindow::SHIPYARD_WINDOW,id.getNum(),h->id.getNum()); - } -} - -void CCartographer::onHeroVisit( const CGHeroInstance * h ) const -{ - if (!wasVisited (h->getOwner()) ) //if hero has not visited yet this cartographer - { - if (cb->getResource(h->tempOwner, Res::GOLD) >= 1000) //if he can afford a map - { - //ask if he wants to buy one - int text=0; - switch (subID) - { - case 0: - text = 25; - break; - case 1: - text = 26; - break; - case 2: - text = 27; - break; - default: - logGlobal->warnStream() << "Unrecognized subtype of cartographer"; - } - assert(text); - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::LIGHTHOUSE; - bd.text.addTxt (MetaString::ADVOB_TXT, text); - cb->showBlockingDialog (&bd); - } - else //if he cannot afford - { - showInfoDialog(h,28,soundBase::CAVEHEAD); - } - } - else //if he already visited carographer - { - showInfoDialog(h,24,soundBase::CAVEHEAD); - } -} - -void CCartographer::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{ - if (answer) //if hero wants to buy map - { - cb->giveResource (hero->tempOwner, Res::GOLD, -1000); - FoWChange fw; - fw.mode = 1; - fw.player = hero->tempOwner; - - //subIDs of different types of cartographers: - //water = 0; land = 1; underground = 2; - cb->getAllTiles (fw.tiles, hero->tempOwner, subID - 1, !subID + 1); //reveal appropriate tiles - cb->sendAndApply (&fw); - cb->setObjProperty (id, 10, hero->tempOwner.getNum()); - } -} - -void CGDenOfthieves::onHeroVisit (const CGHeroInstance * h) const -{ - cb->showThievesGuildWindow(h->tempOwner, id); -} - -void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const -{ - InfoWindow iw; - iw.player = h->tempOwner; - TeamState *ts = cb->gameState()->getPlayerTeam(h->tempOwner); - assert(ts); - TeamID team = ts->id; - - if(!wasVisited(team)) - { - iw.text.addTxt(MetaString::ADVOB_TXT, 96); - cb->sendAndApply(&iw); - - cb->setObjProperty(id, 20, h->tempOwner.getNum()); //increment general visited obelisks counter - - openWindow(OpenWindow::PUZZLE_MAP, h->tempOwner.getNum()); - - cb->setObjProperty(id, 10, h->tempOwner.getNum()); //mark that particular obelisk as visited - } - else - { - iw.text.addTxt(MetaString::ADVOB_TXT, 97); - cb->sendAndApply(&iw); - } - -} - -void CGObelisk::initObj() -{ - obeliskCount++; -} - -const std::string & CGObelisk::getHoverText() const -{ - bool visited = wasVisited(cb->getLocalPlayer()); - hoverName = VLC->objtypeh->getObjectName(ID) + " " + visitedTxt(visited); - return hoverName; -} - -void CGObelisk::setPropertyDer( ui8 what, ui32 val ) -{ - CPlayersVisited::setPropertyDer(what, val); - switch(what) - { - case 20: - assert(val < PlayerColor::PLAYER_LIMIT_I); - visited[TeamID(val)]++; - - if(visited[TeamID(val)] > obeliskCount) - { - logGlobal->errorStream() << "Error: Visited " << visited[TeamID(val)] << "\t\t" << obeliskCount; - assert(0); - } - - break; - } -} - -void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const -{ - if(h->tempOwner != tempOwner) - { - PlayerColor oldOwner = tempOwner; - cb->setOwner(this,h->tempOwner); //not ours? flag it! - showInfoDialog(h,69,soundBase::LIGHTHOUSE); - giveBonusTo(h->tempOwner); - - if(oldOwner < PlayerColor::PLAYER_LIMIT) //remove bonus from old owner - { - RemoveBonus rb(RemoveBonus::PLAYER); - rb.whoID = oldOwner.getNum(); - rb.source = Bonus::OBJECT; - rb.id = id.getNum(); - cb->sendAndApply(&rb); - } - } -} - -void CGLighthouse::initObj() -{ - if(tempOwner < PlayerColor::PLAYER_LIMIT) - { - giveBonusTo(tempOwner); - } -} - -const std::string & CGLighthouse::getHoverText() const -{ - hoverName = VLC->objtypeh->getObjectName(ID); - //TODO: owned by %s player - return hoverName; -} - -void CGLighthouse::giveBonusTo( PlayerColor player ) const -{ - GiveBonus gb(GiveBonus::PLAYER); - gb.bonus.type = Bonus::SEA_MOVEMENT; - gb.bonus.val = 500; - gb.id = player.getNum(); - gb.bonus.duration = Bonus::PERMANENT; - gb.bonus.source = Bonus::OBJECT; - gb.bonus.sid = id.getNum(); - cb->sendAndApply(&gb); -} - -void CArmedInstance::randomizeArmy(int type) -{ - for (auto & elem : stacks) - { - int & randID = elem.second->idRand; - if(randID >= 0) - { - int level = randID / 2; - bool upgrade = randID % 2; - elem.second->setType(VLC->townh->factions[type]->town->creatures[level][upgrade]); - - randID = -1; - } - assert(elem.second->valid(false)); - assert(elem.second->armyObj == this); - } - return; -} - -CArmedInstance::CArmedInstance() -{ - battle = nullptr; -} - -//int CArmedInstance::valOfGlobalBonuses(CSelector selector) const -//{ -//// if (tempOwner != NEUTRAL_PLAYER) -// return cb->gameState()->players[tempOwner].valOfBonuses(selector); -//} - -void CArmedInstance::updateMoraleBonusFromArmy() -{ - if(!validTypes(false)) //object not randomized, don't bother - return; - - Bonus *b = getBonusList().getFirst(Selector::sourceType(Bonus::ARMY).And(Selector::type(Bonus::MORALE))); - if(!b) - { - b = new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, 0, -1); - addNewBonus(b); - } - - //number of alignments and presence of undead - std::set factions; - bool hasUndead = false; - - for(auto slot : Slots()) - { - const CStackInstance * inst = slot.second; - const CCreature * creature = VLC->creh->creatures[inst->getCreatureID()]; - - factions.insert(creature->faction); - // Check for undead flag instead of faction (undead mummies are neutral) - hasUndead |= inst->hasBonusOfType(Bonus::UNDEAD); - } - - size_t factionsInArmy = factions.size(); //town garrison seems to take both sets into account - - // Take Angelic Alliance troop-mixing freedom of non-evil units into account. - if (hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX)) - { - size_t mixableFactions = 0; - - for(TFaction f : factions) - { - if (VLC->townh->factions[f]->alignment != EAlignment::EVIL) - mixableFactions++; - } - if (mixableFactions > 0) - factionsInArmy -= mixableFactions - 1; - } - - if(factionsInArmy == 1) - { - b->val = +1; - b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1 - } - else if (!factions.empty()) // no bonus from empty garrison - { - b->val = 2 - factionsInArmy; - b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factionsInArmy % b->val); //Troops of %d alignments %d - } - boost::algorithm::trim(b->description); - - //-1 modifier for any Undead unit in army - const ui8 UNDEAD_MODIFIER_ID = -2; - Bonus *undeadModifier = getBonusList().getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID)); - if(hasUndead) - { - if(!undeadModifier) - addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::MORALE, Bonus::ARMY, -1, UNDEAD_MODIFIER_ID, VLC->generaltexth->arraytxt[116])); - } - else if(undeadModifier) - removeBonus(undeadModifier); - -} - -void CArmedInstance::armyChanged() -{ - updateMoraleBonusFromArmy(); -} - -CBonusSystemNode * CArmedInstance::whereShouldBeAttached(CGameState *gs) -{ - if(tempOwner < PlayerColor::PLAYER_LIMIT) - return gs->getPlayer(tempOwner); - else - return &gs->globalEffects; -} - -CBonusSystemNode * CArmedInstance::whatShouldBeAttached() -{ - return this; -} - -bool IMarket::getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - { - double effectiveness = std::min((getMarketEfficiency() + 1.0) / 20.0, 0.5); - - double r = VLC->objh->resVals[id1], //value of given resource - g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource - - if(r>g) //if given resource is more expensive than wanted - { - val2 = ceil(r / g); - val1 = 1; - } - else //if wanted resource is more expensive - { - val1 = (g / r) + 0.5; - val2 = 1; - } - } - break; - case EMarketMode::CREATURE_RESOURCE: - { - const double effectivenessArray[] = {0.0, 0.3, 0.45, 0.50, 0.65, 0.7, 0.85, 0.9, 1.0}; - double effectiveness = effectivenessArray[std::min(getMarketEfficiency(), 8)]; - - double r = VLC->creh->creatures[id1]->cost[6], //value of given creature in gold - g = VLC->objh->resVals[id2] / effectiveness; //value of wanted resource - - if(r>g) //if given resource is more expensive than wanted - { - val2 = ceil(r / g); - val1 = 1; - } - else //if wanted resource is more expensive - { - val1 = (g / r) + 0.5; - val2 = 1; - } - } - break; - case EMarketMode::RESOURCE_PLAYER: - val1 = 1; - val2 = 1; - break; - case EMarketMode::RESOURCE_ARTIFACT: - { - double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6); - double r = VLC->objh->resVals[id1], //value of offered resource - g = VLC->arth->artifacts[id2]->price / effectiveness; //value of bought artifact in gold - - if(id1 != 6) //non-gold prices are doubled - r /= 2; - - val1 = std::max(1, (int)((g / r) + 0.5)); //don't sell arts for less than 1 resource - val2 = 1; - } - break; - case EMarketMode::ARTIFACT_RESOURCE: - { - double effectiveness = std::min((getMarketEfficiency() + 3.0) / 20.0, 0.6); - double r = VLC->arth->artifacts[id1]->price * effectiveness, - g = VLC->objh->resVals[id2]; - -// if(id2 != 6) //non-gold prices are doubled -// r /= 2; - - val1 = 1; - val2 = std::max(1, (int)((r / g) + 0.5)); //at least one resource is given in return - } - break; - case EMarketMode::CREATURE_EXP: - { - val1 = 1; - val2 = (VLC->creh->creatures[id1]->AIValue / 40) * 5; - } - break; - case EMarketMode::ARTIFACT_EXP: - { - val1 = 1; - - int givenClass = VLC->arth->artifacts[id1]->getArtClassSerial(); - if(givenClass < 0 || givenClass > 3) - { - val2 = 0; - return false; - } - - static const int expPerClass[] = {1000, 1500, 3000, 6000}; - val2 = expPerClass[givenClass]; - } - break; - default: - assert(0); - return false; - } - - return true; -} - -bool IMarket::allowsTrade(EMarketMode::EMarketMode mode) const -{ - return false; -} - -int IMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::ARTIFACT_RESOURCE: - case EMarketMode::CREATURE_RESOURCE: - return -1; - default: - return 1; - } -} - -std::vector IMarket::availableItemsIds(EMarketMode::EMarketMode mode) const -{ - std::vector ret; - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::ARTIFACT_RESOURCE: - case EMarketMode::CREATURE_RESOURCE: - for (int i = 0; i < 7; i++) - ret.push_back(i); - } - return ret; -} - -const IMarket * IMarket::castFrom(const CGObjectInstance *obj, bool verbose /*= true*/) -{ - switch(obj->ID) - { - case Obj::TOWN: - return static_cast(obj); - case Obj::ALTAR_OF_SACRIFICE: - case Obj::BLACK_MARKET: - case Obj::TRADING_POST: - case Obj::TRADING_POST_SNOW: - case Obj::FREELANCERS_GUILD: - return static_cast(obj); - case Obj::UNIVERSITY: - return static_cast(obj); - default: - if(verbose) - logGlobal->errorStream() << "Cannot cast to IMarket object with ID " << obj->ID; - return nullptr; - } -} - -IMarket::IMarket(const CGObjectInstance *O) - :o(O) -{ - -} - -std::vector IMarket::availableModes() const -{ - std::vector ret; - for (int i = 0; i < EMarketMode::MARTKET_AFTER_LAST_PLACEHOLDER; i++) - if(allowsTrade((EMarketMode::EMarketMode)i)) - ret.push_back((EMarketMode::EMarketMode)i); - - return ret; -} - -void CGMarket::onHeroVisit(const CGHeroInstance * h) const -{ - openWindow(OpenWindow::MARKET_WINDOW,id.getNum(),h->id.getNum()); -} - -int CGMarket::getMarketEfficiency() const -{ - return 5; -} - -bool CGMarket::allowsTrade(EMarketMode::EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::RESOURCE_PLAYER: - switch(ID) - { - case Obj::TRADING_POST: - case Obj::TRADING_POST_SNOW: - return true; - default: - return false; - } - case EMarketMode::CREATURE_RESOURCE: - return ID == Obj::FREELANCERS_GUILD; - //case ARTIFACT_RESOURCE: - case EMarketMode::RESOURCE_ARTIFACT: - return ID == Obj::BLACK_MARKET; - case EMarketMode::ARTIFACT_EXP: - case EMarketMode::CREATURE_EXP: - return ID == Obj::ALTAR_OF_SACRIFICE; //TODO? check here for alignment of visiting hero? - would not be coherent with other checks here - case EMarketMode::RESOURCE_SKILL: - return ID == Obj::UNIVERSITY; - default: - return false; - } -} - -int CGMarket::availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const -{ - return -1; -} - -std::vector CGMarket::availableItemsIds(EMarketMode::EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::RESOURCE_RESOURCE: - case EMarketMode::RESOURCE_PLAYER: - return IMarket::availableItemsIds(mode); - default: - return std::vector(); - } -} - -CGMarket::CGMarket() - :IMarket(this) -{ -} - -std::vector CGBlackMarket::availableItemsIds(EMarketMode::EMarketMode mode) const -{ - switch(mode) - { - case EMarketMode::ARTIFACT_RESOURCE: - return IMarket::availableItemsIds(mode); - case EMarketMode::RESOURCE_ARTIFACT: - { - std::vector ret; - for(const CArtifact *a : artifacts) - if(a) - ret.push_back(a->id); - else - ret.push_back(-1); - return ret; - } - default: - return std::vector(); - } -} - -void CGBlackMarket::newTurn() const -{ - if(cb->getDate(Date::DAY_OF_MONTH) != 1) //new month - return; - - SetAvailableArtifacts saa; - saa.id = id.getNum(); - cb->pickAllowedArtsSet(saa.arts); - cb->sendAndApply(&saa); -} - -void CGUniversity::initObj() -{ - std::vector toChoose; - for(int i = 0; i < GameConstants::SKILL_QUANTITY; ++i) - { - if(cb->isAllowed(2, i)) - { - toChoose.push_back(i); - } - } - if(toChoose.size() < 4) - { - logGlobal->warnStream()<<"Warning: less then 4 available skills was found by University initializer!"; - return; - } - - // get 4 skills - for(int i = 0; i < 4; ++i) - { - // move randomly one skill to selected and remove from list - auto it = RandomGeneratorUtil::nextItem(toChoose, cb->gameState()->getRandomGenerator()); - skills.push_back(*it); - toChoose.erase(it); - } -} - -std::vector CGUniversity::availableItemsIds(EMarketMode::EMarketMode mode) const -{ - switch (mode) - { - case EMarketMode::RESOURCE_SKILL: - return skills; - - default: - return std::vector (); - } -} - -void CGUniversity::onHeroVisit(const CGHeroInstance * h) const -{ - openWindow(OpenWindow::UNIVERSITY_WINDOW,id.getNum(),h->id.getNum()); -} - -GrowthInfo::Entry::Entry(const std::string &format, int _count) - : count(_count) -{ - description = boost::str(boost::format(format) % count); -} - -GrowthInfo::Entry::Entry(int subID, BuildingID building, int _count) - : count(_count) -{ - description = boost::str(boost::format("%s %+d") % VLC->townh->factions[subID]->town->buildings.at(building)->Name() % count); -} - -CTownAndVisitingHero::CTownAndVisitingHero() -{ - setNodeType(TOWN_AND_VISITOR); -} - -int GrowthInfo::totalGrowth() const -{ - int ret = 0; - for(const Entry &entry : entries) - ret += entry.count; - - return ret; -} diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index b33790271..ed83a28f8 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -1,14 +1,10 @@ #pragma once -#include "../lib/CCreatureSet.h" -#include "../lib/CTownHandler.h" -#include "../lib/mapObjects/CObjectClassesHandler.h" -#include "CArtHandler.h" -#include "../lib/ConstTransitivePtr.h" -#include "int3.h" -#include "GameConstants.h" -#include "ResourceSet.h" -#include "CRandomGenerator.h" +#include "ObjectTemplate.h" + +#include "../IGameCallback.h" +#include "../int3.h" +#include "../HeroBonus.h" /* * CObjectHandler.h, part of VCMI engine @@ -20,86 +16,8 @@ * */ -class CGameState; -class CArtifactInstance; -struct MetaString; -struct BattleInfo; -struct QuestInfo; -class IGameCallback; -struct BattleResult; -class CGObjectInstance; -class CScript; class CGHeroInstance; -class CTown; -class CHero; -class CBuilding; -class CSpell; -class CGTownInstance; -class CGTownBuilding; -class CArtifact; -class CSpecObjInfo; -class CCastleEvent; -struct TerrainTile; -struct InfoWindow; -struct Component; -struct BankConfig; -struct UpdateHerospecialty; -struct NewArtifact; -class CGBoat; -class CArtifactSet; -class CCommanderInstance; - -class DLL_LINKAGE CQuest -{ -public: - enum Emission {MISSION_NONE = 0, MISSION_LEVEL = 1, MISSION_PRIMARY_STAT = 2, MISSION_KILL_HERO = 3, MISSION_KILL_CREATURE = 4, - MISSION_ART = 5, MISSION_ARMY = 6, MISSION_RESOURCES = 7, MISSION_HERO = 8, MISSION_PLAYER = 9, MISSION_KEYMASTER = 10}; - enum Eprogress {NOT_ACTIVE, IN_PROGRESS, COMPLETE}; - - si32 qid; //unique quest id for serialization / identification - - Emission missionType; - Eprogress progress; - si32 lastDay; //after this day (first day is 0) mission cannot be completed; if -1 - no limit - - ui32 m13489val; - std::vector m2stats; - std::vector m5arts; //artifacts id - std::vector m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant - std::vector m7resources; //TODO: use resourceset? - - //following field are used only for kill creature/hero missions, the original objects became inaccessible after their removal, so we need to store info needed for messages / hover text - ui8 textOption; - CStackBasicDescriptor stackToKill; - ui8 stackDirection; - std::string heroName; //backup of hero name - si32 heroPortrait; - - std::string firstVisitText, nextVisitText, completedText; - bool isCustomFirst, isCustomNext, isCustomComplete; - - CQuest(){missionType = MISSION_NONE;}; //default constructor - virtual ~CQuest(){}; - - virtual bool checkQuest (const CGHeroInstance * h) const; //determines whether the quest is complete or not - virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; - virtual void getCompletionText (MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h = nullptr) const; - virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry - virtual void completeQuest (const CGHeroInstance * h) const {}; - virtual void addReplacements(MetaString &out, const std::string &base) const; - - bool operator== (const CQuest & quest) const - { - return (quest.qid == qid); - } - - template void serialize(Handler &h, const int version) - { - h & qid & missionType & progress & lastDay & m13489val & m2stats & m5arts & m6creatures & m7resources - & textOption & stackToKill & stackDirection & heroName & heroPortrait - & firstVisitText & nextVisitText & completedText & isCustomFirst & isCustomNext & isCustomComplete; - } -}; +struct BattleResult; class DLL_LINKAGE IObjectInterface { @@ -174,30 +92,6 @@ public: } }; -class DLL_LINKAGE IMarket -{ -public: - const CGObjectInstance *o; - - IMarket(const CGObjectInstance *O); - virtual ~IMarket() {} - - virtual int getMarketEfficiency() const =0; - virtual bool allowsTrade(EMarketMode::EMarketMode mode) const; - virtual int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const; //-1 if unlimited - virtual std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; - - bool getOffer(int id1, int id2, int &val1, int &val2, EMarketMode::EMarketMode mode) const; //val1 - how many units of id1 player has to give to receive val2 units - std::vector availableModes() const; - - static const IMarket *castFrom(const CGObjectInstance *obj, bool verbose = true); - - template void serialize(Handler &h, const int version) - { - h & o; - } -}; - class DLL_LINKAGE CGObjectInstance : public IObjectInterface { public: @@ -269,1114 +163,6 @@ private: CGObjectInstance * obj; }; -class CGHeroPlaceholder : public CGObjectInstance -{ -public: - //subID stores id of hero type. If it's 0xff then following field is used - ui8 power; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & power; - } -}; - -class DLL_LINKAGE CPlayersVisited: public CGObjectInstance -{ -public: - std::set players; //players that visited this object - - bool wasVisited(PlayerColor player) const; - bool wasVisited(TeamID team) const; - void setPropertyDer(ui8 what, ui32 val) override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & players; - } -}; - -class DLL_LINKAGE CArmedInstance: public CGObjectInstance, public CBonusSystemNode, public CCreatureSet -{ -public: - BattleInfo *battle; //set to the current battle, if engaged - - void randomizeArmy(int type); - virtual void updateMoraleBonusFromArmy(); - - void armyChanged() override; - - ////////////////////////////////////////////////////////////////////////// -// int valOfGlobalBonuses(CSelector selector) const; //used only for castle interface ??? - virtual CBonusSystemNode *whereShouldBeAttached(CGameState *gs); - virtual CBonusSystemNode *whatShouldBeAttached(); - ////////////////////////////////////////////////////////////////////////// - - CArmedInstance(); - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGHeroInstance : public CArmedInstance, public IBoatGenerator, public CArtifactSet -{ -public: - enum ECanDig - { - CAN_DIG, LACK_OF_MOVEMENT, WRONG_TERRAIN, TILE_OCCUPIED - }; - ////////////////////////////////////////////////////////////////////////// - - ui8 moveDir; //format: 123 - // 8 4 - // 765 - mutable ui8 isStanding, tacticFormationEnabled; - - ////////////////////////////////////////////////////////////////////////// - - ConstTransitivePtr type; - TExpType exp; //experience points - ui32 level; //current level of hero - std::string name; //may be custom - std::string biography; //if custom - si32 portrait; //may be custom - si32 mana; // remaining spell points - std::vector > secSkills; //first - ID of skill, second - level of skill (1 - basic, 2 - adv., 3 - expert); if hero has ability (-1, -1) it meansthat it should have default secondary abilities - ui32 movement; //remaining movement points - ui8 sex; - bool inTownGarrison; // if hero is in town garrison - ConstTransitivePtr visitedTown; //set if hero is visiting town or in the town garrison - ConstTransitivePtr commander; - const CGBoat *boat; //set to CGBoat when sailing - - - //std::vector artifacts; //hero's artifacts from bag - //std::map artifWorn; //map; positions: 0 - head; 1 - shoulders; 2 - neck; 3 - right hand; 4 - left hand; 5 - torso; 6 - right ring; 7 - left ring; 8 - feet; 9 - misc1; 10 - misc2; 11 - misc3; 12 - misc4; 13 - mach1; 14 - mach2; 15 - mach3; 16 - mach4; 17 - spellbook; 18 - misc5 - std::set spells; //known spells (spell IDs) - std::set visitedObjects; - - struct DLL_LINKAGE Patrol - { - Patrol(){patrolling=false;patrolRadious=-1;}; - bool patrolling; - ui32 patrolRadious; - template void serialize(Handler &h, const int version) - { - h & patrolling & patrolRadious; - } - } patrol; - - struct DLL_LINKAGE HeroSpecial : CBonusSystemNode - { - bool growsWithLevel; - - HeroSpecial(){growsWithLevel = false;}; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & growsWithLevel; - } - }; - - std::vector specialty; - - struct DLL_LINKAGE SecondarySkillsInfo - { - //skills are determined, initialized at map start - //FIXME remove mutable - mutable CRandomGenerator rand; - ui8 magicSchoolCounter; - ui8 wisdomCounter; - - void resetMagicSchoolCounter(); - void resetWisdomCounter(); - - template void serialize(Handler &h, const int version) - { - h & magicSchoolCounter & wisdomCounter & rand; - } - } skillsInfo; - - int3 getSightCenter() const; //"center" tile from which the sight distance is calculated - int getSightRadious() const; //sight distance (should be used if player-owned structure) - ////////////////////////////////////////////////////////////////////////// - - int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral - void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed - - ////////////////////////////////////////////////////////////////////////// - - bool hasSpellbook() const; - EAlignment::EAlignment getAlignment() const; - const std::string &getBiography() const; - bool needsLastStack()const; - ui32 getTileCost(const TerrainTile &dest, const TerrainTile &from) const; //move cost - applying pathfinding skill, road and terrain modifiers. NOT includes diagonal move penalty, last move levelling - ui32 getLowestCreatureSpeed() const; - int3 getPosition(bool h3m = false) const; //h3m=true - returns position of hero object; h3m=false - returns position of hero 'manifestation' - si32 manaRegain() const; //how many points of mana can hero regain "naturally" in one day - bool canWalkOnSea() const; - int getCurrentLuck(int stack=-1, bool town=false) const; - int getSpellCost(const CSpell *sp) const; //do not use during battles -> bonuses from army would be ignored - - // ----- primary and secondary skill, experience, level handling ----- - - /// Returns true if hero has lower level than should upon his experience. - bool gainsLevel() const; - - /// Returns the next primary skill on level up. Can only be called if hero can gain a level up. - PrimarySkill::PrimarySkill nextPrimarySkill() const; - - /// Returns the next secondary skill randomly on level up. Can only be called if hero can gain a level up. - boost::optional nextSecondarySkill() const; - - /// Gets 0, 1 or 2 secondary skills which are proposed on hero level up. - std::vector getLevelUpProposedSecondarySkills() const; - - ui8 getSecSkillLevel(SecondarySkill skill) const; //0 - no skill - - /// Returns true if hero has free secondary skill slot. - bool canLearnSkill() const; - - void setPrimarySkill(PrimarySkill::PrimarySkill primarySkill, si64 value, ui8 abs); - void setSecSkillLevel(SecondarySkill which, int val, bool abs);// abs == 0 - changes by value; 1 - sets to value - void levelUp(std::vector skills); - - int maxMovePoints(bool onLand) const; - int movementPointsAfterEmbark(int MPsBefore, int basicCost, bool disembark = false) const; - - //int getSpellSecLevel(int spell) const; //returns level of secondary ability (fire, water, earth, air magic) known to this hero and applicable to given spell; -1 if error - static int3 convertPosition(int3 src, bool toh3m); //toh3m=true: manifest->h3m; toh3m=false: h3m->manifest - double getFightingStrength() const; // takes attack / defense skill into account - double getMagicStrength() const; // takes knowledge / spell power skill into account - double getHeroStrength() const; // includes fighting and magic strength - ui64 getTotalStrength() const; // includes fighting strength and army strength - TExpType calculateXp(TExpType exp) const; //apply learning skill - ui8 getSpellSchoolLevel(const CSpell * spell, int *outSelectedSchool = nullptr) const; //returns level on which given spell would be cast by this hero (0 - none, 1 - basic etc); optionally returns number of selected school by arg - 0 - air magic, 1 - fire magic, 2 - water magic, 3 - earth magic, - bool canCastThisSpell(const CSpell * spell) const; //determines if this hero can cast given spell; takes into account existing spell in spellbook, existing spellbook and artifact bonuses - CStackBasicDescriptor calculateNecromancy (const BattleResult &battleResult) const; - void showNecromancyDialog(const CStackBasicDescriptor &raisedStack) const; - ECanDig diggingStatus() const; //0 - can dig; 1 - lack of movement; 2 - - - ////////////////////////////////////////////////////////////////////////// - - void setType(si32 ID, si32 subID); - - void initHero(); - void initHero(HeroTypeID SUBID); - - void putArtifact(ArtifactPosition pos, CArtifactInstance *art); - void putInBackpack(CArtifactInstance *art); - void initExp(); - void initArmy(IArmyDescriptor *dst = nullptr); - //void giveArtifact (ui32 aid); - void pushPrimSkill(PrimarySkill::PrimarySkill which, int val); - ui8 maxlevelsToMagicSchool() const; - ui8 maxlevelsToWisdom() const; - void Updatespecialty(); - void recreateSecondarySkillsBonuses(); - void updateSkill(SecondarySkill which, int val); - - CGHeroInstance(); - virtual ~CGHeroInstance(); - ////////////////////////////////////////////////////////////////////////// - // - ArtBearer::ArtBearer bearerType() const override; - ////////////////////////////////////////////////////////////////////////// - - CBonusSystemNode *whereShouldBeAttached(CGameState *gs) override; - std::string nodeName() const override; - void deserializationFix(); - - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; -protected: - void setPropertyDer(ui8 what, ui32 val) override;//synchr - -private: - void levelUpAutomatically(); - -public: - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - h & exp & level & name & biography & portrait & mana & secSkills & movement - & sex & inTownGarrison & spells & patrol & moveDir & skillsInfo; - h & visitedTown & boat; - h & type & specialty & commander; - BONUS_TREE_DESERIALIZATION_FIX - //visitied town pointer will be restored by map serialization method - } -}; - -class DLL_LINKAGE CSpecObjInfo -{ -public: - virtual ~CSpecObjInfo(){}; - PlayerColor player; //owner -}; - -class DLL_LINKAGE CCreGenAsCastleInfo : public virtual CSpecObjInfo -{ -public: - bool asCastle; - ui32 identifier; - ui8 castles[2]; //allowed castles -}; - -class DLL_LINKAGE CCreGenLeveledInfo : public virtual CSpecObjInfo -{ -public: - ui8 minLevel, maxLevel; //minimal and maximal level of creature in dwelling: <0, 6> -}; - -class DLL_LINKAGE CCreGenLeveledCastleInfo : public CCreGenAsCastleInfo, public CCreGenLeveledInfo -{ -}; - -class DLL_LINKAGE CGDwelling : public CArmedInstance -{ -public: - typedef std::vector > > TCreaturesSet; - - CSpecObjInfo * info; //h3m info about dewlling - TCreaturesSet creatures; //creatures[level] -> - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this) & creatures; - } - - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - void newTurn() const override; - void setProperty(ui8 what, ui32 val) override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - -private: - void heroAcceptsCreatures(const CGHeroInstance *h) const; -}; - -class DLL_LINKAGE CGTownBuilding : public IObjectInterface -{ -///basic class for town structures handled as map objects -public: - BuildingID ID; //from buildig list - si32 id; //identifies its index on towns vector - CGTownInstance *town; - - template void serialize(Handler &h, const int version) - { - h & ID & id; - } -}; -class DLL_LINKAGE COPWBonus : public CGTownBuilding -{///used for OPW bonusing structures -public: - std::set visitors; - void setProperty(ui8 what, ui32 val) override; - void onHeroVisit (const CGHeroInstance * h) const override; - - COPWBonus (BuildingID index, CGTownInstance *TOWN); - COPWBonus (){ID = BuildingID::NONE; town = nullptr;}; - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visitors; - } -}; - -class DLL_LINKAGE CTownBonus : public CGTownBuilding -{ -///used for one-time bonusing structures -///feel free to merge inheritance tree -public: - std::set visitors; - void setProperty(ui8 what, ui32 val) override; - void onHeroVisit (const CGHeroInstance * h) const override; - - CTownBonus (BuildingID index, CGTownInstance *TOWN); - CTownBonus (){ID = BuildingID::NONE; town = nullptr;}; - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & visitors; - } -}; - -class DLL_LINKAGE CTownAndVisitingHero : public CBonusSystemNode -{ -public: - CTownAndVisitingHero(); -}; - -struct DLL_LINKAGE GrowthInfo -{ - struct Entry - { - int count; - std::string description; - Entry(const std::string &format, int _count); - Entry(int subID, BuildingID building, int _count); - }; - - std::vector entries; - int totalGrowth() const; -}; - -class DLL_LINKAGE CGTownInstance : public CGDwelling, public IShipyard, public IMarket -{ -public: - enum EFortLevel {NONE = 0, FORT = 1, CITADEL = 2, CASTLE = 3}; - - CTownAndVisitingHero townAndVis; - const CTown * town; - std::string name; // name of town - si32 builded; //how many buildings has been built this turn - si32 destroyed; //how many buildings has been destroyed this turn - ConstTransitivePtr garrisonHero, visitingHero; - ui32 identifier; //special identifier from h3m (only > RoE maps) - si32 alignment; - std::set forbiddenBuildings, builtBuildings; - std::vector bonusingBuildings; - std::vector possibleSpells, obligatorySpells; - std::vector > spells; //spells[level] -> vector of spells, first will be available in guild - std::list events; - std::pair bonusValue;//var to store town bonuses (rampart = resources from mystic pond); - - ////////////////////////////////////////////////////////////////////////// - static std::vector merchantArtifacts; //vector of artifacts available at Artifact merchant, NULLs possible (for making empty space when artifact is bought) - static std::vector universitySkills;//skills for university of magic - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - h & static_cast(*this); - h & name & builded & destroyed & identifier; - h & garrisonHero & visitingHero; - h & alignment & forbiddenBuildings & builtBuildings & bonusValue - & possibleSpells & obligatorySpells & spells & /*strInfo & */events & bonusingBuildings; - - for (std::vector::iterator i = bonusingBuildings.begin(); i!=bonusingBuildings.end(); i++) - (*i)->town = this; - - h & town & townAndVis; - BONUS_TREE_DESERIALIZATION_FIX - - vstd::erase_if(builtBuildings, [this](BuildingID building) -> bool - { - if(!town->buildings.count(building) || !town->buildings.at(building)) - { - logGlobal->errorStream() << boost::format("#1444-like issue in CGTownInstance::serialize. From town %s at %s removing the bogus builtBuildings item %s") - % name % pos % building; - return true; - } - return false; - }); - } - ////////////////////////////////////////////////////////////////////////// - - CBonusSystemNode *whatShouldBeAttached() override; - std::string nodeName() const override; - void updateMoraleBonusFromArmy() override; - void deserializationFix(); - void recreateBuildingsBonuses(); - bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, TPropagatorPtr &prop, int subtype = -1); //returns true if building is built and bonus has been added - bool addBonusIfBuilt(BuildingID building, Bonus::BonusType type, int val, int subtype = -1); //convienence version of above - void setVisitingHero(CGHeroInstance *h); - void setGarrisonedHero(CGHeroInstance *h); - const CArmedInstance *getUpperArmy() const; //garrisoned hero if present or the town itself - - ////////////////////////////////////////////////////////////////////////// - - ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used - int3 getSightCenter() const override; //"center" tile from which the sight distance is calculated - int getSightRadious() const override; //returns sight distance - int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral - void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed - int getMarketEfficiency() const override; //=market count - bool allowsTrade(EMarketMode::EMarketMode mode) const; - std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; - - void setType(si32 ID, si32 subID); - void updateAppearance(); - - ////////////////////////////////////////////////////////////////////////// - - bool needsLastStack() const; - CGTownInstance::EFortLevel fortLevel() const; - int hallLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol - int mageGuildLevel() const; // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol - int getHordeLevel(const int & HID) const; //HID - 0 or 1; returns creature level or -1 if that horde structure is not present - int creatureGrowth(const int & level) const; - GrowthInfo getGrowthInfo(int level) const; - bool hasFort() const; - bool hasCapitol() const; - //checks if building is constructed and town has same subID - bool hasBuilt(BuildingID buildingID) const; - bool hasBuilt(BuildingID buildingID, int townID) const; - TResources dailyIncome() const; //calculates daily income of this town - int spellsAtLevel(int level, bool checkGuild) const; //levels are counted from 1 (1 - 5) - bool armedGarrison() const; //true if town has creatures in garrison or garrisoned hero - int getTownLevel() const; - - void removeCapitols (PlayerColor owner) const; - void addHeroToStructureVisitors(const CGHeroInstance *h, si32 structureInstanceID) const; //hero must be visiting or garrisoned in town - - CGTownInstance(); - virtual ~CGTownInstance(); - - ///IObjectInterface overrides - void newTurn() const override; - void onHeroVisit(const CGHeroInstance * h) const override; - void onHeroLeave(const CGHeroInstance * h) const override; - void initObj() override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; -class DLL_LINKAGE CGPandoraBox : public CArmedInstance -{ -public: - std::string message; - bool hasGuardians; //helper - after battle even though we have no stacks, allows us to know that there was battle - - //gained things: - ui32 gainedExp; - si32 manaDiff; //amount of gained / lost mana - si32 moraleDiff; //morale modifier - si32 luckDiff; //luck modifier - TResources resources;//gained / lost resources - std::vector primskills;//gained / lost prim skills - std::vector abilities; //gained abilities - std::vector abilityLevels; //levels of gained abilities - std::vector artifacts; //gained artifacts - std::vector spells; //gained spells - CCreatureSet creatures; //gained creatures - - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - void heroLevelUpDone(const CGHeroInstance *hero) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & message & hasGuardians & gainedExp & manaDiff & moraleDiff & luckDiff & resources & primskills - & abilities & abilityLevels & artifacts & spells & creatures; - } -protected: - void giveContentsUpToExp(const CGHeroInstance *h) const; - void giveContentsAfterExp(const CGHeroInstance *h) const; -private: - void getText( InfoWindow &iw, bool &afterBattle, int val, int negative, int positive, const CGHeroInstance * h ) const; - void getText( InfoWindow &iw, bool &afterBattle, int text, const CGHeroInstance * h ) const; -}; - -class DLL_LINKAGE CGEvent : public CGPandoraBox //event objects -{ -public: - bool removeAfterVisit; //true if event is removed after occurring - ui8 availableFor; //players whom this event is available for - bool computerActivate; //true if computer player can activate this event - bool humanActivate; //true if human player can activate this event - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & removeAfterVisit & availableFor & computerActivate & humanActivate; - } - - void onHeroVisit(const CGHeroInstance * h) const override; -private: - void activated(const CGHeroInstance * h) const; -}; - -class DLL_LINKAGE CGCreature : public CArmedInstance //creatures on map -{ - enum Action { - FIGHT = -2, FLEE = -1, JOIN_FOR_FREE = 0 //values > 0 mean gold price - }; - -public: - ui32 identifier; //unique code for this monster (used in missions) - si8 character; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage) - std::string message; //message printed for attacking hero - TResources resources; // resources given to hero that has won with monsters - ArtifactID gainedArtifact; //ID of artifact gained to hero, -1 if none - bool neverFlees; //if true, the troops will never flee - bool notGrowingTeam; //if true, number of units won't grow - ui64 temppower; //used to handle fractional stack growth for tiny stacks - - bool refusedJoining; - - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - void initObj() override; - void newTurn() const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - - struct DLL_LINKAGE formationInfo // info about merging stacks after battle back into one - { - si32 basicType; - ui32 randomFormation; //random seed used to determine number of stacks and is there's upgraded stack - template void serialize(Handler &h, const int version) - { - h & basicType & randomFormation; - } - } formation; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & identifier & character & message & resources & gainedArtifact & neverFlees & notGrowingTeam & temppower; - h & refusedJoining & formation; - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -private: - - void fight(const CGHeroInstance *h) const; - void flee( const CGHeroInstance * h ) const; - void fleeDecision(const CGHeroInstance *h, ui32 pursue) const; - void joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const; - - int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0) - -}; - - -class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles -{ -public: - std::string message; - - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & message; - } -}; - -class DLL_LINKAGE IQuestObject -{ -public: - CQuest * quest; - - IQuestObject(): quest(new CQuest()){}; - virtual ~IQuestObject() {}; - virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; - virtual bool checkQuest (const CGHeroInstance * h) const; - - template void serialize(Handler &h, const int version) - { - h & quest; - } -}; - -class DLL_LINKAGE CGSeerHut : public CArmedInstance, public IQuestObject //army is used when giving reward -{ -public: - enum ERewardType {NOTHING, EXPERIENCE, MANA_POINTS, MORALE_BONUS, LUCK_BONUS, RESOURCES, PRIMARY_SKILL, SECONDARY_SKILL, ARTIFACT, SPELL, CREATURE}; - ERewardType rewardType; - si32 rID; //reward ID - si32 rVal; //reward value - std::string seerName; - - CGSeerHut() : IQuestObject(){}; - void initObj() override; - const std::string & getHoverText() const override; - void newTurn() const override; - void onHeroVisit(const CGHeroInstance * h) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - virtual void init(); - int checkDirection() const; //calculates the region of map where monster is placed - void setObjToKill(); //remember creatures / heroes to kill after they are initialized - const CGHeroInstance *getHeroToKill(bool allowNull = false) const; - const CGCreature *getCreatureToKill(bool allowNull = false) const; - void getRolloverText (MetaString &text, bool onHover) const; - void getCompletionText(MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h = nullptr) const; - void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects - virtual void completeQuest (const CGHeroInstance * h) const; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this) & static_cast(*this); - h & rewardType & rID & rVal & seerName; - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; - -class DLL_LINKAGE CGQuestGuard : public CGSeerHut -{ -public: - CGQuestGuard() : CGSeerHut(){}; - void init() override; - void completeQuest (const CGHeroInstance * h) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGWitchHut : public CPlayersVisited -{ -public: - std::vector allowedAbilities; - ui32 ability; - - const std::string & getHoverText() const override; - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & allowedAbilities & ability; - } -}; - - -class DLL_LINKAGE CGScholar : public CGObjectInstance -{ -public: - enum EBonusType {PRIM_SKILL, SECONDARY_SKILL, SPELL, RANDOM = 255}; - EBonusType bonusType; - ui16 bonusID; //ID of skill/spell - -// void giveAnyBonus(const CGHeroInstance * h) const; //TODO: remove - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & bonusType & bonusID; - } -}; - -class DLL_LINKAGE CGGarrison : public CArmedInstance -{ -public: - bool removableUnits; - - ui8 getPassableness() const; - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & removableUnits; - } -}; - -class DLL_LINKAGE CGArtifact : public CArmedInstance -{ -public: - CArtifactInstance *storedArtifact; - std::string message; - - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - void pick( const CGHeroInstance * h ) const; - void initObj() override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & message & storedArtifact; - } -}; - -class DLL_LINKAGE CGResource : public CArmedInstance -{ -public: - ui32 amount; //0 if random - std::string message; - - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - void collectRes(PlayerColor player) const; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & amount & message; - } -}; - -class DLL_LINKAGE CGShrine : public CPlayersVisited -{ -public: - SpellID spell; //id of spell or NONE if random - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this);; - h & spell; - } -}; - -class DLL_LINKAGE CGMine : public CArmedInstance -{ -public: - Res::ERes producedResource; - ui32 producedQuantity; - - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - void flagMine(PlayerColor player) const; - void newTurn() const override; - void initObj() override; - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & producedResource & producedQuantity; - } - ui32 defaultResProduction(); -}; - -class DLL_LINKAGE CGTeleport : public CGObjectInstance //teleports and subterranean gates -{ -public: - static std::map > > objs; //teleports: map[ID][subID] => vector of ids - static std::vector > gates; //subterranean gates: pairs of ids - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - static void postInit(); - static ObjectInstanceID getMatchingGate(ObjectInstanceID id); //receives id of one subterranean gate and returns id of the paired one, -1 if none - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGMagicWell : public CGObjectInstance //objects giving bonuses to luck/morale/movement -{ -public: - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGSirens : public CGObjectInstance -{ -public: - void onHeroVisit(const CGHeroInstance * h) const override; - const std::string & getHoverText() const override; - void initObj() override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGObservatory : public CGObjectInstance //Redwood observatory -{ -public: - void onHeroVisit(const CGHeroInstance * h) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - - -class DLL_LINKAGE CGKeys : public CGObjectInstance //Base class for Keymaster and guards -{ -public: - static std::map > playerKeyMap; //[players][keysowned] - //SubID 0 - lightblue, 1 - green, 2 - red, 3 - darkblue, 4 - brown, 5 - purple, 6 - white, 7 - black - - const std::string getName() const; //depending on color - bool wasMyColorVisited (PlayerColor player) const; - - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; - -class DLL_LINKAGE CGKeymasterTent : public CGKeys -{ -public: - bool wasVisited (PlayerColor player) const; - void onHeroVisit(const CGHeroInstance * h) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject -{ -public: - CGBorderGuard() : IQuestObject(){}; - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; - void getRolloverText (MetaString &text, bool onHover) const; - bool checkQuest (const CGHeroInstance * h) const; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - h & blockVisit; - } -}; - -class DLL_LINKAGE CGBorderGate : public CGBorderGuard -{ -public: - CGBorderGate() : CGBorderGuard(){}; - void onHeroVisit(const CGHeroInstance * h) const override; - - ui8 getPassableness() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); //need to serialize or object will be empty - } -}; - -class DLL_LINKAGE CGBoat : public CGObjectInstance -{ -public: - ui8 direction; - const CGHeroInstance *hero; //hero on board - - void initObj() override; - - CGBoat() - { - hero = nullptr; - direction = 4; - } - template void serialize(Handler &h, const int version) - { - h & static_cast(*this) & direction & hero; - } -}; - -class DLL_LINKAGE CBank : public CArmedInstance -{ - public: - int index; //banks have unusal numbering - see ZCRBANK.txt and initObj() - BankConfig *bc; - double multiplier; //for improved banks script - std::vector artifacts; //fixed and deterministic - ui32 daycounter; - - void initObj() override; - const std::string & getHoverText() const override; - void initialize() const; - void reset(ui16 var1); - void newTurn() const override; - bool wasVisited (PlayerColor player) const override; - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & index & multiplier & artifacts & daycounter & bc; - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; -class DLL_LINKAGE CGPyramid : public CBank -{ -public: - ui16 spell; - - void initObj() override; - const std::string & getHoverText() const override; - void newTurn() const override {}; //empty, no reset - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & spell; - } -}; - -class CGShipyard : public CGObjectInstance, public IShipyard -{ -public: - void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed - CGShipyard(); - void onHeroVisit(const CGHeroInstance * h) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGMagi : public CGObjectInstance -{ -public: - static std::map > eyelist; //[subID][id], supports multiple sets as in H5 - - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - - - -class DLL_LINKAGE CCartographer : public CPlayersVisited -{ -///behaviour varies depending on surface and floor -public: - void onHeroVisit(const CGHeroInstance * h) const override; - void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGDenOfthieves : public CGObjectInstance -{ - void onHeroVisit(const CGHeroInstance * h) const override; -}; - -class DLL_LINKAGE CGObelisk : public CPlayersVisited -{ -public: - static ui8 obeliskCount; //how many obelisks are on map - static std::map visited; //map: team_id => how many obelisks has been visited - - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; - -class DLL_LINKAGE CGLighthouse : public CGObjectInstance -{ -public: - void onHeroVisit(const CGHeroInstance * h) const override; - void initObj() override; - const std::string & getHoverText() const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } - void giveBonusTo( PlayerColor player ) const; -}; - -class DLL_LINKAGE CGMarket : public CGObjectInstance, public IMarket -{ -public: - CGMarket(); - ///IObjectIntercae - void onHeroVisit(const CGHeroInstance * h) const override; //open trading window - - ///IMarket - int getMarketEfficiency() const override; - bool allowsTrade(EMarketMode::EMarketMode mode) const override; - int availableUnits(EMarketMode::EMarketMode mode, int marketItemSerial) const override; //-1 if unlimited - std::vector availableItemsIds(EMarketMode::EMarketMode mode) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGBlackMarket : public CGMarket -{ -public: - std::vector artifacts; //available artifacts - - void newTurn() const override; //reset artifacts for black market every month - std::vector availableItemsIds(EMarketMode::EMarketMode mode) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & artifacts; - } -}; - -class DLL_LINKAGE CGUniversity : public CGMarket -{ -public: - std::vector skills; //available skills - - std::vector availableItemsIds(EMarketMode::EMarketMode mode) const; - void initObj() override;//set skills for trade - void onHeroVisit(const CGHeroInstance * h) const override; //open window - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & skills; - } -}; - struct BankConfig { BankConfig() {level = chance = upgradeChance = combatValue = value = rewardDifficulty = easiest = 0; }; diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp new file mode 100644 index 000000000..67c56a48d --- /dev/null +++ b/lib/mapObjects/CQuest.cpp @@ -0,0 +1,861 @@ +/* + * CObjectHandler.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 "CQuest.h" + +#include "NetPacks.h" +#include "../client/CSoundBase.h" +#include "../CGeneralTextHandler.h" +#include "../CHeroHandler.h" +#include "CObjectClassesHandler.h" + +using namespace boost::assign; + +std::map > CGKeys::playerKeyMap; + +///helpers +static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) +{ + InfoWindow iw; + iw.soundID = soundID; + iw.player = playerID; + iw.text.addTxt(MetaString::ADVOB_TXT,txtID); + IObjectInterface::cb->sendAndApply(&iw); +} + +static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = h->getOwner(); + showInfoDialog(playerID,txtID,soundID); +} + +static std::string & visitedTxt(const bool visited) +{ + int id = visited ? 352 : 353; + return VLC->generaltexth->allTexts[id]; +} + +bool CQuest::checkQuest (const CGHeroInstance * h) const +{ + switch (missionType) + { + case MISSION_NONE: + return true; + case MISSION_LEVEL: + if (m13489val <= h->level) + return true; + return false; + case MISSION_PRIMARY_STAT: + for (int i = 0; i < GameConstants::PRIMARY_SKILLS; ++i) + { + if (h->getPrimSkillLevel(static_cast(i)) < m2stats[i]) + return false; + } + return true; + case MISSION_KILL_HERO: + case MISSION_KILL_CREATURE: + if (!h->cb->getObjByQuestIdentifier(m13489val)) + return true; + return false; + case MISSION_ART: + for (auto & elem : m5arts) + { + if (h->hasArt(elem)) + continue; + return false; //if the artifact was not found + } + return true; + case MISSION_ARMY: + { + std::vector::const_iterator cre; + TSlots::const_iterator it; + ui32 count; + for (cre = m6creatures.begin(); cre != m6creatures.end(); ++cre) + { + for (count = 0, it = h->Slots().begin(); it != h->Slots().end(); ++it) + { + if (it->second->type == cre->type) + count += it->second->count; + } + if (count < cre->count) //not enough creatures of this kind + return false; + } + } + return true; + case MISSION_RESOURCES: + for (Res::ERes i = Res::WOOD; i <= Res::GOLD; vstd::advance(i, +1)) //including Mithril ? + { //Quest has no direct access to callback + if (h->cb->getResource (h->tempOwner, i) < m7resources[i]) + return false; + } + return true; + case MISSION_HERO: + if (m13489val == h->type->ID.getNum()) + return true; + return false; + case MISSION_PLAYER: + if (m13489val == h->getOwner().getNum()) + return true; + return false; + default: + return false; + } +} + +void CQuest::getVisitText (MetaString &iwText, std::vector &components, bool isCustom, bool firstVisit, const CGHeroInstance * h) const +{ + std::string text; + bool failRequirements = (h ? !checkQuest(h) : true); + + if (firstVisit) + { + isCustom = isCustomFirst; + iwText << (text = firstVisitText); + } + else if (failRequirements) + { + isCustom = isCustomNext; + iwText << (text = nextVisitText); + } + switch (missionType) + { + case MISSION_LEVEL: + components.push_back(Component (Component::EXPERIENCE, 0, m13489val, 0)); + if (!isCustom) + iwText.addReplacement(m13489val); + break; + case MISSION_PRIMARY_STAT: + { + MetaString loot; + for (int i = 0; i < 4; ++i) + { + if (m2stats[i]) + { + components.push_back(Component (Component::PRIM_SKILL, i, m2stats[i], 0)); + loot << "%d %s"; + loot.addReplacement(m2stats[i]); + loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); + } + } + if (!isCustom) + iwText.addReplacement(loot.buildList()); + } + break; + case MISSION_KILL_HERO: + components.push_back(Component(Component::HERO_PORTRAIT, heroPortrait, 0, 0)); + if (!isCustom) + addReplacements(iwText, text); + break; + case MISSION_HERO: + //FIXME: portrait may not match hero, if custom portrait was set in map editor + components.push_back(Component (Component::HERO_PORTRAIT, VLC->heroh->heroes[m13489val]->imageIndex, 0, 0)); + if (!isCustom) + iwText.addReplacement(VLC->heroh->heroes[m13489val]->name); + break; + case MISSION_KILL_CREATURE: + { + components.push_back(Component(stackToKill)); + if (!isCustom) + { + addReplacements(iwText, text); + } + } + break; + case MISSION_ART: + { + MetaString loot; + for (auto & elem : m5arts) + { + components.push_back(Component (Component::ARTIFACT, elem, 0, 0)); + loot << "%s"; + loot.addReplacement(MetaString::ART_NAMES, elem); + } + if (!isCustom) + iwText.addReplacement(loot.buildList()); + } + break; + case MISSION_ARMY: + { + MetaString loot; + for (auto & elem : m6creatures) + { + components.push_back(Component(elem)); + loot << "%s"; + loot.addReplacement(elem); + } + if (!isCustom) + iwText.addReplacement(loot.buildList()); + } + break; + case MISSION_RESOURCES: + { + MetaString loot; + for (int i = 0; i < 7; ++i) + { + if (m7resources[i]) + { + components.push_back(Component (Component::RESOURCE, i, m7resources[i], 0)); + loot << "%d %s"; + loot.addReplacement(m7resources[i]); + loot.addReplacement(MetaString::RES_NAMES, i); + } + } + if (!isCustom) + iwText.addReplacement(loot.buildList()); + } + break; + case MISSION_PLAYER: + components.push_back(Component (Component::FLAG, m13489val, 0, 0)); + if (!isCustom) + iwText.addReplacement(VLC->generaltexth->colors[m13489val]); + break; + } +} + +void CQuest::getRolloverText (MetaString &ms, bool onHover) const +{ + if (onHover) + ms << "\n\n"; + + ms << VLC->generaltexth->quests[missionType-1][onHover ? 3 : 4][textOption]; + + switch (missionType) + { + case MISSION_LEVEL: + ms.addReplacement(m13489val); + break; + case MISSION_PRIMARY_STAT: + { + MetaString loot; + for (int i = 0; i < 4; ++i) + { + if (m2stats[i]) + { + loot << "%d %s"; + loot.addReplacement(m2stats[i]); + loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); + } + } + ms.addReplacement(loot.buildList()); + } + break; + case MISSION_KILL_HERO: + ms.addReplacement(heroName); + break; + case MISSION_KILL_CREATURE: + ms.addReplacement(stackToKill); + break; + case MISSION_ART: + { + MetaString loot; + for (auto & elem : m5arts) + { + loot << "%s"; + loot.addReplacement(MetaString::ART_NAMES, elem); + } + ms.addReplacement(loot.buildList()); + } + break; + case MISSION_ARMY: + { + MetaString loot; + for (auto & elem : m6creatures) + { + loot << "%s"; + loot.addReplacement(elem); + } + ms.addReplacement(loot.buildList()); + } + break; + case MISSION_RESOURCES: + { + MetaString loot; + for (int i = 0; i < 7; ++i) + { + if (m7resources[i]) + { + loot << "%d %s"; + loot.addReplacement(m7resources[i]); + loot.addReplacement(MetaString::RES_NAMES, i); + } + } + ms.addReplacement(loot.buildList()); + } + break; + case MISSION_HERO: + ms.addReplacement(VLC->heroh->heroes[m13489val]->name); + break; + case MISSION_PLAYER: + ms.addReplacement(VLC->generaltexth->colors[m13489val]); + break; + default: + break; + } +} + +void CQuest::getCompletionText (MetaString &iwText, std::vector &components, bool isCustom, const CGHeroInstance * h) const +{ + iwText << completedText; + switch (missionType) + { + case CQuest::MISSION_LEVEL: + if (!isCustomComplete) + iwText.addReplacement(m13489val); + break; + case CQuest::MISSION_PRIMARY_STAT: + if (vstd::contains (completedText,'%')) //there's one case when there's nothing to replace + { + MetaString loot; + for (int i = 0; i < 4; ++i) + { + if (m2stats[i]) + { + loot << "%d %s"; + loot.addReplacement(m2stats[i]); + loot.addReplacement(VLC->generaltexth->primarySkillNames[i]); + } + } + if (!isCustomComplete) + iwText.addReplacement(loot.buildList()); + } + break; + case CQuest::MISSION_ART: + { + MetaString loot; + for (auto & elem : m5arts) + { + loot << "%s"; + loot.addReplacement(MetaString::ART_NAMES, elem); + } + if (!isCustomComplete) + iwText.addReplacement(loot.buildList()); + } + break; + case CQuest::MISSION_ARMY: + { + MetaString loot; + for (auto & elem : m6creatures) + { + loot << "%s"; + loot.addReplacement(elem); + } + if (!isCustomComplete) + iwText.addReplacement(loot.buildList()); + } + break; + case CQuest::MISSION_RESOURCES: + { + MetaString loot; + for (int i = 0; i < 7; ++i) + { + if (m7resources[i]) + { + loot << "%d %s"; + loot.addReplacement(m7resources[i]); + loot.addReplacement(MetaString::RES_NAMES, i); + } + } + if (!isCustomComplete) + iwText.addReplacement(loot.buildList()); + } + break; + case MISSION_KILL_HERO: + case MISSION_KILL_CREATURE: + if (!isCustomComplete) + addReplacements(iwText, completedText); + break; + case MISSION_HERO: + if (!isCustomComplete) + iwText.addReplacement(VLC->heroh->heroes[m13489val]->name); + break; + case MISSION_PLAYER: + if (!isCustomComplete) + iwText.addReplacement(VLC->generaltexth->colors[m13489val]); + break; + } +} + +void CGSeerHut::setObjToKill() +{ + if (quest->missionType == CQuest::MISSION_KILL_CREATURE) + { + quest->stackToKill = getCreatureToKill(false)->getStack(SlotID(0)); //FIXME: stacks tend to disappear (desync?) on server :? + assert(quest->stackToKill.type); + quest->stackToKill.count = 0; //no count in info window + quest->stackDirection = checkDirection(); + } + else if (quest->missionType == CQuest::MISSION_KILL_HERO) + { + quest->heroName = getHeroToKill(false)->name; + quest->heroPortrait = getHeroToKill(false)->portrait; + } +} + +void CGSeerHut::init() +{ + seerName = *RandomGeneratorUtil::nextItem(VLC->generaltexth->seerNames, cb->gameState()->getRandomGenerator()); + quest->textOption = cb->gameState()->getRandomGenerator().nextInt(2); +} + +void CGSeerHut::initObj() +{ + init(); + + quest->progress = CQuest::NOT_ACTIVE; + if (quest->missionType) + { + if (!quest->isCustomFirst) + quest->firstVisitText = VLC->generaltexth->quests[quest->missionType-1][0][quest->textOption]; + if (!quest->isCustomNext) + quest->nextVisitText = VLC->generaltexth->quests[quest->missionType-1][1][quest->textOption]; + if (!quest->isCustomComplete) + quest->completedText = VLC->generaltexth->quests[quest->missionType-1][2][quest->textOption]; + } + else + { + quest->progress = CQuest::COMPLETE; + quest->firstVisitText = VLC->generaltexth->seerEmpty[quest->textOption]; + } +} + +void CGSeerHut::getRolloverText (MetaString &text, bool onHover) const +{ + quest->getRolloverText (text, onHover);//TODO: simplify? + if (!onHover) + text.addReplacement(seerName); +} + +const std::string & CGSeerHut::getHoverText() const +{ + switch (ID) + { + case Obj::SEER_HUT: + if (quest->progress != CQuest::NOT_ACTIVE) + { + hoverName = VLC->generaltexth->allTexts[347]; + boost::algorithm::replace_first(hoverName,"%s", seerName); + } + else //just seer hut + hoverName = VLC->objtypeh->getObjectName(ID); + break; + case Obj::QUEST_GUARD: + hoverName = VLC->objtypeh->getObjectName(ID); + break; + default: + logGlobal->debugStream() << "unrecognized quest object"; + } + if (quest->progress & quest->missionType) //rollover when the quest is active + { + MetaString ms; + getRolloverText (ms, true); + hoverName += ms.toString(); + } + return hoverName; +} + +void CQuest::addReplacements(MetaString &out, const std::string &base) const +{ + switch(missionType) + { + case MISSION_KILL_CREATURE: + out.addReplacement(stackToKill); + if (std::count(base.begin(), base.end(), '%') == 2) //say where is placed monster + { + out.addReplacement(VLC->generaltexth->arraytxt[147+stackDirection]); + } + break; + case MISSION_KILL_HERO: + out.addReplacement(heroName); + break; + } +} + +bool IQuestObject::checkQuest(const CGHeroInstance* h) const +{ + return quest->checkQuest(h); +} + +void IQuestObject::getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const +{ + quest->getVisitText (text,components, isCustom, FirstVisit, h); +} + +void CGSeerHut::getCompletionText(MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h) const +{ + quest->getCompletionText (text, components, isCustom, h); + switch (rewardType) + { + case EXPERIENCE: components.push_back(Component (Component::EXPERIENCE, 0, h->calculateXp(rVal), 0)); + break; + case MANA_POINTS: components.push_back(Component (Component::PRIM_SKILL, 5, rVal, 0)); + break; + case MORALE_BONUS: components.push_back(Component (Component::MORALE, 0, rVal, 0)); + break; + case LUCK_BONUS: components.push_back(Component (Component::LUCK, 0, rVal, 0)); + break; + case RESOURCES: components.push_back(Component (Component::RESOURCE, rID, rVal, 0)); + break; + case PRIMARY_SKILL: components.push_back(Component (Component::PRIM_SKILL, rID, rVal, 0)); + break; + case SECONDARY_SKILL: components.push_back(Component (Component::SEC_SKILL, rID, rVal, 0)); + break; + case ARTIFACT: components.push_back(Component (Component::ARTIFACT, rID, 0, 0)); + break; + case SPELL: components.push_back(Component (Component::SPELL, rID, 0, 0)); + break; + case CREATURE: components.push_back(Component (Component::CREATURE, rID, rVal, 0)); + break; + } +} + +void CGSeerHut::setPropertyDer (ui8 what, ui32 val) +{ + switch (what) + { + case 10: + quest->progress = static_cast(val); + break; + } +} + +void CGSeerHut::newTurn() const +{ + if (quest->lastDay >= 0 && quest->lastDay < cb->getDate()-1) //time is up + { + cb->setObjProperty (id, 10, CQuest::COMPLETE); + } +} + +void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.player = h->getOwner(); + if (quest->progress < CQuest::COMPLETE) + { + bool firstVisit = !quest->progress; + bool failRequirements = !checkQuest(h); + bool isCustom=false; + + if (firstVisit) + { + isCustom = quest->isCustomFirst; + cb->setObjProperty (id, 10, CQuest::IN_PROGRESS); + + AddQuest aq; + aq.quest = QuestInfo (quest, this, visitablePos()); + aq.player = h->tempOwner; + cb->sendAndApply (&aq); //TODO: merge with setObjProperty? + } + else if (failRequirements) + { + isCustom = quest->isCustomNext; + } + + if (firstVisit || failRequirements) + { + getVisitText (iw.text, iw.components, isCustom, firstVisit, h); + + cb->showInfoDialog(&iw); + } + if (!failRequirements) // propose completion, also on first visit + { + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundBase::QUEST; + + getCompletionText (bd.text, bd.components, isCustom, h); + + cb->showBlockingDialog (&bd); + return; + } + } + else + { + iw.text << VLC->generaltexth->seerEmpty[quest->textOption]; + if (ID == Obj::SEER_HUT) + iw.text.addReplacement(seerName); + cb->showInfoDialog(&iw); + } +} + +int CGSeerHut::checkDirection() const +{ + int3 cord = getCreatureToKill()->pos; + if ((double)cord.x/(double)cb->getMapSize().x < 0.34) //north + { + if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //northwest + return 8; + else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //north + return 1; + else //northeast + return 2; + } + else if ((double)cord.x/(double)cb->getMapSize().x < 0.67) //horizontal + { + if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //west + return 7; + else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //central + return 9; + else //east + return 3; + } + else //south + { + if ((double)cord.y/(double)cb->getMapSize().y < 0.34) //southwest + return 6; + else if ((double)cord.y/(double)cb->getMapSize().y < 0.67) //south + return 5; + else //southeast + return 4; + } +} + +void CGSeerHut::finishQuest(const CGHeroInstance * h, ui32 accept) const +{ + if (accept) + { + switch (quest->missionType) + { + case CQuest::MISSION_ART: + for (auto & elem : quest->m5arts) + { + cb->removeArtifact(ArtifactLocation(h, h->getArtPos(elem, false))); + } + break; + case CQuest::MISSION_ARMY: + cb->takeCreatures(h->id, quest->m6creatures); + break; + case CQuest::MISSION_RESOURCES: + for (int i = 0; i < 7; ++i) + { + cb->giveResource(h->getOwner(), static_cast(i), -quest->m7resources[i]); + } + break; + default: + break; + } + cb->setObjProperty (id, 10, CQuest::COMPLETE); //mission complete + completeQuest(h); //make sure to remove QuestGuard at the very end + } +} + +void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward +{ + switch (rewardType) + { + case EXPERIENCE: + { + TExpType expVal = h->calculateXp(rVal); + cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, expVal, false); + break; + } + case MANA_POINTS: + { + cb->setManaPoints(h->id, h->mana+rVal); + break; + } + case MORALE_BONUS: case LUCK_BONUS: + { + Bonus hb(Bonus::ONE_WEEK, (rewardType == 3 ? Bonus::MORALE : Bonus::LUCK), + Bonus::OBJECT, rVal, h->id.getNum(), "", -1); + GiveBonus gb; + gb.id = h->id.getNum(); + gb.bonus = hb; + cb->giveHeroBonus(&gb); + } + break; + case RESOURCES: + cb->giveResource(h->getOwner(), static_cast(rID), rVal); + break; + case PRIMARY_SKILL: + cb->changePrimSkill(h, static_cast(rID), rVal, false); + break; + case SECONDARY_SKILL: + cb->changeSecSkill(h, SecondarySkill(rID), rVal, false); + break; + case ARTIFACT: + cb->giveHeroNewArtifact(h, VLC->arth->artifacts[rID],ArtifactPosition::FIRST_AVAILABLE); + break; + case SPELL: + { + std::set spell; + spell.insert (SpellID(rID)); + cb->changeSpells(h, true, spell); + } + break; + case CREATURE: + { + CCreatureSet creatures; + creatures.setCreature(SlotID(0), CreatureID(rID), rVal); + cb->giveCreatures(this, h, creatures, false); + } + break; + default: + break; + } +} + +const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const +{ + const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val); + if(allowNull && !o) + return nullptr; + assert(o && (o->ID == Obj::HERO || o->ID == Obj::PRISON)); + return static_cast(o); +} + +const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const +{ + const CGObjectInstance *o = cb->getObjByQuestIdentifier(quest->m13489val); + if(allowNull && !o) + return nullptr; + assert(o && o->ID == Obj::MONSTER); + return static_cast(o); +} + +void CGSeerHut::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + finishQuest(hero, answer); +} + +void CGQuestGuard::init() +{ + blockVisit = true; + quest->textOption = cb->gameState()->getRandomGenerator().nextInt(3, 5); +} + +void CGQuestGuard::completeQuest(const CGHeroInstance *h) const +{ + cb->removeObject(this); +} + +void CGKeys::setPropertyDer (ui8 what, ui32 val) //101-108 - enable key for player 1-8 +{ + if (what >= 101 && what <= (100 + PlayerColor::PLAYER_LIMIT_I)) + { + PlayerColor player(what-101); + playerKeyMap[player].insert((ui8)val); + } + else + logGlobal->errorStream() << boost::format("Unexpected properties requested to set: what=%d, val=%d") % (int)what % val; +} + +bool CGKeys::wasMyColorVisited (PlayerColor player) const +{ + if (vstd::contains(playerKeyMap[player], subID)) //creates set if it's not there + return true; + else + return false; +} + +const std::string& CGKeys::getHoverText() const +{ + bool visited = wasMyColorVisited (cb->getLocalPlayer()); + hoverName = getName() + "\n" + visitedTxt(visited); + return hoverName; +} + + +const std::string CGKeys::getName() const +{ + std::string name; + name = VLC->generaltexth->tentColors[subID] + " " + VLC->objtypeh->getObjectName(ID); + return name; +} + +bool CGKeymasterTent::wasVisited (PlayerColor player) const +{ + return wasMyColorVisited (player); +} + +void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const +{ + int txt_id; + if (!wasMyColorVisited (h->getOwner()) ) + { + cb->setObjProperty(id, h->tempOwner.getNum()+101, subID); + txt_id=19; + } + else + txt_id=20; + showInfoDialog(h,txt_id,soundBase::CAVEHEAD); +} + +void CGBorderGuard::initObj() +{ + //ui32 m13489val = subID; //store color as quest info + blockVisit = true; +} + +void CGBorderGuard::getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h) const +{ + text << std::pair(11,18); +} + +void CGBorderGuard::getRolloverText (MetaString &text, bool onHover) const +{ + if (!onHover) + text << VLC->generaltexth->tentColors[subID] << " " << VLC->objtypeh->getObjectName(Obj::KEYMASTER); +} + +bool CGBorderGuard::checkQuest (const CGHeroInstance * h) const +{ + return wasMyColorVisited (h->tempOwner); +} + +void CGBorderGuard::onHeroVisit( const CGHeroInstance * h ) const +{ + if (wasMyColorVisited (h->getOwner()) ) + { + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundBase::QUEST; + bd.text.addTxt (MetaString::ADVOB_TXT, 17); + cb->showBlockingDialog (&bd); + } + else + { + showInfoDialog(h,18,soundBase::CAVEHEAD); + + AddQuest aq; + aq.quest = QuestInfo (quest, this, visitablePos()); + aq.player = h->tempOwner; + cb->sendAndApply (&aq); + //TODO: add this quest only once OR check for multiple instances later + } +} + +void CGBorderGuard::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer) + cb->removeObject(this); +} + +void CGBorderGate::onHeroVisit( const CGHeroInstance * h ) const //TODO: passability +{ + if (!wasMyColorVisited (h->getOwner()) ) + { + showInfoDialog(h,18,0); + + AddQuest aq; + aq.quest = QuestInfo (quest, this, visitablePos()); + aq.player = h->tempOwner; + cb->sendAndApply (&aq); + } +} + +ui8 CGBorderGate::getPassableness() const +{ + ui8 ret = 0; + for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) + ret |= wasMyColorVisited(PlayerColor(i))< m2stats; + std::vector m5arts; //artifacts id + std::vector m6creatures; //pair[cre id, cre count], CreatureSet info irrelevant + std::vector m7resources; //TODO: use resourceset? + + //following field are used only for kill creature/hero missions, the original objects became inaccessible after their removal, so we need to store info needed for messages / hover text + ui8 textOption; + CStackBasicDescriptor stackToKill; + ui8 stackDirection; + std::string heroName; //backup of hero name + si32 heroPortrait; + + std::string firstVisitText, nextVisitText, completedText; + bool isCustomFirst, isCustomNext, isCustomComplete; + + CQuest(){missionType = MISSION_NONE;}; //default constructor + virtual ~CQuest(){}; + + virtual bool checkQuest (const CGHeroInstance * h) const; //determines whether the quest is complete or not + virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; + virtual void getCompletionText (MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h = nullptr) const; + virtual void getRolloverText (MetaString &text, bool onHover) const; //hover or quest log entry + virtual void completeQuest (const CGHeroInstance * h) const {}; + virtual void addReplacements(MetaString &out, const std::string &base) const; + + bool operator== (const CQuest & quest) const + { + return (quest.qid == qid); + } + + template void serialize(Handler &h, const int version) + { + h & qid & missionType & progress & lastDay & m13489val & m2stats & m5arts & m6creatures & m7resources + & textOption & stackToKill & stackDirection & heroName & heroPortrait + & firstVisitText & nextVisitText & completedText & isCustomFirst & isCustomNext & isCustomComplete; + } +}; + +class DLL_LINKAGE IQuestObject +{ +public: + CQuest * quest; + + IQuestObject(): quest(new CQuest()){}; + virtual ~IQuestObject() {}; + virtual void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; + virtual bool checkQuest (const CGHeroInstance * h) const; + + template void serialize(Handler &h, const int version) + { + h & quest; + } +}; + +class DLL_LINKAGE CGSeerHut : public CArmedInstance, public IQuestObject //army is used when giving reward +{ +public: + enum ERewardType {NOTHING, EXPERIENCE, MANA_POINTS, MORALE_BONUS, LUCK_BONUS, RESOURCES, PRIMARY_SKILL, SECONDARY_SKILL, ARTIFACT, SPELL, CREATURE}; + ERewardType rewardType; + si32 rID; //reward ID + si32 rVal; //reward value + std::string seerName; + + CGSeerHut() : IQuestObject(){}; + void initObj() override; + const std::string & getHoverText() const override; + void newTurn() const override; + void onHeroVisit(const CGHeroInstance * h) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + virtual void init(); + int checkDirection() const; //calculates the region of map where monster is placed + void setObjToKill(); //remember creatures / heroes to kill after they are initialized + const CGHeroInstance *getHeroToKill(bool allowNull = false) const; + const CGCreature *getCreatureToKill(bool allowNull = false) const; + void getRolloverText (MetaString &text, bool onHover) const; + void getCompletionText(MetaString &text, std::vector &components, bool isCustom, const CGHeroInstance * h = nullptr) const; + void finishQuest (const CGHeroInstance * h, ui32 accept) const; //common for both objects + virtual void completeQuest (const CGHeroInstance * h) const; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this) & static_cast(*this); + h & rewardType & rID & rVal & seerName; + } +protected: + void setPropertyDer(ui8 what, ui32 val) override; +}; + +class DLL_LINKAGE CGQuestGuard : public CGSeerHut +{ +public: + CGQuestGuard() : CGSeerHut(){}; + void init() override; + void completeQuest (const CGHeroInstance * h) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGKeys : public CGObjectInstance //Base class for Keymaster and guards +{ +public: + static std::map > playerKeyMap; //[players][keysowned] + //SubID 0 - lightblue, 1 - green, 2 - red, 3 - darkblue, 4 - brown, 5 - purple, 6 - white, 7 - black + + const std::string getName() const; //depending on color + bool wasMyColorVisited (PlayerColor player) const; + + const std::string & getHoverText() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +protected: + void setPropertyDer(ui8 what, ui32 val) override; +}; + +class DLL_LINKAGE CGKeymasterTent : public CGKeys +{ +public: + bool wasVisited (PlayerColor player) const; + void onHeroVisit(const CGHeroInstance * h) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGBorderGuard : public CGKeys, public IQuestObject +{ +public: + CGBorderGuard() : IQuestObject(){}; + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + void getVisitText (MetaString &text, std::vector &components, bool isCustom, bool FirstVisit, const CGHeroInstance * h = nullptr) const; + void getRolloverText (MetaString &text, bool onHover) const; + bool checkQuest (const CGHeroInstance * h) const; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + h & blockVisit; + } +}; + +class DLL_LINKAGE CGBorderGate : public CGBorderGuard +{ +public: + CGBorderGate() : CGBorderGuard(){}; + void onHeroVisit(const CGHeroInstance * h) const override; + + ui8 getPassableness() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); //need to serialize or object will be empty + } +}; diff --git a/lib/mapObjects/CRewardableConstructor.h b/lib/mapObjects/CRewardableConstructor.h index d374295a7..a7c0546f9 100644 --- a/lib/mapObjects/CRewardableConstructor.h +++ b/lib/mapObjects/CRewardableConstructor.h @@ -1,8 +1,9 @@ #pragma once -#include "mapObjects/CRewardableObject.h" +#include "CRewardableObject.h" #include "CObjectClassesHandler.h" -#include "JsonNode.h" + +#include "../JsonNode.h" /* * CObjectConstructor.h, part of VCMI engine diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index e97009e7b..fd68125ed 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -15,6 +15,8 @@ #include "../client/CSoundBase.h" #include "NetPacks.h" +#include "CObjectClassesHandler.h" + bool CRewardLimiter::heroAllowed(const CGHeroInstance * hero) const { if (dayOfWeek != 0) diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index 50ed240dc..375298f37 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -1,7 +1,9 @@ #pragma once #include "CObjectHandler.h" -#include "NetPacksBase.h" +#include "CGArmedInstance.h" + +#include "../NetPacksBase.h" /* * CRewardableObject.h, part of VCMI engine diff --git a/lib/mapObjects/MapObjects.h b/lib/mapObjects/MapObjects.h new file mode 100644 index 000000000..d7e4e27e0 --- /dev/null +++ b/lib/mapObjects/MapObjects.h @@ -0,0 +1,25 @@ +#pragma once + +/* + * MapObjects.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 + * + */ + +// Helper header that includes all map objects, similar to old CObjectHandler.h +// Possible TODO - remove this header after CObjectHandler.cpp will be fully split into smaller files +#include "CObjectHandler.h" + +#include "CGArmedInstance.h" +#include "CGBank.h" +#include "CGHeroInstance.h" +#include "CGMarket.h" +#include "CGTownInstance.h" +#include "CGPandoraBox.h" +#include "CRewardableObject.h" +#include "MiscObjects.h" +#include "CQuest.h" diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp new file mode 100644 index 000000000..f7b9c431f --- /dev/null +++ b/lib/mapObjects/MiscObjects.cpp @@ -0,0 +1,1574 @@ +/* + * CObjectHandler.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 "MiscObjects.h" + +#include "NetPacks.h" +#include "CGeneralTextHandler.h" +//#include "CHeroHandler.h" +#include "../client/CSoundBase.h" + +#include "CObjectClassesHandler.h" +#include "../CSpellHandler.h" + +using namespace boost::assign; + +std::map > > CGTeleport::objs; +std::vector > CGTeleport::gates; +std::map > CGMagi::eyelist; +ui8 CGObelisk::obeliskCount; //how many obelisks are on map +std::map CGObelisk::visited; //map: team_id => how many obelisks has been visited + +///helpers +static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1) +{ + OpenWindow ow; + ow.window = type; + ow.id1 = id1; + ow.id2 = id2; + IObjectInterface::cb->sendAndApply(&ow); +} + +static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) +{ + InfoWindow iw; + iw.soundID = soundID; + iw.player = playerID; + iw.text.addTxt(MetaString::ADVOB_TXT,txtID); + IObjectInterface::cb->sendAndApply(&iw); +} + +static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = h->getOwner(); + showInfoDialog(playerID,txtID,soundID); +} + +static std::string & visitedTxt(const bool visited) +{ + int id = visited ? 352 : 353; + return VLC->generaltexth->allTexts[id]; +} + +void CPlayersVisited::setPropertyDer( ui8 what, ui32 val ) +{ + if(what == 10) + players.insert(PlayerColor(val)); +} + +bool CPlayersVisited::wasVisited( PlayerColor player ) const +{ + return vstd::contains(players,player); +} + +bool CPlayersVisited::wasVisited( TeamID team ) const +{ + for(auto i : players) + { + if(cb->getPlayer(i)->team == team) + return true; + } + return false; +} + +const std::string & CGCreature::getHoverText() const +{ + if(stacks.empty()) + { + static const std::string errorValue("!!!INVALID_STACK!!!"); + + //should not happen... + logGlobal->errorStream() << "Invalid stack at tile " << pos << ": subID=" << subID << "; id=" << id; + return errorValue; // references to temporary are illegal - use pre-constructed string + } + + MetaString ms; + int pom = stacks.begin()->second->getQuantityID(); + pom = 172 + 3*pom; + ms.addTxt(MetaString::ARRAY_TXT,pom); + ms << " " ; + ms.addTxt(MetaString::CRE_PL_NAMES,subID); + ms.toString(hoverName); + + if(const CGHeroInstance *selHero = cb->getSelectedHero(cb->getCurrentPlayer())) + { + const JsonNode & texts = VLC->generaltexth->localizedTexts["adventureMap"]["monsterThreat"]; + + hoverName += texts["title"].String(); + int choice; + double ratio = ((double)getArmyStrength() / selHero->getTotalStrength()); + if (ratio < 0.1) choice = 0; + else if (ratio < 0.25) choice = 1; + else if (ratio < 0.6) choice = 2; + else if (ratio < 0.9) choice = 3; + else if (ratio < 1.1) choice = 4; + else if (ratio < 1.3) choice = 5; + else if (ratio < 1.8) choice = 6; + else if (ratio < 2.5) choice = 7; + else if (ratio < 4) choice = 8; + else if (ratio < 8) choice = 9; + else if (ratio < 20) choice = 10; + else choice = 11; + hoverName += texts["levels"].Vector()[choice].String(); + } + return hoverName; +} +void CGCreature::onHeroVisit( const CGHeroInstance * h ) const +{ + int action = takenAction(h); + switch( action ) //decide what we do... + { + case FIGHT: + fight(h); + break; + case FLEE: //flee + { + flee(h); + break; + } + case JOIN_FOR_FREE: //join for free + { + BlockingDialog ynd(true,false); + ynd.player = h->tempOwner; + ynd.text.addTxt(MetaString::ADVOB_TXT, 86); + ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID); + cb->showBlockingDialog(&ynd); + break; + } + default: //join for gold + { + assert(action > 0); + + //ask if player agrees to pay gold + BlockingDialog ynd(true,false); + ynd.player = h->tempOwner; + std::string tmp = VLC->generaltexth->advobtxt[90]; + boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(getStackCount(SlotID(0)))); + boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast(action)); + boost::algorithm::replace_first(tmp,"%s",VLC->creh->creatures[subID]->namePl); + ynd.text << tmp; + cb->showBlockingDialog(&ynd); + break; + } + } + +} + +void CGCreature::initObj() +{ + blockVisit = true; + switch(character) + { + case 0: + character = -4; + break; + case 1: + character = cb->gameState()->getRandomGenerator().nextInt(1, 7); + break; + case 2: + character = cb->gameState()->getRandomGenerator().nextInt(1, 10); + break; + case 3: + character = cb->gameState()->getRandomGenerator().nextInt(4, 10); + break; + case 4: + character = 10; + break; + } + + stacks[SlotID(0)]->setType(CreatureID(subID)); + TQuantity &amount = stacks[SlotID(0)]->count; + CCreature &c = *VLC->creh->creatures[subID]; + if(amount == 0) + { + amount = cb->gameState()->getRandomGenerator().nextInt(c.ammMin, c.ammMax); + + if(amount == 0) //armies with 0 creatures are illegal + { + logGlobal->warnStream() << "Problem: stack " << nodeName() << " cannot have 0 creatures. Check properties of " << c.nodeName(); + amount = 1; + } + } + formation.randomFormation = cb->gameState()->getRandomGenerator().nextInt(); + + temppower = stacks[SlotID(0)]->count * 1000; + refusedJoining = false; +} + +void CGCreature::newTurn() const +{//Works only for stacks of single type of size up to 2 millions + if (stacks.begin()->second->count < VLC->modh->settings.CREEP_SIZE && cb->getDate(Date::DAY_OF_WEEK) == 1 && cb->getDate(Date::DAY) > 1) + { + ui32 power = temppower * (100 + VLC->modh->settings.WEEKLY_GROWTH)/100; + cb->setObjProperty(id, ObjProperty::MONSTER_COUNT, std::min (power/1000 , (ui32)VLC->modh->settings.CREEP_SIZE)); //set new amount + cb->setObjProperty(id, ObjProperty::MONSTER_POWER, power); //increase temppower + } + if (VLC->modh->modules.STACK_EXP) + cb->setObjProperty(id, ObjProperty::MONSTER_EXP, VLC->modh->settings.NEUTRAL_STACK_EXP); //for testing purpose +} +void CGCreature::setPropertyDer(ui8 what, ui32 val) +{ + switch (what) + { + case ObjProperty::MONSTER_COUNT: + stacks[SlotID(0)]->count = val; + break; + case ObjProperty::MONSTER_POWER: + temppower = val; + break; + case ObjProperty::MONSTER_EXP: + giveStackExp(val); + break; + case ObjProperty::MONSTER_RESTORE_TYPE: + formation.basicType = val; + break; + case ObjProperty::MONSTER_REFUSED_JOIN: + refusedJoining = val; + break; + } +} + +int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const +{ + //calculate relative strength of hero and creatures armies + double relStrength = double(h->getTotalStrength()) / getArmyStrength(); + + int powerFactor; + if(relStrength >= 7) + powerFactor = 11; + + else if(relStrength >= 1) + powerFactor = (int)(2*(relStrength-1)); + + else if(relStrength >= 0.5) + powerFactor = -1; + + else if(relStrength >= 0.333) + powerFactor = -2; + else + powerFactor = -3; + + std::set myKindCres; //what creatures are the same kind as we + const CCreature * myCreature = VLC->creh->creatures[subID]; + myKindCres.insert(myCreature->idNumber); //we + myKindCres.insert(myCreature->upgrades.begin(), myCreature->upgrades.end()); //our upgrades + + for(ConstTransitivePtr &crea : VLC->creh->creatures) + { + if(vstd::contains(crea->upgrades, myCreature->idNumber)) //it's our base creatures + myKindCres.insert(crea->idNumber); + } + + int count = 0, //how many creatures of similar kind has hero + totalCount = 0; + + for (auto & elem : h->Slots()) + { + if(vstd::contains(myKindCres,elem.second->type->idNumber)) + count += elem.second->count; + totalCount += elem.second->count; + } + + int sympathy = 0; // 0 if hero have no similar creatures + if(count) + sympathy++; // 1 - if hero have at least 1 similar creature + if(count*2 > totalCount) + sympathy++; // 2 - hero have similar creatures more that 50% + + int charisma = powerFactor + h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy; + + if(charisma < character) //creatures will fight + return -2; + + if (allowJoin) + { + if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) + sympathy + 1 >= character) + return 0; //join for free + + else if(h->getSecSkillLevel(SecondarySkill::DIPLOMACY) * 2 + sympathy + 1 >= character) + return VLC->creh->creatures[subID]->cost[6] * getStackCount(SlotID(0)); //join for gold + } + + //we are still here - creatures have not joined hero, flee or fight + + if (charisma > character) + return -1; //flee + else + return -2; //fight +} + +void CGCreature::fleeDecision(const CGHeroInstance *h, ui32 pursue) const +{ + if(refusedJoining) + cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, false); + + if(pursue) + { + fight(h); + } + else + { + cb->removeObject(this); + } +} + +void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const +{ + if(!accept) + { + if(takenAction(h,false) == -1) //they flee + { + cb->setObjProperty(id, ObjProperty::MONSTER_REFUSED_JOIN, true); + flee(h); + } + else //they fight + { + showInfoDialog(h,87,0);//Insulted by your refusal of their offer, the monsters attack! + fight(h); + } + } + else //accepted + { + if (cb->getResource(h->tempOwner, Res::GOLD) < cost) //player don't have enough gold! + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.text << std::pair(1,29); //You don't have enough gold + cb->showInfoDialog(&iw); + + //act as if player refused + joinDecision(h,cost,false); + return; + } + + //take gold + if(cost) + cb->giveResource(h->tempOwner,Res::GOLD,-cost); + + cb->tryJoiningArmy(this, h, true, true); + } +} + +void CGCreature::fight( const CGHeroInstance *h ) const +{ + //split stacks + //TODO: multiple creature types in a stack? + int basicType = stacks.begin()->second->type->idNumber; + cb->setObjProperty(id, ObjProperty::MONSTER_RESTORE_TYPE, basicType); //store info about creature stack + + double relativePower = static_cast(h->getTotalStrength()) / getArmyStrength(); + int stacksCount; + //TODO: number depends on tile type + if (relativePower < 0.5) + { + stacksCount = 7; + } + else if (relativePower < 0.67) + { + stacksCount = 7; + } + else if (relativePower < 1) + { + stacksCount = 6; + } + else if (relativePower < 1.5) + { + stacksCount = 5; + } + else if (relativePower < 2) + { + stacksCount = 4; + } + else + { + stacksCount = 3; + } + SlotID sourceSlot = stacks.begin()->first; + SlotID destSlot; + for (int stacksLeft = stacksCount; stacksLeft > 1; --stacksLeft) + { + int stackSize = stacks.begin()->second->count / stacksLeft; + if (stackSize) + { + if ((destSlot = getFreeSlot()).validSlot()) + cb->moveStack(StackLocation(this, sourceSlot), StackLocation(this, destSlot), stackSize); + else + { + logGlobal->warnStream() <<"Warning! Not enough empty slots to split stack!"; + break; + } + } + else break; + } + if (stacksCount > 1) + { + if (formation.randomFormation % 100 < 50) //upgrade + { + SlotID slotId = SlotID(stacks.size() / 2); + const auto & upgrades = getStack(slotId).type->upgrades; + if(!upgrades.empty()) + { + auto it = RandomGeneratorUtil::nextItem(upgrades, cb->gameState()->getRandomGenerator()); + cb->changeStackType(StackLocation(this, slotId), VLC->creh->creatures[*it]); + } + } + } + + cb->startBattleI(h, this); + +} + +void CGCreature::flee( const CGHeroInstance * h ) const +{ + BlockingDialog ynd(true,false); + ynd.player = h->tempOwner; + ynd.text.addTxt(MetaString::ADVOB_TXT,91); + ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID); + cb->showBlockingDialog(&ynd); +} + +void CGCreature::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + + if(result.winner==0) + { + cb->removeObject(this); + } + else + { + //int killedAmount=0; + //for(std::set >::iterator i=result->casualties[1].begin(); i!=result->casualties[1].end(); i++) + // if(i->first == subID) + // killedAmount += i->second; + //cb->setAmount(id, slots.find(0)->second.second - killedAmount); + + /* + MetaString ms; + int pom = slots.find(0)->second.getQuantityID(); + pom = 174 + 3*pom + 1; + ms << std::pair(6,pom) << " " << std::pair(7,subID); + cb->setHoverName(id,&ms); + cb->setObjProperty(id, 11, slots.begin()->second.count * 1000); + */ + + //merge stacks into one + TSlots::const_iterator i; + CCreature * cre = VLC->creh->creatures[formation.basicType]; + for (i = stacks.begin(); i != stacks.end(); i++) + { + if (cre->isMyUpgrade(i->second->type)) + { + cb->changeStackType (StackLocation(this, i->first), cre); //un-upgrade creatures + } + } + + //first stack has to be at slot 0 -> if original one got killed, move there first remaining stack + if(!hasStackAtSlot(SlotID(0))) + cb->moveStack(StackLocation(this, stacks.begin()->first), StackLocation(this, SlotID(0)), stacks.begin()->second->count); + + while (stacks.size() > 1) //hopefully that's enough + { + // TODO it's either overcomplicated (if we assume there'll be only one stack) or buggy (if we allow multiple stacks... but that'll also cause troubles elsewhere) + i = stacks.end(); + i--; + SlotID slot = getSlotFor(i->second->type); + if (slot == i->first) //no reason to move stack to its own slot + break; + else + cb->moveStack (StackLocation(this, i->first), StackLocation(this, slot), i->second->count); + } + + cb->setObjProperty(id, ObjProperty::MONSTER_POWER, stacks.begin()->second->count * 1000); //remember casualties + } +} + +void CGCreature::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + auto action = takenAction(hero); + if(!refusedJoining && action >= JOIN_FOR_FREE) //higher means price + joinDecision(hero, action, answer); + else if(action != FIGHT) + fleeDecision(hero, answer); + else + assert(0); +} + +void CGMine::onHeroVisit( const CGHeroInstance * h ) const +{ + int relations = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); + + if(relations == 2) //we're visiting our mine + { + cb->showGarrisonDialog(id,h->id,true); + return; + } + else if (relations == 1)//ally + return; + + if(stacksCount()) //Mine is guarded + { + BlockingDialog ynd(true,false); + ynd.player = h->tempOwner; + ynd.text.addTxt(MetaString::ADVOB_TXT, subID == 7 ? 84 : 187); + cb->showBlockingDialog(&ynd); + return; + } + + flagMine(h->tempOwner); + +} + +void CGMine::newTurn() const +{ + if(cb->getDate() == 1) + return; + + if (tempOwner == PlayerColor::NEUTRAL) + return; + + cb->giveResource(tempOwner, producedResource, producedQuantity); +} + +void CGMine::initObj() +{ + if(subID >= 7) //Abandoned Mine + { + //set guardians + int howManyTroglodytes = cb->gameState()->getRandomGenerator().nextInt(100, 199); + auto troglodytes = new CStackInstance(CreatureID::TROGLODYTES, howManyTroglodytes); + putStack(SlotID(0), troglodytes); + + //after map reading tempOwner placeholds bitmask for allowed resources + std::vector possibleResources; + for (int i = 0; i < PlayerColor::PLAYER_LIMIT_I; i++) + if(tempOwner.getNum() & 1<(i)); + + assert(!possibleResources.empty()); + producedResource = *RandomGeneratorUtil::nextItem(possibleResources, cb->gameState()->getRandomGenerator()); + tempOwner = PlayerColor::NEUTRAL; + hoverName = VLC->generaltexth->mines[7].first + "\n" + VLC->generaltexth->allTexts[202] + " " + troglodytes->getQuantityTXT(false) + " " + troglodytes->type->namePl; + } + else + { + producedResource = static_cast(subID); + + MetaString ms; + ms << std::pair(9,producedResource); + if(tempOwner >= PlayerColor::PLAYER_LIMIT) + tempOwner = PlayerColor::NEUTRAL; + else + ms << " (" << std::pair(6,23+tempOwner.getNum()) << ")"; + ms.toString(hoverName); + } + + producedQuantity = defaultResProduction(); +} + +void CGMine::flagMine(PlayerColor player) const +{ + assert(tempOwner != player); + cb->setOwner(this, player); //not ours? flag it! + + MetaString ms; + ms << std::pair(9,subID) << "\n(" << std::pair(6,23+player.getNum()) << ")"; + if(subID == 7) + { + ms << "(%s)"; + ms.addReplacement(MetaString::RES_NAMES, producedResource); + } + cb->setHoverName(this,&ms); + + InfoWindow iw; + iw.soundID = soundBase::FLAGMINE; + iw.text.addTxt(MetaString::MINE_EVNTS,producedResource); //not use subID, abandoned mines uses default mine texts + iw.player = player; + iw.components.push_back(Component(Component::RESOURCE,producedResource,producedQuantity,-1)); + cb->showInfoDialog(&iw); +} + +ui32 CGMine::defaultResProduction() +{ + switch(producedResource) + { + case Res::WOOD: + case Res::ORE: + return 2; + case Res::GOLD: + return 1000; + default: + return 1; + } +} + +void CGMine::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == 0) //attacker won + { + if(subID == 7) + { + showInfoDialog(hero->tempOwner, 85, 0); + } + flagMine(hero->tempOwner); + } +} + +void CGMine::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if(answer) + cb->startBattleI(hero, this); +} + +void CGResource::initObj() +{ + blockVisit = true; + hoverName = VLC->generaltexth->restypes[subID]; + + if(!amount) + { + switch(subID) + { + case 6: + amount = cb->gameState()->getRandomGenerator().nextInt(500, 1000); + break; + case 0: case 2: + amount = cb->gameState()->getRandomGenerator().nextInt(6, 10); + break; + default: + amount = cb->gameState()->getRandomGenerator().nextInt(3, 5); + break; + } + } +} + +void CGResource::onHeroVisit( const CGHeroInstance * h ) const +{ + if(stacksCount()) + { + if(message.size()) + { + BlockingDialog ynd(true,false); + ynd.player = h->getOwner(); + ynd.text << message; + cb->showBlockingDialog(&ynd); + } + else + { + blockingDialogAnswered(h, true); //behave as if player accepted battle + } + } + else + { + if(message.length()) + { + InfoWindow iw; + iw.player = h->tempOwner; + iw.text << message; + cb->showInfoDialog(&iw); + } + collectRes(h->getOwner()); + } +} + +void CGResource::collectRes( PlayerColor player ) const +{ + cb->giveResource(player, static_cast(subID), amount); + ShowInInfobox sii; + sii.player = player; + sii.c = Component(Component::RESOURCE,subID,amount,0); + sii.text.addTxt(MetaString::ADVOB_TXT,113); + sii.text.addReplacement(MetaString::RES_NAMES, subID); + cb->showCompInfo(&sii); + cb->removeObject(this); +} + +void CGResource::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == 0) //attacker won + collectRes(hero->getOwner()); +} + +void CGResource::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if(answer) + cb->startBattleI(hero, this); +} + +void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const +{ + ObjectInstanceID destinationid; + switch(ID) + { + case Obj::MONOLITH_ONE_WAY_ENTRANCE: //one way - find corresponding exit monolith + { + if(vstd::contains(objs,Obj::MONOLITH_ONE_WAY_EXIT) && vstd::contains(objs[Obj::MONOLITH_ONE_WAY_EXIT],subID) && objs[Obj::MONOLITH_ONE_WAY_EXIT][subID].size()) + { + destinationid = *RandomGeneratorUtil::nextItem(objs[Obj::MONOLITH_ONE_WAY_EXIT][subID], cb->gameState()->getRandomGenerator()); + } + else + { + logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; + } + break; + } + case Obj::MONOLITH_TWO_WAY://two way monolith - pick any other one + case Obj::WHIRLPOOL: //Whirlpool + if(vstd::contains(objs,ID) && vstd::contains(objs[ID],subID) && objs[ID][subID].size()>1) + { + //choose another exit + do + { + destinationid = *RandomGeneratorUtil::nextItem(objs[ID][subID], cb->gameState()->getRandomGenerator()); + } while(destinationid == id); + + if (ID == Obj::WHIRLPOOL) + { + if (!h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION)) + { + if (h->Slots().size() > 1 || h->Slots().begin()->second->count > 1) + { //we can't remove last unit + SlotID targetstack = h->Slots().begin()->first; //slot numbers may vary + for(auto i = h->Slots().rbegin(); i != h->Slots().rend(); i++) + { + if (h->getPower(targetstack) > h->getPower(i->first)) + { + targetstack = (i->first); + } + } + + TQuantity countToTake = h->getStackCount(targetstack) * 0.5; + vstd::amax(countToTake, 1); + + + InfoWindow iw; + iw.player = h->tempOwner; + iw.text.addTxt (MetaString::ADVOB_TXT, 168); + iw.components.push_back (Component(CStackBasicDescriptor(h->getCreature(targetstack), countToTake))); + cb->showInfoDialog(&iw); + cb->changeStackCount(StackLocation(h, targetstack), -countToTake); + } + } + } + } + else + logGlobal->warnStream() << "Cannot find corresponding exit monolith for "<< id; + break; + case Obj::SUBTERRANEAN_GATE: //find nearest subterranean gate on the other level + { + destinationid = getMatchingGate(id); + if(destinationid == ObjectInstanceID()) //no exit + { + showInfoDialog(h,153,0);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged. + } + break; + } + } + if(destinationid == ObjectInstanceID()) + { + logGlobal->warnStream() << "Cannot find exit... (obj at " << pos << ") :( "; + return; + } + if (ID == Obj::WHIRLPOOL) + { + std::set tiles = cb->getObj(destinationid)->getBlockedPos(); + auto & tile = *RandomGeneratorUtil::nextItem(tiles, cb->gameState()->getRandomGenerator()); + cb->moveHero(h->id, tile + int3(1,0,0), true); + } + else + cb->moveHero (h->id,CGHeroInstance::convertPosition(cb->getObj(destinationid)->pos,true) - getVisitableOffset(), true); +} + +void CGTeleport::initObj() +{ + int si = subID; + switch (ID) + { + case Obj::SUBTERRANEAN_GATE://ignore subterranean gates subid + case Obj::WHIRLPOOL: + { + si = 0; + break; + } + default: + break; + } + objs[ID][si].push_back(id); +} + +void CGTeleport::postInit() //matches subterranean gates into pairs +{ + //split on underground and surface gates + std::vector gatesSplit[2]; //surface and underground gates + for(auto & elem : objs[Obj::SUBTERRANEAN_GATE][0]) + { + const CGObjectInstance *hlp = cb->getObj(elem); + gatesSplit[hlp->pos.z].push_back(hlp); + } + + //sort by position + std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), [](const CGObjectInstance * a, const CGObjectInstance * b) + { + return a->pos < b->pos; + }); + + for(size_t i = 0; i < gatesSplit[0].size(); i++) + { + const CGObjectInstance *cur = gatesSplit[0][i]; + + //find nearest underground exit + std::pair best(-1, std::numeric_limits::max()); //pair + for(int j = 0; j < gatesSplit[1].size(); j++) + { + const CGObjectInstance *checked = gatesSplit[1][j]; + if(!checked) + continue; + si32 hlp = checked->pos.dist2dSQ(cur->pos); + if(hlp < best.second) + { + best.first = j; + best.second = hlp; + } + } + + if(best.first >= 0) //found pair + { + gates.push_back(std::make_pair(cur->id, gatesSplit[1][best.first]->id)); + gatesSplit[1][best.first] = nullptr; + } + else + gates.push_back(std::make_pair(cur->id, ObjectInstanceID())); + } + objs.erase(Obj::SUBTERRANEAN_GATE); +} + +ObjectInstanceID CGTeleport::getMatchingGate(ObjectInstanceID id) +{ + for(auto & gate : gates) + { + if(gate.first == id) + return gate.second; + if(gate.second == id) + return gate.first; + } + + return ObjectInstanceID(); +} + +void CGArtifact::initObj() +{ + blockVisit = true; + if(ID == Obj::ARTIFACT) + { + hoverName = VLC->arth->artifacts[subID]->Name(); + if(!storedArtifact->artType) + storedArtifact->setType(VLC->arth->artifacts[subID]); + } + if(ID == Obj::SPELL_SCROLL) + subID = 1; + + assert(storedArtifact->artType); + assert(storedArtifact->getParentNodes().size()); + + //assert(storedArtifact->artType->id == subID); //this does not stop desync +} + +void CGArtifact::onHeroVisit( const CGHeroInstance * h ) const +{ + if(!stacksCount()) + { + InfoWindow iw; + iw.player = h->tempOwner; + switch(ID) + { + case Obj::ARTIFACT: + { + iw.soundID = soundBase::treasure; //play sound only for non-scroll arts + iw.components.push_back(Component(Component::ARTIFACT,subID,0,0)); + if(message.length()) + iw.text << message; + else + { + if (VLC->arth->artifacts[subID]->EventText().size()) + iw.text << std::pair (MetaString::ART_EVNTS, subID); + else //fix for mod artifacts with no event text + { + iw.text.addTxt (MetaString::ADVOB_TXT, 183); //% has found treasure + iw.text.addReplacement (h->name); + } + + } + } + break; + case Obj::SPELL_SCROLL: + { + int spellID = storedArtifact->getGivenSpellID(); + iw.components.push_back (Component(Component::SPELL, spellID,0,0)); + iw.text.addTxt (MetaString::ADVOB_TXT,135); + iw.text.addReplacement(MetaString::SPELL_NAME, spellID); + } + break; + } + cb->showInfoDialog(&iw); + pick(h); + } + else + { + if(message.size()) + { + BlockingDialog ynd(true,false); + ynd.player = h->getOwner(); + ynd.text << message; + cb->showBlockingDialog(&ynd); + } + else + { + blockingDialogAnswered(h, true); + } + } +} + +void CGArtifact::pick(const CGHeroInstance * h) const +{ + cb->giveHeroArtifact(h, storedArtifact, ArtifactPosition::FIRST_AVAILABLE); + cb->removeObject(this); +} + +void CGArtifact::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if(result.winner == 0) //attacker won + pick(hero); +} + +void CGArtifact::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if(answer) + cb->startBattleI(hero, this); +} + +void CGWitchHut::initObj() +{ + ability = *RandomGeneratorUtil::nextItem(allowedAbilities, cb->gameState()->getRandomGenerator()); +} + +void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.soundID = soundBase::gazebo; + iw.player = h->getOwner(); + if(!wasVisited(h->tempOwner)) + cb->setObjProperty(id, 10, h->tempOwner.getNum()); + ui32 txt_id; + if(h->getSecSkillLevel(SecondarySkill(ability))) //you already know this skill + { + txt_id =172; + } + else if(!h->canLearnSkill()) //already all skills slots used + { + txt_id = 173; + } + else //give sec skill + { + iw.components.push_back(Component(Component::SEC_SKILL, ability, 1, 0)); + txt_id = 171; + cb->changeSecSkill(h, SecondarySkill(ability), 1, true); + } + + iw.text.addTxt(MetaString::ADVOB_TXT,txt_id); + iw.text.addReplacement(MetaString::SEC_SKILL_NAME, ability); + cb->showInfoDialog(&iw); +} + +const std::string & CGWitchHut::getHoverText() const +{ + hoverName = VLC->objtypeh->getObjectName(ID); + if(wasVisited(cb->getLocalPlayer())) + { + hoverName += "\n" + VLC->generaltexth->allTexts[356]; // + (learn %s) + boost::algorithm::replace_first(hoverName,"%s",VLC->generaltexth->skillName[ability]); + const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); + if(h && h->getSecSkillLevel(SecondarySkill(ability))) //hero knows that ability + hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned) + } + return hoverName; +} + +void CGMagicWell::onHeroVisit( const CGHeroInstance * h ) const +{ + int message; + + if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Well today + { + message = 78;//"A second drink at the well in one day will not help you." + } + else if(h->mana < h->manaLimit()) + { + giveDummyBonus(h->id); + cb->setManaPoints(h->id,h->manaLimit()); + message = 77; + } + else + { + message = 79; + } + showInfoDialog(h,message,soundBase::faerie); +} + +const std::string & CGMagicWell::getHoverText() const +{ + getNameVis(hoverName); + return hoverName; +} + +void CGObservatory::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.player = h->tempOwner; + switch (ID) + { + case Obj::REDWOOD_OBSERVATORY: + case Obj::PILLAR_OF_FIRE: + { + iw.soundID = soundBase::LIGHTHOUSE; + iw.text.addTxt(MetaString::ADVOB_TXT,98 + (ID==Obj::PILLAR_OF_FIRE)); + + FoWChange fw; + fw.player = h->tempOwner; + fw.mode = 1; + cb->getTilesInRange (fw.tiles, pos, 20, h->tempOwner, 1); + cb->sendAndApply (&fw); + break; + } + case Obj::COVER_OF_DARKNESS: + { + iw.text.addTxt (MetaString::ADVOB_TXT, 31); + hideTiles(h->tempOwner, 20); + break; + } + } + cb->showInfoDialog(&iw); +} + +void CGShrine::onHeroVisit( const CGHeroInstance * h ) const +{ + if(spell == SpellID::NONE) + { + logGlobal->errorStream() << "Not initialized shrine visited!"; + return; + } + + if(!wasVisited(h->tempOwner)) + cb->setObjProperty(id, 10, h->tempOwner.getNum()); + + InfoWindow iw; + iw.soundID = soundBase::temple; + iw.player = h->getOwner(); + iw.text.addTxt(MetaString::ADVOB_TXT,127 + ID - 88); + iw.text.addTxt(MetaString::SPELL_NAME,spell); + iw.text << "."; + + if(!h->getArt(ArtifactPosition::SPELLBOOK)) + { + iw.text.addTxt(MetaString::ADVOB_TXT,131); + } + else if(ID == Obj::SHRINE_OF_MAGIC_THOUGHT && !h->getSecSkillLevel(SecondarySkill::WISDOM)) //it's third level spell and hero doesn't have wisdom + { + iw.text.addTxt(MetaString::ADVOB_TXT,130); + } + else if(vstd::contains(h->spells,spell))//hero already knows the spell + { + iw.text.addTxt(MetaString::ADVOB_TXT,174); + } + else //give spell + { + std::set spells; + spells.insert(spell); + cb->changeSpells(h, true, spells); + + iw.components.push_back(Component(Component::SPELL,spell,0,0)); + } + + cb->showInfoDialog(&iw); +} + +void CGShrine::initObj() +{ + if(spell == SpellID::NONE) //spell not set + { + int level = ID-87; + std::vector possibilities; + cb->getAllowedSpells (possibilities, level); + + if(possibilities.empty()) + { + logGlobal->errorStream() << "Error: cannot init shrine, no allowed spells!"; + return; + } + + spell = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator()); + } +} + +const std::string & CGShrine::getHoverText() const +{ + hoverName = VLC->objtypeh->getObjectName(ID); + if(wasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current + { + hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s) + boost::algorithm::replace_first(hoverName,"%s", spell.toSpell()->name); + const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); + if(h && vstd::contains(h->spells,spell)) //hero knows that ability + hoverName += "\n\n" + VLC->generaltexth->allTexts[354]; // (Already learned) + } + return hoverName; +} + +void CGSignBottle::initObj() +{ + //if no text is set than we pick random from the predefined ones + if(message.empty()) + { + message = *RandomGeneratorUtil::nextItem(VLC->generaltexth->randsign, cb->gameState()->getRandomGenerator()); + } + + if(ID == Obj::OCEAN_BOTTLE) + { + blockVisit = true; + } +} + +void CGSignBottle::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.soundID = soundBase::STORE; + iw.player = h->getOwner(); + iw.text << message; + cb->showInfoDialog(&iw); + + if(ID == Obj::OCEAN_BOTTLE) + cb->removeObject(this); +} + +//TODO: remove +//void CGScholar::giveAnyBonus( const CGHeroInstance * h ) const +//{ +// +//} + +void CGScholar::onHeroVisit( const CGHeroInstance * h ) const +{ + + EBonusType type = bonusType; + int bid = bonusID; + //check if the bonus if applicable, if not - give primary skill (always possible) + int ssl = h->getSecSkillLevel(SecondarySkill(bid)); //current sec skill level, used if bonusType == 1 + if((type == SECONDARY_SKILL + && ((ssl == 3) || (!ssl && !h->canLearnSkill()))) ////hero already has expert level in the skill or (don't know skill and doesn't have free slot) + || (type == SPELL && (!h->getArt(ArtifactPosition::SPELLBOOK) || vstd::contains(h->spells, (ui32) bid) + || ( SpellID(bid).toSpell()->level > h->getSecSkillLevel(SecondarySkill::WISDOM) + 2) + ))) //hero doesn't have a spellbook or already knows the spell or doesn't have Wisdom + { + type = PRIM_SKILL; + bid = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS - 1); + } + + InfoWindow iw; + iw.soundID = soundBase::gazebo; + iw.player = h->getOwner(); + iw.text.addTxt(MetaString::ADVOB_TXT,115); + + switch (type) + { + case PRIM_SKILL: + cb->changePrimSkill(h,static_cast(bid),+1); + iw.components.push_back(Component(Component::PRIM_SKILL,bid,+1,0)); + break; + case SECONDARY_SKILL: + cb->changeSecSkill(h,SecondarySkill(bid),+1); + iw.components.push_back(Component(Component::SEC_SKILL,bid,ssl+1,0)); + break; + case SPELL: + { + std::set hlp; + hlp.insert(SpellID(bid)); + cb->changeSpells(h,true,hlp); + iw.components.push_back(Component(Component::SPELL,bid,0,0)); + } + break; + default: + logGlobal->errorStream() << "Error: wrong bonus type (" << (int)type << ") for Scholar!\n"; + return; + } + + cb->showInfoDialog(&iw); + cb->removeObject(this); +} + +void CGScholar::initObj() +{ + blockVisit = true; + if(bonusType == RANDOM) + { + bonusType = static_cast(cb->gameState()->getRandomGenerator().nextInt(2)); + switch(bonusType) + { + case PRIM_SKILL: + bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::PRIMARY_SKILLS -1); + break; + case SECONDARY_SKILL: + bonusID = cb->gameState()->getRandomGenerator().nextInt(GameConstants::SKILL_QUANTITY -1); + break; + case SPELL: + std::vector possibilities; + for (int i = 1; i < 6; ++i) + cb->getAllowedSpells (possibilities, i); + bonusID = *RandomGeneratorUtil::nextItem(possibilities, cb->gameState()->getRandomGenerator()); + break; + } + } +} + +void CGGarrison::onHeroVisit (const CGHeroInstance *h) const +{ + int ally = cb->gameState()->getPlayerRelations(h->tempOwner, tempOwner); + if (!ally && stacksCount() > 0) { + //TODO: Find a way to apply magic garrison effects in battle. + cb->startBattleI(h, this); + return; + } + + //New owner. + if (!ally) + cb->setOwner(this, h->tempOwner); + + cb->showGarrisonDialog(id, h->id, removableUnits); +} + +ui8 CGGarrison::getPassableness() const +{ + if ( !stacksCount() )//empty - anyone can visit + return GameConstants::ALL_PLAYERS; + if ( tempOwner == PlayerColor::NEUTRAL )//neutral guarded - no one can visit + return 0; + + ui8 mask = 0; + TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner); + for(PlayerColor it : ts->players) + mask |= 1<tempOwner; + cv.focusTime = 2000; + + FoWChange fw; + fw.player = h->tempOwner; + fw.mode = 1; + + for(auto it : eyelist[subID]) + { + const CGObjectInstance *eye = cb->getObj(it); + + cb->getTilesInRange (fw.tiles, eye->pos, 10, h->tempOwner, 1); + cb->sendAndApply(&fw); + cv.pos = eye->pos; + + cb->sendAndApply(&cv); + } + cv.pos = h->getPosition(false); + cb->sendAndApply(&cv); + } + } + else if (ID == Obj::EYE_OF_MAGI) + { + showInfoDialog(h,48,soundBase::invalid); + } + +} +void CGBoat::initObj() +{ + hero = nullptr; +} + +void CGSirens::initObj() +{ + blockVisit = true; +} + +const std::string & CGSirens::getHoverText() const +{ + getNameVis(hoverName); + return hoverName; +} + +void CGSirens::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.soundID = soundBase::DANGER; + iw.player = h->tempOwner; + if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Sirens + { + iw.text.addTxt(MetaString::ADVOB_TXT,133); + } + else + { + giveDummyBonus(h->id, Bonus::ONE_BATTLE); + TExpType xp = 0; + + for (auto i = h->Slots().begin(); i != h->Slots().end(); i++) + { + TQuantity drown = i->second->count * 0.3; + if(drown) + { + cb->changeStackCount(StackLocation(h, i->first), -drown); + xp += drown * i->second->type->valOfBonuses(Bonus::STACK_HEALTH); + } + } + + if(xp) + { + xp = h->calculateXp(xp); + iw.text.addTxt(MetaString::ADVOB_TXT,132); + iw.text.addReplacement(xp); + cb->changePrimSkill(h, PrimarySkill::EXPERIENCE, xp, false); + } + else + { + iw.text.addTxt(MetaString::ADVOB_TXT,134); + } + } + cb->showInfoDialog(&iw); + +} + +CGShipyard::CGShipyard() + :IShipyard(this) +{ +} + +void CGShipyard::getOutOffsets( std::vector &offsets ) const +{ + // H J L K I + // A x S x B + // C E G F D + offsets += int3(-3,0,0), int3(1,0,0), //AB + int3(-3,1,0), int3(1,1,0), int3(-2,1,0), int3(0,1,0), int3(-1,1,0), //CDEFG + int3(-3,-1,0), int3(1,-1,0), int3(-2,-1,0), int3(0,-1,0), int3(-1,-1,0); //HIJKL +} + +void CGShipyard::onHeroVisit( const CGHeroInstance * h ) const +{ + if(!cb->gameState()->getPlayerRelations(tempOwner, h->tempOwner)) + cb->setOwner(this, h->tempOwner); + + auto s = shipyardStatus(); + if(s != IBoatGenerator::GOOD) + { + InfoWindow iw; + iw.player = tempOwner; + getProblemText(iw.text, h); + cb->showInfoDialog(&iw); + } + else + { + openWindow(OpenWindow::SHIPYARD_WINDOW,id.getNum(),h->id.getNum()); + } +} + +void CCartographer::onHeroVisit( const CGHeroInstance * h ) const +{ + if (!wasVisited (h->getOwner()) ) //if hero has not visited yet this cartographer + { + if (cb->getResource(h->tempOwner, Res::GOLD) >= 1000) //if he can afford a map + { + //ask if he wants to buy one + int text=0; + switch (subID) + { + case 0: + text = 25; + break; + case 1: + text = 26; + break; + case 2: + text = 27; + break; + default: + logGlobal->warnStream() << "Unrecognized subtype of cartographer"; + } + assert(text); + BlockingDialog bd (true, false); + bd.player = h->getOwner(); + bd.soundID = soundBase::LIGHTHOUSE; + bd.text.addTxt (MetaString::ADVOB_TXT, text); + cb->showBlockingDialog (&bd); + } + else //if he cannot afford + { + showInfoDialog(h,28,soundBase::CAVEHEAD); + } + } + else //if he already visited carographer + { + showInfoDialog(h,24,soundBase::CAVEHEAD); + } +} + +void CCartographer::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{ + if (answer) //if hero wants to buy map + { + cb->giveResource (hero->tempOwner, Res::GOLD, -1000); + FoWChange fw; + fw.mode = 1; + fw.player = hero->tempOwner; + + //subIDs of different types of cartographers: + //water = 0; land = 1; underground = 2; + cb->getAllTiles (fw.tiles, hero->tempOwner, subID - 1, !subID + 1); //reveal appropriate tiles + cb->sendAndApply (&fw); + cb->setObjProperty (id, 10, hero->tempOwner.getNum()); + } +} + +void CGDenOfthieves::onHeroVisit (const CGHeroInstance * h) const +{ + cb->showThievesGuildWindow(h->tempOwner, id); +} + +void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const +{ + InfoWindow iw; + iw.player = h->tempOwner; + TeamState *ts = cb->gameState()->getPlayerTeam(h->tempOwner); + assert(ts); + TeamID team = ts->id; + + if(!wasVisited(team)) + { + iw.text.addTxt(MetaString::ADVOB_TXT, 96); + cb->sendAndApply(&iw); + + cb->setObjProperty(id, 20, h->tempOwner.getNum()); //increment general visited obelisks counter + + openWindow(OpenWindow::PUZZLE_MAP, h->tempOwner.getNum()); + + cb->setObjProperty(id, 10, h->tempOwner.getNum()); //mark that particular obelisk as visited + } + else + { + iw.text.addTxt(MetaString::ADVOB_TXT, 97); + cb->sendAndApply(&iw); + } + +} + +void CGObelisk::initObj() +{ + obeliskCount++; +} + +const std::string & CGObelisk::getHoverText() const +{ + bool visited = wasVisited(cb->getLocalPlayer()); + hoverName = VLC->objtypeh->getObjectName(ID) + " " + visitedTxt(visited); + return hoverName; +} + +void CGObelisk::setPropertyDer( ui8 what, ui32 val ) +{ + CPlayersVisited::setPropertyDer(what, val); + switch(what) + { + case 20: + assert(val < PlayerColor::PLAYER_LIMIT_I); + visited[TeamID(val)]++; + + if(visited[TeamID(val)] > obeliskCount) + { + logGlobal->errorStream() << "Error: Visited " << visited[TeamID(val)] << "\t\t" << obeliskCount; + assert(0); + } + + break; + } +} + +void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const +{ + if(h->tempOwner != tempOwner) + { + PlayerColor oldOwner = tempOwner; + cb->setOwner(this,h->tempOwner); //not ours? flag it! + showInfoDialog(h,69,soundBase::LIGHTHOUSE); + giveBonusTo(h->tempOwner); + + if(oldOwner < PlayerColor::PLAYER_LIMIT) //remove bonus from old owner + { + RemoveBonus rb(RemoveBonus::PLAYER); + rb.whoID = oldOwner.getNum(); + rb.source = Bonus::OBJECT; + rb.id = id.getNum(); + cb->sendAndApply(&rb); + } + } +} + +void CGLighthouse::initObj() +{ + if(tempOwner < PlayerColor::PLAYER_LIMIT) + { + giveBonusTo(tempOwner); + } +} + +const std::string & CGLighthouse::getHoverText() const +{ + hoverName = VLC->objtypeh->getObjectName(ID); + //TODO: owned by %s player + return hoverName; +} + +void CGLighthouse::giveBonusTo( PlayerColor player ) const +{ + GiveBonus gb(GiveBonus::PLAYER); + gb.bonus.type = Bonus::SEA_MOVEMENT; + gb.bonus.val = 500; + gb.id = player.getNum(); + gb.bonus.duration = Bonus::PERMANENT; + gb.bonus.source = Bonus::OBJECT; + gb.bonus.sid = id.getNum(); + cb->sendAndApply(&gb); +} diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h new file mode 100644 index 000000000..26315b04d --- /dev/null +++ b/lib/mapObjects/MiscObjects.h @@ -0,0 +1,377 @@ +#pragma once + +#include "CObjectHandler.h" +#include "CGArmedInstance.h" + +/* + * CObjectHandler.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 CPlayersVisited: public CGObjectInstance +{ +public: + std::set players; //players that visited this object + + bool wasVisited(PlayerColor player) const; + bool wasVisited(TeamID team) const; + void setPropertyDer(ui8 what, ui32 val) override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & players; + } +}; + +class DLL_LINKAGE CGCreature : public CArmedInstance //creatures on map +{ + enum Action { + FIGHT = -2, FLEE = -1, JOIN_FOR_FREE = 0 //values > 0 mean gold price + }; + +public: + ui32 identifier; //unique code for this monster (used in missions) + si8 character; //character of this set of creatures (0 - the most friendly, 4 - the most hostile) => on init changed to -4 (compliant) ... 10 value (savage) + std::string message; //message printed for attacking hero + TResources resources; // resources given to hero that has won with monsters + ArtifactID gainedArtifact; //ID of artifact gained to hero, -1 if none + bool neverFlees; //if true, the troops will never flee + bool notGrowingTeam; //if true, number of units won't grow + ui64 temppower; //used to handle fractional stack growth for tiny stacks + + bool refusedJoining; + + void onHeroVisit(const CGHeroInstance * h) const override; + const std::string & getHoverText() const override; + void initObj() override; + void newTurn() const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + + struct DLL_LINKAGE formationInfo // info about merging stacks after battle back into one + { + si32 basicType; + ui32 randomFormation; //random seed used to determine number of stacks and is there's upgraded stack + template void serialize(Handler &h, const int version) + { + h & basicType & randomFormation; + } + } formation; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & identifier & character & message & resources & gainedArtifact & neverFlees & notGrowingTeam & temppower; + h & refusedJoining & formation; + } +protected: + void setPropertyDer(ui8 what, ui32 val) override; +private: + + void fight(const CGHeroInstance *h) const; + void flee( const CGHeroInstance * h ) const; + void fleeDecision(const CGHeroInstance *h, ui32 pursue) const; + void joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const; + + int takenAction(const CGHeroInstance *h, bool allowJoin=true) const; //action on confrontation: -2 - fight, -1 - flee, >=0 - will join for given value of gold (may be 0) + +}; + + +class DLL_LINKAGE CGSignBottle : public CGObjectInstance //signs and ocean bottles +{ +public: + std::string message; + + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & message; + } +}; + +class DLL_LINKAGE CGWitchHut : public CPlayersVisited +{ +public: + std::vector allowedAbilities; + ui32 ability; + + const std::string & getHoverText() const override; + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & allowedAbilities & ability; + } +}; + +class DLL_LINKAGE CGScholar : public CGObjectInstance +{ +public: + enum EBonusType {PRIM_SKILL, SECONDARY_SKILL, SPELL, RANDOM = 255}; + EBonusType bonusType; + ui16 bonusID; //ID of skill/spell + +// void giveAnyBonus(const CGHeroInstance * h) const; //TODO: remove + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & bonusType & bonusID; + } +}; + +class DLL_LINKAGE CGGarrison : public CArmedInstance +{ +public: + bool removableUnits; + + ui8 getPassableness() const; + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & removableUnits; + } +}; + +class DLL_LINKAGE CGArtifact : public CArmedInstance +{ +public: + CArtifactInstance *storedArtifact; + std::string message; + + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + void pick( const CGHeroInstance * h ) const; + void initObj() override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & message & storedArtifact; + } +}; + +class DLL_LINKAGE CGResource : public CArmedInstance +{ +public: + ui32 amount; //0 if random + std::string message; + + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + void collectRes(PlayerColor player) const; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & amount & message; + } +}; + +class DLL_LINKAGE CGShrine : public CPlayersVisited +{ +public: + SpellID spell; //id of spell or NONE if random + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + const std::string & getHoverText() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this);; + h & spell; + } +}; + +class DLL_LINKAGE CGMine : public CArmedInstance +{ +public: + Res::ERes producedResource; + ui32 producedQuantity; + + void onHeroVisit(const CGHeroInstance * h) const override; + void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + void flagMine(PlayerColor player) const; + void newTurn() const override; + void initObj() override; + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & producedResource & producedQuantity; + } + ui32 defaultResProduction(); +}; + +class DLL_LINKAGE CGTeleport : public CGObjectInstance //teleports and subterranean gates +{ +public: + static std::map > > objs; //teleports: map[ID][subID] => vector of ids + static std::vector > gates; //subterranean gates: pairs of ids + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + static void postInit(); + static ObjectInstanceID getMatchingGate(ObjectInstanceID id); //receives id of one subterranean gate and returns id of the paired one, -1 if none + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGMagicWell : public CGObjectInstance //objects giving bonuses to luck/morale/movement +{ +public: + void onHeroVisit(const CGHeroInstance * h) const override; + const std::string & getHoverText() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGSirens : public CGObjectInstance +{ +public: + void onHeroVisit(const CGHeroInstance * h) const override; + const std::string & getHoverText() const override; + void initObj() override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGObservatory : public CGObjectInstance //Redwood observatory +{ +public: + void onHeroVisit(const CGHeroInstance * h) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGBoat : public CGObjectInstance +{ +public: + ui8 direction; + const CGHeroInstance *hero; //hero on board + + void initObj() override; + + CGBoat() + { + hero = nullptr; + direction = 4; + } + template void serialize(Handler &h, const int version) + { + h & static_cast(*this) & direction & hero; + } +}; + +class CGShipyard : public CGObjectInstance, public IShipyard +{ +public: + void getOutOffsets(std::vector &offsets) const; //offsets to obj pos when we boat can be placed + CGShipyard(); + void onHeroVisit(const CGHeroInstance * h) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGMagi : public CGObjectInstance +{ +public: + static std::map > eyelist; //[subID][id], supports multiple sets as in H5 + + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + + + +class DLL_LINKAGE CCartographer : public CPlayersVisited +{ +///behaviour varies depending on surface and floor +public: + void onHeroVisit(const CGHeroInstance * h) const override; + void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGDenOfthieves : public CGObjectInstance +{ + void onHeroVisit(const CGHeroInstance * h) const override; +}; + +class DLL_LINKAGE CGObelisk : public CPlayersVisited +{ +public: + static ui8 obeliskCount; //how many obelisks are on map + static std::map visited; //map: team_id => how many obelisks has been visited + + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + const std::string & getHoverText() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +protected: + void setPropertyDer(ui8 what, ui32 val) override; +}; + +class DLL_LINKAGE CGLighthouse : public CGObjectInstance +{ +public: + void onHeroVisit(const CGHeroInstance * h) const override; + void initObj() override; + const std::string & getHoverText() const override; + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } + void giveBonusTo( PlayerColor player ) const; +}; diff --git a/lib/mapping/CCampaignHandler.cpp b/lib/mapping/CCampaignHandler.cpp index 455def111..9935ed153 100644 --- a/lib/mapping/CCampaignHandler.cpp +++ b/lib/mapping/CCampaignHandler.cpp @@ -10,7 +10,7 @@ #include "../CGeneralTextHandler.h" #include "../StartInfo.h" #include "../CArtHandler.h" //for hero crossover -#include "../mapObjects/CObjectHandler.h" //for hero crossover +#include "../mapObjects/CGHeroInstance.h"//for hero crossover #include "../CHeroHandler.h" #include "CMapService.h" #include "CMap.h" diff --git a/lib/mapping/CMap.cpp b/lib/mapping/CMap.cpp index 8991beef3..15e66c26e 100644 --- a/lib/mapping/CMap.cpp +++ b/lib/mapping/CMap.cpp @@ -7,6 +7,7 @@ #include "../CTownHandler.h" #include "../CHeroHandler.h" #include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/CGHeroInstance.h" #include "../CGeneralTextHandler.h" #include "../CSpellHandler.h" #include "CMapEditManager.h" diff --git a/lib/mapping/CMap.h b/lib/mapping/CMap.h index e14a29a99..44223539d 100644 --- a/lib/mapping/CMap.h +++ b/lib/mapping/CMap.h @@ -12,7 +12,9 @@ #pragma once #include "../ConstTransitivePtr.h" -#include "../mapObjects/CObjectHandler.h" +#include "../mapObjects/MiscObjects.h" // To serialize static props +#include "../mapObjects/CQuest.h" // To serialize static props +#include "../mapObjects/CGTownInstance.h" // To serialize static props #include "../ResourceSet.h" #include "../int3.h" #include "../GameConstants.h" diff --git a/lib/mapping/CMapEditManager.cpp b/lib/mapping/CMapEditManager.cpp index a6885ee7e..a778dbad1 100644 --- a/lib/mapping/CMapEditManager.cpp +++ b/lib/mapping/CMapEditManager.cpp @@ -4,6 +4,7 @@ #include "../JsonNode.h" #include "../filesystem/Filesystem.h" #include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/CGHeroInstance.h" #include "../VCMI_Lib.h" MapRect::MapRect() : x(0), y(0), z(0), width(0), height(0) diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 7066e8e4b..21ddf65ee 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -20,9 +20,8 @@ #include "../CCreatureHandler.h" #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" -#include "../mapObjects/CObjectHandler.h" -#include "../mapObjects/CRewardableObject.h" #include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/MapObjects.h" #include "../VCMI_Lib.h" #include "../NetPacksBase.h" diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index b4213ba85..20419bae9 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -14,7 +14,8 @@ #include "CMapService.h" #include "../GameConstants.h" #include "../ResourceSet.h" -#include "../mapObjects/CObjectClassesHandler.h" +//#include "../mapObjects/CObjectClassesHandler.h" +#include "../mapObjects/ObjectTemplate.h" #include "../int3.h" diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 184a3d01e..4e1ea49cd 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -4,14 +4,13 @@ #include "../NetPacks.h" #include "../VCMI_Lib.h" #include "../CArtHandler.h" -#include "../mapObjects/CObjectHandler.h" -#include "../mapObjects/CRewardableObject.h" #include "../CGameState.h" #include "../CHeroHandler.h" #include "../CTownHandler.h" #include "../CModHandler.h" //needed? #include "../mapObjects/CObjectClassesHandler.h" #include "../mapObjects/CRewardableConstructor.h" +#include "../mapObjects/MapObjects.h" /* * RegisterTypes.h, part of VCMI engine diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index a0ae6b39c..28fe1ba0a 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -5,8 +5,8 @@ #include "../VCMI_Lib.h" #include "../CGeneralTextHandler.h" #include "../mapping/CMapEditManager.h" -#include "../mapObjects/CObjectHandler.h" -#include "../mapObjects/CObjectClassesHandler.h" +//#include "../mapObjects/CObjectHandler.h" +//#include "../mapObjects/CObjectClassesHandler.h" #include "../CTownHandler.h" #include "../StringConstants.h" #include "../filesystem/Filesystem.h" diff --git a/lib/rmg/CMapGenerator.h b/lib/rmg/CMapGenerator.h index 0d32b783b..9b56c11d2 100644 --- a/lib/rmg/CMapGenerator.h +++ b/lib/rmg/CMapGenerator.h @@ -15,7 +15,6 @@ #include "../CRandomGenerator.h" #include "CMapGenOptions.h" #include "CRmgTemplateZone.h" -#include "../mapObjects/CObjectHandler.h" #include "../int3.h" class CMap; diff --git a/lib/rmg/CRmgTemplateZone.cpp b/lib/rmg/CRmgTemplateZone.cpp index cca3719aa..4aa180d22 100644 --- a/lib/rmg/CRmgTemplateZone.cpp +++ b/lib/rmg/CRmgTemplateZone.cpp @@ -18,6 +18,8 @@ #include "../CTownHandler.h" #include "../CCreatureHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" + class CMap; class CMapEditManager; diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 42f5d7a6b..63578c820 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -8,9 +8,9 @@ #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CBuildingHandler.h" -#include "../lib/mapObjects/CObjectClassesHandler.h" +//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CHeroHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 1ee49b599..96a085799 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -8,7 +8,7 @@ #include "../lib/Connection.h" #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" -#include "../lib/mapObjects/CObjectClassesHandler.h" +//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" @@ -26,7 +26,7 @@ #include "../lib/VCMIDirs.h" #include "CGameHandler.h" #include "../lib/mapping/CMapInfo.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/GameConstants.h" #include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/CConfigHandler.h" diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 6a3dbb614..4ccea2a53 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -2,7 +2,7 @@ #include "../lib/NetPacks.h" #include "CGameHandler.h" -#include "../lib/mapObjects/CObjectHandler.h" +//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/IGameCallback.h" #include "../lib/mapping/CMap.h" #include "../lib/CGameState.h" From 76bf5351c6649eed80237319a8e5d8cd5178c5c5 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jun 2014 19:57:43 +0300 Subject: [PATCH 25/33] Line endings are now unix-style --- lib/mapObjects/CObjectClassesHandler.cpp | 666 ++++++------ lib/mapObjects/CObjectClassesHandler.h | 348 +++---- lib/mapObjects/CObjectHandler.cpp | 1212 +++++++++++----------- lib/mapObjects/CObjectHandler.h | 408 ++++---- 4 files changed, 1317 insertions(+), 1317 deletions(-) diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 86d20d519..cbb1233dd 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -1,333 +1,333 @@ -#include "StdInc.h" -#include "CObjectClassesHandler.h" - -#include "filesystem/Filesystem.h" -#include "filesystem/CBinaryReader.h" -#include "../lib/VCMI_Lib.h" -#include "GameConstants.h" -#include "StringConstants.h" -#include "CGeneralTextHandler.h" -#include "CModHandler.h" -#include "JsonNode.h" - -#include "CRewardableConstructor.h" -#include "MapObjects.h" - -/* - * CObjectClassesHandler.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 - * - */ - -CObjectClassesHandler::CObjectClassesHandler() -{ -#define SET_HANDLER_CLASS(STRING, CLASSNAME) handlerConstructors[STRING] = std::make_shared; -#define SET_HANDLER(STRING, TYPENAME) handlerConstructors[STRING] = std::make_shared > - - // list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code - //WARNING: should be in sync with registerTypesMapObjectTypes function - SET_HANDLER_CLASS("configurable", CRewardableConstructor); - - SET_HANDLER("", CGObjectInstance); - SET_HANDLER("generic", CGObjectInstance); - - SET_HANDLER("market", CGMarket); - SET_HANDLER("bank", CBank); - SET_HANDLER("cartographer", CCartographer); - SET_HANDLER("artifact", CGArtifact); - SET_HANDLER("blackMarket", CGBlackMarket); - SET_HANDLER("boat", CGBoat); - SET_HANDLER("bonusingObject", CGBonusingObject); - SET_HANDLER("borderGate", CGBorderGate); - SET_HANDLER("borderGuard", CGBorderGuard); - SET_HANDLER("monster", CGCreature); - SET_HANDLER("denOfThieves", CGDenOfthieves); - SET_HANDLER("dwelling", CGDwelling); - SET_HANDLER("event", CGEvent); - SET_HANDLER("garrison", CGGarrison); - SET_HANDLER("hero", CGHeroInstance); - SET_HANDLER("heroPlaceholder", CGHeroPlaceholder); - SET_HANDLER("keymaster", CGKeymasterTent); - SET_HANDLER("lighthouse", CGLighthouse); - SET_HANDLER("magi", CGMagi); - SET_HANDLER("magicSpring", CGMagicSpring); - SET_HANDLER("magicWell", CGMagicWell); - SET_HANDLER("market", CGMarket); - SET_HANDLER("mine", CGMine); - SET_HANDLER("obelisk", CGObelisk); - SET_HANDLER("observatory", CGObservatory); - SET_HANDLER("onceVisitable", CGOnceVisitable); - SET_HANDLER("pandora", CGPandoraBox); - SET_HANDLER("pickable", CGPickable); - SET_HANDLER("pyramid", CGPyramid); - SET_HANDLER("questGuard", CGQuestGuard); - SET_HANDLER("resource", CGResource); - SET_HANDLER("scholar", CGScholar); - SET_HANDLER("seerHut", CGSeerHut); - SET_HANDLER("shipyard", CGShipyard); - SET_HANDLER("shrine", CGShrine); - SET_HANDLER("sign", CGSignBottle); - SET_HANDLER("siren", CGSirens); - SET_HANDLER("teleport", CGTeleport); - SET_HANDLER("town", CGTownInstance); - SET_HANDLER("university", CGUniversity); - SET_HANDLER("oncePerHero", CGVisitableOPH); - SET_HANDLER("oncePerWeek", CGVisitableOPW); - SET_HANDLER("witch", CGWitchHut); - -#undef SET_HANDLER_CLASS -#undef SET_HANDLER -} - -template -void readTextFile(Container & objects, std::string path) -{ - CLegacyConfigParser parser(path); - size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else - parser.endLine(); - - for (size_t i=0; i CObjectClassesHandler::loadLegacyData(size_t dataSize) -{ - readTextFile(legacyTemplates, "Data/Objects.txt"); - readTextFile(legacyTemplates, "Data/Heroes.txt"); - - std::vector ret(dataSize);// create storage for 256 objects - assert(dataSize == 256); - - CLegacyConfigParser parser("Data/ObjNames.txt"); - for (size_t i=0; i<256; i++) - { - ret[i]["name"].String() = parser.readString(); - parser.endLine(); - } - return ret; -} - -/// selects preferred ID (or subID) for new object -template -si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 defaultID) -{ - if (!fixedID.isNull() && fixedID.Float() < defaultID) - return fixedID.Float(); // H3M object with fixed ID - - if (map.empty()) - return defaultID; // no objects loaded, keep gap for H3M objects - if (map.rbegin()->first > defaultID) - return map.rbegin()->first + 1; // some modded objects loaded, return next available - - return defaultID; // some H3M objects loaded, first modded found -} - -void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContainter * obj) -{ - auto handler = handlerConstructors.at(obj->handlerName)(); - handler->init(entry); - - si32 id = selectNextID(entry["index"], obj->objects, 1000); - handler->setType(obj->id, id); - - if (handler->getTemplates().empty()) - { - auto range = legacyTemplates.equal_range(std::make_pair(obj->id, id)); - for (auto & templ : boost::make_iterator_range(range.first, range.second)) - { - handler->addTemplate(templ.second); - } - legacyTemplates.erase(range.first, range.second); - } - - obj->objects[id] = handler; -} - -CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json) -{ - auto obj = new ObjectContainter(); - obj->name = json["name"].String(); - obj->handlerName = json["handler"].String(); - obj->base = json["base"]; // FIXME: when this data will be actually merged? - obj->id = selectNextID(json["index"], objects, 256); - for (auto entry : json["types"].Struct()) - { - loadObjectEntry(entry.second, obj); - } - return obj; -} - -void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) -{ - auto object = loadFromJson(data); - 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); - - 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::createObject(std::string name, 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(config["index"].isNull()); - config["index"].Float() = subID.get(); - } - - JsonUtils::inherit(config, objects.at(ID)->base); - - loadObjectEntry(config, objects[ID]); -} - -void CObjectClassesHandler::eraseObject(si32 ID, si32 subID) -{ - assert(objects.count(ID)); - assert(objects.at(ID)->objects.count(subID)); - objects.at(ID)->objects.erase(subID); -} - -std::vector CObjectClassesHandler::getDefaultAllowed() const -{ - return std::vector(); //TODO? -} - -TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) const -{ - if (objects.count(type)) - { - if (objects.at(type)->objects.count(subtype)) - return objects.at(type)->objects.at(subtype); - } - logGlobal->errorStream() << "Failed to find object of type " << type << ":" << subtype; - assert(0); // FIXME: throw error? - return nullptr; -} - -void CObjectClassesHandler::beforeValidate(JsonNode & object) -{ - for (auto & entry : object["types"].Struct()) - { - JsonUtils::inherit(entry.second, object["base"]); - for (auto & templ : entry.second["templates"].Struct()) - { - JsonUtils::inherit(templ.second, entry.second["base"]); - } - } -} - -void CObjectClassesHandler::afterLoadFinalization() -{ - legacyTemplates.clear(); // whatever left there is no longer needed - for (auto entry : objects) - { - for (auto obj : entry.second->objects) - { - if (obj.second->getTemplates().empty()) - logGlobal->warnStream() << "No templates found for " << entry.first << ":" << obj.first; - } - } -} - -std::string CObjectClassesHandler::getObjectName(si32 type) const -{ - assert(objects.count(type)); - return objects.at(type)->name; -} - -void AObjectTypeHandler::setType(si32 type, si32 subtype) -{ - this->type = type; - this->subtype = subtype; -} - -void AObjectTypeHandler::init(const JsonNode & input) -{ - base = input["base"]; - for (auto entry : input["templates"].Struct()) - { - entry.second.setType(JsonNode::DATA_STRUCT); - JsonUtils::inherit(entry.second, base); - - ObjectTemplate tmpl; - tmpl.id = Obj(type); - tmpl.subid = subtype; - tmpl.stringID = entry.first; // FIXME: create "fullID" - type.object.template? - tmpl.readJson(entry.second); - templates.push_back(tmpl); - } -} - -bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const -{ - return true; // by default - accept all. -} - -void AObjectTypeHandler::addTemplate(ObjectTemplate templ) -{ - templ.id = Obj(type); - templ.subid = subtype; - templates.push_back(templ); -} - -void AObjectTypeHandler::addTemplate(JsonNode config) -{ - config.setType(JsonNode::DATA_STRUCT); // ensure that input is not null - JsonUtils::inherit(config, base); - ObjectTemplate tmpl; - tmpl.id = Obj(type); - tmpl.subid = subtype; - tmpl.stringID = ""; // TODO? - tmpl.readJson(config); - addTemplate(tmpl); -} - -std::vector AObjectTypeHandler::getTemplates() const -{ - return templates; -} - -std::vector AObjectTypeHandler::getTemplates(si32 terrainType) const// FIXME: replace with ETerrainType -{ - std::vector ret = getTemplates(); - std::vector filtered; - - std::copy_if(ret.begin(), ret.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj) - { - return obj.canBePlacedAt(ETerrainType(terrainType)); - }); - // it is possible that there are no templates usable on specific terrain. In this case - return list before filtering - return filtered.empty() ? ret : filtered; -} - -boost::optional AObjectTypeHandler::getOverride(si32 terrainType, const CGObjectInstance * object) const -{ - std::vector ret = getTemplates(terrainType); - for (auto & tmpl : ret) - { - if (objectFilter(object, tmpl)) - return tmpl; - } - return boost::optional(); -} +#include "StdInc.h" +#include "CObjectClassesHandler.h" + +#include "filesystem/Filesystem.h" +#include "filesystem/CBinaryReader.h" +#include "../lib/VCMI_Lib.h" +#include "GameConstants.h" +#include "StringConstants.h" +#include "CGeneralTextHandler.h" +#include "CModHandler.h" +#include "JsonNode.h" + +#include "CRewardableConstructor.h" +#include "MapObjects.h" + +/* + * CObjectClassesHandler.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 + * + */ + +CObjectClassesHandler::CObjectClassesHandler() +{ +#define SET_HANDLER_CLASS(STRING, CLASSNAME) handlerConstructors[STRING] = std::make_shared; +#define SET_HANDLER(STRING, TYPENAME) handlerConstructors[STRING] = std::make_shared > + + // list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code + //WARNING: should be in sync with registerTypesMapObjectTypes function + SET_HANDLER_CLASS("configurable", CRewardableConstructor); + + SET_HANDLER("", CGObjectInstance); + SET_HANDLER("generic", CGObjectInstance); + + SET_HANDLER("market", CGMarket); + SET_HANDLER("bank", CBank); + SET_HANDLER("cartographer", CCartographer); + SET_HANDLER("artifact", CGArtifact); + SET_HANDLER("blackMarket", CGBlackMarket); + SET_HANDLER("boat", CGBoat); + SET_HANDLER("bonusingObject", CGBonusingObject); + SET_HANDLER("borderGate", CGBorderGate); + SET_HANDLER("borderGuard", CGBorderGuard); + SET_HANDLER("monster", CGCreature); + SET_HANDLER("denOfThieves", CGDenOfthieves); + SET_HANDLER("dwelling", CGDwelling); + SET_HANDLER("event", CGEvent); + SET_HANDLER("garrison", CGGarrison); + SET_HANDLER("hero", CGHeroInstance); + SET_HANDLER("heroPlaceholder", CGHeroPlaceholder); + SET_HANDLER("keymaster", CGKeymasterTent); + SET_HANDLER("lighthouse", CGLighthouse); + SET_HANDLER("magi", CGMagi); + SET_HANDLER("magicSpring", CGMagicSpring); + SET_HANDLER("magicWell", CGMagicWell); + SET_HANDLER("market", CGMarket); + SET_HANDLER("mine", CGMine); + SET_HANDLER("obelisk", CGObelisk); + SET_HANDLER("observatory", CGObservatory); + SET_HANDLER("onceVisitable", CGOnceVisitable); + SET_HANDLER("pandora", CGPandoraBox); + SET_HANDLER("pickable", CGPickable); + SET_HANDLER("pyramid", CGPyramid); + SET_HANDLER("questGuard", CGQuestGuard); + SET_HANDLER("resource", CGResource); + SET_HANDLER("scholar", CGScholar); + SET_HANDLER("seerHut", CGSeerHut); + SET_HANDLER("shipyard", CGShipyard); + SET_HANDLER("shrine", CGShrine); + SET_HANDLER("sign", CGSignBottle); + SET_HANDLER("siren", CGSirens); + SET_HANDLER("teleport", CGTeleport); + SET_HANDLER("town", CGTownInstance); + SET_HANDLER("university", CGUniversity); + SET_HANDLER("oncePerHero", CGVisitableOPH); + SET_HANDLER("oncePerWeek", CGVisitableOPW); + SET_HANDLER("witch", CGWitchHut); + +#undef SET_HANDLER_CLASS +#undef SET_HANDLER +} + +template +void readTextFile(Container & objects, std::string path) +{ + CLegacyConfigParser parser(path); + size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else + parser.endLine(); + + for (size_t i=0; i CObjectClassesHandler::loadLegacyData(size_t dataSize) +{ + readTextFile(legacyTemplates, "Data/Objects.txt"); + readTextFile(legacyTemplates, "Data/Heroes.txt"); + + std::vector ret(dataSize);// create storage for 256 objects + assert(dataSize == 256); + + CLegacyConfigParser parser("Data/ObjNames.txt"); + for (size_t i=0; i<256; i++) + { + ret[i]["name"].String() = parser.readString(); + parser.endLine(); + } + return ret; +} + +/// selects preferred ID (or subID) for new object +template +si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 defaultID) +{ + if (!fixedID.isNull() && fixedID.Float() < defaultID) + return fixedID.Float(); // H3M object with fixed ID + + if (map.empty()) + return defaultID; // no objects loaded, keep gap for H3M objects + if (map.rbegin()->first > defaultID) + return map.rbegin()->first + 1; // some modded objects loaded, return next available + + return defaultID; // some H3M objects loaded, first modded found +} + +void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContainter * obj) +{ + auto handler = handlerConstructors.at(obj->handlerName)(); + handler->init(entry); + + si32 id = selectNextID(entry["index"], obj->objects, 1000); + handler->setType(obj->id, id); + + if (handler->getTemplates().empty()) + { + auto range = legacyTemplates.equal_range(std::make_pair(obj->id, id)); + for (auto & templ : boost::make_iterator_range(range.first, range.second)) + { + handler->addTemplate(templ.second); + } + legacyTemplates.erase(range.first, range.second); + } + + obj->objects[id] = handler; +} + +CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json) +{ + auto obj = new ObjectContainter(); + obj->name = json["name"].String(); + obj->handlerName = json["handler"].String(); + obj->base = json["base"]; // FIXME: when this data will be actually merged? + obj->id = selectNextID(json["index"], objects, 256); + for (auto entry : json["types"].Struct()) + { + loadObjectEntry(entry.second, obj); + } + return obj; +} + +void CObjectClassesHandler::loadObject(std::string scope, std::string name, const JsonNode & data) +{ + auto object = loadFromJson(data); + 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); + + 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::createObject(std::string name, 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(config["index"].isNull()); + config["index"].Float() = subID.get(); + } + + JsonUtils::inherit(config, objects.at(ID)->base); + + loadObjectEntry(config, objects[ID]); +} + +void CObjectClassesHandler::eraseObject(si32 ID, si32 subID) +{ + assert(objects.count(ID)); + assert(objects.at(ID)->objects.count(subID)); + objects.at(ID)->objects.erase(subID); +} + +std::vector CObjectClassesHandler::getDefaultAllowed() const +{ + return std::vector(); //TODO? +} + +TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) const +{ + if (objects.count(type)) + { + if (objects.at(type)->objects.count(subtype)) + return objects.at(type)->objects.at(subtype); + } + logGlobal->errorStream() << "Failed to find object of type " << type << ":" << subtype; + assert(0); // FIXME: throw error? + return nullptr; +} + +void CObjectClassesHandler::beforeValidate(JsonNode & object) +{ + for (auto & entry : object["types"].Struct()) + { + JsonUtils::inherit(entry.second, object["base"]); + for (auto & templ : entry.second["templates"].Struct()) + { + JsonUtils::inherit(templ.second, entry.second["base"]); + } + } +} + +void CObjectClassesHandler::afterLoadFinalization() +{ + legacyTemplates.clear(); // whatever left there is no longer needed + for (auto entry : objects) + { + for (auto obj : entry.second->objects) + { + if (obj.second->getTemplates().empty()) + logGlobal->warnStream() << "No templates found for " << entry.first << ":" << obj.first; + } + } +} + +std::string CObjectClassesHandler::getObjectName(si32 type) const +{ + assert(objects.count(type)); + return objects.at(type)->name; +} + +void AObjectTypeHandler::setType(si32 type, si32 subtype) +{ + this->type = type; + this->subtype = subtype; +} + +void AObjectTypeHandler::init(const JsonNode & input) +{ + base = input["base"]; + for (auto entry : input["templates"].Struct()) + { + entry.second.setType(JsonNode::DATA_STRUCT); + JsonUtils::inherit(entry.second, base); + + ObjectTemplate tmpl; + tmpl.id = Obj(type); + tmpl.subid = subtype; + tmpl.stringID = entry.first; // FIXME: create "fullID" - type.object.template? + tmpl.readJson(entry.second); + templates.push_back(tmpl); + } +} + +bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const +{ + return true; // by default - accept all. +} + +void AObjectTypeHandler::addTemplate(ObjectTemplate templ) +{ + templ.id = Obj(type); + templ.subid = subtype; + templates.push_back(templ); +} + +void AObjectTypeHandler::addTemplate(JsonNode config) +{ + config.setType(JsonNode::DATA_STRUCT); // ensure that input is not null + JsonUtils::inherit(config, base); + ObjectTemplate tmpl; + tmpl.id = Obj(type); + tmpl.subid = subtype; + tmpl.stringID = ""; // TODO? + tmpl.readJson(config); + addTemplate(tmpl); +} + +std::vector AObjectTypeHandler::getTemplates() const +{ + return templates; +} + +std::vector AObjectTypeHandler::getTemplates(si32 terrainType) const// FIXME: replace with ETerrainType +{ + std::vector ret = getTemplates(); + std::vector filtered; + + std::copy_if(ret.begin(), ret.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj) + { + return obj.canBePlacedAt(ETerrainType(terrainType)); + }); + // it is possible that there are no templates usable on specific terrain. In this case - return list before filtering + return filtered.empty() ? ret : filtered; +} + +boost::optional AObjectTypeHandler::getOverride(si32 terrainType, const CGObjectInstance * object) const +{ + std::vector ret = getTemplates(terrainType); + for (auto & tmpl : ret) + { + if (objectFilter(object, tmpl)) + return tmpl; + } + return boost::optional(); +} diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 17b072321..9a750e71f 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -1,174 +1,174 @@ -#pragma once - -#include "ObjectTemplate.h" - -#include "../GameConstants.h" -#include "../ConstTransitivePtr.h" -#include "../IHandlerBase.h" - -/* - * CObjectClassesHandler.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 JsonNode; -class CRandomGenerator; - -class IObjectInfo -{ -public: - virtual bool givesResources() const = 0; - - virtual bool givesExperience() const = 0; - virtual bool givesMana() const = 0; - virtual bool givesMovement() const = 0; - - virtual bool givesPrimarySkills() const = 0; - virtual bool givesSecondarySkills() const = 0; - - virtual bool givesArtifacts() const = 0; - virtual bool givesCreatures() const = 0; - virtual bool givesSpells() const = 0; - - virtual bool givesBonuses() const = 0; -}; - -class CGObjectInstance; - -class AObjectTypeHandler -{ - si32 type; - si32 subtype; - - JsonNode base; /// describes base template - - std::vector templates; -protected: - - virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; -public: - virtual ~AObjectTypeHandler(){} - - void setType(si32 type, si32 subtype); - - /// loads templates from Json structure using fields "base" and "templates" - virtual void init(const JsonNode & input); - - void addTemplate(ObjectTemplate templ); - void addTemplate(JsonNode config); - - /// returns all templates, without any filters - std::vector getTemplates() const; - - /// returns all templates that can be placed on specific terrain type - std::vector getTemplates(si32 terrainType) const; - - /// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle) - /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server) - boost::optional getOverride(si32 terrainType, const CGObjectInstance * object) const; - - /// Creates object and set up core properties (like ID/subID). Object is NOT initialized - /// to allow creating objects before game start (e.g. map loading) - virtual CGObjectInstance * create(ObjectTemplate tmpl) const = 0; - - /// Configures object properties. Should be re-entrable, resetting state of the object if necessarily - virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0; - - /// Returns object configuration, if available. Othervice returns NULL - virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const = 0; - - template void serialize(Handler &h, const int version) - { - h & type & subtype & templates; - } -}; - -/// Class that is used for objects that do not have dedicated handler -template -class CDefaultObjectTypeHandler : public AObjectTypeHandler -{ - CGObjectInstance * create(ObjectTemplate tmpl) const - { - auto obj = new ObjectType(); - obj->ID = tmpl.id; - obj->subID = tmpl.subid; - obj->appearance = tmpl; - return obj; - } - - virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const - { - } - - virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const - { - return nullptr; - } -}; - -typedef std::shared_ptr TObjectTypeHandler; - -class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase -{ - /// Small internal structure that contains information on specific group of objects - /// (creating separate entity is overcomplicating at least at this point) - struct ObjectContainter - { - si32 id; - - std::string name; // human-readable name - std::string handlerName; // ID of handler that controls this object, shoul be determined using hadlerConstructor map - - JsonNode base; - std::map objects; - - template void serialize(Handler &h, const int version) - { - h & base & objects; - } - }; - - typedef std::multimap, ObjectTemplate> TTemplatesContainer; - - /// list of object handlers, each of them handles only one type - std::map objects; - - /// map that is filled during contruction with all known handlers. Not serializeable - std::map > handlerConstructors; - - /// container with H3 templates, used only during loading - TTemplatesContainer legacyTemplates; - - void loadObjectEntry(const JsonNode & entry, ObjectContainter * obj); - ObjectContainter * loadFromJson(const JsonNode & json); -public: - CObjectClassesHandler(); - - std::vector loadLegacyData(size_t dataSize) override; - - 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 createObject(std::string name, JsonNode config, si32 ID, boost::optional subID = boost::optional()); - void eraseObject(si32 ID, si32 subID); - - void beforeValidate(JsonNode & object) override; - void afterLoadFinalization() override; - - std::vector getDefaultAllowed() const override; - - /// returns handler for specified object (ID-based). ObjectHandler keeps ownership - TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const; - - std::string getObjectName(si32 type) const; - - template void serialize(Handler &h, const int version) - { - h & objects; - } -}; +#pragma once + +#include "ObjectTemplate.h" + +#include "../GameConstants.h" +#include "../ConstTransitivePtr.h" +#include "../IHandlerBase.h" + +/* + * CObjectClassesHandler.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 JsonNode; +class CRandomGenerator; + +class IObjectInfo +{ +public: + virtual bool givesResources() const = 0; + + virtual bool givesExperience() const = 0; + virtual bool givesMana() const = 0; + virtual bool givesMovement() const = 0; + + virtual bool givesPrimarySkills() const = 0; + virtual bool givesSecondarySkills() const = 0; + + virtual bool givesArtifacts() const = 0; + virtual bool givesCreatures() const = 0; + virtual bool givesSpells() const = 0; + + virtual bool givesBonuses() const = 0; +}; + +class CGObjectInstance; + +class AObjectTypeHandler +{ + si32 type; + si32 subtype; + + JsonNode base; /// describes base template + + std::vector templates; +protected: + + virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; +public: + virtual ~AObjectTypeHandler(){} + + void setType(si32 type, si32 subtype); + + /// loads templates from Json structure using fields "base" and "templates" + virtual void init(const JsonNode & input); + + void addTemplate(ObjectTemplate templ); + void addTemplate(JsonNode config); + + /// returns all templates, without any filters + std::vector getTemplates() const; + + /// returns all templates that can be placed on specific terrain type + std::vector getTemplates(si32 terrainType) const; + + /// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle) + /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server) + boost::optional getOverride(si32 terrainType, const CGObjectInstance * object) const; + + /// Creates object and set up core properties (like ID/subID). Object is NOT initialized + /// to allow creating objects before game start (e.g. map loading) + virtual CGObjectInstance * create(ObjectTemplate tmpl) const = 0; + + /// Configures object properties. Should be re-entrable, resetting state of the object if necessarily + virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0; + + /// Returns object configuration, if available. Othervice returns NULL + virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const = 0; + + template void serialize(Handler &h, const int version) + { + h & type & subtype & templates; + } +}; + +/// Class that is used for objects that do not have dedicated handler +template +class CDefaultObjectTypeHandler : public AObjectTypeHandler +{ + CGObjectInstance * create(ObjectTemplate tmpl) const + { + auto obj = new ObjectType(); + obj->ID = tmpl.id; + obj->subID = tmpl.subid; + obj->appearance = tmpl; + return obj; + } + + virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const + { + } + + virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const + { + return nullptr; + } +}; + +typedef std::shared_ptr TObjectTypeHandler; + +class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase +{ + /// Small internal structure that contains information on specific group of objects + /// (creating separate entity is overcomplicating at least at this point) + struct ObjectContainter + { + si32 id; + + std::string name; // human-readable name + std::string handlerName; // ID of handler that controls this object, shoul be determined using hadlerConstructor map + + JsonNode base; + std::map objects; + + template void serialize(Handler &h, const int version) + { + h & base & objects; + } + }; + + typedef std::multimap, ObjectTemplate> TTemplatesContainer; + + /// list of object handlers, each of them handles only one type + std::map objects; + + /// map that is filled during contruction with all known handlers. Not serializeable + std::map > handlerConstructors; + + /// container with H3 templates, used only during loading + TTemplatesContainer legacyTemplates; + + void loadObjectEntry(const JsonNode & entry, ObjectContainter * obj); + ObjectContainter * loadFromJson(const JsonNode & json); +public: + CObjectClassesHandler(); + + std::vector loadLegacyData(size_t dataSize) override; + + 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 createObject(std::string name, JsonNode config, si32 ID, boost::optional subID = boost::optional()); + void eraseObject(si32 ID, si32 subID); + + void beforeValidate(JsonNode & object) override; + void afterLoadFinalization() override; + + std::vector getDefaultAllowed() const override; + + /// returns handler for specified object (ID-based). ObjectHandler keeps ownership + TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const; + + std::string getObjectName(si32 type) const; + + template void serialize(Handler &h, const int version) + { + h & objects; + } +}; diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index bd05c0bda..b1289ebfd 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -1,606 +1,606 @@ -/* - * CObjectHandler.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 "CObjectHandler.h" - -#include "NetPacks.h" -#include "CGeneralTextHandler.h" -#include "CHeroHandler.h" -#include "../client/CSoundBase.h" - -#include "CObjectClassesHandler.h" - -using namespace boost::assign; - -IGameCallback * IObjectInterface::cb = nullptr; - -///helpers -static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1) -{ - OpenWindow ow; - ow.window = type; - ow.id1 = id1; - ow.id2 = id2; - IObjectInterface::cb->sendAndApply(&ow); -} - -static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) -{ - InfoWindow iw; - iw.soundID = soundID; - iw.player = playerID; - iw.text.addTxt(MetaString::ADVOB_TXT,txtID); - IObjectInterface::cb->sendAndApply(&iw); -} - -/*static void showInfoDialog(const ObjectInstanceID heroID, const ui32 txtID, const ui16 soundID) -{ - const PlayerColor playerID = IObjectInterface::cb->getOwner(heroID); - showInfoDialog(playerID,txtID,soundID); -}*/ - -static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) -{ - const PlayerColor playerID = h->getOwner(); - showInfoDialog(playerID,txtID,soundID); -} - -static std::string & visitedTxt(const bool visited) -{ - int id = visited ? 352 : 353; - return VLC->generaltexth->allTexts[id]; -} - -///IObjectInterface -void IObjectInterface::onHeroVisit(const CGHeroInstance * h) const -{} - -void IObjectInterface::onHeroLeave(const CGHeroInstance * h) const -{} - -void IObjectInterface::newTurn () const -{} - -IObjectInterface::~IObjectInterface() -{} - -IObjectInterface::IObjectInterface() -{} - -void IObjectInterface::initObj() -{} - -void IObjectInterface::setProperty( ui8 what, ui32 val ) -{} - -bool IObjectInterface::wasVisited (PlayerColor player) const -{ - return false; -} -bool IObjectInterface::wasVisited (const CGHeroInstance * h) const -{ - return false; -} - -void IObjectInterface::postInit() -{} - -void IObjectInterface::preInit() -{} - -void IObjectInterface::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{} - -void IObjectInterface::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const -{} - -void IObjectInterface::garrisonDialogClosed(const CGHeroInstance *hero) const -{} - -void IObjectInterface::heroLevelUpDone(const CGHeroInstance *hero) const -{} - -// Bank helper. Find the creature ID and their number, and store the -// result in storage (either guards or reward creatures). -static void readCreatures(const JsonNode &creature, std::vector< std::pair > &storage) -{ - std::pair creInfo = std::make_pair(CreatureID::NONE, 0); - - //TODO: replace numeric id's with mod-friendly string id's - creInfo.second = creature["number"].Float(); - creInfo.first = CreatureID((si32)creature["id"].Float()); - storage.push_back(creInfo); -} - -// Bank helper. Process a bank level. -static void readBankLevel(const JsonNode &level, BankConfig &bc) -{ - int idx; - - bc.chance = level["chance"].Float(); - - for(const JsonNode &creature : level["guards"].Vector()) - { - readCreatures(creature, bc.guards); - } - - bc.upgradeChance = level["upgrade_chance"].Float(); - bc.combatValue = level["combat_value"].Float(); - - bc.resources = Res::ResourceSet(level["reward_resources"]); - - for(const JsonNode &creature : level["reward_creatures"].Vector()) - { - readCreatures(creature, bc.creatures); - } - - bc.artifacts.resize(4); - idx = 0; - for(const JsonNode &artifact : level["reward_artifacts"].Vector()) - { - bc.artifacts[idx] = artifact.Float(); - idx ++; - } - - bc.value = level["value"].Float(); - bc.rewardDifficulty = level["profitability"].Float(); - bc.easiest = level["easiest"].Float(); -} - -CObjectHandler::CObjectHandler() -{ - logGlobal->traceStream() << "\t\tReading cregens "; - - const JsonNode config(ResourceID("config/dwellings.json")); - for(const JsonNode &dwelling : config["dwellings"].Vector()) - { - cregens[dwelling["dwelling"].Float()] = CreatureID((si32)dwelling["creature"].Float()); - } - logGlobal->traceStream() << "\t\tDone loading cregens!"; - - logGlobal->traceStream() << "\t\tReading resources prices "; - const JsonNode config2(ResourceID("config/resources.json")); - for(const JsonNode &price : config2["resources_prices"].Vector()) - { - resVals.push_back(price.Float()); - } - logGlobal->traceStream() << "\t\tDone loading resource prices!"; - - logGlobal->traceStream() << "\t\tReading banks configs"; - const JsonNode config3(ResourceID("config/bankconfig.json")); - int bank_num = 0; - for(const JsonNode &bank : config3["banks"].Vector()) - { - creBanksNames[bank_num] = bank["name"].String(); - - int level_num = 0; - for(const JsonNode &level : bank["levels"].Vector()) - { - banksInfo[bank_num].push_back(new BankConfig); - BankConfig &bc = *banksInfo[bank_num].back(); - bc.level = level_num; - - readBankLevel(level, bc); - level_num ++; - } - - bank_num ++; - } - logGlobal->traceStream() << "\t\tDone loading banks configs"; -} - -CObjectHandler::~CObjectHandler() -{ - for(auto & mapEntry : banksInfo) - { - for(auto & vecEntry : mapEntry.second) - { - vecEntry.dellNull(); - } - } -} - -int CObjectHandler::bankObjToIndex (const CGObjectInstance * obj) -{ - switch (obj->ID) //find appriopriate key - { - case Obj::CREATURE_BANK: - return obj->subID; - case Obj::DERELICT_SHIP: - return 8; - case Obj::DRAGON_UTOPIA: - return 10; - case Obj::CRYPT: - return 9; - case Obj::SHIPWRECK: - return 7; - case Obj::PYRAMID: - return 21; - default: - logGlobal->warnStream() << "Unrecognized Bank indetifier!"; - return 0; - } -} -PlayerColor CGObjectInstance::getOwner() const -{ - //if (state) - // return state->owner; - //else - return tempOwner; //won't have owner -} - -CGObjectInstance::CGObjectInstance(): - pos(-1,-1,-1), - ID(Obj::NO_OBJ), - subID(-1), - tempOwner(PlayerColor::UNFLAGGABLE), - blockVisit(false) -{ -} -CGObjectInstance::~CGObjectInstance() -{ - //if (state) - // delete state; - //state=nullptr; -} - -const std::string & CGObjectInstance::getHoverText() const -{ - return hoverName; -} -void CGObjectInstance::setOwner(PlayerColor ow) -{ - //if (state) - // state->owner = ow; - //else - tempOwner = ow; -} -int CGObjectInstance::getWidth() const//returns width of object graphic in tiles -{ - return appearance.getWidth(); -} -int CGObjectInstance::getHeight() const //returns height of object graphic in tiles -{ - return appearance.getHeight(); -} -bool CGObjectInstance::visitableAt(int x, int y) const //returns true if object is visitable at location (x, y) form left top tile of image (x, y in tiles) -{ - return appearance.isVisitableAt(pos.x - x, pos.y - y); -} -bool CGObjectInstance::blockingAt(int x, int y) const -{ - return appearance.isBlockedAt(pos.x - x, pos.y - y); -} - -bool CGObjectInstance::coveringAt(int x, int y) const -{ - return appearance.isVisibleAt(pos.x - x, pos.y - y); -} - -std::set CGObjectInstance::getBlockedPos() const -{ - std::set ret; - for(int w=0; w CGObjectInstance::getBlockedOffsets() const -{ - std::set ret; - for(int w=0; w cmp.appearance.printPriority; - - if(pos.y != cmp.pos.y) - return pos.y < cmp.pos.y; - - if(cmp.ID==Obj::HERO && ID!=Obj::HERO) - return true; - if(cmp.ID!=Obj::HERO && ID==Obj::HERO) - return false; - - if(!isVisitable() && cmp.isVisitable()) - return true; - if(!cmp.isVisitable() && isVisitable()) - return false; - if(this->pos.xgameState()->map->getTile(visitablePos()); - - this->ID = Obj(ID); - this->subID = subID; - this->appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(tile.terType).front(); - - //recalculate blockvis tiles - new appearance might have different blockmap than before - cb->gameState()->map->removeBlockVisTiles(this, true); - cb->gameState()->map->addBlockVisTiles(this); -} - -void CGObjectInstance::initObj() -{ - switch(ID) - { - case Obj::TAVERN: - blockVisit = true; - break; - } -} - -void CGObjectInstance::setProperty( ui8 what, ui32 val ) -{ - switch(what) - { - case ObjProperty::OWNER: - tempOwner = PlayerColor(val); - break; - case ObjProperty::BLOCKVIS: - blockVisit = val; - break; - case ObjProperty::ID: - ID = Obj(val); - break; - case ObjProperty::SUBID: - subID = val; - break; - } - setPropertyDer(what, val); -} - -void CGObjectInstance::setPropertyDer( ui8 what, ui32 val ) -{} - -int3 CGObjectInstance::getSightCenter() const -{ - //return vistiable tile if possible - for(int i=0; i < 8; i++) - for(int j=0; j < 6; j++) - if(visitableAt(i,j)) - return(pos + int3(i-7, j-5, 0)); - return pos; -} - -int CGObjectInstance::getSightRadious() const -{ - return 3; -} -void CGObjectInstance::getSightTiles(std::unordered_set &tiles) const //returns reference to the set -{ - cb->getTilesInRange(tiles, getSightCenter(), getSightRadious(), tempOwner, 1); -} -void CGObjectInstance::hideTiles(PlayerColor ourplayer, int radius) const -{ - for (auto i = cb->gameState()->teams.begin(); i != cb->gameState()->teams.end(); i++) - { - if ( !vstd::contains(i->second.players, ourplayer ))//another team - { - for (auto & elem : i->second.players) - if ( cb->getPlayer(elem)->status == EPlayerStatus::INGAME )//seek for living player (if any) - { - FoWChange fw; - fw.mode = 0; - fw.player = elem; - cb->getTilesInRange (fw.tiles, pos, radius, (elem), -1); - cb->sendAndApply (&fw); - break; - } - } - } -} -int3 CGObjectInstance::getVisitableOffset() const -{ - for(int y = 0; y < appearance.getHeight(); y++) - for (int x = 0; x < appearance.getWidth(); x++) - if (appearance.isVisitableAt(x, y)) - return int3(x,y,0); - - logGlobal->warnStream() << "Warning: getVisitableOffset called on non-visitable obj!"; - return int3(0,0,0); -} - -void CGObjectInstance::getNameVis( std::string &hname ) const -{ - const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); - hname = VLC->objtypeh->getObjectName(ID); - if(h) - { - const bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); - hname + " " + visitedTxt(visited); - } -} - -void CGObjectInstance::giveDummyBonus(ObjectInstanceID heroID, ui8 duration) const -{ - GiveBonus gbonus; - gbonus.bonus.type = Bonus::NONE; - gbonus.id = heroID.getNum(); - gbonus.bonus.duration = duration; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - cb->giveHeroBonus(&gbonus); -} - -void CGObjectInstance::onHeroVisit( const CGHeroInstance * h ) const -{ - switch(ID) - { - case Obj::HILL_FORT: - { - openWindow(OpenWindow::HILL_FORT_WINDOW,id.getNum(),h->id.getNum()); - } - break; - case Obj::SANCTUARY: - { - //You enter the sanctuary and immediately feel as if a great weight has been lifted off your shoulders. You feel safe here. - showInfoDialog(h,114,soundBase::GETPROTECTION); - } - break; - case Obj::TAVERN: - { - openWindow(OpenWindow::TAVERN_WINDOW,h->id.getNum(),id.getNum()); - } - break; - } -} - -ui8 CGObjectInstance::getPassableness() const -{ - return 0; -} - -int3 CGObjectInstance::visitablePos() const -{ - return pos - getVisitableOffset(); -} - -bool CGObjectInstance::isVisitable() const -{ - return appearance.isVisitable(); -} - -bool CGObjectInstance::passableFor(PlayerColor color) const -{ - return getPassableness() & 1<obj->subID == obj->subID; -} - -int3 IBoatGenerator::bestLocation() const -{ - std::vector offsets; - getOutOffsets(offsets); - - for (auto & offset : offsets) - { - if (const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map - { - if (tile->terType == ETerrainType::WATER && (!tile->blocked || tile->blockingObjects.front()->ID == 8)) //and is water and is not blocked or is blocked by boat - return o->pos + offset; - } - } - return int3 (-1,-1,-1); -} - -IBoatGenerator::EGeneratorState IBoatGenerator::shipyardStatus() const -{ - int3 tile = bestLocation(); - const TerrainTile *t = IObjectInterface::cb->getTile(tile); - if(!t) - return TILE_BLOCKED; //no available water - else if(!t->blockingObjects.size()) - return GOOD; //OK - else if(t->blockingObjects.front()->ID == Obj::BOAT) - return BOAT_ALREADY_BUILT; //blocked with boat - else - return TILE_BLOCKED; //blocked -} - -int IBoatGenerator::getBoatType() const -{ - //We make good ships by default - return 1; -} - - -IBoatGenerator::IBoatGenerator(const CGObjectInstance *O) -: o(O) -{ -} - -void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visitor) const -{ - switch(shipyardStatus()) - { - case BOAT_ALREADY_BUILT: - out.addTxt(MetaString::GENERAL_TXT, 51); - break; - case TILE_BLOCKED: - if(visitor) - { - out.addTxt(MetaString::GENERAL_TXT, 134); - out.addReplacement(visitor->name); - } - else - out.addTxt(MetaString::ADVOB_TXT, 189); - break; - case NO_WATER: - logGlobal->errorStream() << "Shipyard without water!!! " << o->pos << "\t" << o->id; - return; - } -} - -void IShipyard::getBoatCost( std::vector &cost ) const -{ - cost.resize(GameConstants::RESOURCE_QUANTITY); - cost[Res::WOOD] = 10; - cost[Res::GOLD] = 1000; -} - -IShipyard::IShipyard(const CGObjectInstance *O) - : IBoatGenerator(O) -{ -} - -IShipyard * IShipyard::castFrom( CGObjectInstance *obj ) -{ - if(!obj) - return nullptr; - - if(obj->ID == Obj::TOWN) - { - return static_cast(obj); - } - else if(obj->ID == Obj::SHIPYARD) - { - return static_cast(obj); - } - else - { - return nullptr; - } -} - -const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj ) -{ - return castFrom(const_cast(obj)); -} +/* + * CObjectHandler.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 "CObjectHandler.h" + +#include "NetPacks.h" +#include "CGeneralTextHandler.h" +#include "CHeroHandler.h" +#include "../client/CSoundBase.h" + +#include "CObjectClassesHandler.h" + +using namespace boost::assign; + +IGameCallback * IObjectInterface::cb = nullptr; + +///helpers +static void openWindow(const OpenWindow::EWindow type, const int id1, const int id2 = -1) +{ + OpenWindow ow; + ow.window = type; + ow.id1 = id1; + ow.id2 = id2; + IObjectInterface::cb->sendAndApply(&ow); +} + +static void showInfoDialog(const PlayerColor playerID, const ui32 txtID, const ui16 soundID) +{ + InfoWindow iw; + iw.soundID = soundID; + iw.player = playerID; + iw.text.addTxt(MetaString::ADVOB_TXT,txtID); + IObjectInterface::cb->sendAndApply(&iw); +} + +/*static void showInfoDialog(const ObjectInstanceID heroID, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = IObjectInterface::cb->getOwner(heroID); + showInfoDialog(playerID,txtID,soundID); +}*/ + +static void showInfoDialog(const CGHeroInstance* h, const ui32 txtID, const ui16 soundID) +{ + const PlayerColor playerID = h->getOwner(); + showInfoDialog(playerID,txtID,soundID); +} + +static std::string & visitedTxt(const bool visited) +{ + int id = visited ? 352 : 353; + return VLC->generaltexth->allTexts[id]; +} + +///IObjectInterface +void IObjectInterface::onHeroVisit(const CGHeroInstance * h) const +{} + +void IObjectInterface::onHeroLeave(const CGHeroInstance * h) const +{} + +void IObjectInterface::newTurn () const +{} + +IObjectInterface::~IObjectInterface() +{} + +IObjectInterface::IObjectInterface() +{} + +void IObjectInterface::initObj() +{} + +void IObjectInterface::setProperty( ui8 what, ui32 val ) +{} + +bool IObjectInterface::wasVisited (PlayerColor player) const +{ + return false; +} +bool IObjectInterface::wasVisited (const CGHeroInstance * h) const +{ + return false; +} + +void IObjectInterface::postInit() +{} + +void IObjectInterface::preInit() +{} + +void IObjectInterface::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{} + +void IObjectInterface::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const +{} + +void IObjectInterface::garrisonDialogClosed(const CGHeroInstance *hero) const +{} + +void IObjectInterface::heroLevelUpDone(const CGHeroInstance *hero) const +{} + +// Bank helper. Find the creature ID and their number, and store the +// result in storage (either guards or reward creatures). +static void readCreatures(const JsonNode &creature, std::vector< std::pair > &storage) +{ + std::pair creInfo = std::make_pair(CreatureID::NONE, 0); + + //TODO: replace numeric id's with mod-friendly string id's + creInfo.second = creature["number"].Float(); + creInfo.first = CreatureID((si32)creature["id"].Float()); + storage.push_back(creInfo); +} + +// Bank helper. Process a bank level. +static void readBankLevel(const JsonNode &level, BankConfig &bc) +{ + int idx; + + bc.chance = level["chance"].Float(); + + for(const JsonNode &creature : level["guards"].Vector()) + { + readCreatures(creature, bc.guards); + } + + bc.upgradeChance = level["upgrade_chance"].Float(); + bc.combatValue = level["combat_value"].Float(); + + bc.resources = Res::ResourceSet(level["reward_resources"]); + + for(const JsonNode &creature : level["reward_creatures"].Vector()) + { + readCreatures(creature, bc.creatures); + } + + bc.artifacts.resize(4); + idx = 0; + for(const JsonNode &artifact : level["reward_artifacts"].Vector()) + { + bc.artifacts[idx] = artifact.Float(); + idx ++; + } + + bc.value = level["value"].Float(); + bc.rewardDifficulty = level["profitability"].Float(); + bc.easiest = level["easiest"].Float(); +} + +CObjectHandler::CObjectHandler() +{ + logGlobal->traceStream() << "\t\tReading cregens "; + + const JsonNode config(ResourceID("config/dwellings.json")); + for(const JsonNode &dwelling : config["dwellings"].Vector()) + { + cregens[dwelling["dwelling"].Float()] = CreatureID((si32)dwelling["creature"].Float()); + } + logGlobal->traceStream() << "\t\tDone loading cregens!"; + + logGlobal->traceStream() << "\t\tReading resources prices "; + const JsonNode config2(ResourceID("config/resources.json")); + for(const JsonNode &price : config2["resources_prices"].Vector()) + { + resVals.push_back(price.Float()); + } + logGlobal->traceStream() << "\t\tDone loading resource prices!"; + + logGlobal->traceStream() << "\t\tReading banks configs"; + const JsonNode config3(ResourceID("config/bankconfig.json")); + int bank_num = 0; + for(const JsonNode &bank : config3["banks"].Vector()) + { + creBanksNames[bank_num] = bank["name"].String(); + + int level_num = 0; + for(const JsonNode &level : bank["levels"].Vector()) + { + banksInfo[bank_num].push_back(new BankConfig); + BankConfig &bc = *banksInfo[bank_num].back(); + bc.level = level_num; + + readBankLevel(level, bc); + level_num ++; + } + + bank_num ++; + } + logGlobal->traceStream() << "\t\tDone loading banks configs"; +} + +CObjectHandler::~CObjectHandler() +{ + for(auto & mapEntry : banksInfo) + { + for(auto & vecEntry : mapEntry.second) + { + vecEntry.dellNull(); + } + } +} + +int CObjectHandler::bankObjToIndex (const CGObjectInstance * obj) +{ + switch (obj->ID) //find appriopriate key + { + case Obj::CREATURE_BANK: + return obj->subID; + case Obj::DERELICT_SHIP: + return 8; + case Obj::DRAGON_UTOPIA: + return 10; + case Obj::CRYPT: + return 9; + case Obj::SHIPWRECK: + return 7; + case Obj::PYRAMID: + return 21; + default: + logGlobal->warnStream() << "Unrecognized Bank indetifier!"; + return 0; + } +} +PlayerColor CGObjectInstance::getOwner() const +{ + //if (state) + // return state->owner; + //else + return tempOwner; //won't have owner +} + +CGObjectInstance::CGObjectInstance(): + pos(-1,-1,-1), + ID(Obj::NO_OBJ), + subID(-1), + tempOwner(PlayerColor::UNFLAGGABLE), + blockVisit(false) +{ +} +CGObjectInstance::~CGObjectInstance() +{ + //if (state) + // delete state; + //state=nullptr; +} + +const std::string & CGObjectInstance::getHoverText() const +{ + return hoverName; +} +void CGObjectInstance::setOwner(PlayerColor ow) +{ + //if (state) + // state->owner = ow; + //else + tempOwner = ow; +} +int CGObjectInstance::getWidth() const//returns width of object graphic in tiles +{ + return appearance.getWidth(); +} +int CGObjectInstance::getHeight() const //returns height of object graphic in tiles +{ + return appearance.getHeight(); +} +bool CGObjectInstance::visitableAt(int x, int y) const //returns true if object is visitable at location (x, y) form left top tile of image (x, y in tiles) +{ + return appearance.isVisitableAt(pos.x - x, pos.y - y); +} +bool CGObjectInstance::blockingAt(int x, int y) const +{ + return appearance.isBlockedAt(pos.x - x, pos.y - y); +} + +bool CGObjectInstance::coveringAt(int x, int y) const +{ + return appearance.isVisibleAt(pos.x - x, pos.y - y); +} + +std::set CGObjectInstance::getBlockedPos() const +{ + std::set ret; + for(int w=0; w CGObjectInstance::getBlockedOffsets() const +{ + std::set ret; + for(int w=0; w cmp.appearance.printPriority; + + if(pos.y != cmp.pos.y) + return pos.y < cmp.pos.y; + + if(cmp.ID==Obj::HERO && ID!=Obj::HERO) + return true; + if(cmp.ID!=Obj::HERO && ID==Obj::HERO) + return false; + + if(!isVisitable() && cmp.isVisitable()) + return true; + if(!cmp.isVisitable() && isVisitable()) + return false; + if(this->pos.xgameState()->map->getTile(visitablePos()); + + this->ID = Obj(ID); + this->subID = subID; + this->appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(tile.terType).front(); + + //recalculate blockvis tiles - new appearance might have different blockmap than before + cb->gameState()->map->removeBlockVisTiles(this, true); + cb->gameState()->map->addBlockVisTiles(this); +} + +void CGObjectInstance::initObj() +{ + switch(ID) + { + case Obj::TAVERN: + blockVisit = true; + break; + } +} + +void CGObjectInstance::setProperty( ui8 what, ui32 val ) +{ + switch(what) + { + case ObjProperty::OWNER: + tempOwner = PlayerColor(val); + break; + case ObjProperty::BLOCKVIS: + blockVisit = val; + break; + case ObjProperty::ID: + ID = Obj(val); + break; + case ObjProperty::SUBID: + subID = val; + break; + } + setPropertyDer(what, val); +} + +void CGObjectInstance::setPropertyDer( ui8 what, ui32 val ) +{} + +int3 CGObjectInstance::getSightCenter() const +{ + //return vistiable tile if possible + for(int i=0; i < 8; i++) + for(int j=0; j < 6; j++) + if(visitableAt(i,j)) + return(pos + int3(i-7, j-5, 0)); + return pos; +} + +int CGObjectInstance::getSightRadious() const +{ + return 3; +} +void CGObjectInstance::getSightTiles(std::unordered_set &tiles) const //returns reference to the set +{ + cb->getTilesInRange(tiles, getSightCenter(), getSightRadious(), tempOwner, 1); +} +void CGObjectInstance::hideTiles(PlayerColor ourplayer, int radius) const +{ + for (auto i = cb->gameState()->teams.begin(); i != cb->gameState()->teams.end(); i++) + { + if ( !vstd::contains(i->second.players, ourplayer ))//another team + { + for (auto & elem : i->second.players) + if ( cb->getPlayer(elem)->status == EPlayerStatus::INGAME )//seek for living player (if any) + { + FoWChange fw; + fw.mode = 0; + fw.player = elem; + cb->getTilesInRange (fw.tiles, pos, radius, (elem), -1); + cb->sendAndApply (&fw); + break; + } + } + } +} +int3 CGObjectInstance::getVisitableOffset() const +{ + for(int y = 0; y < appearance.getHeight(); y++) + for (int x = 0; x < appearance.getWidth(); x++) + if (appearance.isVisitableAt(x, y)) + return int3(x,y,0); + + logGlobal->warnStream() << "Warning: getVisitableOffset called on non-visitable obj!"; + return int3(0,0,0); +} + +void CGObjectInstance::getNameVis( std::string &hname ) const +{ + const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer()); + hname = VLC->objtypeh->getObjectName(ID); + if(h) + { + const bool visited = h->hasBonusFrom(Bonus::OBJECT,ID); + hname + " " + visitedTxt(visited); + } +} + +void CGObjectInstance::giveDummyBonus(ObjectInstanceID heroID, ui8 duration) const +{ + GiveBonus gbonus; + gbonus.bonus.type = Bonus::NONE; + gbonus.id = heroID.getNum(); + gbonus.bonus.duration = duration; + gbonus.bonus.source = Bonus::OBJECT; + gbonus.bonus.sid = ID; + cb->giveHeroBonus(&gbonus); +} + +void CGObjectInstance::onHeroVisit( const CGHeroInstance * h ) const +{ + switch(ID) + { + case Obj::HILL_FORT: + { + openWindow(OpenWindow::HILL_FORT_WINDOW,id.getNum(),h->id.getNum()); + } + break; + case Obj::SANCTUARY: + { + //You enter the sanctuary and immediately feel as if a great weight has been lifted off your shoulders. You feel safe here. + showInfoDialog(h,114,soundBase::GETPROTECTION); + } + break; + case Obj::TAVERN: + { + openWindow(OpenWindow::TAVERN_WINDOW,h->id.getNum(),id.getNum()); + } + break; + } +} + +ui8 CGObjectInstance::getPassableness() const +{ + return 0; +} + +int3 CGObjectInstance::visitablePos() const +{ + return pos - getVisitableOffset(); +} + +bool CGObjectInstance::isVisitable() const +{ + return appearance.isVisitable(); +} + +bool CGObjectInstance::passableFor(PlayerColor color) const +{ + return getPassableness() & 1<obj->subID == obj->subID; +} + +int3 IBoatGenerator::bestLocation() const +{ + std::vector offsets; + getOutOffsets(offsets); + + for (auto & offset : offsets) + { + if (const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offset, false)) //tile is in the map + { + if (tile->terType == ETerrainType::WATER && (!tile->blocked || tile->blockingObjects.front()->ID == 8)) //and is water and is not blocked or is blocked by boat + return o->pos + offset; + } + } + return int3 (-1,-1,-1); +} + +IBoatGenerator::EGeneratorState IBoatGenerator::shipyardStatus() const +{ + int3 tile = bestLocation(); + const TerrainTile *t = IObjectInterface::cb->getTile(tile); + if(!t) + return TILE_BLOCKED; //no available water + else if(!t->blockingObjects.size()) + return GOOD; //OK + else if(t->blockingObjects.front()->ID == Obj::BOAT) + return BOAT_ALREADY_BUILT; //blocked with boat + else + return TILE_BLOCKED; //blocked +} + +int IBoatGenerator::getBoatType() const +{ + //We make good ships by default + return 1; +} + + +IBoatGenerator::IBoatGenerator(const CGObjectInstance *O) +: o(O) +{ +} + +void IBoatGenerator::getProblemText(MetaString &out, const CGHeroInstance *visitor) const +{ + switch(shipyardStatus()) + { + case BOAT_ALREADY_BUILT: + out.addTxt(MetaString::GENERAL_TXT, 51); + break; + case TILE_BLOCKED: + if(visitor) + { + out.addTxt(MetaString::GENERAL_TXT, 134); + out.addReplacement(visitor->name); + } + else + out.addTxt(MetaString::ADVOB_TXT, 189); + break; + case NO_WATER: + logGlobal->errorStream() << "Shipyard without water!!! " << o->pos << "\t" << o->id; + return; + } +} + +void IShipyard::getBoatCost( std::vector &cost ) const +{ + cost.resize(GameConstants::RESOURCE_QUANTITY); + cost[Res::WOOD] = 10; + cost[Res::GOLD] = 1000; +} + +IShipyard::IShipyard(const CGObjectInstance *O) + : IBoatGenerator(O) +{ +} + +IShipyard * IShipyard::castFrom( CGObjectInstance *obj ) +{ + if(!obj) + return nullptr; + + if(obj->ID == Obj::TOWN) + { + return static_cast(obj); + } + else if(obj->ID == Obj::SHIPYARD) + { + return static_cast(obj); + } + else + { + return nullptr; + } +} + +const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj ) +{ + return castFrom(const_cast(obj)); +} diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index ed83a28f8..640ae1c3e 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -1,204 +1,204 @@ -#pragma once - -#include "ObjectTemplate.h" - -#include "../IGameCallback.h" -#include "../int3.h" -#include "../HeroBonus.h" - -/* - * CObjectHandler.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 CGHeroInstance; -struct BattleResult; - -class DLL_LINKAGE IObjectInterface -{ -public: - static IGameCallback *cb; - - IObjectInterface(); - virtual ~IObjectInterface(); - - virtual void onHeroVisit(const CGHeroInstance * h) const; - virtual void onHeroLeave(const CGHeroInstance * h) const; - 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; - virtual void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const; - virtual void garrisonDialogClosed(const CGHeroInstance *hero) const; - virtual void heroLevelUpDone(const CGHeroInstance *hero) const; - -//unified interface, AI helpers - virtual bool wasVisited (PlayerColor player) const; - virtual bool wasVisited (const CGHeroInstance * h) const; - - static void preInit(); //called before objs receive their initObj - static void postInit();//called after objs receive their initObj - - template void serialize(Handler &h, const int version) - { - logGlobal->errorStream() << "IObjectInterface serialized, unexpected, should not happen!"; - } -}; - -class DLL_LINKAGE IBoatGenerator -{ -public: - const CGObjectInstance *o; - - IBoatGenerator(const CGObjectInstance *O); - virtual ~IBoatGenerator() {} - - virtual int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral - virtual void getOutOffsets(std::vector &offsets) const =0; //offsets to obj pos when we boat can be placed - int3 bestLocation() const; //returns location when the boat should be placed - - enum EGeneratorState {GOOD, BOAT_ALREADY_BUILT, TILE_BLOCKED, NO_WATER}; - EGeneratorState shipyardStatus() const; //0 - can buid, 1 - there is already a boat at dest tile, 2 - dest tile is blocked, 3 - no water - void getProblemText(MetaString &out, const CGHeroInstance *visitor = nullptr) const; - - template void serialize(Handler &h, const int version) - { - h & o; - } -}; - -class DLL_LINKAGE IShipyard : public IBoatGenerator -{ -public: - IShipyard(const CGObjectInstance *O); - virtual ~IShipyard() {} - - virtual void getBoatCost(std::vector &cost) const; - - static const IShipyard *castFrom(const CGObjectInstance *obj); - static IShipyard *castFrom(CGObjectInstance *obj); - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - } -}; - -class DLL_LINKAGE CGObjectInstance : public IObjectInterface -{ -public: - mutable std::string hoverName; - int3 pos; //h3m pos - Obj ID; - si32 subID; //normal subID (this one from OH3 maps ;]) - ObjectInstanceID id;//number of object in map's vector - ObjectTemplate appearance; - - PlayerColor tempOwner; - bool blockVisit; //if non-zero then blocks the tile but is visitable from neighbouring tile - - virtual ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used - virtual int3 getSightCenter() const; //"center" tile from which the sight distance is calculated - virtual int getSightRadious() const; //sight distance (should be used if player-owned structure) - bool passableFor(PlayerColor color) const; - void getSightTiles(std::unordered_set &tiles) const; //returns reference to the set - PlayerColor getOwner() const; - void setOwner(PlayerColor ow); - int getWidth() const; //returns width of object graphic in tiles - int getHeight() const; //returns height of object graphic in tiles - virtual bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) (h3m pos) - virtual int3 getVisitableOffset() const; //returns (x,y,0) offset to first visitable tile from bottom right obj tile (0,0,0) (h3m pos) - int3 visitablePos() const; - bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) (h3m pos) - bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) (h3m pos) - std::set getBlockedPos() const; //returns set of positions blocked by this object - std::set getBlockedOffsets() const; //returns set of relative positions blocked by this object - bool isVisitable() const; //returns true if object is visitable - bool operator<(const CGObjectInstance & cmp) const; //screen printing priority comparing - void hideTiles(PlayerColor ourplayer, int radius) const; - CGObjectInstance(); - virtual ~CGObjectInstance(); - //CGObjectInstance(const CGObjectInstance & right); - //CGObjectInstance& operator=(const CGObjectInstance & right); - virtual const std::string & getHoverText() const; - - void setType(si32 ID, si32 subID); - - ///IObjectInterface - void initObj() override; - void onHeroVisit(const CGHeroInstance * h) const override; - void setProperty(ui8 what, ui32 val) override;//synchr - - friend class CGameHandler; - - - template void serialize(Handler &h, const int version) - { - h & hoverName & pos & ID & subID & id & tempOwner & blockVisit & appearance; - //definfo is handled by map serializer - } -protected: - virtual void setPropertyDer(ui8 what, ui32 val);//synchr - - void getNameVis(std::string &hname) const; - void giveDummyBonus(ObjectInstanceID heroID, ui8 duration = Bonus::ONE_DAY) const; -}; - -/// function object which can be used to find an object with an specific sub ID -class CGObjectInstanceBySubIdFinder -{ -public: - CGObjectInstanceBySubIdFinder(CGObjectInstance * obj); - bool operator()(CGObjectInstance * obj) const; - -private: - CGObjectInstance * obj; -}; - -struct BankConfig -{ - BankConfig() {level = chance = upgradeChance = combatValue = value = rewardDifficulty = easiest = 0; }; - ui8 level; //1 - 4, how hard the battle will be - ui8 chance; //chance for this level being chosen - ui8 upgradeChance; //chance for creatures to be in upgraded versions - std::vector< std::pair > guards; //creature ID, amount - ui32 combatValue; //how hard are guards of this level - Res::ResourceSet resources; //resources given in case of victory - std::vector< std::pair > creatures; //creatures granted in case of victory (creature ID, amount) - std::vector artifacts; //number of artifacts given in case of victory [0] -> treasure, [1] -> minor [2] -> major [3] -> relic - ui32 value; //overall value of given things - ui32 rewardDifficulty; //proportion of reward value to difficulty of guards; how profitable is this creature Bank config - ui16 easiest; //?!? - - template void serialize(Handler &h, const int version) - { - h & level & chance & upgradeChance & guards & combatValue & resources & creatures & artifacts & value & rewardDifficulty & easiest; - } -}; - -class DLL_LINKAGE CObjectHandler -{ -public: - std::map cregens; //type 17. dwelling subid -> creature ID - std::map > > banksInfo; //[index][preset] - std::map creBanksNames; //[crebank index] -> name of this creature bank - std::vector resVals; //default values of resources in gold - - CObjectHandler(); - ~CObjectHandler(); - - int bankObjToIndex (const CGObjectInstance * obj); - - template void serialize(Handler &h, const int version) - { - h & cregens & banksInfo & creBanksNames & resVals; - } -}; +#pragma once + +#include "ObjectTemplate.h" + +#include "../IGameCallback.h" +#include "../int3.h" +#include "../HeroBonus.h" + +/* + * CObjectHandler.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 CGHeroInstance; +struct BattleResult; + +class DLL_LINKAGE IObjectInterface +{ +public: + static IGameCallback *cb; + + IObjectInterface(); + virtual ~IObjectInterface(); + + virtual void onHeroVisit(const CGHeroInstance * h) const; + virtual void onHeroLeave(const CGHeroInstance * h) const; + 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; + virtual void blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) const; + virtual void garrisonDialogClosed(const CGHeroInstance *hero) const; + virtual void heroLevelUpDone(const CGHeroInstance *hero) const; + +//unified interface, AI helpers + virtual bool wasVisited (PlayerColor player) const; + virtual bool wasVisited (const CGHeroInstance * h) const; + + static void preInit(); //called before objs receive their initObj + static void postInit();//called after objs receive their initObj + + template void serialize(Handler &h, const int version) + { + logGlobal->errorStream() << "IObjectInterface serialized, unexpected, should not happen!"; + } +}; + +class DLL_LINKAGE IBoatGenerator +{ +public: + const CGObjectInstance *o; + + IBoatGenerator(const CGObjectInstance *O); + virtual ~IBoatGenerator() {} + + virtual int getBoatType() const; //0 - evil (if a ship can be evil...?), 1 - good, 2 - neutral + virtual void getOutOffsets(std::vector &offsets) const =0; //offsets to obj pos when we boat can be placed + int3 bestLocation() const; //returns location when the boat should be placed + + enum EGeneratorState {GOOD, BOAT_ALREADY_BUILT, TILE_BLOCKED, NO_WATER}; + EGeneratorState shipyardStatus() const; //0 - can buid, 1 - there is already a boat at dest tile, 2 - dest tile is blocked, 3 - no water + void getProblemText(MetaString &out, const CGHeroInstance *visitor = nullptr) const; + + template void serialize(Handler &h, const int version) + { + h & o; + } +}; + +class DLL_LINKAGE IShipyard : public IBoatGenerator +{ +public: + IShipyard(const CGObjectInstance *O); + virtual ~IShipyard() {} + + virtual void getBoatCost(std::vector &cost) const; + + static const IShipyard *castFrom(const CGObjectInstance *obj); + static IShipyard *castFrom(CGObjectInstance *obj); + + template void serialize(Handler &h, const int version) + { + h & static_cast(*this); + } +}; + +class DLL_LINKAGE CGObjectInstance : public IObjectInterface +{ +public: + mutable std::string hoverName; + int3 pos; //h3m pos + Obj ID; + si32 subID; //normal subID (this one from OH3 maps ;]) + ObjectInstanceID id;//number of object in map's vector + ObjectTemplate appearance; + + PlayerColor tempOwner; + bool blockVisit; //if non-zero then blocks the tile but is visitable from neighbouring tile + + virtual ui8 getPassableness() const; //bitmap - if the bit is set the corresponding player can pass through the visitable tiles of object, even if it's blockvis; if not set - default properties from definfo are used + virtual int3 getSightCenter() const; //"center" tile from which the sight distance is calculated + virtual int getSightRadious() const; //sight distance (should be used if player-owned structure) + bool passableFor(PlayerColor color) const; + void getSightTiles(std::unordered_set &tiles) const; //returns reference to the set + PlayerColor getOwner() const; + void setOwner(PlayerColor ow); + int getWidth() const; //returns width of object graphic in tiles + int getHeight() const; //returns height of object graphic in tiles + virtual bool visitableAt(int x, int y) const; //returns true if object is visitable at location (x, y) (h3m pos) + virtual int3 getVisitableOffset() const; //returns (x,y,0) offset to first visitable tile from bottom right obj tile (0,0,0) (h3m pos) + int3 visitablePos() const; + bool blockingAt(int x, int y) const; //returns true if object is blocking location (x, y) (h3m pos) + bool coveringAt(int x, int y) const; //returns true if object covers with picture location (x, y) (h3m pos) + std::set getBlockedPos() const; //returns set of positions blocked by this object + std::set getBlockedOffsets() const; //returns set of relative positions blocked by this object + bool isVisitable() const; //returns true if object is visitable + bool operator<(const CGObjectInstance & cmp) const; //screen printing priority comparing + void hideTiles(PlayerColor ourplayer, int radius) const; + CGObjectInstance(); + virtual ~CGObjectInstance(); + //CGObjectInstance(const CGObjectInstance & right); + //CGObjectInstance& operator=(const CGObjectInstance & right); + virtual const std::string & getHoverText() const; + + void setType(si32 ID, si32 subID); + + ///IObjectInterface + void initObj() override; + void onHeroVisit(const CGHeroInstance * h) const override; + void setProperty(ui8 what, ui32 val) override;//synchr + + friend class CGameHandler; + + + template void serialize(Handler &h, const int version) + { + h & hoverName & pos & ID & subID & id & tempOwner & blockVisit & appearance; + //definfo is handled by map serializer + } +protected: + virtual void setPropertyDer(ui8 what, ui32 val);//synchr + + void getNameVis(std::string &hname) const; + void giveDummyBonus(ObjectInstanceID heroID, ui8 duration = Bonus::ONE_DAY) const; +}; + +/// function object which can be used to find an object with an specific sub ID +class CGObjectInstanceBySubIdFinder +{ +public: + CGObjectInstanceBySubIdFinder(CGObjectInstance * obj); + bool operator()(CGObjectInstance * obj) const; + +private: + CGObjectInstance * obj; +}; + +struct BankConfig +{ + BankConfig() {level = chance = upgradeChance = combatValue = value = rewardDifficulty = easiest = 0; }; + ui8 level; //1 - 4, how hard the battle will be + ui8 chance; //chance for this level being chosen + ui8 upgradeChance; //chance for creatures to be in upgraded versions + std::vector< std::pair > guards; //creature ID, amount + ui32 combatValue; //how hard are guards of this level + Res::ResourceSet resources; //resources given in case of victory + std::vector< std::pair > creatures; //creatures granted in case of victory (creature ID, amount) + std::vector artifacts; //number of artifacts given in case of victory [0] -> treasure, [1] -> minor [2] -> major [3] -> relic + ui32 value; //overall value of given things + ui32 rewardDifficulty; //proportion of reward value to difficulty of guards; how profitable is this creature Bank config + ui16 easiest; //?!? + + template void serialize(Handler &h, const int version) + { + h & level & chance & upgradeChance & guards & combatValue & resources & creatures & artifacts & value & rewardDifficulty & easiest; + } +}; + +class DLL_LINKAGE CObjectHandler +{ +public: + std::map cregens; //type 17. dwelling subid -> creature ID + std::map > > banksInfo; //[index][preset] + std::map creBanksNames; //[crebank index] -> name of this creature bank + std::vector resVals; //default values of resources in gold + + CObjectHandler(); + ~CObjectHandler(); + + int bankObjToIndex (const CGObjectInstance * obj); + + template void serialize(Handler &h, const int version) + { + h & cregens & banksInfo & creBanksNames & resVals; + } +}; From b2e8c923835a63bc43a43911f690f2e22c189014 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jun 2014 20:26:50 +0300 Subject: [PATCH 26/33] Cleanup: - removed commented-out #includ'es - renamed some files to match name of class --- AI/VCAI/AIUtility.cpp | 1 - AI/VCAI/AIUtility.h | 1 - AI/VCAI/Fuzzy.cpp | 1 - AI/VCAI/Goals.h | 1 - AI/VCAI/VCAI.h | 2 -- CCallback.cpp | 1 - client/CHeroWindow.h | 2 -- client/CMT.cpp | 2 -- client/CPlayerInterface.cpp | 3 --- client/CPreGame.cpp | 2 -- client/CQuestLog.cpp | 1 - client/CSpellWindow.cpp | 1 - client/Client.cpp | 2 -- client/GUIClasses.cpp | 1 - client/Graphics.cpp | 3 --- client/NetPacksClient.cpp | 2 -- client/battle/CBattleInterface.cpp | 1 - client/battle/CBattleInterfaceClasses.cpp | 1 - client/battle/CCreatureAnimation.h | 1 - lib/CGameState.h | 1 - lib/CMakeLists.txt | 4 ++-- .../{CGArmedInstance.cpp => CArmedInstance.cpp} | 4 ++-- .../{CGArmedInstance.h => CArmedInstance.h} | 2 +- lib/mapObjects/{CGBank.cpp => CBank.cpp} | 8 ++++---- lib/mapObjects/{CGBank.h => CBank.h} | 4 ++-- lib/mapObjects/CGHeroInstance.cpp | 10 +++++----- lib/mapObjects/CGHeroInstance.h | 4 ++-- lib/mapObjects/CGMarket.cpp | 7 ++++--- lib/mapObjects/CGMarket.h | 2 +- lib/mapObjects/CGPandoraBox.cpp | 4 ++-- lib/mapObjects/CGPandoraBox.h | 5 ++--- lib/mapObjects/CGTownInstance.cpp | 4 ++-- lib/mapObjects/CGTownInstance.h | 4 ++-- lib/mapObjects/CObjectClassesHandler.cpp | 14 +++++++------- lib/mapObjects/CObjectHandler.cpp | 6 +++--- lib/mapObjects/CQuest.cpp | 5 +++-- lib/mapObjects/CQuest.h | 4 ++-- lib/mapObjects/CRewardableConstructor.cpp | 8 ++++---- lib/mapObjects/CRewardableConstructor.h | 2 +- lib/mapObjects/CRewardableObject.cpp | 7 ++++--- lib/mapObjects/CRewardableObject.h | 2 +- lib/mapObjects/MapObjects.h | 4 ++-- lib/mapObjects/MiscObjects.cpp | 7 +++---- lib/mapObjects/MiscObjects.h | 4 ++-- lib/mapObjects/ObjectTemplate.cpp | 14 +++++++------- lib/mapObjects/ObjectTemplate.h | 2 +- lib/mapping/MapFormatH3M.h | 1 - lib/rmg/CMapGenerator.cpp | 2 -- server/CGameHandler.cpp | 2 -- server/CVCMIServer.cpp | 2 -- server/NetPacksServer.cpp | 1 - server/StdInc.h | 3 --- 52 files changed, 71 insertions(+), 111 deletions(-) rename lib/mapObjects/{CGArmedInstance.cpp => CArmedInstance.cpp} (97%) rename lib/mapObjects/{CGArmedInstance.h => CArmedInstance.h} (96%) rename lib/mapObjects/{CGBank.cpp => CBank.cpp} (99%) rename lib/mapObjects/{CGBank.h => CBank.h} (96%) diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index 783b2266e..1cc87ce7d 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -3,7 +3,6 @@ #include "VCAI.h" #include "../../lib/UnlockGuard.h" -//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" diff --git a/AI/VCAI/AIUtility.h b/AI/VCAI/AIUtility.h index cfa678a55..fb3988987 100644 --- a/AI/VCAI/AIUtility.h +++ b/AI/VCAI/AIUtility.h @@ -5,7 +5,6 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" #include "../../lib/CSpellHandler.h" -//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/Connection.h" #include "../../lib/CGameState.h" #include "../../lib/mapping/CMap.h" diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index 63547cd72..cddff4d9a 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -6,7 +6,6 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/VCMI_Lib.h" #include "../../CCallback.h" -//#include "Goals.cpp" #include "VCAI.h" /* diff --git a/AI/VCAI/Goals.h b/AI/VCAI/Goals.h index 2c459c817..de8824906 100644 --- a/AI/VCAI/Goals.h +++ b/AI/VCAI/Goals.h @@ -1,7 +1,6 @@ #pragma once #include "../../lib/VCMI_Lib.h" -//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/CBuildingHandler.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" diff --git a/AI/VCAI/VCAI.h b/AI/VCAI/VCAI.h index 116913daf..b04d7fffc 100644 --- a/AI/VCAI/VCAI.h +++ b/AI/VCAI/VCAI.h @@ -4,7 +4,6 @@ #include "Goals.h" #include "../../lib/AI_Base.h" #include "../../CCallback.h" -//#include "../../lib/mapObjects/CObjectClassesHandler.h" #include "../../lib/CThreadHelper.h" @@ -14,7 +13,6 @@ #include "../../lib/CCreatureHandler.h" #include "../../lib/CTownHandler.h" #include "../../lib/CSpellHandler.h" -//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/Connection.h" #include "../../lib/CGameState.h" #include "../../lib/mapping/CMap.h" diff --git a/CCallback.cpp b/CCallback.cpp index 38392c512..d3ee913a6 100644 --- a/CCallback.cpp +++ b/CCallback.cpp @@ -12,7 +12,6 @@ #include "lib/mapObjects/CObjectClassesHandler.h" #include "lib/CGeneralTextHandler.h" #include "lib/CHeroHandler.h" -//#include "lib/mapObjects/CObjectHandler.h" #include "lib/Connection.h" #include "lib/NetPacks.h" #include "client/mapHandler.h" diff --git a/client/CHeroWindow.h b/client/CHeroWindow.h index d23443f64..3d922d0c4 100644 --- a/client/CHeroWindow.h +++ b/client/CHeroWindow.h @@ -4,8 +4,6 @@ #include "gui/CIntObjectClasses.h" #include "GUIClasses.h" -//#include "CPlayerInterface.h" - /* * CHeroWindow.h, part of VCMI engine * diff --git a/client/CMT.cpp b/client/CMT.cpp index 22e2f9d33..070b2cce1 100644 --- a/client/CMT.cpp +++ b/client/CMT.cpp @@ -34,7 +34,6 @@ #include "CMessage.h" #include "../lib/CModHandler.h" #include "../lib/CTownHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CScriptingModule.h" #include "../lib/GameConstants.h" @@ -44,7 +43,6 @@ #ifdef _WIN32 #include "SDL_syswm.h" #endif -//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/UnlockGuard.h" #include "CMT.h" diff --git a/client/CPlayerInterface.cpp b/client/CPlayerInterface.cpp index 00ff6e880..68d53d1ca 100644 --- a/client/CPlayerInterface.cpp +++ b/client/CPlayerInterface.cpp @@ -1,5 +1,4 @@ #include "StdInc.h" -//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "CAdvmapInterface.h" #include "battle/CBattleInterface.h" #include "battle/CBattleInterfaceClasses.h" @@ -13,7 +12,6 @@ #include "CQuestLog.h" #include "CMessage.h" #include "CPlayerInterface.h" -//#include "gui/SDL_Extensions.h" #include "gui/SDL_Extensions.h" #include "../lib/CConfigHandler.h" #include "battle/CCreatureAnimation.h" @@ -21,7 +19,6 @@ #include "../lib/CArtHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/Connection.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" diff --git a/client/CPreGame.cpp b/client/CPreGame.cpp index d4ba8fd91..a8b042788 100644 --- a/client/CPreGame.cpp +++ b/client/CPreGame.cpp @@ -11,11 +11,9 @@ #include "gui/CCursorHandler.h" #include "CAnimation.h" #include "CDefHandler.h" -//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CHeroHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/mapping/CCampaignHandler.h" #include "../lib/CCreatureHandler.h" #include "../lib/JsonNode.h" diff --git a/client/CQuestLog.cpp b/client/CQuestLog.cpp index a85758b04..1eb621ce4 100644 --- a/client/CQuestLog.cpp +++ b/client/CQuestLog.cpp @@ -16,7 +16,6 @@ #include "../lib/CGameState.h" #include "../lib/CArtHandler.h" #include "../lib/NetPacksBase.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "gui/CGuiHandler.h" #include "gui/CIntObjectClasses.h" diff --git a/client/CSpellWindow.cpp b/client/CSpellWindow.cpp index 34168743a..36c269967 100644 --- a/client/CSpellWindow.cpp +++ b/client/CSpellWindow.cpp @@ -3,7 +3,6 @@ #include "Graphics.h" #include "CDefHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CGeneralTextHandler.h" #include "CVideoHandler.h" diff --git a/client/Client.cpp b/client/Client.cpp index 9d0c27d42..528895cfd 100644 --- a/client/Client.cpp +++ b/client/Client.cpp @@ -11,11 +11,9 @@ #include "../lib/BattleState.h" #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" -//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CBuildingHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/Connection.h" diff --git a/client/GUIClasses.cpp b/client/GUIClasses.cpp index f183a39c5..3ffa2bd1e 100644 --- a/client/GUIClasses.cpp +++ b/client/GUIClasses.cpp @@ -24,7 +24,6 @@ #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CModHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CTownHandler.h" #include "../lib/CondSh.h" diff --git a/client/Graphics.cpp b/client/Graphics.cpp index d1025adc4..c929ee60a 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -12,13 +12,10 @@ #include "../CCallback.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CCreatureHandler.h" #include "CBitmapHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" -//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGameState.h" #include "../lib/JsonNode.h" #include "../lib/vcmi_endian.h" diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 051b72d20..48e141a67 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -9,9 +9,7 @@ #include "CGameInfo.h" #include "../lib/Connection.h" #include "../lib/CGeneralTextHandler.h" -//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CHeroHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/VCMI_Lib.h" #include "../lib/mapping/CMap.h" #include "../lib/VCMIDirs.h" diff --git a/client/battle/CBattleInterface.cpp b/client/battle/CBattleInterface.cpp index 6219f692d..5179ce3b8 100644 --- a/client/battle/CBattleInterface.cpp +++ b/client/battle/CBattleInterface.cpp @@ -6,7 +6,6 @@ #include "../CAdvmapInterface.h" #include "../CAnimation.h" #include "../CBitmapHandler.h" -//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/CHeroHandler.h" # include "../CDefHandler.h" #include "../../lib/CSpellHandler.h" diff --git a/client/battle/CBattleInterfaceClasses.cpp b/client/battle/CBattleInterfaceClasses.cpp index 81ef34d0e..fcf3d878e 100644 --- a/client/battle/CBattleInterfaceClasses.cpp +++ b/client/battle/CBattleInterfaceClasses.cpp @@ -16,7 +16,6 @@ #include "../../lib/CGeneralTextHandler.h" #include "../../lib/NetPacks.h" #include "../../lib/CCreatureHandler.h" -//#include "../../lib/mapObjects/CObjectHandler.h" #include "../../lib/BattleState.h" #include "../CMusicHandler.h" #include "../CVideoHandler.h" diff --git a/client/battle/CCreatureAnimation.h b/client/battle/CCreatureAnimation.h index bae803d51..f8fb967f0 100644 --- a/client/battle/CCreatureAnimation.h +++ b/client/battle/CCreatureAnimation.h @@ -1,7 +1,6 @@ #pragma once #include "../../lib/FunctionList.h" -//#include "../CDefHandler.h" #include "../CAnimation.h" /* diff --git a/lib/CGameState.h b/lib/CGameState.h index 51178bf77..0d8a8c8f2 100644 --- a/lib/CGameState.h +++ b/lib/CGameState.h @@ -14,7 +14,6 @@ #include "IGameCallback.h" #include "ResourceSet.h" #include "int3.h" -//#include "mapObjects/CObjectHandler.h" #include "CRandomGenerator.h" /* diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 5900f6053..361a0b362 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -19,8 +19,8 @@ set(lib_SRCS filesystem/Filesystem.cpp filesystem/ResourceID.cpp - mapObjects/CGArmedInstance.cpp - mapObjects/CGBank.cpp + mapObjects/CArmedInstance.cpp + mapObjects/CBank.cpp mapObjects/CGHeroInstance.cpp mapObjects/CGMarket.cpp mapObjects/CGPandoraBox.cpp diff --git a/lib/mapObjects/CGArmedInstance.cpp b/lib/mapObjects/CArmedInstance.cpp similarity index 97% rename from lib/mapObjects/CGArmedInstance.cpp rename to lib/mapObjects/CArmedInstance.cpp index 1cfd17f70..050c10f38 100644 --- a/lib/mapObjects/CGArmedInstance.cpp +++ b/lib/mapObjects/CArmedInstance.cpp @@ -1,5 +1,5 @@ /* - * CObjectHandler.cpp, part of VCMI engine + * CArmedInstance.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -9,7 +9,7 @@ */ #include "StdInc.h" -#include "CGArmedInstance.h" +#include "CArmedInstance.h" #include "../CTownHandler.h" #include "../CCreatureHandler.h" diff --git a/lib/mapObjects/CGArmedInstance.h b/lib/mapObjects/CArmedInstance.h similarity index 96% rename from lib/mapObjects/CGArmedInstance.h rename to lib/mapObjects/CArmedInstance.h index ecc024acc..f85b8a07e 100644 --- a/lib/mapObjects/CGArmedInstance.h +++ b/lib/mapObjects/CArmedInstance.h @@ -4,7 +4,7 @@ #include "../CCreatureSet.h" /* - * CObjectHandler.h, part of VCMI engine + * CArmedInstance.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/mapObjects/CGBank.cpp b/lib/mapObjects/CBank.cpp similarity index 99% rename from lib/mapObjects/CGBank.cpp rename to lib/mapObjects/CBank.cpp index 48e5a2c5c..cd6e06ebb 100644 --- a/lib/mapObjects/CGBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -1,5 +1,5 @@ /* - * CObjectHandler.cpp, part of VCMI engine + * CBank.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -9,10 +9,10 @@ */ #include "StdInc.h" -#include "CGBank.h" +#include "CBank.h" -#include "NetPacks.h" -#include "CGeneralTextHandler.h" +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" #include "../client/CSoundBase.h" using namespace boost::assign; diff --git a/lib/mapObjects/CGBank.h b/lib/mapObjects/CBank.h similarity index 96% rename from lib/mapObjects/CGBank.h rename to lib/mapObjects/CBank.h index 14eff6f31..71e1bc00d 100644 --- a/lib/mapObjects/CGBank.h +++ b/lib/mapObjects/CBank.h @@ -1,10 +1,10 @@ #pragma once #include "CObjectHandler.h" -#include "CGArmedInstance.h" +#include "CArmedInstance.h" /* - * CObjectHandler.h, part of VCMI engine + * CBank.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 22a365e7a..1c3aa9a01 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -1,5 +1,5 @@ /* - * CObjectHandler.cpp, part of VCMI engine + * CGHeroInstance.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -11,11 +11,11 @@ #include "StdInc.h" #include "CGHeroInstance.h" -#include "NetPacks.h" -#include "CGeneralTextHandler.h" -#include "CHeroHandler.h" +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" +#include "../CHeroHandler.h" #include "../client/CSoundBase.h" -#include "CSpellHandler.h" +#include "../CSpellHandler.h" #include "CObjectClassesHandler.h" diff --git a/lib/mapObjects/CGHeroInstance.h b/lib/mapObjects/CGHeroInstance.h index 75da7346e..327c0725b 100644 --- a/lib/mapObjects/CGHeroInstance.h +++ b/lib/mapObjects/CGHeroInstance.h @@ -1,13 +1,13 @@ #pragma once #include "CObjectHandler.h" -#include "CGArmedInstance.h" +#include "CArmedInstance.h" #include "../CArtHandler.h" // For CArtifactSet #include "../CRandomGenerator.h" /* - * CObjectHandler.h, part of VCMI engine + * CGHeroInstance.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/mapObjects/CGMarket.cpp b/lib/mapObjects/CGMarket.cpp index 887c02128..98205974e 100644 --- a/lib/mapObjects/CGMarket.cpp +++ b/lib/mapObjects/CGMarket.cpp @@ -1,5 +1,6 @@ /* - * CObjectHandler.cpp, part of VCMI engine + * + * CGMarket.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -11,8 +12,8 @@ #include "StdInc.h" #include "CGMarket.h" -#include "NetPacks.h" -#include "CGeneralTextHandler.h" +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" using namespace boost::assign; diff --git a/lib/mapObjects/CGMarket.h b/lib/mapObjects/CGMarket.h index b28fde8cf..8f010a705 100644 --- a/lib/mapObjects/CGMarket.h +++ b/lib/mapObjects/CGMarket.h @@ -3,7 +3,7 @@ #include "CObjectHandler.h" /* - * CObjectHandler.h, part of VCMI engine + * CGMarket.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index ea55c25be..6919fa23d 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -1,5 +1,5 @@ /* - * CObjectHandler.cpp, part of VCMI engine + * CGPandoraBox.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -11,7 +11,7 @@ #include "StdInc.h" #include "CGPandoraBox.h" -#include "NetPacks.h" +#include "../NetPacks.h" #include "../client/CSoundBase.h" #include "../CSpellHandler.h" diff --git a/lib/mapObjects/CGPandoraBox.h b/lib/mapObjects/CGPandoraBox.h index 72508cdb9..227615cdf 100644 --- a/lib/mapObjects/CGPandoraBox.h +++ b/lib/mapObjects/CGPandoraBox.h @@ -1,11 +1,10 @@ #pragma once #include "CObjectHandler.h" -#include "CGArmedInstance.h" - +#include "CArmedInstance.h" /* - * CObjectHandler.h, part of VCMI engine + * CGPandoraBox.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 53efa35b6..4ec26050e 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -1,5 +1,5 @@ /* - * CObjectHandler.cpp, part of VCMI engine + * CGTownInstance.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -11,7 +11,7 @@ #include "StdInc.h" #include "CGTownInstance.h" -#include "NetPacks.h" +#include "../NetPacks.h" #include "../CGeneralTextHandler.h" using namespace boost::assign; diff --git a/lib/mapObjects/CGTownInstance.h b/lib/mapObjects/CGTownInstance.h index 6ad935bc3..fda070a30 100644 --- a/lib/mapObjects/CGTownInstance.h +++ b/lib/mapObjects/CGTownInstance.h @@ -2,12 +2,12 @@ #include "CObjectHandler.h" #include "CGMarket.h" // For IMarket interface -#include "CGArmedInstance.h" +#include "CArmedInstance.h" #include "../CTownHandler.h" // For CTown /* - * CObjectHandler.h, part of VCMI engine + * CGTownInstance.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index cbb1233dd..8cc66317c 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -1,14 +1,14 @@ #include "StdInc.h" #include "CObjectClassesHandler.h" -#include "filesystem/Filesystem.h" -#include "filesystem/CBinaryReader.h" +#include "../filesystem/Filesystem.h" +#include "../filesystem/CBinaryReader.h" #include "../lib/VCMI_Lib.h" -#include "GameConstants.h" -#include "StringConstants.h" -#include "CGeneralTextHandler.h" -#include "CModHandler.h" -#include "JsonNode.h" +#include "../GameConstants.h" +#include "../StringConstants.h" +#include "../CGeneralTextHandler.h" +#include "../CModHandler.h" +#include "../JsonNode.h" #include "CRewardableConstructor.h" #include "MapObjects.h" diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index b1289ebfd..a8ffc532b 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -11,9 +11,9 @@ #include "StdInc.h" #include "CObjectHandler.h" -#include "NetPacks.h" -#include "CGeneralTextHandler.h" -#include "CHeroHandler.h" +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" +#include "../CHeroHandler.h" #include "../client/CSoundBase.h" #include "CObjectClassesHandler.h" diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 67c56a48d..80a4d917e 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -1,5 +1,6 @@ /* - * CObjectHandler.cpp, part of VCMI engine + * + * CQuest.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -11,7 +12,7 @@ #include "StdInc.h" #include "CQuest.h" -#include "NetPacks.h" +#include "../NetPacks.h" #include "../client/CSoundBase.h" #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" diff --git a/lib/mapObjects/CQuest.h b/lib/mapObjects/CQuest.h index 6323b2864..aa5bf0684 100644 --- a/lib/mapObjects/CQuest.h +++ b/lib/mapObjects/CQuest.h @@ -1,13 +1,13 @@ #pragma once #include "CObjectHandler.h" -#include "CGArmedInstance.h" +#include "CArmedInstance.h" #include "../CCreatureSet.h" #include "../NetPacksBase.h" /* - * CObjectHandler.h, part of VCMI engine + * CQuest.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/mapObjects/CRewardableConstructor.cpp b/lib/mapObjects/CRewardableConstructor.cpp index a03a66465..d50736d30 100644 --- a/lib/mapObjects/CRewardableConstructor.cpp +++ b/lib/mapObjects/CRewardableConstructor.cpp @@ -1,12 +1,12 @@ #include "StdInc.h" #include "CRewardableConstructor.h" -#include "CRandomGenerator.h" -#include "StringConstants.h" -#include "CCreatureHandler.h" +#include "../CRandomGenerator.h" +#include "../StringConstants.h" +#include "../CCreatureHandler.h" /* - * CObjectConstructor.cpp, part of VCMI engine + * CRewardableConstructor.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/mapObjects/CRewardableConstructor.h b/lib/mapObjects/CRewardableConstructor.h index a7c0546f9..d15e597d4 100644 --- a/lib/mapObjects/CRewardableConstructor.h +++ b/lib/mapObjects/CRewardableConstructor.h @@ -6,7 +6,7 @@ #include "../JsonNode.h" /* - * CObjectConstructor.h, part of VCMI engine + * CRewardableConstructor.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index fd68125ed..51789c546 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -1,4 +1,5 @@ /* + * * CRewardableObject.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder @@ -10,10 +11,10 @@ #include "StdInc.h" #include "CRewardableObject.h" -#include "CHeroHandler.h" -#include "CGeneralTextHandler.h" +#include "../CHeroHandler.h" +#include "../CGeneralTextHandler.h" #include "../client/CSoundBase.h" -#include "NetPacks.h" +#include "../NetPacks.h" #include "CObjectClassesHandler.h" diff --git a/lib/mapObjects/CRewardableObject.h b/lib/mapObjects/CRewardableObject.h index 375298f37..58d83c2c0 100644 --- a/lib/mapObjects/CRewardableObject.h +++ b/lib/mapObjects/CRewardableObject.h @@ -1,7 +1,7 @@ #pragma once #include "CObjectHandler.h" -#include "CGArmedInstance.h" +#include "CArmedInstance.h" #include "../NetPacksBase.h" diff --git a/lib/mapObjects/MapObjects.h b/lib/mapObjects/MapObjects.h index d7e4e27e0..44eeb4495 100644 --- a/lib/mapObjects/MapObjects.h +++ b/lib/mapObjects/MapObjects.h @@ -14,8 +14,8 @@ // Possible TODO - remove this header after CObjectHandler.cpp will be fully split into smaller files #include "CObjectHandler.h" -#include "CGArmedInstance.h" -#include "CGBank.h" +#include "CArmedInstance.h" +#include "CBank.h" #include "CGHeroInstance.h" #include "CGMarket.h" #include "CGTownInstance.h" diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index f7b9c431f..98603744d 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -1,5 +1,5 @@ /* - * CObjectHandler.cpp, part of VCMI engine + * MiscObjects.cpp, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * @@ -11,9 +11,8 @@ #include "StdInc.h" #include "MiscObjects.h" -#include "NetPacks.h" -#include "CGeneralTextHandler.h" -//#include "CHeroHandler.h" +#include "../NetPacks.h" +#include "../CGeneralTextHandler.h" #include "../client/CSoundBase.h" #include "CObjectClassesHandler.h" diff --git a/lib/mapObjects/MiscObjects.h b/lib/mapObjects/MiscObjects.h index 26315b04d..c53db75d7 100644 --- a/lib/mapObjects/MiscObjects.h +++ b/lib/mapObjects/MiscObjects.h @@ -1,10 +1,10 @@ #pragma once #include "CObjectHandler.h" -#include "CGArmedInstance.h" +#include "CArmedInstance.h" /* - * CObjectHandler.h, part of VCMI engine + * MiscObjects.h, part of VCMI engine * * Authors: listed in file AUTHORS in main folder * diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 34c2aaa3f..74ff7a424 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -1,15 +1,15 @@ #include "StdInc.h" #include "CObjectClassesHandler.h" -#include "filesystem/Filesystem.h" -#include "filesystem/CBinaryReader.h" +#include "../filesystem/Filesystem.h" +#include "../filesystem/CBinaryReader.h" #include "../lib/VCMI_Lib.h" -#include "GameConstants.h" -#include "StringConstants.h" -#include "CGeneralTextHandler.h" +#include "../GameConstants.h" +#include "../StringConstants.h" +#include "../CGeneralTextHandler.h" #include "CObjectHandler.h" -#include "CModHandler.h" -#include "JsonNode.h" +#include "../CModHandler.h" +#include "../JsonNode.h" #include "CRewardableConstructor.h" diff --git a/lib/mapObjects/ObjectTemplate.h b/lib/mapObjects/ObjectTemplate.h index 2c9230fc2..89353b26c 100644 --- a/lib/mapObjects/ObjectTemplate.h +++ b/lib/mapObjects/ObjectTemplate.h @@ -1,6 +1,6 @@ #pragma once -#include "GameConstants.h" +#include "../GameConstants.h" /* * ObjectTemplate.h, part of VCMI engine diff --git a/lib/mapping/MapFormatH3M.h b/lib/mapping/MapFormatH3M.h index 20419bae9..dd2bc5be5 100644 --- a/lib/mapping/MapFormatH3M.h +++ b/lib/mapping/MapFormatH3M.h @@ -14,7 +14,6 @@ #include "CMapService.h" #include "../GameConstants.h" #include "../ResourceSet.h" -//#include "../mapObjects/CObjectClassesHandler.h" #include "../mapObjects/ObjectTemplate.h" #include "../int3.h" diff --git a/lib/rmg/CMapGenerator.cpp b/lib/rmg/CMapGenerator.cpp index 28fe1ba0a..35450d39d 100644 --- a/lib/rmg/CMapGenerator.cpp +++ b/lib/rmg/CMapGenerator.cpp @@ -5,8 +5,6 @@ #include "../VCMI_Lib.h" #include "../CGeneralTextHandler.h" #include "../mapping/CMapEditManager.h" -//#include "../mapObjects/CObjectHandler.h" -//#include "../mapObjects/CObjectClassesHandler.h" #include "../CTownHandler.h" #include "../StringConstants.h" #include "../filesystem/Filesystem.h" diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index 63578c820..cd5f7413b 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -8,9 +8,7 @@ #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" #include "../lib/CBuildingHandler.h" -//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CHeroHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/CSpellHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CTownHandler.h" diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index 96a085799..d152cb97c 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -8,7 +8,6 @@ #include "../lib/Connection.h" #include "../lib/CModHandler.h" #include "../lib/CArtHandler.h" -//#include "../lib/mapObjects/CObjectClassesHandler.h" #include "../lib/CGeneralTextHandler.h" #include "../lib/CHeroHandler.h" #include "../lib/CTownHandler.h" @@ -26,7 +25,6 @@ #include "../lib/VCMIDirs.h" #include "CGameHandler.h" #include "../lib/mapping/CMapInfo.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/GameConstants.h" #include "../lib/logging/CBasicLogConfigurator.h" #include "../lib/CConfigHandler.h" diff --git a/server/NetPacksServer.cpp b/server/NetPacksServer.cpp index 4ccea2a53..597479fb1 100644 --- a/server/NetPacksServer.cpp +++ b/server/NetPacksServer.cpp @@ -2,7 +2,6 @@ #include "../lib/NetPacks.h" #include "CGameHandler.h" -//#include "../lib/mapObjects/CObjectHandler.h" #include "../lib/IGameCallback.h" #include "../lib/mapping/CMap.h" #include "../lib/CGameState.h" diff --git a/server/StdInc.h b/server/StdInc.h index 747622649..7418241e0 100644 --- a/server/StdInc.h +++ b/server/StdInc.h @@ -4,10 +4,7 @@ #include #include //no i/o just types -//#include -//#include #include #include #include #include -//#include From 44742814cdb3da12cf7d7639ae35c3e5a254eade Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Thu, 5 Jun 2014 23:51:24 +0300 Subject: [PATCH 27/33] More cleanup: - moved SoundBase.h to lib since it contains shared data - added RMG info to object format. Note that data is not yet imported in configs - slightly updated API of object handlers --- client/CAdvmapInterface.cpp | 2 +- client/CMakeLists.txt | 2 - client/CMusicHandler.h | 2 +- client/NetPacksClient.cpp | 2 +- lib/CArtHandler.cpp | 4 +- lib/CCreatureHandler.cpp | 4 +- lib/CHeroHandler.cpp | 2 +- lib/CMakeLists.txt | 1 + {client => lib}/CSoundBase.h | 0 lib/CTownHandler.cpp | 4 +- lib/JsonNode.h | 21 ++++----- lib/mapObjects/CBank.cpp | 2 +- lib/mapObjects/CGHeroInstance.cpp | 2 +- lib/mapObjects/CGPandoraBox.cpp | 2 +- lib/mapObjects/CObjectClassesHandler.cpp | 51 ++++++++++++++++++++-- lib/mapObjects/CObjectClassesHandler.h | 54 +++++++++++++++++++----- lib/mapObjects/CObjectHandler.cpp | 2 +- lib/mapObjects/CQuest.cpp | 2 +- lib/mapObjects/CRewardableObject.cpp | 2 +- lib/mapObjects/MiscObjects.cpp | 2 +- server/CGameHandler.cpp | 2 +- 21 files changed, 118 insertions(+), 47 deletions(-) rename {client => lib}/CSoundBase.h (100%) diff --git a/client/CAdvmapInterface.cpp b/client/CAdvmapInterface.cpp index 09feb557f..4dfba348d 100644 --- a/client/CAdvmapInterface.cpp +++ b/client/CAdvmapInterface.cpp @@ -25,7 +25,7 @@ #include "CPreGame.h" #include "../lib/VCMI_Lib.h" #include "../lib/CSpellHandler.h" -#include "CSoundBase.h" +#include "../lib/CSoundBase.h" #include "../lib/CGameState.h" #include "CMusicHandler.h" #include "gui/CGuiHandler.h" diff --git a/client/CMakeLists.txt b/client/CMakeLists.txt index 9f78671ce..b963a530c 100644 --- a/client/CMakeLists.txt +++ b/client/CMakeLists.txt @@ -48,8 +48,6 @@ set(client_SRCS ) set(client_HEADERS - CSoundBase.h - gui/SDL_Pixels.h ) diff --git a/client/CMusicHandler.h b/client/CMusicHandler.h index 2f77deca1..d5f87981c 100644 --- a/client/CMusicHandler.h +++ b/client/CMusicHandler.h @@ -1,7 +1,7 @@ #pragma once #include "../lib/CConfigHandler.h" -#include "CSoundBase.h" +#include "../lib/CSoundBase.h" #include "../lib/CCreatureHandler.h" /* diff --git a/client/NetPacksClient.cpp b/client/NetPacksClient.cpp index 48e141a67..7f6f5028c 100644 --- a/client/NetPacksClient.cpp +++ b/client/NetPacksClient.cpp @@ -14,7 +14,7 @@ #include "../lib/mapping/CMap.h" #include "../lib/VCMIDirs.h" #include "../lib/CSpellHandler.h" -#include "CSoundBase.h" +#include "../lib/CSoundBase.h" #include "mapHandler.h" #include "GUIClasses.h" #include "../lib/CConfigHandler.h" diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 7be90644e..72e9f5065 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -651,7 +651,7 @@ void CArtHandler::afterLoadFinalization() for (CArtifact * art : artifacts) { - VLC->objtypeh->createObject(art->Name(), JsonNode(), Obj::ARTIFACT, art->id.num); + VLC->objtypeh->loadSubObject(art->Name(), JsonNode(), Obj::ARTIFACT, art->id.num); if (!art->advMapDef.empty()) { @@ -664,7 +664,7 @@ void CArtHandler::afterLoadFinalization() } // 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->eraseObject(Obj::ARTIFACT, art->id); + VLC->objtypeh->removeSubObject(Obj::ARTIFACT, art->id); } } diff --git a/lib/CCreatureHandler.cpp b/lib/CCreatureHandler.cpp index a7e890cdd..f4a1aa2f3 100644 --- a/lib/CCreatureHandler.cpp +++ b/lib/CCreatureHandler.cpp @@ -1118,7 +1118,7 @@ void CCreatureHandler::afterLoadFinalization() { for (CCreature * crea : creatures) { - VLC->objtypeh->createObject(crea->nameSing, JsonNode(), Obj::MONSTER, crea->idNumber.num); + VLC->objtypeh->loadSubObject(crea->nameSing, JsonNode(), Obj::MONSTER, crea->idNumber.num); if (!crea->advMapDef.empty()) { JsonNode templ; @@ -1128,7 +1128,7 @@ void CCreatureHandler::afterLoadFinalization() // 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->eraseObject(Obj::MONSTER, crea->idNumber.num); + VLC->objtypeh->removeSubObject(Obj::MONSTER, crea->idNumber.num); } } diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index de6f318fb..b8e3bea74 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -235,7 +235,7 @@ void CHeroClassHandler::afterLoadFinalization() for (CHeroClass * hc : heroClasses) { - VLC->objtypeh->createObject(hc->identifier, JsonNode(), Obj::HERO, hc->id); + VLC->objtypeh->loadSubObject(hc->identifier, JsonNode(), Obj::HERO, hc->id); if (!hc->imageMapMale.empty()) { JsonNode templ; diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 361a0b362..2658ecb14 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -104,6 +104,7 @@ set(lib_HEADERS mapObjects/MapObjects.h + CSoundBase.h AI_Base.h CondSh.h ConstTransitivePtr.h diff --git a/client/CSoundBase.h b/lib/CSoundBase.h similarity index 100% rename from client/CSoundBase.h rename to lib/CSoundBase.h diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 16a4b2cbc..34670b0a7 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -763,7 +763,7 @@ void CTownHandler::afterLoadFinalization() { if (fact->town) { - VLC->objtypeh->createObject(fact->identifier, JsonNode(), Obj::TOWN, fact->index); + VLC->objtypeh->loadSubObject(fact->identifier, JsonNode(), Obj::TOWN, fact->index); if (!fact->town->clientInfo.advMapCastle.empty()) { JsonNode templ; @@ -782,7 +782,7 @@ void CTownHandler::afterLoadFinalization() JsonNode templ; templ["animation"].String() = fact->town->dwellings[i]; - VLC->objtypeh->createObject("", JsonNode(), Obj::CREATURE_GENERATOR1, 80 + cre); + VLC->objtypeh->loadSubObject("", JsonNode(), Obj::CREATURE_GENERATOR1, 80 + cre); VLC->objtypeh->getHandlerFor(Obj::CREATURE_GENERATOR1, 80 + cre)->addTemplate(templ); VLC->objh->cregens[80 + cre] = cre; //map of dwelling -> creature id } diff --git a/lib/JsonNode.h b/lib/JsonNode.h index c1cc6fb5d..dfe9aba0b 100644 --- a/lib/JsonNode.h +++ b/lib/JsonNode.h @@ -108,19 +108,14 @@ public: template void serialize(Handler &h, const int version) { h & meta; - // simple saving - save json in its string interpretation - if (h.saving) - { - std::ostringstream stream; - stream << *this; - std::string str = stream.str(); - h & str; - } - else - { - std::string str; - h & str; - JsonNode(str.c_str(), str.size()).swap(*this); + h & type; + switch (type) { + break; case DATA_NULL: + break; case DATA_BOOL: h & data.Bool; + break; case DATA_FLOAT: h & data.Float; + break; case DATA_STRING: h & data.String; + break; case DATA_VECTOR: h & data.Vector; + break; case DATA_STRUCT: h & data.Struct; } } }; diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index cd6e06ebb..0302310d9 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -13,7 +13,7 @@ #include "../NetPacks.h" #include "../CGeneralTextHandler.h" -#include "../client/CSoundBase.h" +#include "../CSoundBase.h" using namespace boost::assign; diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 1c3aa9a01..977f3b05e 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -14,7 +14,7 @@ #include "../NetPacks.h" #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" -#include "../client/CSoundBase.h" +#include "../CSoundBase.h" #include "../CSpellHandler.h" #include "CObjectClassesHandler.h" diff --git a/lib/mapObjects/CGPandoraBox.cpp b/lib/mapObjects/CGPandoraBox.cpp index 6919fa23d..8f102e0a0 100644 --- a/lib/mapObjects/CGPandoraBox.cpp +++ b/lib/mapObjects/CGPandoraBox.cpp @@ -12,7 +12,7 @@ #include "CGPandoraBox.h" #include "../NetPacks.h" -#include "../client/CSoundBase.h" +#include "../CSoundBase.h" #include "../CSpellHandler.h" diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 8cc66317c..acadb524c 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -185,7 +185,7 @@ void CObjectClassesHandler::loadObject(std::string scope, std::string name, cons VLC->modh->identifiers.registerObject(scope, "object", name, object->id); } -void CObjectClassesHandler::createObject(std::string name, JsonNode config, si32 ID, boost::optional subID) +void CObjectClassesHandler::loadSubObject(std::string name, JsonNode config, si32 ID, boost::optional subID) { config.setType(JsonNode::DATA_STRUCT); // ensure that input is not NULL assert(objects.count(ID)); @@ -201,7 +201,7 @@ void CObjectClassesHandler::createObject(std::string name, JsonNode config, si32 loadObjectEntry(config, objects[ID]); } -void CObjectClassesHandler::eraseObject(si32 ID, si32 subID) +void CObjectClassesHandler::removeSubObject(si32 ID, si32 subID) { assert(objects.count(ID)); assert(objects.at(ID)->objects.count(subID)); @@ -225,6 +225,28 @@ TObjectTypeHandler CObjectClassesHandler::getHandlerFor(si32 type, si32 subtype) return nullptr; } +std::set CObjectClassesHandler::knownObjects() const +{ + std::set ret; + + for (auto entry : objects) + ret.insert(entry.first); + + return ret; +} + +std::set CObjectClassesHandler::knownSubObjects(si32 primaryID) const +{ + std::set ret; + + if (objects.count(primaryID)) + { + for (auto entry : objects.at(primaryID)->objects) + ret.insert(entry.first); + } + return ret; +} + void CObjectClassesHandler::beforeValidate(JsonNode & object) { for (auto & entry : object["types"].Struct()) @@ -239,7 +261,6 @@ void CObjectClassesHandler::beforeValidate(JsonNode & object) void CObjectClassesHandler::afterLoadFinalization() { - legacyTemplates.clear(); // whatever left there is no longer needed for (auto entry : objects) { for (auto obj : entry.second->objects) @@ -262,9 +283,26 @@ void AObjectTypeHandler::setType(si32 type, si32 subtype) this->subtype = subtype; } +static ui32 loadJsonOrMax(const JsonNode & input) +{ + if (input.isNull()) + return std::numeric_limits::max(); + else + return input.Float(); +} + void AObjectTypeHandler::init(const JsonNode & input) { base = input["base"]; + + if (!input["rmg"].isNull()) + { + rmgInfo.value = input["rmg"]["value"].Float(); + rmgInfo.mapLimit = loadJsonOrMax(input["rmg"]["mapLimit"]); + rmgInfo.zoneLimit = loadJsonOrMax(input["rmg"]["zoneLimit"]); + rmgInfo.rarity = input["rmg"]["rarity"].Float(); + } // else block is not needed - set in constructor + for (auto entry : input["templates"].Struct()) { entry.second.setType(JsonNode::DATA_STRUCT); @@ -281,7 +319,7 @@ void AObjectTypeHandler::init(const JsonNode & input) bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const { - return true; // by default - accept all. + return false; // by default there are no overrides } void AObjectTypeHandler::addTemplate(ObjectTemplate templ) @@ -331,3 +369,8 @@ boost::optional AObjectTypeHandler::getOverride(si32 terrainType } return boost::optional(); } + +const RandomMapInfo & AObjectTypeHandler::getRMGInfo() +{ + return rmgInfo; +} diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 9a750e71f..39a289741 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -19,6 +19,34 @@ class JsonNode; class CRandomGenerator; +/// Structure that describes placement rules for this object in random map +struct RandomMapInfo +{ + /// How valuable this object is, 1k = worthless, 10k = Utopia-level + ui32 value; + + /// How many of such objects can be placed on map, 0 = object can not be placed by RMG + ui32 mapLimit; + + /// How many of such objects can be placed in one zone, 0 = unplaceable + ui32 zoneLimit; + + /// Rarity of object, 5 = extremely rare, 100 = common + ui32 rarity; + + RandomMapInfo(): + value(0), + mapLimit(0), + zoneLimit(0), + rarity(0) + {} + + template void serialize(Handler &h, const int version) + { + h & value & mapLimit & zoneLimit & rarity; + } +}; + class IObjectInfo { public: @@ -42,6 +70,8 @@ class CGObjectInstance; class AObjectTypeHandler { + RandomMapInfo rmgInfo; + si32 type; si32 subtype; @@ -56,22 +86,22 @@ public: void setType(si32 type, si32 subtype); - /// loads templates from Json structure using fields "base" and "templates" + /// loads generic data from Json structure virtual void init(const JsonNode & input); void addTemplate(ObjectTemplate templ); void addTemplate(JsonNode config); - /// returns all templates, without any filters + /// returns all templates matching parameters std::vector getTemplates() const; - - /// returns all templates that can be placed on specific terrain type std::vector getTemplates(si32 terrainType) const; /// returns preferred template for this object, if present (e.g. one of 3 possible templates for town - village, fort and castle) /// note that appearance will not be changed - this must be done separately (either by assignment or via pack from server) boost::optional getOverride(si32 terrainType, const CGObjectInstance * object) const; + const RandomMapInfo & getRMGInfo(); + /// Creates object and set up core properties (like ID/subID). Object is NOT initialized /// to allow creating objects before game start (e.g. map loading) virtual CGObjectInstance * create(ObjectTemplate tmpl) const = 0; @@ -84,7 +114,7 @@ public: template void serialize(Handler &h, const int version) { - h & type & subtype & templates; + h & type & subtype & templates & rmgInfo; } }; @@ -129,7 +159,7 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase template void serialize(Handler &h, const int version) { - h & base & objects; + h & name & handlerName & base & objects; } }; @@ -138,10 +168,10 @@ class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase /// list of object handlers, each of them handles only one type std::map objects; - /// map that is filled during contruction with all known handlers. Not serializeable + /// map that is filled during contruction with all known handlers. Not serializeable due to usage of std::function std::map > handlerConstructors; - /// container with H3 templates, used only during loading + /// container with H3 templates, used only during loading, no need to serialize it TTemplatesContainer legacyTemplates; void loadObjectEntry(const JsonNode & entry, ObjectContainter * obj); @@ -154,14 +184,18 @@ 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 createObject(std::string name, JsonNode config, si32 ID, boost::optional subID = boost::optional()); - void eraseObject(si32 ID, si32 subID); + void loadSubObject(std::string name, JsonNode config, si32 ID, boost::optional subID = boost::optional()); + void removeSubObject(si32 ID, si32 subID); void beforeValidate(JsonNode & object) override; void afterLoadFinalization() override; std::vector getDefaultAllowed() const override; + /// Queries to detect loaded objects + std::set knownObjects() const; + std::set knownSubObjects(si32 primaryID) const; + /// returns handler for specified object (ID-based). ObjectHandler keeps ownership TObjectTypeHandler getHandlerFor(si32 type, si32 subtype) const; diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index a8ffc532b..5bdd8a10d 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -14,7 +14,7 @@ #include "../NetPacks.h" #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" -#include "../client/CSoundBase.h" +#include "../CSoundBase.h" #include "CObjectClassesHandler.h" diff --git a/lib/mapObjects/CQuest.cpp b/lib/mapObjects/CQuest.cpp index 80a4d917e..c899b4071 100644 --- a/lib/mapObjects/CQuest.cpp +++ b/lib/mapObjects/CQuest.cpp @@ -13,7 +13,7 @@ #include "CQuest.h" #include "../NetPacks.h" -#include "../client/CSoundBase.h" +#include "../CSoundBase.h" #include "../CGeneralTextHandler.h" #include "../CHeroHandler.h" #include "CObjectClassesHandler.h" diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 51789c546..500d1c2c3 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -13,7 +13,7 @@ #include "CRewardableObject.h" #include "../CHeroHandler.h" #include "../CGeneralTextHandler.h" -#include "../client/CSoundBase.h" +#include "../CSoundBase.h" #include "../NetPacks.h" #include "CObjectClassesHandler.h" diff --git a/lib/mapObjects/MiscObjects.cpp b/lib/mapObjects/MiscObjects.cpp index 98603744d..4fbfade1b 100644 --- a/lib/mapObjects/MiscObjects.cpp +++ b/lib/mapObjects/MiscObjects.cpp @@ -13,7 +13,7 @@ #include "../NetPacks.h" #include "../CGeneralTextHandler.h" -#include "../client/CSoundBase.h" +#include "../CSoundBase.h" #include "CObjectClassesHandler.h" #include "../CSpellHandler.h" diff --git a/server/CGameHandler.cpp b/server/CGameHandler.cpp index cd5f7413b..98255ea42 100644 --- a/server/CGameHandler.cpp +++ b/server/CGameHandler.cpp @@ -21,7 +21,7 @@ #include "../lib/mapping/CMap.h" #include "../lib/VCMIDirs.h" #include "../lib/ScopeGuard.h" -#include "../client/CSoundBase.h" +#include "../lib/CSoundBase.h" #include "CGameHandler.h" #include "CVCMIServer.h" #include "../lib/CCreatureSet.h" From 7cfd9a0903e6ab1d24d450424cb8c69ff23b3c35 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sat, 14 Jun 2014 18:42:13 +0300 Subject: [PATCH 28/33] First part of implementation of some type-specific handlers. TODO: - merge dwellings.json into object configs - proper implementation of overrides for towns - fully connect new API to RMG/H3M --- config/objects/generic.json | 76 +++++------ config/objects/moddables.json | 10 ++ lib/CMakeLists.txt | 1 + lib/mapObjects/CObjectClassesHandler.cpp | 33 ++++- lib/mapObjects/CObjectClassesHandler.h | 35 ++--- lib/mapObjects/CRewardableConstructor.cpp | 2 +- lib/mapObjects/CRewardableConstructor.h | 2 +- lib/mapObjects/CommonConstructors.cpp | 153 ++++++++++++++++++++++ lib/mapObjects/CommonConstructors.h | 104 +++++++++++++++ lib/registerTypes/RegisterTypes.h | 8 +- 10 files changed, 350 insertions(+), 74 deletions(-) create mode 100644 lib/mapObjects/CommonConstructors.cpp create mode 100644 lib/mapObjects/CommonConstructors.h diff --git a/config/objects/generic.json b/config/objects/generic.json index 9ba8fe83c..7097dbedc 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -4,7 +4,7 @@ "altarOfSacrifice" : { "index" :2, "handler": "market" }, "tradingPost" : { "index" :221, "handler": "market" }, - "tradingPostDUPLICATE" : { "index" :99, "handler": "market" }, + "tradingPostDUPLICATE" : { "index" :99, "handler": "market" }, "freelancersGuild" : { "index" :213, "handler": "market" }, "blackMarket" : { "index" :7, "handler": "blackMarket" }, @@ -93,41 +93,41 @@ "rocklands" : { "index" :231, "handler": "generic" }, /// Decorations - "cactus" : { "index" :116, "handler": "generic" }, - "canyon" : { "index" :117, "handler": "generic" }, - "crater" : { "index" :118, "handler": "generic" }, - "deadVegetation" : { "index" :119, "handler": "generic" }, - "flowers" : { "index" :120, "handler": "generic" }, - "frozenLake" : { "index" :121, "handler": "generic" }, - "hole" : { "index" :124, "handler": "generic" }, - "kelp" : { "index" :125, "handler": "generic" }, - "lake" : { "index" :126, "handler": "generic" }, - "lavaFlow" : { "index" :127, "handler": "generic" }, - "lavaLake" : { "index" :128, "handler": "generic" }, - "mushrooms" : { "index" :129, "handler": "generic" }, - "log" : { "index" :130, "handler": "generic" }, - "mandrake" : { "index" :131, "handler": "generic" }, - "moss" : { "index" :132, "handler": "generic" }, - "mound" : { "index" :133, "handler": "generic" }, - "mountain" : { "index" :134, "handler": "generic" }, - "oakTrees" : { "index" :135, "handler": "generic" }, - "outcropping" : { "index" :136, "handler": "generic" }, - "pineTrees" : { "index" :137, "handler": "generic" }, - "riverDelta" : { "index" :143, "handler": "generic" }, - "rock" : { "index" :147, "handler": "generic" }, - "sandDune" : { "index" :148, "handler": "generic" }, - "sandPit" : { "index" :149, "handler": "generic" }, - "shrub" : { "index" :150, "handler": "generic" }, - "skull" : { "index" :151, "handler": "generic" }, - "stump" : { "index" :153, "handler": "generic" }, - "trees" : { "index" :155, "handler": "generic" }, - "volcano" : { "index" :158, "handler": "generic" }, - "reef" : { "index" :161, "handler": "generic" }, - "lakeDUPLICATE" : { "index" :177, "handler": "generic" }, - "treesDUPLICATE" : { "index" :199, "handler": "generic" }, - "desertHills" : { "index" :206, "handler": "generic" }, - "dirtHills" : { "index" :207, "handler": "generic" }, - "grassHills" : { "index" :208, "handler": "generic" }, - "roughHills" : { "index" :209, "handler": "generic" }, - "subterraneanRocks" : { "index" :210, "handler": "generic" } + "cactus" : { "index" :116, "handler": "static" }, + "canyon" : { "index" :117, "handler": "static" }, + "crater" : { "index" :118, "handler": "static" }, + "deadVegetation" : { "index" :119, "handler": "static" }, + "flowers" : { "index" :120, "handler": "static" }, + "frozenLake" : { "index" :121, "handler": "static" }, + "hole" : { "index" :124, "handler": "static" }, + "kelp" : { "index" :125, "handler": "static" }, + "lake" : { "index" :126, "handler": "static" }, + "lavaFlow" : { "index" :127, "handler": "static" }, + "lavaLake" : { "index" :128, "handler": "static" }, + "mushrooms" : { "index" :129, "handler": "static" }, + "log" : { "index" :130, "handler": "static" }, + "mandrake" : { "index" :131, "handler": "static" }, + "moss" : { "index" :132, "handler": "static" }, + "mound" : { "index" :133, "handler": "static" }, + "mountain" : { "index" :134, "handler": "static" }, + "oakTrees" : { "index" :135, "handler": "static" }, + "outcropping" : { "index" :136, "handler": "static" }, + "pineTrees" : { "index" :137, "handler": "static" }, + "riverDelta" : { "index" :143, "handler": "static" }, + "rock" : { "index" :147, "handler": "static" }, + "sandDune" : { "index" :148, "handler": "static" }, + "sandPit" : { "index" :149, "handler": "static" }, + "shrub" : { "index" :150, "handler": "static" }, + "skull" : { "index" :151, "handler": "static" }, + "stump" : { "index" :153, "handler": "static" }, + "trees" : { "index" :155, "handler": "static" }, + "volcano" : { "index" :158, "handler": "static" }, + "reef" : { "index" :161, "handler": "static" }, + "lakeDUPLICATE" : { "index" :177, "handler": "static" }, + "treesDUPLICATE" : { "index" :199, "handler": "static" }, + "desertHills" : { "index" :206, "handler": "static" }, + "dirtHills" : { "index" :207, "handler": "static" }, + "grassHills" : { "index" :208, "handler": "static" }, + "roughHills" : { "index" :209, "handler": "static" }, + "subterraneanRocks" : { "index" :210, "handler": "static" } } diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 7bf54f971..0e765e276 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -56,6 +56,16 @@ "index" :98, "handler": "town", "base" : { + "filter" : { + // village image - fort not present + "village" : [ "noneOf", [ "fort" ] ], + // fort image - fort is here but not capitol + "fort" : [ "allOf", [ "fort" ], [ "noneOf", ["capitol" ] ] ], + // capitol image only when capitol is built + "capitol" : [ "capitol" ] + }, + + // "faction" : "stringID", // should be set by engine "base" : { "visitableFrom" : [ "---", "+++", "+++" ], "mask" : [ diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 2658ecb14..6ef239348 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -27,6 +27,7 @@ set(lib_SRCS mapObjects/CGTownInstance.cpp mapObjects/CObjectClassesHandler.cpp mapObjects/CObjectHandler.cpp + mapObjects/CommonConstructors.cpp mapObjects/CQuest.cpp mapObjects/CRewardableConstructor.cpp mapObjects/CRewardableObject.cpp diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index acadb524c..18cc4ab06 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -11,6 +11,7 @@ #include "../JsonNode.h" #include "CRewardableConstructor.h" +#include "CommonConstructors.h" #include "MapObjects.h" /* @@ -31,10 +32,14 @@ CObjectClassesHandler::CObjectClassesHandler() // list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code //WARNING: should be in sync with registerTypesMapObjectTypes function SET_HANDLER_CLASS("configurable", CRewardableConstructor); + SET_HANDLER_CLASS("dwelling", CDwellingInstanceConstructor); + SET_HANDLER_CLASS("hero", CHeroInstanceConstructor); + SET_HANDLER_CLASS("town", CTownInstanceConstructor); + + SET_HANDLER_CLASS("static", CObstacleConstructor); + SET_HANDLER_CLASS("", CObstacleConstructor); - SET_HANDLER("", CGObjectInstance); SET_HANDLER("generic", CGObjectInstance); - SET_HANDLER("market", CGMarket); SET_HANDLER("bank", CBank); SET_HANDLER("cartographer", CCartographer); @@ -46,10 +51,8 @@ CObjectClassesHandler::CObjectClassesHandler() SET_HANDLER("borderGuard", CGBorderGuard); SET_HANDLER("monster", CGCreature); SET_HANDLER("denOfThieves", CGDenOfthieves); - SET_HANDLER("dwelling", CGDwelling); SET_HANDLER("event", CGEvent); SET_HANDLER("garrison", CGGarrison); - SET_HANDLER("hero", CGHeroInstance); SET_HANDLER("heroPlaceholder", CGHeroPlaceholder); SET_HANDLER("keymaster", CGKeymasterTent); SET_HANDLER("lighthouse", CGLighthouse); @@ -73,7 +76,6 @@ CObjectClassesHandler::CObjectClassesHandler() SET_HANDLER("sign", CGSignBottle); SET_HANDLER("siren", CGSirens); SET_HANDLER("teleport", CGTeleport); - SET_HANDLER("town", CGTownInstance); SET_HANDLER("university", CGUniversity); SET_HANDLER("oncePerHero", CGVisitableOPH); SET_HANDLER("oncePerWeek", CGVisitableOPW); @@ -197,7 +199,7 @@ void CObjectClassesHandler::loadSubObject(std::string name, JsonNode config, si3 } JsonUtils::inherit(config, objects.at(ID)->base); - + logGlobal->errorStream() << "JSON: " << config; loadObjectEntry(config, objects[ID]); } @@ -265,6 +267,7 @@ void CObjectClassesHandler::afterLoadFinalization() { for (auto obj : entry.second->objects) { + obj.second->afterLoadFinalization(); if (obj.second->getTemplates().empty()) logGlobal->warnStream() << "No templates found for " << entry.first << ":" << obj.first; } @@ -315,6 +318,7 @@ void AObjectTypeHandler::init(const JsonNode & input) tmpl.readJson(entry.second); templates.push_back(tmpl); } + initTypeData(input); } bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const @@ -322,6 +326,10 @@ bool AObjectTypeHandler::objectFilter(const CGObjectInstance *, const ObjectTemp return false; // by default there are no overrides } +void AObjectTypeHandler::initTypeData(const JsonNode & input) +{ +} + void AObjectTypeHandler::addTemplate(ObjectTemplate templ) { templ.id = Obj(type); @@ -331,6 +339,9 @@ void AObjectTypeHandler::addTemplate(ObjectTemplate templ) void AObjectTypeHandler::addTemplate(JsonNode config) { + logGlobal->errorStream() << "INPUT FOR: " << type << ":" << subtype << " " << config; + logGlobal->errorStream() << "BASE FOR: " << type << ":" << subtype << " " << base; + config.setType(JsonNode::DATA_STRUCT); // ensure that input is not null JsonUtils::inherit(config, base); ObjectTemplate tmpl; @@ -338,6 +349,7 @@ void AObjectTypeHandler::addTemplate(JsonNode config) tmpl.subid = subtype; tmpl.stringID = ""; // TODO? tmpl.readJson(config); + logGlobal->errorStream() << "DATA FOR: " << type << ":" << subtype << " " << config; addTemplate(tmpl); } @@ -374,3 +386,12 @@ const RandomMapInfo & AObjectTypeHandler::getRMGInfo() { return rmgInfo; } + +bool AObjectTypeHandler::isStaticObject() +{ + return false; // most of classes are not static +} + +void AObjectTypeHandler::afterLoadFinalization() +{ +} diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 39a289741..1dd7e4b91 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -81,13 +81,16 @@ class AObjectTypeHandler protected: virtual bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; + + /// initialization for classes that inherit this one + virtual void initTypeData(const JsonNode & input); public: virtual ~AObjectTypeHandler(){} void setType(si32 type, si32 subtype); - /// loads generic data from Json structure - virtual void init(const JsonNode & input); + /// loads generic data from Json structure and passes it towards type-specific constructors + void init(const JsonNode & input); void addTemplate(ObjectTemplate templ); void addTemplate(JsonNode config); @@ -102,11 +105,16 @@ public: const RandomMapInfo & getRMGInfo(); + virtual bool isStaticObject(); + + virtual void afterLoadFinalization(); + /// Creates object and set up core properties (like ID/subID). Object is NOT initialized /// to allow creating objects before game start (e.g. map loading) virtual CGObjectInstance * create(ObjectTemplate tmpl) const = 0; /// Configures object properties. Should be re-entrable, resetting state of the object if necessarily + /// 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 @@ -118,29 +126,6 @@ public: } }; -/// Class that is used for objects that do not have dedicated handler -template -class CDefaultObjectTypeHandler : public AObjectTypeHandler -{ - CGObjectInstance * create(ObjectTemplate tmpl) const - { - auto obj = new ObjectType(); - obj->ID = tmpl.id; - obj->subID = tmpl.subid; - obj->appearance = tmpl; - return obj; - } - - virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const - { - } - - virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const - { - return nullptr; - } -}; - typedef std::shared_ptr TObjectTypeHandler; class DLL_LINKAGE CObjectClassesHandler : public IHandlerBase diff --git a/lib/mapObjects/CRewardableConstructor.cpp b/lib/mapObjects/CRewardableConstructor.cpp index d50736d30..d2abb8336 100644 --- a/lib/mapObjects/CRewardableConstructor.cpp +++ b/lib/mapObjects/CRewardableConstructor.cpp @@ -266,7 +266,7 @@ CRewardableConstructor::CRewardableConstructor() { } -void CRewardableConstructor::init(const JsonNode & config) +void CRewardableConstructor::initTypeData(const JsonNode & config) { AObjectTypeHandler::init(config); objectInfo.init(config); diff --git a/lib/mapObjects/CRewardableConstructor.h b/lib/mapObjects/CRewardableConstructor.h index d15e597d4..a22679167 100644 --- a/lib/mapObjects/CRewardableConstructor.h +++ b/lib/mapObjects/CRewardableConstructor.h @@ -46,9 +46,9 @@ class CRewardableConstructor : public AObjectTypeHandler { CRandomRewardObjectInfo objectInfo; + void initTypeData(const JsonNode & config) override; public: CRewardableConstructor(); - void init(const JsonNode & config) override; CGObjectInstance * create(ObjectTemplate tmpl) const override; diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp new file mode 100644 index 000000000..6686903f0 --- /dev/null +++ b/lib/mapObjects/CommonConstructors.cpp @@ -0,0 +1,153 @@ +#include "StdInc.h" +#include "CommonConstructors.h" + +#include "CGTownInstance.h" +#include "CGHeroInstance.h" +#include "../mapping/CMap.h" +#include "../CHeroHandler.h" +#include "../CCreatureHandler.h" + +/* + * CommonConstructors.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 + * + */ + +CObstacleConstructor::CObstacleConstructor() +{ +} + +bool CObstacleConstructor::isStaticObject() +{ + return true; +} + +CTownInstanceConstructor::CTownInstanceConstructor() +{ +} + +void CTownInstanceConstructor::initTypeData(const JsonNode & input) +{ + VLC->modh->identifiers.requestIdentifier("faction", input["faction"], + [&](si32 index) { faction = VLC->townh->factions[index]; }); + + filtersJson = input["filters"]; +} + +void CTownInstanceConstructor::afterLoadFinalization() +{ + for (auto entry : filtersJson.Struct()) + { + filters[entry.first] = LogicalExpression(entry.second, [&](const JsonNode & node) + { + return BuildingID(VLC->modh->identifiers.getIdentifier("building." + faction->identifier, node.Vector()[0]).get()); + }); + } +} + +bool CTownInstanceConstructor::objectFilter(const CGObjectInstance * object, const ObjectTemplate & templ) const +{ + auto town = dynamic_cast(object); + + auto buildTest = [&](const BuildingID & id) + { + return town->hasBuilt(id); + }; + + if (filters.count(templ.stringID)) + return filters.at(templ.stringID).test(buildTest); + return false; +} + +CGObjectInstance * CTownInstanceConstructor::create(ObjectTemplate tmpl) const +{ + CGTownInstance * obj = createTyped(tmpl); + obj->town = faction->town; + obj->tempOwner = PlayerColor::NEUTRAL; + return obj; +} + +void CTownInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +{ + auto templ = getOverride(object->cb->getTile(object->pos)->terType, object); + if (templ) + object->appearance = templ.get(); +} + +CHeroInstanceConstructor::CHeroInstanceConstructor() +{ + +} + +void CHeroInstanceConstructor::initTypeData(const JsonNode & input) +{ + VLC->modh->identifiers.requestIdentifier("heroClass", input["heroClass"], + [&](si32 index) { heroClass = VLC->heroh->classes.heroClasses[index]; }); +} + +bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, const ObjectTemplate & templ) const +{ + return false; +} + +CGObjectInstance * CHeroInstanceConstructor::create(ObjectTemplate tmpl) const +{ + CGHeroInstance * obj = createTyped(tmpl); + obj->type = nullptr; //FIXME: set to valid value. somehow. + return obj; +} + +void CHeroInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +{ + +} + +CDwellingInstanceConstructor::CDwellingInstanceConstructor() +{ + +} + +void CDwellingInstanceConstructor::initTypeData(const JsonNode & input) +{ + const JsonVector & levels = input["creatures"].Vector(); + availableCreatures.resize(levels.size()); + for (size_t i=0; imodh->identifiers.requestIdentifier("creature", creatures[j], [&] (si32 index) + { + availableCreatures[i][j] = VLC->creh->creatures[index]; + }); + } + } +} + +bool CDwellingInstanceConstructor::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const +{ + return false; +} + +CGObjectInstance * CDwellingInstanceConstructor::create(ObjectTemplate tmpl) const +{ + CGDwelling * obj = createTyped(tmpl); + for (auto entry : availableCreatures) + { + obj->creatures.resize(obj->creatures.size()+1); + + for (const CCreature * cre : entry) + obj->creatures.back().second.push_back(cre->idNumber); + } + return obj; +} + +void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +{ + +} diff --git a/lib/mapObjects/CommonConstructors.h b/lib/mapObjects/CommonConstructors.h new file mode 100644 index 000000000..7730341eb --- /dev/null +++ b/lib/mapObjects/CommonConstructors.h @@ -0,0 +1,104 @@ +#pragma once + +#include "CObjectClassesHandler.h" +#include "../CTownHandler.h" // for building ID-based filters + +/* + * CommonConstructors.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 CGTownInstance; +class CGHeroInstance; +class CGDwelling; +//class CGArtifact; +//class CGCreature; +class CHeroClass; + +/// Class that is used for objects that do not have dedicated handler +template +class CDefaultObjectTypeHandler : public AObjectTypeHandler +{ +protected: + ObjectType * createTyped(ObjectTemplate tmpl) const + { + auto obj = new ObjectType(); + obj->ID = tmpl.id; + obj->subID = tmpl.subid; + obj->appearance = tmpl; + return obj; + } +public: + CDefaultObjectTypeHandler(){} + + CGObjectInstance * create(ObjectTemplate tmpl) const + { + return createTyped(tmpl); + } + + virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const + { + } + + virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const + { + return nullptr; + } +}; + +class CObstacleConstructor : public CDefaultObjectTypeHandler +{ +public: + CObstacleConstructor(); + bool isStaticObject(); +}; + +class CTownInstanceConstructor : public CDefaultObjectTypeHandler +{ + JsonNode filtersJson; +protected: + bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; + +public: + CFaction * faction; + std::map> filters; + + CTownInstanceConstructor(); + CGObjectInstance * create(ObjectTemplate tmpl) const; + void initTypeData(const JsonNode & input); + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; + void afterLoadFinalization(); +}; + +class CHeroInstanceConstructor : public CDefaultObjectTypeHandler +{ +protected: + bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; + +public: + CHeroClass * heroClass; + + CHeroInstanceConstructor(); + CGObjectInstance * create(ObjectTemplate tmpl) const; + void initTypeData(const JsonNode & input); + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; +}; + +class CDwellingInstanceConstructor : public CDefaultObjectTypeHandler +{ +protected: + bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; + +public: + std::vector> availableCreatures; + + CDwellingInstanceConstructor(); + CGObjectInstance * create(ObjectTemplate tmpl) const; + void initTypeData(const JsonNode & input); + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; +}; diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 4e1ea49cd..7940a1bab 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -10,6 +10,7 @@ #include "../CModHandler.h" //needed? #include "../mapObjects/CObjectClassesHandler.h" #include "../mapObjects/CRewardableConstructor.h" +#include "../mapObjects/CommonConstructors.h" #include "../mapObjects/MapObjects.h" /* @@ -77,6 +78,10 @@ template void registerTypesMapObjectTypes(Serializer &s) { s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); + s.template registerType(); #define REGISTER_GENERIC_HANDLER(TYPENAME) s.template registerType >() @@ -92,10 +97,8 @@ void registerTypesMapObjectTypes(Serializer &s) REGISTER_GENERIC_HANDLER(CGBorderGuard); REGISTER_GENERIC_HANDLER(CGCreature); REGISTER_GENERIC_HANDLER(CGDenOfthieves); - REGISTER_GENERIC_HANDLER(CGDwelling); REGISTER_GENERIC_HANDLER(CGEvent); REGISTER_GENERIC_HANDLER(CGGarrison); - REGISTER_GENERIC_HANDLER(CGHeroInstance); REGISTER_GENERIC_HANDLER(CGHeroPlaceholder); REGISTER_GENERIC_HANDLER(CGKeymasterTent); REGISTER_GENERIC_HANDLER(CGLighthouse); @@ -119,7 +122,6 @@ void registerTypesMapObjectTypes(Serializer &s) REGISTER_GENERIC_HANDLER(CGSignBottle); REGISTER_GENERIC_HANDLER(CGSirens); REGISTER_GENERIC_HANDLER(CGTeleport); - REGISTER_GENERIC_HANDLER(CGTownInstance); REGISTER_GENERIC_HANDLER(CGUniversity); REGISTER_GENERIC_HANDLER(CGVisitableOPH); REGISTER_GENERIC_HANDLER(CGVisitableOPW); From 836c466f81ec8ea4495e08eb5914fdfe0322443b Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 15 Jun 2014 19:43:01 +0300 Subject: [PATCH 29/33] - Implementation of overrides for towns - Converted dwellings.json into new format - Implemented "mapObject" entry in town format - Removed capital/fort/village fields from town in favor of overrides --- client/CKingdomInterface.cpp | 2 +- config/dwellings.json | 108 ------------------- config/factions/castle.json | 10 +- config/factions/conflux.json | 10 +- config/factions/dungeon.json | 10 +- config/factions/fortress.json | 10 +- config/factions/inferno.json | 10 +- config/factions/necropolis.json | 10 +- config/factions/rampart.json | 10 +- config/factions/stronghold.json | 10 +- config/factions/tower.json | 10 +- config/gameConfig.json | 1 + config/objects/dwellings.json | 126 +++++++++++++++++++++++ config/objects/moddables.json | 27 ++--- lib/CGameState.cpp | 29 +++--- lib/CModHandler.cpp | 15 +-- lib/CTownHandler.cpp | 53 +++++----- lib/CTownHandler.h | 6 +- lib/JsonNode.cpp | 2 +- lib/VCMI_Lib.cpp | 4 +- lib/mapObjects/CGTownInstance.cpp | 59 ++--------- lib/mapObjects/CObjectClassesHandler.cpp | 8 +- lib/mapObjects/CObjectClassesHandler.h | 2 +- lib/mapObjects/CObjectHandler.cpp | 11 +- lib/mapObjects/CObjectHandler.h | 3 +- lib/mapObjects/CommonConstructors.cpp | 75 ++++++++++++-- lib/mapObjects/CommonConstructors.h | 7 +- 27 files changed, 340 insertions(+), 288 deletions(-) delete mode 100644 config/dwellings.json create mode 100644 config/objects/dwellings.json diff --git a/client/CKingdomInterface.cpp b/client/CKingdomInterface.cpp index c604eab85..83f730515 100644 --- a/client/CKingdomInterface.cpp +++ b/client/CKingdomInterface.cpp @@ -500,7 +500,7 @@ void CKingdomInterface::generateObjectsList(const std::vectorsubID]; if (info.count++ == 0) { - info.hoverText = CGI->creh->creatures[CGI->objh->cregens.find(object->subID)->second]->namePl; + info.hoverText = object->getHoverText(); info.imageID = object->subID; } } diff --git a/config/dwellings.json b/config/dwellings.json deleted file mode 100644 index 192b868e2..000000000 --- a/config/dwellings.json +++ /dev/null @@ -1,108 +0,0 @@ -{ - // Indicate which dwelling produces which creature - // Note that it is 1<->n connection since - // a creature can be produced by more than one dwelling. - "dwellings": [ - { "dwelling": 0, "creature": 106 }, - { "dwelling": 1, "creature": 96 }, - { "dwelling": 2, "creature": 74 }, - { "dwelling": 3, "creature": 66 }, - { "dwelling": 4, "creature": 68 }, - { "dwelling": 5, "creature": 10 }, - { "dwelling": 6, "creature": 14 }, - { "dwelling": 7, "creature": 112 }, - { "dwelling": 8, "creature": 12 }, - { "dwelling": 9, "creature": 94 }, - { "dwelling": 10, "creature": 54 }, - { "dwelling": 11, "creature": 104 }, - { "dwelling": 12, "creature": 16 }, - { "dwelling": 13, "creature": 113 }, - { "dwelling": 14, "creature": 52 }, - { "dwelling": 15, "creature": 18 }, - { "dwelling": 16, "creature": 114 }, - { "dwelling": 17, "creature": 30 }, - { "dwelling": 18, "creature": 36 }, - { "dwelling": 19, "creature": 86 }, - { "dwelling": 20, "creature": 98 }, - { "dwelling": 21, "creature": 84 }, - { "dwelling": 22, "creature": 44 }, - { "dwelling": 23, "creature": 102 }, - { "dwelling": 24, "creature": 26 }, - { "dwelling": 25, "creature": 4 }, - { "dwelling": 26, "creature": 72 }, - { "dwelling": 27, "creature": 46 }, - { "dwelling": 28, "creature": 110 }, - { "dwelling": 29, "creature": 42 }, - { "dwelling": 30, "creature": 100 }, - { "dwelling": 31, "creature": 34 }, - { "dwelling": 32, "creature": 80 }, - { "dwelling": 33, "creature": 76 }, - { "dwelling": 34, "creature": 78 }, - { "dwelling": 35, "creature": 8 }, - { "dwelling": 36, "creature": 38 }, - { "dwelling": 37, "creature": 48 }, - { "dwelling": 38, "creature": 90 }, - { "dwelling": 39, "creature": 88 }, - { "dwelling": 40, "creature": 50 }, - { "dwelling": 41, "creature": 82 }, - { "dwelling": 42, "creature": 92 }, - { "dwelling": 43, "creature": 28 }, - { "dwelling": 44, "creature": 40 }, - { "dwelling": 45, "creature": 22 }, - { "dwelling": 46, "creature": 70 }, - { "dwelling": 47, "creature": 115 }, - { "dwelling": 48, "creature": 60 }, - { "dwelling": 49, "creature": 108 }, - { "dwelling": 50, "creature": 20 }, - { "dwelling": 51, "creature": 24 }, - { "dwelling": 52, "creature": 64 }, - { "dwelling": 53, "creature": 62 }, - { "dwelling": 54, "creature": 56 }, - { "dwelling": 55, "creature": 58 }, - { "dwelling": 56, "creature": 0 }, - { "dwelling": 57, "creature": 2 }, - { "dwelling": 58, "creature": 6 }, - { "dwelling": 59, "creature": 118 }, - { "dwelling": 60, "creature": 120 }, - { "dwelling": 61, "creature": 130 }, - { "dwelling": 62, "creature": 132 }, - { "dwelling": 63, "creature": 133 }, - { "dwelling": 64, "creature": 134 }, - { "dwelling": 65, "creature": 135 }, - { "dwelling": 66, "creature": 136 }, - { "dwelling": 67, "creature": 137 }, - { "dwelling": 68, "creature": 24 }, - { "dwelling": 69, "creature": 112 }, - { "dwelling": 70, "creature": 113 }, - { "dwelling": 71, "creature": 114 }, - { "dwelling": 72, "creature": 115 }, - { "dwelling": 73, "creature": 138 }, - { "dwelling": 74, "creature": 139 }, - { "dwelling": 75, "creature": 140 }, - { "dwelling": 76, "creature": 141 }, - { "dwelling": 77, "creature": 142 }, - { "dwelling": 78, "creature": 143 }, - { "dwelling": 79, "creature": 144 }, - { "dwelling": 80, "creature": 150 }, - { "dwelling": 81, "creature": 151 }, - { "dwelling": 82, "creature": 152 }, - { "dwelling": 83, "creature": 153 }, - { "dwelling": 84, "creature": 154 }, - { "dwelling": 85, "creature": 155 }, - { "dwelling": 86, "creature": 156 }, - { "dwelling": 87, "creature": 157 }, - { "dwelling": 88, "creature": 158 }, - { "dwelling": 89, "creature": 171 }, - { "dwelling": 90, "creature": 170 }, - { "dwelling": 91, "creature": 168 }, - { "dwelling": 92, "creature": 172 }, - { "dwelling": 93, "creature": 164 }, - { "dwelling": 94, "creature": 169 }, - { "dwelling": 95, "creature": 173 }, - { "dwelling": 96, "creature": 192 }, - { "dwelling": 97, "creature": 193 }, - { "dwelling": 98, "creature": 194 }, - { "dwelling": 99, "creature": 195 }, - { "dwelling": 100, "creature": 196 } - ] -} diff --git a/config/factions/castle.json b/config/factions/castle.json index 0c3cfba91..1ce63d763 100644 --- a/config/factions/castle.json +++ b/config/factions/castle.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCcasx0.def", - "village" : "AVCCAST0.DEF", - "capitol" : "AVCCASZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCcasx0.def" }, + "village" : { "animation" : "AVCCAST0.DEF" }, + "capitol" : { "animation" : "AVCCASZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/conflux.json b/config/factions/conflux.json index 1d787b4cd..b34e83a29 100644 --- a/config/factions/conflux.json +++ b/config/factions/conflux.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "avchforx.def", - "village" : "AVCHFOR0.DEF", - "capitol" : "AVCHFORZ.DEF" + "templates" : { + "castle" : { "animation" : "avchforx.def" }, + "village" : { "animation" : "AVCHFOR0.DEF" }, + "capitol" : { "animation" : "AVCHFORZ.DEF" } + } }, "structures" : { diff --git a/config/factions/dungeon.json b/config/factions/dungeon.json index 94340f036..7c15be082 100644 --- a/config/factions/dungeon.json +++ b/config/factions/dungeon.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCdunx0.def", - "village" : "AVCDUNG0.DEF", - "capitol" : "AVCDUNZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCdunx0.def" }, + "village" : { "animation" : "AVCDUNG0.DEF" }, + "capitol" : { "animation" : "AVCDUNZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/fortress.json b/config/factions/fortress.json index 2c2e84842..7dc1c924b 100644 --- a/config/factions/fortress.json +++ b/config/factions/fortress.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCftrx0.def", - "village" : "AVCFTRT0.DEF", - "capitol" : "AVCFORZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCftrx0.def" }, + "village" : { "animation" : "AVCFTRT0.DEF" }, + "capitol" : { "animation" : "AVCFORZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/inferno.json b/config/factions/inferno.json index 173d01bbb..15797e446 100644 --- a/config/factions/inferno.json +++ b/config/factions/inferno.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCinfx0.def", - "village" : "AVCINFT0.DEF", - "capitol" : "AVCINFZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCinfx0.def" }, + "village" : { "animation" : "AVCINFT0.DEF" }, + "capitol" : { "animation" : "AVCINFZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/necropolis.json b/config/factions/necropolis.json index 887606b6c..a12f4cd88 100644 --- a/config/factions/necropolis.json +++ b/config/factions/necropolis.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCnecx0.def", - "village" : "AVCNECR0.DEF", - "capitol" : "AVCNECZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCnecx0.def" }, + "village" : { "animation" : "AVCNECR0.DEF" }, + "capitol" : { "animation" : "AVCNECZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/rampart.json b/config/factions/rampart.json index c1cde4afb..af09ed901 100644 --- a/config/factions/rampart.json +++ b/config/factions/rampart.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCramx0.def", - "village" : "AVCRAMP0.DEF", - "capitol" : "AVCRAMZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCramx0.def" }, + "village" : { "animation" : "AVCRAMP0.DEF" }, + "capitol" : { "animation" : "AVCRAMZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/stronghold.json b/config/factions/stronghold.json index f45037261..f88e38472 100644 --- a/config/factions/stronghold.json +++ b/config/factions/stronghold.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCstrx0.def", - "village" : "AVCSTRO0.DEF", - "capitol" : "AVCSTRZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCstrx0.def" }, + "village" : { "animation" : "AVCSTRO0.DEF" }, + "capitol" : { "animation" : "AVCSTRZ0.DEF" } + } }, "structures" : { diff --git a/config/factions/tower.json b/config/factions/tower.json index 99983052d..5a3f61cda 100644 --- a/config/factions/tower.json +++ b/config/factions/tower.json @@ -66,11 +66,13 @@ }, "town" : { - "adventureMap" : + "mapObject" : { - "castle" : "AVCtowx0.def", - "village" : "AVCTOWR0.DEF", - "capitol" : "AVCTOWZ0.DEF" + "templates" : { + "castle" : { "animation" : "AVCtowx0.def" }, + "village" : { "animation" : "AVCTOWR0.DEF" }, + "capitol" : { "animation" : "AVCTOWZ0.DEF" } + } }, "structures" : { diff --git a/config/gameConfig.json b/config/gameConfig.json index 2f208ade0..ad35ef0e9 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -49,6 +49,7 @@ [ "config/objects/generic.json", "config/objects/moddables.json", + "config/objects/dwellings.json", "config/objects/rewardable.json" ], diff --git a/config/objects/dwellings.json b/config/objects/dwellings.json new file mode 100644 index 000000000..8b5c83f52 --- /dev/null +++ b/config/objects/dwellings.json @@ -0,0 +1,126 @@ +{ + "creatureGeneratorCommon" : { + "index" :17, + "handler": "dwelling", + "base" : { + "base" : { + "visitableFrom" : [ "---", "+++", "+++" ], + "mask" : [ "VVV", "VBB", "VAA" ] + } + }, + "types" : { + "basiliskPit" : { "index" : 0, "creatures" : [ [ "basilisk" ] ] }, + "behemothCrag" : { "index" : 1, "creatures" : [ [ "behemoth" ] ], "guards" : true }, + "pillarOfEyes" : { "index" : 2, "creatures" : [ [ "beholder" ] ] }, + "hallOfDarkness" : { "index" : 3, "creatures" : [ [ "blackKnight" ] ], "guards" : true }, + "dragonVault" : { "index" : 4, "creatures" : [ [ "boneDragon" ] ], "guards" : true }, + "trainingGrounds" : { "index" : 5, "creatures" : [ [ "cavalier" ] ], "guards" : true }, + "centaurStables" : { "index" : 6, "creatures" : [ [ "centaur" ] ] }, + "airConflux" : { "index" : 7, "creatures" : [ [ "airElemental" ] ] }, + "portalOfGlory" : { "index" : 8, "creatures" : [ [ "angel" ] ], "guards" : true }, + "cyclopsCave" : { "index" : 9, "creatures" : [ [ "cyclop" ] ], "guards" : true }, + "forsakenPalace" : { "index" : 10, "creatures" : [ [ "devil" ] ], "guards" : true }, + "serpentFlyHive" : { "index" : 11, "creatures" : [ [ "serpentFly" ] ] }, + "dwarfCottage" : { "index" : 12, "creatures" : [ [ "dwarf" ] ] }, + "earthConflux" : { "index" : 13, "creatures" : [ [ "earthElemental" ] ], "guards" : true }, + "fireLake" : { "index" : 14, "creatures" : [ [ "efreet" ] ], "guards" : true }, + "homestead" : { "index" : 15, "creatures" : [ [ "woodElf" ] ] }, + "fireConflux" : { "index" : 16, "creatures" : [ [ "fireElemental" ] ] }, + "parapet" : { "index" : 17, "creatures" : [ [ "stoneGargoyle" ] ] }, + "altarOfWishes" : { "index" : 18, "creatures" : [ [ "genie" ] ], "guards" : true }, + "wolfPen" : { "index" : 19, "creatures" : [ [ "goblinWolfRider" ] ] }, + "gnollHut" : { "index" : 20, "creatures" : [ [ "gnoll" ] ] }, + "goblinBarracks" : { "index" : 21, "creatures" : [ [ "goblin" ] ] }, + "hallOfSins" : { "index" : 22, "creatures" : [ [ "gog" ] ] }, + "gorgonLair" : { "index" : 23, "creatures" : [ [ "gorgon" ] ], "guards" : true }, + "dragonCliffs" : { "index" : 24, "creatures" : [ [ "greenDragon" ] ], "guards" : true }, + "griffinTower" : { "index" : 25, "creatures" : [ [ "griffin" ] ] }, + "harpyLoft" : { "index" : 26, "creatures" : [ [ "harpy" ] ] }, + "kennels" : { "index" : 27, "creatures" : [ [ "hellHound" ] ] }, + "hydraPond" : { "index" : 28, "creatures" : [ [ "hydra" ] ], "guards" : true }, + "impCrucible" : { "index" : 29, "creatures" : [ [ "imp" ] ] }, + "lizardDen" : { "index" : 30, "creatures" : [ [ "lizardman" ] ] }, + "mageTower" : { "index" : 31, "creatures" : [ [ "mage" ] ] }, + "manticoreLair" : { "index" : 32, "creatures" : [ [ "manticore" ] ], "guards" : true }, + "medusaChapel" : { "index" : 33, "creatures" : [ [ "medusa" ] ] }, + "labyrinth" : { "index" : 34, "creatures" : [ [ "minotaur" ] ], "guards" : true }, + "monastery" : { "index" : 35, "creatures" : [ [ "monk" ] ], "guards" : true }, + "goldenPavilion" : { "index" : 36, "creatures" : [ [ "naga" ] ], "guards" : true }, + "demonGate" : { "index" : 37, "creatures" : [ [ "demon" ] ] }, + "ogreFort" : { "index" : 38, "creatures" : [ [ "ogre" ] ] }, + "orcTower" : { "index" : 39, "creatures" : [ [ "orc" ] ] }, + "hellHole" : { "index" : 40, "creatures" : [ [ "pitFiend" ] ], "guards" : true }, + "dragonCave" : { "index" : 41, "creatures" : [ [ "redDragon" ] ], "guards" : true }, + "cliffNest" : { "index" : 42, "creatures" : [ [ "roc" ] ], "guards" : true }, + "workshop" : { "index" : 43, "creatures" : [ [ "gremlin" ] ] }, + "cloudTemple" : { "index" : 44, "creatures" : [ [ "giant" ] ], "guards" : true }, + "dendroidArches" : { "index" : 45, "creatures" : [ [ "dendroidGuard" ] ], "guards" : true }, + "warren" : { "index" : 46, "creatures" : [ [ "troglodyte" ] ] }, + "waterConflux" : { "index" : 47, "creatures" : [ [ "waterElemental" ] ] }, + "tombOfSouls" : { "index" : 48, "creatures" : [ [ "wight" ] ] }, + "wyvernNest" : { "index" : 49, "creatures" : [ [ "wyvern" ] ], "guards" : true }, + "enchantedSpring" : { "index" : 50, "creatures" : [ [ "pegasus" ] ] }, + "unicornGladeBig" : { "index" : 51, "creatures" : [ [ "unicorn" ] ], "guards" : true }, + "mausoleum" : { "index" : 52, "creatures" : [ [ "lich" ] ], "guards" : true }, + "estate" : { "index" : 53, "creatures" : [ [ "vampire" ] ] }, + "cursedTemple" : { "index" : 54, "creatures" : [ [ "skeleton" ] ] }, + "graveyard" : { "index" : 55, "creatures" : [ [ "walkingDead" ] ] }, + "guardhouse" : { "index" : 56, "creatures" : [ [ "pikeman" ] ] }, + "archersTower" : { "index" : 57, "creatures" : [ [ "archer" ] ] }, + "barracks" : { "index" : 58, "creatures" : [ [ "swordsman" ] ] }, + "magicLantern" : { "index" : 59, "creatures" : [ [ "pixie" ] ] }, + "altarOfThought" : { "index" : 60, "creatures" : [ [ "psychicElemental" ] ], "guards" : true }, + "pyre" : { "index" : 61, "creatures" : [ [ "firebird" ] ], "guards" : true }, + "frozenCliffs" : { "index" : 62, "creatures" : [ [ "azureDragon" ] ], "guards" : true }, + "crystalCavern" : { "index" : 63, "creatures" : [ [ "crystalDragon" ] ], "guards" : true }, + "magicForest" : { "index" : 64, "creatures" : [ [ "fairieDragon" ] ], "guards" : true }, + "sulfurousLair" : { "index" : 65, "creatures" : [ [ "rustDragon" ] ], "guards" : true }, + "enchantersHollow" : { "index" : 66, "creatures" : [ [ "enchanter" ] ], "guards" : true }, + "treetopTower" : { "index" : 67, "creatures" : [ [ "sharpshooter" ] ], "guards" : true }, + "unicornGlade" : { "index" : 68, "creatures" : [ [ "unicorn" ] ], "guards" : true }, + "altarOfAir" : { "index" : 69, "creatures" : [ [ "airElemental" ] ] }, + "altarOfEarth" : { "index" : 70, "creatures" : [ [ "earthElemental" ] ], "guards" : true }, + "altarOfFire" : { "index" : 71, "creatures" : [ [ "fireElemental" ] ] }, + "altarOfWater" : { "index" : 72, "creatures" : [ [ "waterElemental" ] ] }, + "thatchedHut" : { "index" : 73, "creatures" : [ [ "halfling" ] ] }, + "hovel" : { "index" : 74, "creatures" : [ [ "peasant" ] ] }, + "boarGlen" : { "index" : 75, "creatures" : [ [ "boar" ] ] }, + "tombOfCurses" : { "index" : 76, "creatures" : [ [ "mummy" ] ] }, + "nomadTent" : { "index" : 77, "creatures" : [ [ "nomad" ] ] }, + "rogueCavern" : { "index" : 78, "creatures" : [ [ "rogue" ] ] }, + "trollBridge" : { "index" : 79, "creatures" : [ [ "troll" ] ], "guards" : true } + } + }, + // subtype: unique special dwellings - golem factory and elemental conflux + "creatureGeneratorSpecial" : { + "index" :20, + "handler": "dwelling", + "types" : { + "elementalConflux" : { + "index" : 0, + "creatures" : [ // 4 separate "levels" to give them separate growth + [ "airElemental" ], + [ "waterElemental" ], + [ "fireElemental" ], + [ "earthElemental" ] + ], + "guards" : { + "earthElemental" : 12 + } + }, + "golemFactory" : { + "index" : 1, + "creatures" : [ // 4 separate "levels" to give them separate growth + [ "ironGolem" ], + [ "stoneGolem" ], + [ "goldGolem" ], + [ "diamondGolem" ] + ], + "guards" : { + "goldGolem" : 9, + "diamondGolem" : 6 + } + } + } + }, +} diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 0e765e276..be5a9cf8e 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -48,6 +48,16 @@ "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VA" ] } + }, + "types" : { + "wood" : { "index" : 0 }, + "mercury" : { "index" : 1 }, + "ore" : { "index" : 2 }, + "sulfur" : { "index" : 3 }, + "crystal" : { "index" : 4 }, + "gems" : { "index" : 5 }, + "gold" : { "index" : 6 }, + "mithril" : { "index" : 7 } // TODO: move to WoG? } }, @@ -56,7 +66,7 @@ "index" :98, "handler": "town", "base" : { - "filter" : { + "filters" : { // village image - fort not present "village" : [ "noneOf", [ "fort" ] ], // fort image - fort is here but not capitol @@ -125,21 +135,6 @@ // subtype: faction ID "randomDwellingFaction" : { "index" :218, "handler": "dwelling" }, - // subtype: not well defined, describes various dwellings that can be placed as random - "creatureGeneratorCommon" : { - "index" :17, - "handler": "dwelling", - "base" : { - "base" : { - "visitableFrom" : [ "---", "+++", "+++" ], - "mask" : [ "VVV", "VBB", "VAA" ] - } - } - }, - - // subtype: unique special dwellings - golem factory, elemental conflux - "creatureGeneratorSpecial" : { "index" :20, "handler": "dwelling" }, - // don't have subtypes (at least now), but closely connected to this objects "spellScroll" : { "index" :93, "handler": "artifact" }, "heroPlaceholder" : { "index" :214, "handler": "heroPlaceholder" } diff --git a/lib/CGameState.cpp b/lib/CGameState.cpp index f570e766f..45b1510a0 100644 --- a/lib/CGameState.cpp +++ b/lib/CGameState.cpp @@ -622,23 +622,28 @@ std::pair CGameState::pickObject (CGObjectInstance *obj) std::pair result(Obj::NO_OBJ, -1); CreatureID cid = VLC->townh->factions[faction]->town->creatures[level][0]; - //golem factory is not in list of cregens but can be placed as random object - static const CreatureID factoryCreatures[] = {CreatureID::STONE_GOLEM, CreatureID::IRON_GOLEM, - CreatureID::GOLD_GOLEM, CreatureID::DIAMOND_GOLEM}; - std::vector factory(factoryCreatures, factoryCreatures + ARRAY_COUNT(factoryCreatures)); - if (vstd::contains(factory, cid)) - result = std::make_pair(Obj::CREATURE_GENERATOR4, 1); - //NOTE: this will pick last dwelling with this creature (Mantis #900) //check for block map equality is better but more complex solution - for(auto &iter : VLC->objh->cregens) - if (iter.second == cid) - result = std::make_pair(Obj::CREATURE_GENERATOR1, iter.first); + auto testID = [&](Obj primaryID) -> void + { + auto dwellingIDs = VLC->objtypeh->knownSubObjects(primaryID); + for (si32 entry : dwellingIDs) + { + auto handler = dynamic_cast(VLC->objtypeh->getHandlerFor(primaryID, entry).get()); + + if (handler->producesCreature(VLC->creh->creatures[cid])) + result = std::make_pair(primaryID, entry); + } + }; + + testID(Obj::CREATURE_GENERATOR1); + if (result.first == Obj::NO_OBJ) + testID(Obj::CREATURE_GENERATOR4); if (result.first == Obj::NO_OBJ) { - logGlobal->errorStream() << "Error: failed to find creature for dwelling of "<< int(faction) << " of level " << int(level); - result = std::make_pair(Obj::CREATURE_GENERATOR1, RandomGeneratorUtil::nextItem(VLC->objh->cregens, rand)->first); + logGlobal->errorStream() << "Error: failed to find dwelling for "<< VLC->townh->factions[faction]->name << " of level " << int(level); + result = std::make_pair(Obj::CREATURE_GENERATOR1, *RandomGeneratorUtil::nextItem(VLC->objtypeh->knownSubObjects(Obj::CREATURE_GENERATOR1), rand)); } return result; diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index 456a0575b..e4147c01e 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -128,7 +128,7 @@ boost::optional CIdentifierStorage::getIdentifier(std::string scope, std:: if (idList.size() == 1) return idList.front().id; if (!silent) - logGlobal->errorStream() << "Failed to resolve identifier " << name << " from mod " << scope; + logGlobal->errorStream() << "Failed to resolve identifier " << name << " of type " << type << " from mod " << scope; return boost::optional(); } @@ -141,7 +141,7 @@ boost::optional CIdentifierStorage::getIdentifier(std::string type, const if (idList.size() == 1) return idList.front().id; if (!silent) - logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " from mod " << type; + logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " of type " << type << " from mod " << name.meta; return boost::optional(); } @@ -155,7 +155,7 @@ boost::optional CIdentifierStorage::getIdentifier(const JsonNode & name, b if (idList.size() == 1) return idList.front().id; if (!silent) - logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " from mod " << name.meta; + logGlobal->errorStream() << "Failed to resolve identifier " << name.String() << " of type " << pair2.first << " from mod " << name.meta; return boost::optional(); } @@ -223,7 +223,9 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request) } if (request.optional && identifiers.empty()) // failed to resolve optinal ID + { return true; + } // error found. Try to generate some debug info if (identifiers.size() == 0) @@ -244,16 +246,17 @@ void CIdentifierStorage::finalize() { bool errorsFound = false; - for(const ObjectCallback & request : scheduledRequests) + //Note: we may receive new requests during resolution phase -> end may change -> range for can't be used + for(auto it = scheduledRequests.begin(); it != scheduledRequests.end(); it++) { - errorsFound |= !resolveIdentifier(request); + errorsFound |= !resolveIdentifier(*it); } if (errorsFound) { for(auto object : registeredObjects) { - logGlobal->traceStream() << object.first << " -> " << object.second.id; + logGlobal->traceStream() << object.second.scope << " : " << object.first << " -> " << object.second.id; } logGlobal->errorStream() << "All known identifiers were dumped into log file"; } diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index 34670b0a7..b937b8bc6 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -554,10 +554,6 @@ void CTownHandler::loadClientData(CTown &town, const JsonNode & source) info.tavernVideo = "TAVERN.BIK"; //end of legacy assignment - info.advMapVillage = source["adventureMap"]["village"].String(); - info.advMapCastle = source["adventureMap"]["castle"].String(); - info.advMapCapitol = source["adventureMap"]["capitol"].String(); - loadTownHall(town, source["hallSlots"]); loadStructures(town, source["structures"]); loadSiegeScreen(town, source["siege"]); @@ -723,6 +719,8 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod auto object = loadFromJson(data, name); object->index = factions.size(); + factions.push_back(object); + if (object->town) { auto & info = object->town->clientInfo; @@ -730,9 +728,16 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod info.icons[0][1] = 8 + object->index * 4 + 1; info.icons[1][0] = 8 + object->index * 4 + 2; info.icons[1][1] = 8 + object->index * 4 + 3; - } - factions.push_back(object); + VLC->modh->identifiers.requestIdentifier(scope, "object", "town", [=](si32 index) + { + // register town once objects are loaded + JsonNode config = data["town"]["mapObject"]; + config["faction"].String() = object->identifier; + config["faction"].meta = scope; + VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index); + }); + } VLC->modh->identifiers.registerObject(scope, "faction", name, object->index); } @@ -741,6 +746,9 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod { auto object = loadFromJson(data, name); object->index = index; + assert(factions[index] == nullptr); // ensure that this id was not loaded before + factions[index] = object; + if (object->town) { auto & info = object->town->clientInfo; @@ -748,10 +756,16 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod info.icons[0][1] = (GameConstants::F_NUMBER + object->index) * 2 + 1; info.icons[1][0] = object->index * 2 + 0; info.icons[1][1] = object->index * 2 + 1; - } - assert(factions[index] == nullptr); // ensure that this id was not loaded before - factions[index] = object; + VLC->modh->identifiers.requestIdentifier(scope, "object", "town", [=](si32 index) + { + // register town once objects are loaded + JsonNode config = data["town"]["mapObject"]; + config["faction"].String() = object->identifier; + config["faction"].meta = scope; + VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index); + }); + } VLC->modh->identifiers.registerObject(scope, "faction", name, object->index); } @@ -761,31 +775,20 @@ void CTownHandler::afterLoadFinalization() initializeRequirements(); for (CFaction * fact : factions) { + // MODS COMPATIBILITY FOR 0.96 if (fact->town) { - VLC->objtypeh->loadSubObject(fact->identifier, JsonNode(), Obj::TOWN, fact->index); - if (!fact->town->clientInfo.advMapCastle.empty()) - { - JsonNode templ; - templ["animation"].String() = fact->town->clientInfo.advMapCastle; - VLC->objtypeh->getHandlerFor(Obj::TOWN, fact->index)->addTemplate(templ); - } - assert(fact->town->dwellings.size() == fact->town->dwellingNames.size()); for (size_t i=0; itown->dwellings.size(); i++) { //both unupgraded and upgraded get same dwelling for (auto cre : fact->town->creatures[i]) { - if (VLC->objh->cregens.count(cre) == 0) - { - JsonNode templ; - templ["animation"].String() = fact->town->dwellings[i]; + JsonNode templ; + templ["animation"].String() = fact->town->dwellings[i]; - VLC->objtypeh->loadSubObject("", JsonNode(), Obj::CREATURE_GENERATOR1, 80 + cre); - VLC->objtypeh->getHandlerFor(Obj::CREATURE_GENERATOR1, 80 + cre)->addTemplate(templ); - VLC->objh->cregens[80 + cre] = cre; //map of dwelling -> creature id - } + VLC->objtypeh->loadSubObject("", JsonNode(), Obj::CREATURE_GENERATOR1, 100 + cre); + VLC->objtypeh->getHandlerFor(Obj::CREATURE_GENERATOR1, 100 + cre)->addTemplate(templ); } } } diff --git a/lib/CTownHandler.h b/lib/CTownHandler.h index 6f9c0a266..5fcebcbd9 100644 --- a/lib/CTownHandler.h +++ b/lib/CTownHandler.h @@ -190,10 +190,6 @@ public: /// NOTE: index in vector is meaningless. Vector used instead of list for a bit faster access std::vector > structures; - std::string advMapVillage; - std::string advMapCastle; - std::string advMapCapitol; - std::string siegePrefix; std::vector siegePositions; CreatureID siegeShooter; // shooter creature ID @@ -201,7 +197,7 @@ public: template void serialize(Handler &h, const int version) { h & icons & iconSmall & iconLarge & tavernVideo & musicTheme & townBackground & guildBackground & guildWindow & buildingsIcons & hallBackground; - h & advMapVillage & advMapCastle & advMapCapitol & hallSlots & structures; + h & hallSlots & structures; h & siegePrefix & siegePositions & siegeShooter; } } clientInfo; diff --git a/lib/JsonNode.cpp b/lib/JsonNode.cpp index 03f9ce6d7..e5f6bf58a 100644 --- a/lib/JsonNode.cpp +++ b/lib/JsonNode.cpp @@ -737,7 +737,7 @@ void JsonUtils::mergeCopy(JsonNode & dest, JsonNode source) void JsonUtils::inherit(JsonNode & descendant, const JsonNode & base) { - JsonNode inheritedNode(base); + JsonNode inheritedNode(base); merge(inheritedNode,descendant); descendant.swap(inheritedNode); } diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 706c21886..695070a75 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -45,11 +45,11 @@ DLL_LINKAGE void preinitDLL(CConsoleHandler *Console) DLL_LINKAGE void loadDLLClasses() { - try +// try { VLC->init(); } - HANDLE_EXCEPTION; +// HANDLE_EXCEPTION; } const IBonusTypeHandler * LibClasses::getBth() const diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 4ec26050e..5c279b9ae 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -13,6 +13,7 @@ #include "../NetPacks.h" #include "../CGeneralTextHandler.h" +#include "../mapObjects/CObjectClassesHandler.h" using namespace boost::assign; @@ -24,54 +25,18 @@ void CGDwelling::initObj() switch(ID) { case Obj::CREATURE_GENERATOR1: + case Obj::CREATURE_GENERATOR4: { - CreatureID crid = VLC->objh->cregens[subID]; - const CCreature *crs = VLC->creh->creatures[crid]; + VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, cb->gameState()->getRandomGenerator()); - creatures.resize(1); - creatures[0].second.push_back(crid); - if (subID >= VLC->generaltexth->creGens.size()) //very messy workaround - { - auto & dwellingNames = VLC->townh->factions[crs->faction]->town->dwellingNames; - assert (dwellingNames.size() > crs->level - 1); - hoverName = dwellingNames[crs->level - 1]; - } - else - hoverName = VLC->generaltexth->creGens[subID]; - if(crs->level > 4) - putStack(SlotID(0), new CStackInstance(crs, (crs->growth) * 3)); if (getOwner() != PlayerColor::NEUTRAL) cb->gameState()->players[getOwner()].dwellings.push_back (this); } - break; + //putStack(SlotID(0), new CStackInstance(CreatureID::GOLD_GOLEM, 9)); + //putStack(SlotID(1), new CStackInstance(CreatureID::DIAMOND_GOLEM, 6)); - case Obj::CREATURE_GENERATOR4: - creatures.resize(4); - if(subID == 1) //Golem Factory - { - creatures[0].second.push_back(CreatureID::STONE_GOLEM); - creatures[1].second.push_back(CreatureID::IRON_GOLEM); - creatures[2].second.push_back(CreatureID::GOLD_GOLEM); - creatures[3].second.push_back(CreatureID::DIAMOND_GOLEM); - //guards - putStack(SlotID(0), new CStackInstance(CreatureID::GOLD_GOLEM, 9)); - putStack(SlotID(1), new CStackInstance(CreatureID::DIAMOND_GOLEM, 6)); - } - else if(subID == 0) // Elemental Conflux - { - creatures[0].second.push_back(CreatureID::AIR_ELEMENTAL); - creatures[1].second.push_back(CreatureID::FIRE_ELEMENTAL); - creatures[2].second.push_back(CreatureID::EARTH_ELEMENTAL); - creatures[3].second.push_back(CreatureID::WATER_ELEMENTAL); - //guards - putStack(SlotID(0), new CStackInstance(CreatureID::EARTH_ELEMENTAL, 12)); - } - else - { - assert(0); - } - hoverName = VLC->generaltexth->creGens4[subID]; - break; + //putStack(SlotID(0), new CStackInstance(CreatureID::EARTH_ELEMENTAL, 12)); + break; case Obj::REFUGEE_CAMP: //is handled within newturn func @@ -811,12 +776,10 @@ void CGTownInstance::setType(si32 ID, si32 subID) void CGTownInstance::updateAppearance() { - if (!hasFort()) - appearance.animationFile = town->clientInfo.advMapVillage; - else if(hasCapitol()) - appearance.animationFile = town->clientInfo.advMapCapitol; - else - appearance.animationFile = town->clientInfo.advMapCastle; + //FIXME: not the best way to do this + auto app = VLC->objtypeh->getHandlerFor(ID, subID)->getOverride(cb->gameState()->getTile(visitablePos())->terType, this); + if (app) + appearance = app.get(); } std::string CGTownInstance::nodeName() const diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 18cc4ab06..19ab0badc 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -153,6 +153,7 @@ void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContai } obj->objects[id] = handler; + logGlobal->debugStream() << "Loaded object " << obj->id << ":" << id; } CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(const JsonNode & json) @@ -198,8 +199,9 @@ void CObjectClassesHandler::loadSubObject(std::string name, JsonNode config, si3 config["index"].Float() = subID.get(); } + std::string oldMeta = config.meta; // FIXME: move into inheritNode? JsonUtils::inherit(config, objects.at(ID)->base); - logGlobal->errorStream() << "JSON: " << config; + config.setMeta(oldMeta); loadObjectEntry(config, objects[ID]); } @@ -339,9 +341,6 @@ void AObjectTypeHandler::addTemplate(ObjectTemplate templ) void AObjectTypeHandler::addTemplate(JsonNode config) { - logGlobal->errorStream() << "INPUT FOR: " << type << ":" << subtype << " " << config; - logGlobal->errorStream() << "BASE FOR: " << type << ":" << subtype << " " << base; - config.setType(JsonNode::DATA_STRUCT); // ensure that input is not null JsonUtils::inherit(config, base); ObjectTemplate tmpl; @@ -349,7 +348,6 @@ void AObjectTypeHandler::addTemplate(JsonNode config) tmpl.subid = subtype; tmpl.stringID = ""; // TODO? tmpl.readJson(config); - logGlobal->errorStream() << "DATA FOR: " << type << ":" << subtype << " " << config; addTemplate(tmpl); } diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 1dd7e4b91..441f57a26 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -68,7 +68,7 @@ public: class CGObjectInstance; -class AObjectTypeHandler +class AObjectTypeHandler : public boost::noncopyable { RandomMapInfo rmgInfo; diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 5bdd8a10d..d8f2ac4ef 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -157,15 +157,6 @@ static void readBankLevel(const JsonNode &level, BankConfig &bc) CObjectHandler::CObjectHandler() { - logGlobal->traceStream() << "\t\tReading cregens "; - - const JsonNode config(ResourceID("config/dwellings.json")); - for(const JsonNode &dwelling : config["dwellings"].Vector()) - { - cregens[dwelling["dwelling"].Float()] = CreatureID((si32)dwelling["creature"].Float()); - } - logGlobal->traceStream() << "\t\tDone loading cregens!"; - logGlobal->traceStream() << "\t\tReading resources prices "; const JsonNode config2(ResourceID("config/resources.json")); for(const JsonNode &price : config2["resources_prices"].Vector()) @@ -342,10 +333,10 @@ void CGObjectInstance::setType(si32 ID, si32 subID) this->ID = Obj(ID); this->subID = subID; - this->appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(tile.terType).front(); //recalculate blockvis tiles - new appearance might have different blockmap than before cb->gameState()->map->removeBlockVisTiles(this, true); + this->appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(tile.terType).at(0); cb->gameState()->map->addBlockVisTiles(this); } diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index 640ae1c3e..6b980d187 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -187,7 +187,6 @@ struct BankConfig class DLL_LINKAGE CObjectHandler { public: - std::map cregens; //type 17. dwelling subid -> creature ID std::map > > banksInfo; //[index][preset] std::map creBanksNames; //[crebank index] -> name of this creature bank std::vector resVals; //default values of resources in gold @@ -199,6 +198,6 @@ public: template void serialize(Handler &h, const int version) { - h & cregens & banksInfo & creBanksNames & resVals; + h & banksInfo & creBanksNames & resVals; } }; diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index 6686903f0..4df85eb5b 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -26,23 +26,27 @@ bool CObstacleConstructor::isStaticObject() return true; } -CTownInstanceConstructor::CTownInstanceConstructor() +CTownInstanceConstructor::CTownInstanceConstructor(): + faction(nullptr) { } void CTownInstanceConstructor::initTypeData(const JsonNode & input) { - VLC->modh->identifiers.requestIdentifier("faction", input["faction"], - [&](si32 index) { faction = VLC->townh->factions[index]; }); + VLC->modh->identifiers.requestIdentifier("faction", input["faction"], [&](si32 index) + { + faction = VLC->townh->factions[index]; + }); filtersJson = input["filters"]; } void CTownInstanceConstructor::afterLoadFinalization() { + assert(faction); for (auto entry : filtersJson.Struct()) { - filters[entry.first] = LogicalExpression(entry.second, [&](const JsonNode & node) + filters[entry.first] = LogicalExpression(entry.second, [this](const JsonNode & node) { return BuildingID(VLC->modh->identifiers.getIdentifier("building." + faction->identifier, node.Vector()[0]).get()); }); @@ -121,12 +125,13 @@ void CDwellingInstanceConstructor::initTypeData(const JsonNode & input) availableCreatures[i].resize(creatures.size()); for (size_t j=0; jmodh->identifiers.requestIdentifier("creature", creatures[j], [&] (si32 index) + VLC->modh->identifiers.requestIdentifier("creature", creatures[j], [=] (si32 index) { availableCreatures[i][j] = VLC->creh->creatures[index]; }); } } + guards = input["guards"]; } bool CDwellingInstanceConstructor::objectFilter(const CGObjectInstance *, const ObjectTemplate &) const @@ -137,17 +142,69 @@ bool CDwellingInstanceConstructor::objectFilter(const CGObjectInstance *, const CGObjectInstance * CDwellingInstanceConstructor::create(ObjectTemplate tmpl) const { CGDwelling * obj = createTyped(tmpl); - for (auto entry : availableCreatures) - { - obj->creatures.resize(obj->creatures.size()+1); + obj->creatures.resize(availableCreatures.size()); + for (auto & entry : availableCreatures) + { for (const CCreature * cre : entry) obj->creatures.back().second.push_back(cre->idNumber); } return obj; } -void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +namespace { + si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0) + { + if (value.isNull()) + return defaultValue; + if (value.getType() == JsonNode::DATA_FLOAT) + return value.Float(); + si32 min = value["min"].Float(); + si32 max = value["max"].Float(); + return rng.getIntRange(min, max)(); + } + std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (auto & pair : value.Struct()) + { + CStackBasicDescriptor stack; + stack.type = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier(pair.second.meta, "creature", pair.first).get()]; + stack.count = loadValue(pair.second, rng); + ret.push_back(stack); + } + return ret; + } +} + +void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator &rng) const +{ + CGDwelling * dwelling = dynamic_cast(object); + + dwelling->creatures.clear(); + dwelling->creatures.resize(availableCreatures.size()); + + for (auto & entry : availableCreatures) + { + for (const CCreature * cre : entry) + dwelling->creatures.back().second.push_back(cre->idNumber); + } + + for (auto & stack : loadCreatures(guards, rng)) + { + dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.type->idNumber, stack.count)); + } +} + +bool CDwellingInstanceConstructor::producesCreature(const CCreature * crea) const +{ + for (auto & entry : availableCreatures) + { + for (const CCreature * cre : entry) + if (crea == cre) + return true; + } + return false; } diff --git a/lib/mapObjects/CommonConstructors.h b/lib/mapObjects/CommonConstructors.h index 7730341eb..f4f70d79b 100644 --- a/lib/mapObjects/CommonConstructors.h +++ b/lib/mapObjects/CommonConstructors.h @@ -91,14 +91,19 @@ public: class CDwellingInstanceConstructor : public CDefaultObjectTypeHandler { + std::vector> availableCreatures; + + JsonNode guards; + protected: bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; public: - std::vector> availableCreatures; CDwellingInstanceConstructor(); CGObjectInstance * create(ObjectTemplate tmpl) const; void initTypeData(const JsonNode & input); void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; + + bool producesCreature(const CCreature * crea) const; }; From 89b89ff85dc4cf989fa44e8419cdfb18cf7db4b9 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 15 Jun 2014 23:25:10 +0300 Subject: [PATCH 30/33] Bugfixing. Game seems to be working without major bugs. --- lib/CHeroHandler.cpp | 17 ++++++++- lib/CModHandler.cpp | 12 +++++- lib/CModHandler.h | 12 +++++- lib/CTownHandler.cpp | 20 ---------- lib/mapObjects/CRewardableObject.cpp | 53 +++++++++++++-------------- lib/mapObjects/CommonConstructors.cpp | 7 +++- lib/mapping/CCampaignHandler.h | 1 - 7 files changed, 70 insertions(+), 52 deletions(-) diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index b8e3bea74..887c0b02c 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -202,6 +202,14 @@ void CHeroClassHandler::loadObject(std::string scope, std::string name, const Js heroClasses.push_back(object); + VLC->modh->identifiers.requestIdentifier(scope, "object", "hero", [=](si32 index) + { + JsonNode classConf; + classConf["heroClass"].String() = name; + classConf.setMeta(scope); + VLC->objtypeh->loadSubObject(name, classConf, index, object->id); + }); + VLC->modh->identifiers.registerObject(scope, "heroClass", name, object->id); } @@ -213,6 +221,14 @@ void CHeroClassHandler::loadObject(std::string scope, std::string name, const Js assert(heroClasses[index] == nullptr); // ensure that this id was not loaded before heroClasses[index] = object; + VLC->modh->identifiers.requestIdentifier(scope, "object", "hero", [=](si32 index) + { + JsonNode classConf; + classConf["heroClass"].String() = name; + classConf.setMeta(scope); + VLC->objtypeh->loadSubObject(name, classConf, index, object->id); + }); + VLC->modh->identifiers.registerObject(scope, "heroClass", name, object->id); } @@ -235,7 +251,6 @@ void CHeroClassHandler::afterLoadFinalization() for (CHeroClass * hc : heroClasses) { - VLC->objtypeh->loadSubObject(hc->identifier, JsonNode(), Obj::HERO, hc->id); if (!hc->imageMapMale.empty()) { JsonNode templ; diff --git a/lib/CModHandler.cpp b/lib/CModHandler.cpp index e4147c01e..9cd4b8072 100644 --- a/lib/CModHandler.cpp +++ b/lib/CModHandler.cpp @@ -26,6 +26,11 @@ * */ +CIdentifierStorage::CIdentifierStorage(): + state(LOADING) +{ +} + void CIdentifierStorage::checkIdentifier(std::string & ID) { if (boost::algorithm::ends_with(ID, ".")) @@ -81,7 +86,10 @@ void CIdentifierStorage::requestIdentifier(ObjectCallback callback) assert(!callback.localScope.empty()); - scheduledRequests.push_back(callback); + if (state != FINISHED) // enqueue request if loading is still in progress + scheduledRequests.push_back(callback); + else // execute immediately for "late" requests + resolveIdentifier(callback); } void CIdentifierStorage::requestIdentifier(std::string scope, std::string type, std::string name, const std::function & callback) @@ -244,6 +252,7 @@ bool CIdentifierStorage::resolveIdentifier(const ObjectCallback & request) void CIdentifierStorage::finalize() { + state = FINALIZING; bool errorsFound = false; //Note: we may receive new requests during resolution phase -> end may change -> range for can't be used @@ -261,6 +270,7 @@ void CIdentifierStorage::finalize() logGlobal->errorStream() << "All known identifiers were dumped into log file"; } assert(errorsFound == false); + state = FINISHED; } CContentHandler::ContentTypeHandler::ContentTypeHandler(IHandlerBase * handler, std::string objectName): diff --git a/lib/CModHandler.h b/lib/CModHandler.h index 21f486356..279e634e7 100644 --- a/lib/CModHandler.h +++ b/lib/CModHandler.h @@ -25,6 +25,13 @@ class IHandlerBase; /// if possible, objects ID's should be in format ., camelCase e.g. "creature.grandElf" class CIdentifierStorage { + enum ELoadingState + { + LOADING, + FINALIZING, + FINISHED + }; + struct ObjectCallback // entry created on ID request { std::string localScope; /// scope from which this ID was requested @@ -52,6 +59,8 @@ class CIdentifierStorage std::multimap registeredObjects; std::vector scheduledRequests; + ELoadingState state; + /// Check if identifier can be valid (camelCase, point as separator) void checkIdentifier(std::string & ID); @@ -59,6 +68,7 @@ class CIdentifierStorage bool resolveIdentifier(const ObjectCallback & callback); std::vector getPossibleIdentifiers(const ObjectCallback & callback); public: + CIdentifierStorage(); /// request identifier for specific object name. /// Function callback will be called during ID resolution phase of loading void requestIdentifier(std::string scope, std::string type, std::string name, const std::function & callback); @@ -82,7 +92,7 @@ public: template void serialize(Handler &h, const int version) { - h & registeredObjects; + h & registeredObjects & state; } }; diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index b937b8bc6..bd49bd885 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -773,26 +773,6 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod void CTownHandler::afterLoadFinalization() { initializeRequirements(); - for (CFaction * fact : factions) - { - // MODS COMPATIBILITY FOR 0.96 - if (fact->town) - { - assert(fact->town->dwellings.size() == fact->town->dwellingNames.size()); - for (size_t i=0; itown->dwellings.size(); i++) - { - //both unupgraded and upgraded get same dwelling - for (auto cre : fact->town->creatures[i]) - { - JsonNode templ; - templ["animation"].String() = fact->town->dwellings[i]; - - VLC->objtypeh->loadSubObject("", JsonNode(), Obj::CREATURE_GENERATOR1, 100 + cre); - VLC->objtypeh->getHandlerFor(Obj::CREATURE_GENERATOR1, 100 + cre)->addTemplate(templ); - } - } - } - } } void CTownHandler::initializeRequirements() diff --git a/lib/mapObjects/CRewardableObject.cpp b/lib/mapObjects/CRewardableObject.cpp index 500d1c2c3..5fc3731ff 100644 --- a/lib/mapObjects/CRewardableObject.cpp +++ b/lib/mapObjects/CRewardableObject.cpp @@ -629,23 +629,22 @@ void CGBonusingObject::initObj() }; info.resize(1); - CVisitInfo & visit = info[0]; switch(ID) { case Obj::BUOY: blockVisit = true; - configureMessage(visit, 21, 22, soundBase::MORALE); - configureBonus(visit, Bonus::MORALE, +1, 94); + configureMessage(info[0], 21, 22, soundBase::MORALE); + configureBonus(info[0], Bonus::MORALE, +1, 94); break; case Obj::SWAN_POND: - configureMessage(visit, 29, 30, soundBase::LUCK); - configureBonus(visit, Bonus::LUCK, 2, 67); - visit.reward.movePercentage = 0; + configureMessage(info[0], 29, 30, soundBase::LUCK); + configureBonus(info[0], Bonus::LUCK, 2, 67); + info[0].reward.movePercentage = 0; break; case Obj::FAERIE_RING: - configureMessage(visit, 49, 50, soundBase::LUCK); - configureBonus(visit, Bonus::LUCK, 2, 71); + configureMessage(info[0], 49, 50, soundBase::LUCK); + configureBonus(info[0], Bonus::LUCK, 2, 71); break; case Obj::FOUNTAIN_OF_FORTUNE: selectMode = SELECT_RANDOM; @@ -673,19 +672,19 @@ void CGBonusingObject::initObj() break; case Obj::MERMAID: blockVisit = true; - configureMessage(visit, 83, 82, soundBase::LUCK); - configureBonus(visit, Bonus::LUCK, 1, 72); + configureMessage(info[0], 83, 82, soundBase::LUCK); + configureBonus(info[0], Bonus::LUCK, 1, 72); break; case Obj::RALLY_FLAG: - configureMessage(visit, 111, 110, soundBase::MORALE); - configureBonus(visit, Bonus::MORALE, 1, 102); - configureBonus(visit, Bonus::LUCK, 1, 102); - visit.reward.movePoints = 400; + configureMessage(info[0], 111, 110, soundBase::MORALE); + configureBonus(info[0], Bonus::MORALE, 1, 102); + configureBonus(info[0], Bonus::LUCK, 1, 102); + info[0].reward.movePoints = 400; break; case Obj::OASIS: - configureMessage(visit, 95, 94, soundBase::MORALE); - configureBonus(visit, Bonus::MORALE, 1, 95); - visit.reward.movePoints = 800; + configureMessage(info[0], 95, 94, soundBase::MORALE); + configureBonus(info[0], Bonus::MORALE, 1, 95); + info[0].reward.movePoints = 800; break; case Obj::TEMPLE: info[0].limiter.dayOfWeek = 7; @@ -693,24 +692,24 @@ void CGBonusingObject::initObj() configureBonus(info[0], Bonus::MORALE, 2, 96); configureBonus(info[1], Bonus::MORALE, 1, 97); - configureMessage(visit, 140, 141, soundBase::temple); + configureMessage(info[0], 140, 141, soundBase::temple); configureMessage(info[1], 140, 141, soundBase::temple); break; case Obj::WATERING_HOLE: - configureMessage(visit, 166, 167, soundBase::MORALE); - configureBonus(visit, Bonus::MORALE, 1, 100); - visit.reward.movePoints = 400; + configureMessage(info[0], 166, 167, soundBase::MORALE); + configureBonus(info[0], Bonus::MORALE, 1, 100); + info[0].reward.movePoints = 400; break; case Obj::FOUNTAIN_OF_YOUTH: - configureMessage(visit, 57, 58, soundBase::MORALE); - configureBonus(visit, Bonus::MORALE, 1, 103); - visit.reward.movePoints = 400; + configureMessage(info[0], 57, 58, soundBase::MORALE); + configureBonus(info[0], Bonus::MORALE, 1, 103); + info[0].reward.movePoints = 400; break; case Obj::STABLES: - configureMessage(visit, 137, 136, soundBase::STORE); + configureMessage(info[0], 137, 136, soundBase::STORE); - configureBonusDuration(visit, Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, 600, 0); - visit.reward.movePoints = 600; + configureBonusDuration(info[0], Bonus::ONE_WEEK, Bonus::LAND_MOVEMENT, 600, 0); + info[0].reward.movePoints = 600; //TODO: upgrade champions to cavaliers /* bool someUpgradeDone = false; diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index 4df85eb5b..30b653433 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -192,7 +192,12 @@ void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CR dwelling->creatures.back().second.push_back(cre->idNumber); } - for (auto & stack : loadCreatures(guards, rng)) + if (guards.getType() == JsonNode::DATA_BOOL) + { + const CCreature * crea = availableCreatures.at(0).at(0); + dwelling->putStack(SlotID(0), new CStackInstance(crea->idNumber, crea->growth * 3 )); + } + else for (auto & stack : loadCreatures(guards, rng)) { dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.type->idNumber, stack.count)); } diff --git a/lib/mapping/CCampaignHandler.h b/lib/mapping/CCampaignHandler.h index e1d72a359..23f894d77 100644 --- a/lib/mapping/CCampaignHandler.h +++ b/lib/mapping/CCampaignHandler.h @@ -1,6 +1,5 @@ #pragma once -#include "../../Global.h" #include "../../lib/GameConstants.h" /* From 09d595e3856d1807aa02e07d1fefd9097971e7f0 Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Mon, 16 Jun 2014 19:27:26 +0300 Subject: [PATCH 31/33] - Implemented "mapObject" entry for hero classes - Updated schemas - Some bugfixing --- client/Graphics.cpp | 13 ++- client/mapHandler.cpp | 4 +- config/heroClasses.json | 126 +++++++---------------- config/schemas/faction.json | 42 ++------ config/schemas/heroClass.json | 30 ++---- config/schemas/objectType.json | 2 +- lib/CHeroHandler.cpp | 3 +- lib/CTownHandler.cpp | 9 ++ lib/mapObjects/CGHeroInstance.cpp | 6 +- lib/mapObjects/CGTownInstance.cpp | 1 + lib/mapObjects/CObjectClassesHandler.cpp | 4 +- lib/mapObjects/CObjectHandler.h | 2 +- lib/mapObjects/CommonConstructors.cpp | 24 +++++ lib/mapObjects/CommonConstructors.h | 21 ++++ lib/mapObjects/ObjectTemplate.cpp | 2 - 15 files changed, 125 insertions(+), 164 deletions(-) diff --git a/client/Graphics.cpp b/client/Graphics.cpp index c929ee60a..b8d65ef0e 100644 --- a/client/Graphics.cpp +++ b/client/Graphics.cpp @@ -22,6 +22,7 @@ #include "../lib/GameConstants.h" #include "../lib/CStopWatch.h" #include "CAnimation.h" +#include "../lib/mapObjects/CObjectClassesHandler.h" using namespace boost::assign; using namespace CSDL_Ext; @@ -145,13 +146,11 @@ void Graphics::loadHeroAnims() for(auto & elem : CGI->heroh->classes.heroClasses) { - const CHeroClass * hc = elem; - - if (!vstd::contains(heroAnims, hc->imageMapFemale)) - heroAnims[hc->imageMapFemale] = loadHeroAnim(hc->imageMapFemale, rotations); - - if (!vstd::contains(heroAnims, hc->imageMapMale)) - heroAnims[hc->imageMapMale] = loadHeroAnim(hc->imageMapMale, rotations); + for (auto & templ : VLC->objtypeh->getHandlerFor(Obj::HERO, elem->id)->getTemplates()) + { + if (!heroAnims.count(templ.animationFile)) + heroAnims[templ.animationFile] = loadHeroAnim(templ.animationFile, rotations); + } } boatAnims.push_back(loadHeroAnim("AB01_.DEF", rotations)); diff --git a/client/mapHandler.cpp b/client/mapHandler.cpp index ade34616e..9e89e7504 100644 --- a/client/mapHandler.cpp +++ b/client/mapHandler.cpp @@ -541,10 +541,8 @@ void CMapHandler::terrainRect( int3 top_tile, ui8 anim, const std::vector< std:: //pick graphics of hero (or boat if hero is sailing) if (themp->boat) iv = &graphics->boatAnims[themp->boat->subID]->ourImages; - else if (themp->sex) - iv = &graphics->heroAnims[themp->type->heroClass->imageMapFemale]->ourImages; else - iv = &graphics->heroAnims[themp->type->heroClass->imageMapMale]->ourImages; + iv = &graphics->heroAnims[themp->appearance.animationFile]->ourImages; //pick appropriate flag set if(themp->boat) diff --git a/config/heroClasses.json b/config/heroClasses.json index 0a1449113..1486e73a9 100644 --- a/config/heroClasses.json +++ b/config/heroClasses.json @@ -6,11 +6,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "zealot", - "animation": - { - "battle" : { "male" : "CH00.DEF", "female" : "CH01.DEF" }, - "map": { "male" : "AH00_.def", "female" : "AH00_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH00_.def" } } }, + "animation": { "battle" : { "male" : "CH00.DEF", "female" : "CH01.DEF" } } }, "cleric" : { @@ -19,11 +16,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "zealot", - "animation": - { - "battle" : { "male" : "CH00.DEF", "female" : "CH01.DEF" }, - "map": { "male" : "AH01_.def", "female" : "AH01_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH01_.def" } } }, + "animation": { "battle" : { "male" : "CH00.DEF", "female" : "CH01.DEF" } } }, "ranger" : { @@ -32,11 +26,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "grandElf", - "animation": - { - "battle" : { "male" : "CH02.DEF", "female" : "CH03.DEF" }, - "map": { "male" : "AH02_.def", "female" : "AH02_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH02_.def" } } }, + "animation": { "battle" : { "male" : "CH02.DEF", "female" : "CH03.DEF" } } }, "druid" : { @@ -45,11 +36,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "grandElf", - "animation": - { - "battle" : { "male" : "CH02.DEF", "female" : "CH03.DEF" }, - "map": { "male" : "AH03_.def", "female" : "AH03_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH03_.def" } } }, + "animation": { "battle" : { "male" : "CH02.DEF", "female" : "CH03.DEF" } } }, "alchemist" : { @@ -58,11 +46,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "archMage", - "animation": - { - "battle" : { "male" : "CH05.DEF", "female" : "CH04.DEF" }, - "map": { "male" : "AH04_.def", "female" : "AH04_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH04_.def" } } }, + "animation": { "battle" : { "male" : "CH05.DEF", "female" : "CH04.DEF" } } }, "wizard" : { @@ -71,11 +56,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "archMage", - "animation": - { - "battle" : { "male" : "CH05.DEF", "female" : "CH04.DEF" }, - "map": { "male" : "AH05_.def", "female" : "AH05_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH05_.def" } } }, + "animation": { "battle" : { "male" : "CH05.DEF", "female" : "CH04.DEF" } } }, "demoniac" : { @@ -84,11 +66,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "magog", - "animation": - { - "battle" : { "male" : "CH06.DEF", "female" : "CH07.DEF" }, - "map": { "male" : "AH06_.def", "female" : "AH06_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH06_.def" } } }, + "animation": { "battle" : { "male" : "CH06.DEF", "female" : "CH07.DEF" } } }, "heretic" : { @@ -97,11 +76,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "magog", - "animation": - { - "battle" : { "male" : "CH06.DEF", "female" : "CH07.DEF" }, - "map": { "male" : "AH07_.def", "female" : "AH07_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH07_.def" } } }, + "animation": { "battle" : { "male" : "CH06.DEF", "female" : "CH07.DEF" } } }, "deathknight" : { @@ -110,11 +86,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "powerLich", - "animation": - { - "battle" : { "male" : "CH08.DEF", "female" : "CH09.DEF" }, - "map": { "male" : "AH08_.def", "female" : "AH08_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH08_.def" } } }, + "animation": { "battle" : { "male" : "CH08.DEF", "female" : "CH09.DEF" } } }, "necromancer" : { @@ -123,11 +96,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "powerLich", - "animation": - { - "battle" : { "male" : "CH08.DEF", "female" : "CH09.DEF" }, - "map": { "male" : "AH09_.def", "female" : "AH09_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH09_.def" } } }, + "animation": { "battle" : { "male" : "CH08.DEF", "female" : "CH09.DEF" } } }, "warlock" : { @@ -136,11 +106,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "medusaQueen", - "animation": - { - "battle" : { "male" : "CH010.DEF", "female" : "CH11.DEF" }, - "map": { "male" : "AH10_.def", "female" : "AH10_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH10_.def" } } }, + "animation": { "battle" : { "male" : "CH010.DEF", "female" : "CH11.DEF" } } }, "overlord" : { @@ -149,11 +116,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "medusaQueen", - "animation": - { - "battle" : { "male" : "CH010.DEF", "female" : "CH11.DEF" }, - "map": { "male" : "AH11_.def", "female" : "AH11_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH11_.def" } } }, + "animation": { "battle" : { "male" : "CH010.DEF", "female" : "CH11.DEF" } } }, "barbarian" : { @@ -162,11 +126,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "orcChieftain", - "animation": - { - "battle" : { "male" : "CH013.DEF", "female" : "CH012.DEF" }, - "map": { "male" : "AH12_.def", "female" : "AH12_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH12_.def" } } }, + "animation": { "battle" : { "male" : "CH013.DEF", "female" : "CH012.DEF" } } }, "battlemage" : { @@ -175,11 +136,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "orcChieftain", - "animation": - { - "battle" : { "male" : "CH013.DEF", "female" : "CH012.DEF" }, - "map": { "male" : "AH13_.def", "female" : "AH13_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH13_.def" } } }, + "animation": { "battle" : { "male" : "CH013.DEF", "female" : "CH012.DEF" } } }, "beastmaster" : { @@ -188,11 +146,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "lizardWarrior", - "animation": - { - "battle" : { "male" : "CH014.DEF", "female" : "CH015.DEF" }, - "map": { "male" : "AH14_.def", "female" : "AH14_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH14_.def" } } }, + "animation": { "battle" : { "male" : "CH014.DEF", "female" : "CH015.DEF" } } }, "witch" : { @@ -201,11 +156,8 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "lizardWarrior", - "animation": - { - "battle" : { "male" : "CH014.DEF", "female" : "CH015.DEF" }, - "map": { "male" : "AH15_.def", "female" : "AH15_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH15_.def" } } }, + "animation": { "battle" : { "male" : "CH014.DEF", "female" : "CH015.DEF" } } }, "planeswalker" : { @@ -214,11 +166,8 @@ "defaultTavern" : 5, "affinity" : "might", "commander" : "iceElemental", - "animation": - { - "battle" : { "male" : "CH16.DEF", "female" : "CH16.DEF" }, - "map": { "male" : "AH16_.def", "female" : "AH16_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH16_.def" } } }, + "animation": { "battle" : { "male" : "CH16.DEF", "female" : "CH16.DEF" } } }, "elementalist" : { @@ -227,10 +176,7 @@ "defaultTavern" : 5, "affinity" : "magic", "commander" : "iceElemental", - "animation": - { - "battle" : { "male" : "CH17.DEF", "female" : "CH17.DEF" }, - "map": { "male" : "AH17_.def", "female" : "AH17_.def" } - } + "mapObject" : { "templates" : { "default" : { "animation" : "AH17_.def" } } }, + "animation": { "battle" : { "male" : "CH17.DEF", "female" : "CH17.DEF" } } } } diff --git a/config/schemas/faction.json b/config/schemas/faction.json index 3684ae185..5607eaa76 100644 --- a/config/schemas/faction.json +++ b/config/schemas/faction.json @@ -104,46 +104,16 @@ "type":"object", "additionalProperties" : false, "required" : [ - "adventureMap", "buildingsIcons", "buildings", "creatures", "guildWindow", "names", + "mapObject", "buildingsIcons", "buildings", "creatures", "guildWindow", "names", "hallBackground", "hallSlots", "horde", "mageGuild", "moatDamage", "defaultTavern", "tavernVideo", "guildBackground", "musicTheme", "siege", "structures", "townBackground", "warMachine" ], "description": "town", "properties":{ - "adventureMap": { - "type":"object", - "additionalProperties" : false, - "description": "Paths to images of object on adventure map", - "required" : [ "capitol", "castle", "village" ], - "properties":{ - "capitol": { - "type":"string", - "description": "Town with capitol", - "format" : "defFile" - }, - "castle": { - "type":"string", - "description": "Town with built fort", - "format" : "defFile" - }, - "village": { - "type":"string", - "description": "Village without built fort", - "format" : "defFile" - }, - "dwellings" : { - "type" : "array", - "minItems" : 7, - "maxItems" : 7, - "description" : "Dwellings on adventure map", - "items" : { - "type" : "object", - "additionalProperties" : false, - "required" : [ "name", "graphics" ], - "properties" : { - "name": { "type":"string" }, - "graphics": { "type":"string", "format" : "defFile" } - } - } + "mapObject" : { + "properties" : { + "filters" : { + "type" : "object", + "additionalProperties" : { "type" : "array" } } } }, diff --git a/config/schemas/heroClass.json b/config/schemas/heroClass.json index 59bf42529..d10731694 100644 --- a/config/schemas/heroClass.json +++ b/config/schemas/heroClass.json @@ -6,7 +6,7 @@ "required" : [ "animation", "faction", "highLevelChance", "lowLevelChance", "name", "primarySkills", "secondarySkills", "tavern", "defaultTavern", - "affinity", "commander" + "affinity", "commander", "mapObject" ], "additionalProperties" : false, @@ -15,7 +15,7 @@ "type":"object", "additionalProperties" : false, "description": "Files related to hero animation", - "required": [ "battle", "map" ], + "required": [ "battle" ], "properties":{ "battle": { "type":"object", @@ -34,24 +34,14 @@ "format" : "defFile" } } - }, - "map": { - "type":"object", - "additionalProperties" : false, - "description": "Hero animations for adventure map", - "required": [ "female", "male" ], - "properties":{ - "female": { - "type":"string", - "description": "Female version. Warning: not implemented!", - "format" : "defFile" - }, - "male": { - "type":"string", - "description": "Male version", - "format" : "defFile" - } - } + } + } + }, + "mapObject" : { + "properties" : { + "filters" : { + "type" : "object", + "additionalProperties" : { "type" : "array" } } } }, diff --git a/config/schemas/objectType.json b/config/schemas/objectType.json index d4923e374..8a704735f 100644 --- a/config/schemas/objectType.json +++ b/config/schemas/objectType.json @@ -17,7 +17,7 @@ "base": { "type" : "object" }, - "types": { + "templates": { "type":"object", "additionalProperties": { "$ref" : "vcmi:objectTemplate" diff --git a/lib/CHeroHandler.cpp b/lib/CHeroHandler.cpp index 887c0b02c..b24aba25c 100644 --- a/lib/CHeroHandler.cpp +++ b/lib/CHeroHandler.cpp @@ -104,6 +104,7 @@ CHeroClass *CHeroClassHandler::loadFromJson(const JsonNode & node) heroClass->imageBattleFemale = node["animation"]["battle"]["female"].String(); heroClass->imageBattleMale = node["animation"]["battle"]["male"].String(); + //MODS COMPATIBILITY FOR 0.96 heroClass->imageMapFemale = node["animation"]["map"]["female"].String(); heroClass->imageMapMale = node["animation"]["map"]["male"].String(); @@ -223,7 +224,7 @@ void CHeroClassHandler::loadObject(std::string scope, std::string name, const Js VLC->modh->identifiers.requestIdentifier(scope, "object", "hero", [=](si32 index) { - JsonNode classConf; + JsonNode classConf = data["mapObject"]; classConf["heroClass"].String() = name; classConf.setMeta(scope); VLC->objtypeh->loadSubObject(name, classConf, index, object->id); diff --git a/lib/CTownHandler.cpp b/lib/CTownHandler.cpp index bd49bd885..10c9bc2cd 100644 --- a/lib/CTownHandler.cpp +++ b/lib/CTownHandler.cpp @@ -764,6 +764,15 @@ void CTownHandler::loadObject(std::string scope, std::string name, const JsonNod config["faction"].String() = object->identifier; config["faction"].meta = scope; VLC->objtypeh->loadSubObject(object->identifier, config, index, object->index); + + // MODS COMPATIBILITY FOR 0.96 + auto & advMap = data["town"]["adventureMap"]; + if (!advMap["fort"].isNull()) + { + JsonNode config; + config["appearance"] = advMap["fort"]; + VLC->objtypeh->getHandlerFor(index, object->index)->addTemplate(config); + } }); } diff --git a/lib/mapObjects/CGHeroInstance.cpp b/lib/mapObjects/CGHeroInstance.cpp index 977f3b05e..61e5da8c5 100644 --- a/lib/mapObjects/CGHeroInstance.cpp +++ b/lib/mapObjects/CGHeroInstance.cpp @@ -232,9 +232,9 @@ void CGHeroInstance::initHero(HeroTypeID SUBID) void CGHeroInstance::setType(si32 ID, si32 subID) { assert(ID == Obj::HERO); // just in case - CGObjectInstance::setType(ID, subID); type = VLC->heroh->heroes[subID]; portrait = type->imageIndex; + CGObjectInstance::setType(ID, type->heroClass->id); randomizeArmy(type->heroClass->faction); } @@ -472,6 +472,10 @@ void CGHeroInstance::initObj() skillsInfo.resetMagicSchoolCounter(); skillsInfo.resetWisdomCounter(); + auto customApp = VLC->objtypeh->getHandlerFor(ID, type->heroClass->id)->getOverride(cb->gameState()->getTile(visitablePos())->terType, this); + if (customApp) + appearance = customApp.get(); + for(const auto &spec : type->spec) //TODO: unfity with bonus system { auto bonus = new Bonus(); diff --git a/lib/mapObjects/CGTownInstance.cpp b/lib/mapObjects/CGTownInstance.cpp index 5c279b9ae..ed7d51c84 100644 --- a/lib/mapObjects/CGTownInstance.cpp +++ b/lib/mapObjects/CGTownInstance.cpp @@ -563,6 +563,7 @@ void CGTownInstance::initObj() //add special bonuses from buildings recreateBuildingsBonuses(); + updateAppearance(); } void CGTownInstance::newTurn() const diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 19ab0badc..cd963f395 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -105,7 +105,7 @@ void readTextFile(Container & objects, std::string path) std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) { readTextFile(legacyTemplates, "Data/Objects.txt"); - readTextFile(legacyTemplates, "Data/Heroes.txt"); + //readTextFile(legacyTemplates, "Data/Heroes.txt"); std::vector ret(dataSize);// create storage for 256 objects assert(dataSize == 256); @@ -161,7 +161,7 @@ CObjectClassesHandler::ObjectContainter * CObjectClassesHandler::loadFromJson(co auto obj = new ObjectContainter(); obj->name = json["name"].String(); obj->handlerName = json["handler"].String(); - obj->base = json["base"]; // FIXME: when this data will be actually merged? + obj->base = json["base"]; obj->id = selectNextID(json["index"], objects, 256); for (auto entry : json["types"].Struct()) { diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index 6b980d187..a9ab899b0 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -130,7 +130,7 @@ public: //CGObjectInstance& operator=(const CGObjectInstance & right); virtual const std::string & getHoverText() const; - void setType(si32 ID, si32 subID); + virtual void setType(si32 ID, si32 subID); ///IObjectInterface void initObj() override; diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index 30b653433..792088726 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -91,10 +91,34 @@ void CHeroInstanceConstructor::initTypeData(const JsonNode & input) { VLC->modh->identifiers.requestIdentifier("heroClass", input["heroClass"], [&](si32 index) { heroClass = VLC->heroh->classes.heroClasses[index]; }); + + filtersJson = input["filters"]; +} + +void CHeroInstanceConstructor::afterLoadFinalization() +{ + for (auto entry : filtersJson.Struct()) + { + filters[entry.first] = LogicalExpression(entry.second, [this](const JsonNode & node) + { + return HeroTypeID(VLC->modh->identifiers.getIdentifier("hero", node.Vector()[0]).get()); + }); + } } bool CHeroInstanceConstructor::objectFilter(const CGObjectInstance * object, const ObjectTemplate & templ) const { + auto hero = dynamic_cast(object); + + auto heroTest = [&](const HeroTypeID & id) + { + return hero->type->ID == id; + }; + + if (filters.count(templ.stringID)) + { + return filters.at(templ.stringID).test(heroTest); + } return false; } diff --git a/lib/mapObjects/CommonConstructors.h b/lib/mapObjects/CommonConstructors.h index f4f70d79b..da3db805c 100644 --- a/lib/mapObjects/CommonConstructors.h +++ b/lib/mapObjects/CommonConstructors.h @@ -73,20 +73,35 @@ public: void initTypeData(const JsonNode & input); void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; void afterLoadFinalization(); + + template void serialize(Handler &h, const int version) + { + h & filtersJson & faction & filters; + h & static_cast&>(*this); + } }; class CHeroInstanceConstructor : public CDefaultObjectTypeHandler { + JsonNode filtersJson; protected: bool objectFilter(const CGObjectInstance *, const ObjectTemplate &) const; public: CHeroClass * heroClass; + std::map> filters; CHeroInstanceConstructor(); CGObjectInstance * create(ObjectTemplate tmpl) const; void initTypeData(const JsonNode & input); void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; + void afterLoadFinalization(); + + template void serialize(Handler &h, const int version) + { + h & filtersJson & heroClass & filters; + h & static_cast&>(*this); + } }; class CDwellingInstanceConstructor : public CDefaultObjectTypeHandler @@ -106,4 +121,10 @@ public: void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; bool producesCreature(const CCreature * crea) const; + + template void serialize(Handler &h, const int version) + { + h & availableCreatures & guards; + h & static_cast&>(*this); + } }; diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index 74ff7a424..cd438a05f 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -185,8 +185,6 @@ void ObjectTemplate::readMap(CBinaryReader & reader) void ObjectTemplate::readJson(const JsonNode &node) { - //id = Obj(node["basebase"].Float()); // temporary, should be removed and determined indirectly via object type parent (e.g. base->base) - //subid = node["base"].Float(); animationFile = node["animation"].String(); const JsonVector & visitDirs = node["visitableFrom"].Vector(); From 0a71e89f58d761b770ae97afb52d73053ddd35cf Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Tue, 17 Jun 2014 14:57:47 +0300 Subject: [PATCH 32/33] Created large number of missing objects in configs Minor bugfixes in code --- config/objects/generic.json | 8 +- config/objects/moddables.json | 110 ++++++++++++++++++----- lib/VCMI_Lib.cpp | 4 +- lib/mapObjects/CObjectClassesHandler.cpp | 36 ++++---- lib/mapObjects/CObjectHandler.cpp | 3 +- lib/mapObjects/ObjectTemplate.cpp | 3 + lib/registerTypes/RegisterTypes.h | 1 + server/CVCMIServer.cpp | 2 +- 8 files changed, 123 insertions(+), 44 deletions(-) diff --git a/config/objects/generic.json b/config/objects/generic.json index 7097dbedc..759214acd 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -1,6 +1,13 @@ { /// These are objects that can not be configured, either due to /// their hardcoded status or because they don't have any configurable functionality + "prison" : { + "index" :62, + "handler": "prison", + "types" : { + "prison" : { "index" : 0 } + } + }, "altarOfSacrifice" : { "index" :2, "handler": "market" }, "tradingPost" : { "index" :221, "handler": "market" }, @@ -38,7 +45,6 @@ "magicWell" : { "index" :49, "handler": "magicWell" }, "obelisk" : { "index" :57, "handler": "obelisk" }, "oceanBottle" : { "index" :59, "handler": "sign" }, - "prison" : { "index" :62, "handler": "hero" }, "pyramid" : { "index" :63, "handler": "pyramid" }, "scholar" : { "index" :81, "handler": "scholar" }, "shipyard" : { "index" :87, "handler": "shipyard" }, diff --git a/config/objects/moddables.json b/config/objects/moddables.json index be5a9cf8e..195e4989d 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -1,7 +1,7 @@ { /// These are objects that have subtypes that change various aspects of their mechanics /// Should be made configurable (either directly or via other parts of modding system ASAP) - /// Editing these objects either directly or via mod may have negative effect on game + /// Editing these objects either directly or via mod may have negative effect on game since they are handled by engine // subtype: artifact ID "artifact" : { @@ -50,13 +50,13 @@ } }, "types" : { - "wood" : { "index" : 0 }, - "mercury" : { "index" : 1 }, - "ore" : { "index" : 2 }, - "sulfur" : { "index" : 3 }, - "crystal" : { "index" : 4 }, - "gems" : { "index" : 5 }, - "gold" : { "index" : 6 }, + "wood" : { "index" : 0, "templates" : { "res" : { "animation" : "AVTwood0.def" } } }, + "mercury" : { "index" : 1, "templates" : { "res" : { "animation" : "AVTmerc0.def" } } }, + "ore" : { "index" : 2, "templates" : { "res" : { "animation" : "AVTore0.def" } } }, + "sulfur" : { "index" : 3, "templates" : { "res" : { "animation" : "AVTsulf0.def" } } }, + "crystal" : { "index" : 4, "templates" : { "res" : { "animation" : "AVTcrys0.def" } } }, + "gems" : { "index" : 5, "templates" : { "res" : { "animation" : "AVTgems0.def" } } }, + "gold" : { "index" : 6, "templates" : { "res" : { "animation" : "AVTgold0.def" } } }, "mithril" : { "index" : 7 } // TODO: move to WoG? } }, @@ -99,19 +99,93 @@ "visitableFrom" : [ "+++", "+-+", "+++" ], "mask" : [ "VVV", "VAV" ] } + }, + "types" : { + "evil" : { "index" : 0 }, + "good" : { "index" : 1 }, + "neutral" : { "index" : 2 }, } }, // subtype: color of guard - "borderGuard" : { "index" :9, "handler": "borderGuard" }, - "borderGate" : { "index" :212, "handler": "borderGate" }, - "keymasterTent" : { "index" :10, "handler": "keymaster" }, - - // subtype: different content - "creatureBank" : { "index" :16, "handler": "bank" }, + "borderGuard" : { + "index" :9, + "handler": "borderGuard", + "types" : { + "lblue" : { "index" : 0 }, + "green" : { "index" : 1 }, + "red" : { "index" : 2 }, + "dblue" : { "index" : 3 }, + "brown" : { "index" : 4 }, + "purple" : { "index" : 5 }, + "white" : { "index" : 6 }, + "black" : { "index" : 7 } + } + }, + "borderGate" : { + "index" :212, + "handler": "borderGate", + "types" : { + "lblue" : { "index" : 0 }, + "green" : { "index" : 1 }, + "red" : { "index" : 2 }, + "dblue" : { "index" : 3 }, + "brown" : { "index" : 4 }, + "purple" : { "index" : 5 }, + "white" : { "index" : 6 }, + "black" : { "index" : 7 } + } + }, + "keymasterTent" : { + "index" :10, + "handler": "keymaster", + "types" : { + "lblue" : { "index" : 0 }, + "green" : { "index" : 1 }, + "red" : { "index" : 2 }, + "dblue" : { "index" : 3 }, + "brown" : { "index" : 4 }, + "purple" : { "index" : 5 }, + "white" : { "index" : 6 }, + "black" : { "index" : 7 } + } + }, // subtype: different revealed areas - "cartographer" : { "index" :13, "handler": "cartographer" }, + "cartographer" : { + "index" :13, + "handler": "cartographer", + "types" : { + "water" : { "index" : 0 }, + "land" : { "index" : 1 }, + "subterra" : { "index" : 2 } + } + }, + + // subtype: resource ID + "mine" : { + "index" :53, + "handler": "mine", + "types" : { + "sawmill" : { "index" : 0 }, + "alchemistLab" : { "index" : 1 }, + "orePit" : { "index" : 2 }, + "sulfurDune" : { "index" : 3 }, + "crystalCavern" : { "index" : 4 }, + "gemPond" : { "index" : 5 }, + "goldMine" : { "index" : 6 }, + } + }, + "abandonedMine" : { + "index" :220, + "handler": "mine", + "types" : { + "mine" : { "index" : 7 } + } + }, + + // subtype: different content + "creatureBank" : { "index" :16, "handler": "bank" }, // subtype: 0 = normal, 1 = anti-magic "garrisonHorizontal" : { "index" :33, "handler": "garrison" }, @@ -121,13 +195,9 @@ "monolithOneWayEntrance" : { "index" :43, "handler": "teleport" }, "monolithOneWayExit" : { "index" :44, "handler": "teleport" }, "monolithTwoWay" : { "index" :45, "handler": "teleport" }, - - // subtype: resource ID - "mine" : { "index" :53, "handler": "mine" }, - "abandonedMine" : { "index" :220, "handler": "mine" }, // subtype: different appearance. That's all? - "seerHut" : { "index" :83, "handler": "seerHut" }, + "seerHut" : { "index" :83, "handler": "seerHut" }, // subtype: level "randomDwellingLvl" : { "index" :217, "handler": "dwelling" }, diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 695070a75..706c21886 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -45,11 +45,11 @@ DLL_LINKAGE void preinitDLL(CConsoleHandler *Console) DLL_LINKAGE void loadDLLClasses() { -// try + try { VLC->init(); } -// HANDLE_EXCEPTION; + HANDLE_EXCEPTION; } const IBonusTypeHandler * LibClasses::getBth() const diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index cd963f395..553a40a15 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -30,7 +30,7 @@ CObjectClassesHandler::CObjectClassesHandler() #define SET_HANDLER(STRING, TYPENAME) handlerConstructors[STRING] = std::make_shared > // list of all known handlers, hardcoded for now since the only way to add new objects is via C++ code - //WARNING: should be in sync with registerTypesMapObjectTypes function + //Note: should be in sync with registerTypesMapObjectTypes function SET_HANDLER_CLASS("configurable", CRewardableConstructor); SET_HANDLER_CLASS("dwelling", CDwellingInstanceConstructor); SET_HANDLER_CLASS("hero", CHeroInstanceConstructor); @@ -66,6 +66,7 @@ CObjectClassesHandler::CObjectClassesHandler() SET_HANDLER("onceVisitable", CGOnceVisitable); SET_HANDLER("pandora", CGPandoraBox); SET_HANDLER("pickable", CGPickable); + SET_HANDLER("prison", CGHeroInstance); SET_HANDLER("pyramid", CGPyramid); SET_HANDLER("questGuard", CGQuestGuard); SET_HANDLER("resource", CGResource); @@ -85,10 +86,9 @@ CObjectClassesHandler::CObjectClassesHandler() #undef SET_HANDLER } -template -void readTextFile(Container & objects, std::string path) +std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) { - CLegacyConfigParser parser(path); + CLegacyConfigParser parser("Data/Objects.txt"); size_t totalNumber = parser.readNumber(); // first line contains number of objects to read and nothing else parser.endLine(); @@ -97,24 +97,18 @@ void readTextFile(Container & objects, std::string path) ObjectTemplate templ; templ.readTxt(parser); parser.endLine(); - typename Container::key_type key(templ.id.num, templ.subid); - objects.insert(std::make_pair(key, templ)); + std::pair key(templ.id.num, templ.subid); + legacyTemplates.insert(std::make_pair(key, templ)); } -} - -std::vector CObjectClassesHandler::loadLegacyData(size_t dataSize) -{ - readTextFile(legacyTemplates, "Data/Objects.txt"); - //readTextFile(legacyTemplates, "Data/Heroes.txt"); std::vector ret(dataSize);// create storage for 256 objects assert(dataSize == 256); - CLegacyConfigParser parser("Data/ObjNames.txt"); + CLegacyConfigParser namesParser("Data/ObjNames.txt"); for (size_t i=0; i<256; i++) { - ret[i]["name"].String() = parser.readString(); - parser.endLine(); + ret[i]["name"].String() = namesParser.readString(); + namesParser.endLine(); } return ret; } @@ -358,15 +352,19 @@ std::vector AObjectTypeHandler::getTemplates() const std::vector AObjectTypeHandler::getTemplates(si32 terrainType) const// FIXME: replace with ETerrainType { - std::vector ret = getTemplates(); + std::vector templates = getTemplates(); std::vector filtered; - std::copy_if(ret.begin(), ret.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj) + std::copy_if(templates.begin(), templates.end(), std::back_inserter(filtered), [&](const ObjectTemplate & obj) { return obj.canBePlacedAt(ETerrainType(terrainType)); }); - // it is possible that there are no templates usable on specific terrain. In this case - return list before filtering - return filtered.empty() ? ret : filtered; + // H3 defines allowed terrains in a weird way - artifacts, monsters and resources have faulty masks here + // Perhaps we should re-define faulty templates and remove this workaround (already done for resources) + if (type == Obj::ARTIFACT || type == Obj::MONSTER) + return templates; + else + return filtered; } boost::optional AObjectTypeHandler::getOverride(si32 terrainType, const CGObjectInstance * object) const diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index d8f2ac4ef..828a7ce9a 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -336,7 +336,8 @@ void CGObjectInstance::setType(si32 ID, si32 subID) //recalculate blockvis tiles - new appearance might have different blockmap than before cb->gameState()->map->removeBlockVisTiles(this, true); - this->appearance = VLC->objtypeh->getHandlerFor(ID, subID)->getTemplates(tile.terType).at(0); + auto handler = VLC->objtypeh->getHandlerFor(ID, subID); + appearance = handler->getTemplates(tile.terType).at(0); cb->gameState()->map->addBlockVisTiles(this); } diff --git a/lib/mapObjects/ObjectTemplate.cpp b/lib/mapObjects/ObjectTemplate.cpp index cd438a05f..6cec38ba1 100644 --- a/lib/mapObjects/ObjectTemplate.cpp +++ b/lib/mapObjects/ObjectTemplate.cpp @@ -213,6 +213,9 @@ void ObjectTemplate::readJson(const JsonNode &node) allowedTerrains.insert(ETerrainType(i)); } + if (allowedTerrains.empty()) + logGlobal->warnStream() << "Loaded template without allowed terrains!"; + auto charToTile = [&](const char & ch) -> ui8 { switch (ch) diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 7940a1bab..6bc8ca0ba 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -100,6 +100,7 @@ void registerTypesMapObjectTypes(Serializer &s) REGISTER_GENERIC_HANDLER(CGEvent); REGISTER_GENERIC_HANDLER(CGGarrison); REGISTER_GENERIC_HANDLER(CGHeroPlaceholder); + REGISTER_GENERIC_HANDLER(CGHeroInstance); REGISTER_GENERIC_HANDLER(CGKeymasterTent); REGISTER_GENERIC_HANDLER(CGLighthouse); REGISTER_GENERIC_HANDLER(CGMagi); diff --git a/server/CVCMIServer.cpp b/server/CVCMIServer.cpp index d152cb97c..31c558746 100644 --- a/server/CVCMIServer.cpp +++ b/server/CVCMIServer.cpp @@ -107,7 +107,7 @@ void CPregameServer::handleConnection(CConnection *cpc) while(state == RUNNING) boost::this_thread::sleep(boost::posix_time::milliseconds(50)); } } - } + } catch (const std::exception& e) { boost::unique_lock queueLock(mx); From ab475195ac4e6e18644a7e072b1cd8122a18006e Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 22 Jun 2014 13:39:40 +0300 Subject: [PATCH 33/33] Banks now use new scheme as well - Implemented Bank Constructor object. - Merged Pyramid object into common Bank class. Banks can now grant spells as part of their reward. - Move bank config code to config/objects/creatureBanks.json. Note: WoG banks are not updated yet, should be moved to WoG mod. - Updated AI code so it can correctly evaluate bank danger (should be generic enough for use with other objects) - New files JsonRandom.* that contain routines for loading random objects from Json (still WiP but should be stable) --- AI/VCAI/AIUtility.cpp | 5 +- AI/VCAI/Fuzzy.cpp | 59 +- AI/VCAI/Fuzzy.h | 3 +- config/bankconfig.json | 685 +-------------- config/gameConfig.json | 1 + config/objects/creatureBanks.json | 975 ++++++++++++++++++++++ config/objects/dwellings.json | 14 +- config/objects/generic.json | 6 - config/objects/moddables.json | 3 - lib/CArtHandler.cpp | 60 +- lib/CArtHandler.h | 10 +- lib/CMakeLists.txt | 1 + lib/NetPacks.h | 3 +- lib/VCMI_Lib.cpp | 4 +- lib/mapObjects/CBank.cpp | 442 ++++------ lib/mapObjects/CBank.h | 42 +- lib/mapObjects/CObjectClassesHandler.cpp | 8 +- lib/mapObjects/CObjectClassesHandler.h | 47 +- lib/mapObjects/CObjectHandler.cpp | 101 --- lib/mapObjects/CObjectHandler.h | 28 +- lib/mapObjects/CRewardableConstructor.cpp | 147 +--- lib/mapObjects/CRewardableConstructor.h | 2 +- lib/mapObjects/CommonConstructors.cpp | 203 ++++- lib/mapObjects/CommonConstructors.h | 63 +- lib/mapObjects/JsonRandom.cpp | 225 +++++ lib/mapObjects/JsonRandom.h | 50 ++ lib/mapping/MapFormatH3M.cpp | 2 +- lib/registerTypes/RegisterTypes.h | 4 +- 28 files changed, 1822 insertions(+), 1371 deletions(-) create mode 100644 config/objects/creatureBanks.json create mode 100644 lib/mapObjects/JsonRandom.cpp create mode 100644 lib/mapObjects/JsonRandom.h diff --git a/AI/VCAI/AIUtility.cpp b/AI/VCAI/AIUtility.cpp index 1cc87ce7d..e50f6a7aa 100644 --- a/AI/VCAI/AIUtility.cpp +++ b/AI/VCAI/AIUtility.cpp @@ -5,6 +5,7 @@ #include "../../lib/UnlockGuard.h" #include "../../lib/CConfigHandler.h" #include "../../lib/CHeroHandler.h" +#include "../../lib/mapObjects/CBank.h" /* * AIUtility.cpp, part of VCMI engine @@ -302,11 +303,11 @@ ui64 evaluateDanger(const CGObjectInstance *obj) case Obj::SHIPWRECK: //shipwreck case Obj::DERELICT_SHIP: //derelict ship // case Obj::PYRAMID: - return fh->estimateBankDanger (VLC->objh->bankObjToIndex(obj)); + return fh->estimateBankDanger (dynamic_cast(obj)); case Obj::PYRAMID: { if(obj->subID == 0) - return fh->estimateBankDanger (VLC->objh->bankObjToIndex(obj)); + return fh->estimateBankDanger (dynamic_cast(obj)); else return 0; } diff --git a/AI/VCAI/Fuzzy.cpp b/AI/VCAI/Fuzzy.cpp index cddff4d9a..db173016b 100644 --- a/AI/VCAI/Fuzzy.cpp +++ b/AI/VCAI/Fuzzy.cpp @@ -3,6 +3,7 @@ #include #include "../../lib/mapObjects/MapObjects.h" +#include "../../lib/mapObjects/CommonConstructors.h" #include "../../lib/CCreatureHandler.h" #include "../../lib/VCMI_Lib.h" #include "../../CCallback.h" @@ -41,16 +42,6 @@ struct armyStructure ui32 maxSpeed; }; -ui64 evaluateBankConfig (BankConfig * bc) -{ - ui64 danger = 0; - for (auto opt : bc->guards) - { - danger += VLC->creh->creatures[opt.first]->fightValue * opt.second; - } - return danger; -} - armyStructure evaluateArmyStructure (const CArmedInstance * army) { ui64 totalStrenght = army->getArmyStrength(); @@ -208,42 +199,26 @@ void FuzzyHelper::initTacticalAdvantage() } } -ui64 FuzzyHelper::estimateBankDanger (int ID) +ui64 FuzzyHelper::estimateBankDanger (const CBank * bank) { - std::vector > & configs = VLC->objh->banksInfo[ID]; + auto info = VLC->objtypeh->getHandlerFor(bank->ID, bank->subID)->getObjectInfo(bank->appearance); + ui64 val = std::numeric_limits::max(); try { - switch (configs.size()) - { - case 4: - try - { - for (int i = 0; i < 4; ++i) - { - int bankVal = evaluateBankConfig (VLC->objh->banksInfo[ID][i]); - bankDanger->term("Bank" + boost::lexical_cast(i))->setMinimum(bankVal * 0.5f); - bankDanger->term("Bank" + boost::lexical_cast(i))->setMaximum(bankVal * 1.5f); - } - //comparison purposes - //int averageValue = (evaluateBankConfig (VLC->objh->banksInfo[ID][0]) + evaluateBankConfig (VLC->objh->banksInfo[ID][3])) * 0.5; - //dynamic_cast(bankInput->term("SET"))->setValue(0.5); - bankInput->setInput (0.5); - engine.process (BANK_DANGER); - //engine.process(); - val = bankDanger->output().defuzzify(); //some expected value of this bank - } - catch (fl::FuzzyException & fe) - { - logAi->errorStream() << fe.name() << ": " << fe.message(); - } - break; - case 1: //rare case - Pyramid - val = evaluateBankConfig (VLC->objh->banksInfo[ID][0]); - break; - default: - logAi->warnStream() << ("Uhnandled bank config!"); - } + bankDanger->term("Bank0")->setMinimum(info->minGuards().totalStrength * 0.5f); + bankDanger->term("Bank0")->setMaximum(info->minGuards().totalStrength * 1.5f); + + bankDanger->term("Bank1")->setMinimum(info->maxGuards().totalStrength * 0.5f); + bankDanger->term("Bank1")->setMaximum(info->maxGuards().totalStrength * 1.5f); + + //comparison purposes + //int averageValue = (evaluateBankConfig (VLC->objh->banksInfo[ID][0]) + evaluateBankConfig (VLC->objh->banksInfo[ID][3])) * 0.5; + //dynamic_cast(bankInput->term("SET"))->setValue(0.5); + bankInput->setInput (0.5); + engine.process (BANK_DANGER); + //engine.process(); + val = bankDanger->output().defuzzify(); //some expected value of this bank } catch (fl::FuzzyException & fe) { diff --git a/AI/VCAI/Fuzzy.h b/AI/VCAI/Fuzzy.h index 5d9a28ab9..c81bb5569 100644 --- a/AI/VCAI/Fuzzy.h +++ b/AI/VCAI/Fuzzy.h @@ -14,6 +14,7 @@ class VCAI; class CArmedInstance; +class CBank; class FuzzyHelper { @@ -72,7 +73,7 @@ public: float evaluate (Goals::AbstractGoal & g); void setPriority (Goals::TSubgoal & g); - ui64 estimateBankDanger (int ID); + ui64 estimateBankDanger (const CBank * bank); float getTacticalAdvantage (const CArmedInstance *we, const CArmedInstance *enemy); //returns factor how many times enemy is stronger than us Goals::TSubgoal chooseSolution (Goals::TGoalVec vec); diff --git a/config/bankconfig.json b/config/bankconfig.json index d610606cd..778bd2f43 100644 --- a/config/bankconfig.json +++ b/config/bankconfig.json @@ -1,675 +1,9 @@ //Resources: Wood, Mercury, Ore, Sulfur, Crystal, Gems, Gold //Artifacts: Treasure, Minor, Major, Relic +//NOTE: all H3M banks were moved to objects/creatureBanks.json +//Remaining part should be moved to WoG mod { "banks": [ - { - "name" : "Cyclops Stockpile", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 20, "id": 94 } ], - "upgrade_chance": 50, - "combat_value": 506, - "reward_resources": - { - "wood" : 4, - "mercury" : 4, - "ore" : 4, - "sulfur" : 4, - "crystal" : 4, - "gems" : 4, - "gold" : 0 - }, - "value": 10000, - "profitability": 20, - "easiest": 100 - }, - - { - "chance": 30, - "guards": [ { "number": 30, "id": 94 } ], - "upgrade_chance": 50, - "combat_value": 760, - "reward_resources": - { - "wood" : 6, - "mercury" : 6, - "ore" : 6, - "sulfur" : 6, - "crystal" : 6, - "gems" : 6 - }, - "value": 15000, - "profitability": 20, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 40, "id": 94 } ], - "upgrade_chance": 50, - "combat_value": 1013, - "reward_resources": - { - "wood" : 8, - "mercury" : 8, - "ore" : 8, - "sulfur" : 8, - "crystal" : 8, - "gems" : 8 - }, - "value": 20000, - "profitability": 20, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 50, "id": 94 } ], - "upgrade_chance": 50, - "combat_value": 1266, - "reward_resources": - { - "wood" : 10, - "mercury" : 10, - "ore" : 10, - "sulfur" : 10, - "crystal" : 10, - "gems" : 10 - }, - "value": 25000, - "profitability": 20, - "easiest": 250 - } - ] - }, - - { - "name" : "Dwarven Treasury", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 50, "id": 16 } ], - "upgrade_chance": 50, - "combat_value": 194, - "reward_resources": - { - "crystal" : 2, - "gold" : 2500 - }, - "value": 3500, - "profitability": 18, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 75, "id": 16 } ], - "upgrade_chance": 50, - "combat_value": 291, - "reward_resources": - { - "crystal" : 3, - "gold" : 4000 - }, - "value": 5500, - "profitability": 19, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 100, "id": 16 } ], - "upgrade_chance": 50, - "combat_value": 388, - "reward_resources": - { - "crystal" : 5, - "gold" : 5000 - }, - "value": 7500, - "profitability": 19, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 150, "id": 16 } ], - "upgrade_chance": 50, - "combat_value": 582, - "reward_resources": - { - "crystal" : 10, - "gold" : 7500 - }, - "value": 12500, - "profitability": 21, - "easiest": 300 - } - ] - }, - - { - "name" : "Griffin Conservatory", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 50, "id": 4 } ], - "upgrade_chance": 50, - "combat_value": 351, - "reward_creatures": [ { "number": 1, "id": 12 } ], - "value": 3000, - "profitability": 9, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 100, "id": 4 } ], - "upgrade_chance": 50, - "combat_value": 702, - "reward_creatures": [ { "number": 2, "id": 12 } ], - "value": 6000, - "profitability": 9, - "easiest": 200 - }, - { - "chance": 30, - "guards": [ { "number": 150, "id": 4 } ], - "upgrade_chance": 50, - "combat_value": 1053, - "reward_creatures": [ { "number": 3, "id": 12 } ], - "value": 9000, - "profitability": 9, - "easiest": 300 - }, - { - "chance": 10, - "guards": [ { "number": 200, "id": 4 } ], - "upgrade_chance": 50, - "combat_value": 1404, - "reward_creatures": [ { "number": 4, "id": 12 } ], - "value": 12000, - "profitability": 9, - "easiest": 400 - } - ] - }, - - { - "name" : "Imp Cache", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 100, "id": 42 } ], - "upgrade_chance": 50, - "combat_value": 100, - "reward_resources": - { - "wood" : 0, - "mercury" : 2, - "ore" : 0, - "sulfur" : 0, - "crystal" : 0, - "gems" : 0, - "gold" : 1000 - }, - "value": 2000, - "profitability": 20, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 150, "id": 42 } ], - "upgrade_chance": 50, - "combat_value": 150, - "reward_resources": - { - "mercury" : 3, - "gold" : 1500 - }, - "value": 3000, - "profitability": 20, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 200, "id": 42 } ], - "upgrade_chance": 50, - "combat_value": 200, - "reward_resources": - { - "mercury" : 4, - "gold" : 2000 - }, - "value": 4000, - "profitability": 20, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 300, "id": 42 } ], - "upgrade_chance": 50, - "combat_value": 300, - "reward_resources": - { - "mercury" : 6, - "gold" : 3000 - }, - "value": 6000, - "profitability": 20, - "easiest": 300 - } - ] - }, - - { - "name" : "Medusa Stores", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 20, "id": 76 } ], - "upgrade_chance": 50, - "combat_value": 207, - "reward_resources": - { - "sulfur" : 5, - "gold" : 2000 - }, - "value": 4500, - "profitability": 22, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 30, "id": 76 } ], - "upgrade_chance": 50, - "combat_value": 310, - "reward_resources": - { - "sulfur" : 6, - "gold" : 3000 - }, - "value": 6000, - "profitability": 19, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 40, "id": 76 } ], - "upgrade_chance": 50, - "combat_value": 414, - "reward_resources": - { - "sulfur" : 8, - "gold" : 4000 - }, - "value": 8000, - "profitability": 19, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 50, "id": 76 } ], - "upgrade_chance": 50, - "combat_value": 517, - "reward_resources": - { - "sulfur" : 10, - "gold" : 5000 - }, - "value": 10000, - "profitability": 19, - "easiest": 250 - } - ] - }, - - { - "name" : "Naga Bank", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 10, "id": 38 } ], - "upgrade_chance": 50, - "combat_value": 403, - "reward_resources": - { - "gems" : 8, - "gold" : 4000 - }, - "value": 8000, - "profitability": 20, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 15, "id": 38 } ], - "upgrade_chance": 50, - "combat_value": 605, - "reward_resources": - { - "gems" : 12, - "gold" : 6000 - }, - "value": 12000, - "profitability": 20, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 20, "id": 38 } ], - "upgrade_chance": 50, - "combat_value": 806, - "reward_resources": - { - "gems" : 16, - "gold" : 8000 - }, - "value": 16000, - "profitability": 20, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 30, "id": 38 } ], - "upgrade_chance": 50, - "combat_value": 1210, - "reward_resources": - { - "gems" : 24, - "gold" : 12000 - }, - "value": 24000, - "profitability": 20, - "easiest": 300 - } - ] - }, - - { - "name" : "Dragon Fly Hive", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 30, "id": 105} ], - "upgrade_chance": 0, - "combat_value": 154, - "reward_creatures": [ { "number": 4, "id": 108 } ], - "value": 3200, - "profitability": 21, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 45, "id": 105 } ], - "upgrade_chance": 0, - "combat_value": 230, - "reward_creatures": [ { "number": 6, "id": 108 } ], - "value": 4800, - "profitability": 21, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 60, "id": 105 } ], - "upgrade_chance": 0, - "combat_value": 307, - "reward_creatures": [ { "number": 8, "id": 108 } ], - "value": 6400, - "profitability": 21, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 90, "id": 105 } ], - "upgrade_chance": 0, - "combat_value": 461, - "reward_creatures": [ { "number": 12, "id": 108 } ], - "value": 9600, - "profitability": 21, - "easiest": 300 - } - ] - }, - - { - "name" : "Shipwreck", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 10, "id": 60 } ], - "upgrade_chance": 0, - "combat_value": 31, - "reward_resources": - { - "gold" : 2000 - }, - "value": 2000, - "profitability": 65, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 15, "id": 60 } ], - "upgrade_chance": 0, - "combat_value": 46, - "reward_resources": - { - "gold" : 3000 - }, - "value": 3000, - "profitability": 65, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 25, "id": 60 } ], - "upgrade_chance": 0, - "combat_value": 77, - "reward_resources": - { - "gold" : 4000 - }, - "reward_artifacts": [ 1, 0, 0, 0 ], - "value": 5000, - "profitability": 65, - "easiest": 250 - }, - { - "chance": 10, - "guards": [ { "number": 50, "id": 60 } ], - "upgrade_chance": 0, - "combat_value": 154, - "reward_resources": - { - "gold" : 5000 - }, - "reward_artifacts": [ 0, 1, 0, 0 ], - "value": 7000, - "profitability": 45, - "easiest": 500 - } - ] - }, - - { - "name" : "Derelict Ship", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 20, "id": 115 } ], - "upgrade_chance": 0, - "combat_value": 138, - "reward_resources": - { - "gold" : 3000 - }, - "value": 3000, - "profitability": 22, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 30, "id": 115 } ], - "upgrade_chance": 0, - "combat_value": 207, - "reward_resources": - { - "gold" : 3000 - }, - "reward_artifacts": [ 1, 0, 0, 0 ], - "value": 4000, - "profitability": 19, - "easiest": 150 - }, - { - "chance": 30, - "guards": [ { "number": 40, "id": 115 } ], - "upgrade_chance": 0, - "combat_value": 276, - "reward_resources": - { - "gold" : 4000 - }, - "reward_artifacts": [ 1, 0, 0, 0 ], - "value": 5000, - "profitability": 18, - "easiest": 200 - }, - { - "chance": 10, - "guards": [ { "number": 60, "id": 115 } ], - "upgrade_chance": 0, - "combat_value": 414, - "reward_resources": - { - "gold" : 6000 - }, - "reward_artifacts": [ 0, 1, 0, 0 ], - "value": 8000, - "profitability": 19, - "easiest": 300 - } - ] - }, - - { - "name" : "Crypt", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 30, "id": 56 }, { "number": 20, "id": 58 }, { "number": 0, "id": 60 } , { "number": 0, "id": 62 } ], - "upgrade_chance": 0, - "combat_value": 75, - "reward_resources": - { - "gold" : 1500 - }, - "value": 1500, - "profitability": 20, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 25, "id": 56 }, { "number": 20, "id": 58 }, { "number": 5, "id": 60 }, { "number": 0, "id": 62 } ], - "upgrade_chance": 0, - "combat_value": 94, - "reward_resources": - { - "gold" : 2000 - }, - "value": 2000, - "profitability": 21, - "easiest": 126 - }, - { - "chance": 30, - "guards": [ { "number": 20, "id": 56 }, { "number": 20, "id": 58 }, { "number": 10, "id": 60 }, { "number": 5, "id": 62 } ], - "upgrade_chance": 0, - "combat_value": 169, - "reward_resources": - { - "gold" : 2500 - }, - "reward_artifacts": [ 1, 0, 0, 0 ], - "value": 3500, - "profitability": 21, - "easiest": 225 - }, - { - "chance": 10, - "guards": [ { "number": 20, "id": 56 }, { "number": 20, "id": 58 }, { "number": 10, "id": 60 }, { "number": 10, "id": 62 } ], - "upgrade_chance": 0, - "combat_value": 225, - "reward_resources": - { - "gold" : 5000 - }, - "reward_artifacts": [ 1, 0, 0, 0 ], - "value": 6000, - "profitability": 27, - "easiest": 299 - } - ] - }, - - { - "name" : "Dragon Utopia", - "levels": [ - { - "chance": 30, - "guards": [ { "number": 8, "id": 26 }, { "number": 5, "id": 82 }, { "number": 2, "id": 27 }, { "number": 1, "id": 83 } ], - "upgrade_chance": 0, - "combat_value": 769, - "reward_resources": - { - "gold" : 20000 - }, - "reward_artifacts": [ 1, 1, 1, 1 ], - "value": 38000, - "profitability": 21, - "easiest": 100 - }, - { - "chance": 30, - "guards": [ { "number": 8, "id": 26 }, { "number": 6, "id": 82 }, { "number": 3, "id": 27 }, { "number": 2, "id": 83 } ], - "upgrade_chance": 0, - "combat_value": 209, - "reward_resources": - { - "gold" : 30000 - }, - "reward_artifacts": [ 0, 1, 1, 2 ], - "value": 57000, - "profitability": 26, - "easiest": 125 - }, - { - "chance": 30, - "guards": [ { "number": 8, "id": 26 }, { "number": 6, "id": 82 }, { "number": 4, "id": 27 }, { "number": 3, "id": 83 } ], - "upgrade_chance": 0, - "combat_value": 556, - "reward_resources": - { - "gold" : 40000 - }, - "reward_artifacts": [ 0, 0, 1, 3 ], - "value": 75000, - "profitability": 29, - "easiest": 145 - }, - { - "chance": 10, - "guards": [ { "number": 8, "id": 26 }, { "number": 7, "id": 82 }, { "number": 6, "id": 27 }, { "number": 5, "id": 83 } ], - "upgrade_chance": 0, - "combat_value": 343, - "reward_resources": - { - "gold" : 50000 - }, - "reward_artifacts": [ 0, 0, 0, 4 ], - "value": 90000, - "profitability": 27, - "easiest": 189 - } - ] - }, - { "name" : "Hunting Lodge", "levels": [ @@ -1252,21 +586,6 @@ "easiest": 250 } ] - }, - - { - "name" : "Pyramid", - "levels": [ - { - "chance": 100, - "guards": [ { "number": 20, "id": 116 }, { "number": 10, "id": 117 }, { "number": 20, "id": 116 }, { "number": 10, "id": 117 } ], - "upgrade_chance": 0, - "combat_value": 786, - "value": 15000, - "profitability": 19, - "easiest": 100 - } - ] } ] } diff --git a/config/gameConfig.json b/config/gameConfig.json index ad35ef0e9..801287573 100644 --- a/config/gameConfig.json +++ b/config/gameConfig.json @@ -49,6 +49,7 @@ [ "config/objects/generic.json", "config/objects/moddables.json", + "config/objects/creatureBanks.json", "config/objects/dwellings.json", "config/objects/rewardable.json" ], diff --git a/config/objects/creatureBanks.json b/config/objects/creatureBanks.json new file mode 100644 index 000000000..b0d0c7218 --- /dev/null +++ b/config/objects/creatureBanks.json @@ -0,0 +1,975 @@ +{ + "creatureBank" : { + "index" :16, + "handler": "bank", + "types" : { + "cyclopsStockpile" : + { + "index" : 0, + "name" : "Cyclops Stockpile", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 4, "type": "cyclop" }, + { "amount": 4, "type": "cyclop" }, + { "amount": 4, "type": "cyclop", "upgradeChance": 50 }, + { "amount": 4, "type": "cyclop" }, + { "amount": 4, "type": "cyclop" } + ], + + "combat_value": 506, + "reward" : { + "value": 10000, + "resources": + { + "wood" : 4, + "mercury" : 4, + "ore" : 4, + "sulfur" : 4, + "crystal" : 4, + "gems" : 4, + "gold" : 0 + } + } + }, + + { + "chance": 30, + "guards": [ + { "amount": 6, "type": "cyclop" }, + { "amount": 6, "type": "cyclop" }, + { "amount": 6, "type": "cyclop", "upgradeChance": 50 }, + { "amount": 6, "type": "cyclop" }, + { "amount": 6, "type": "cyclop" } + ], + "combat_value": 760, + "reward" : { + "value": 15000, + "resources": + { + "wood" : 6, + "mercury" : 6, + "ore" : 6, + "sulfur" : 6, + "crystal" : 6, + "gems" : 6 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "cyclop" }, + { "amount": 8, "type": "cyclop" }, + { "amount": 8, "type": "cyclop", "upgradeChance": 50 }, + { "amount": 8, "type": "cyclop" }, + { "amount": 8, "type": "cyclop" } + ], + "combat_value": 1013, + "reward" : { + "value": 20000 + "resources": + { + "wood" : 8, + "mercury" : 8, + "ore" : 8, + "sulfur" : 8, + "crystal" : 8, + "gems" : 8 + } + } + }, + { + "chance": 10, + "guards": [ + { "amount": 10, "type": "cyclop" }, + { "amount": 10, "type": "cyclop" }, + { "amount": 10, "type": "cyclop", "upgradeChance": 50 }, + { "amount": 10, "type": "cyclop" }, + { "amount": 10, "type": "cyclop" } + ], + "combat_value": 1266, + "reward" : { + "value": 25000, + "resources": + { + "wood" : 10, + "mercury" : 10, + "ore" : 10, + "sulfur" : 10, + "crystal" : 10, + "gems" : 10 + } + } + } + ] + }, + "dwarvenTreasury" : { + "index" : 1, + "resetDuraition" : 28, + "name" : "Dwarven Treasury", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 10, "type": "dwarf" }, + { "amount": 10, "type": "dwarf" }, + { "amount": 10, "type": "dwarf", "upgradeChance": 50 }, + { "amount": 10, "type": "dwarf" }, + { "amount": 10, "type": "dwarf" } + ], + "combat_value": 194, + "reward" : { + "value": 3500, + "resources": + { + "crystal" : 2, + "gold" : 2500 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 15, "type": "dwarf" }, + { "amount": 15, "type": "dwarf" }, + { "amount": 15, "type": "dwarf", "upgradeChance": 50 }, + { "amount": 15, "type": "dwarf" }, + { "amount": 15, "type": "dwarf" } + ], + "combat_value": 291, + "reward" : { + "value": 5500, + "resources": + { + "crystal" : 3, + "gold" : 4000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 20, "type": "dwarf" }, + { "amount": 20, "type": "dwarf" }, + { "amount": 20, "type": "dwarf", "upgradeChance": 50 }, + { "amount": 20, "type": "dwarf" }, + { "amount": 20, "type": "dwarf" } + ], + "combat_value": 388, + "reward" : { + "value": 7500, + "resources": + { + "crystal" : 5, + "gold" : 5000 + } + } + }, + { + "chance": 10, + "guards": [ + { "amount": 30, "type": "dwarf" }, + { "amount": 30, "type": "dwarf" }, + { "amount": 30, "type": "dwarf", "upgradeChance": 50 }, + { "amount": 30, "type": "dwarf" }, + { "amount": 30, "type": "dwarf" } + ], + "combat_value": 582, + "reward" : { + "value": 12500, + "resources": + { + "crystal" : 10, + "gold" : 7500 + } + } + } + ] + }, + "griffinConservatory" : { + "index" : 2, + "resetDuraition" : 28, + "name" : "Griffin Conservatory", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 10, "type": "griffin" }, + { "amount": 10, "type": "griffin" }, + { "amount": 10, "type": "griffin", "upgradeChance": 50 }, + { "amount": 10, "type": "griffin" }, + { "amount": 10, "type": "griffin" } + ], + "combat_value": 351, + "reward" : { + "value": 3000, + "creatures": [ { "amount": 1, "type": "angel" } ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 20, "type": "griffin" }, + { "amount": 20, "type": "griffin" }, + { "amount": 20, "type": "griffin", "upgradeChance": 50 }, + { "amount": 20, "type": "griffin" }, + { "amount": 20, "type": "griffin" } + ], + "combat_value": 702, + "reward" : { + "value": 6000, + "creatures": [ { "amount": 2, "type": "angel" } ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 30, "type": "griffin" }, + { "amount": 30, "type": "griffin" }, + { "amount": 30, "type": "griffin", "upgradeChance": 50 }, + { "amount": 30, "type": "griffin" }, + { "amount": 30, "type": "griffin" } + ], + "combat_value": 1053, + "reward" : { + "value": 9000, + "creatures": [ { "amount": 3, "type": "angel" } ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 40, "type": "griffin" }, + { "amount": 40, "type": "griffin" }, + { "amount": 40, "type": "griffin", "upgradeChance": 50 }, + { "amount": 40, "type": "griffin" }, + { "amount": 40, "type": "griffin" } + ], + "combat_value": 1404, + "reward" : { + "value": 12000, + "creatures": [ { "amount": 4, "type": "angel" } ] + } + } + ] + }, + "inpCache" : { + "index" : 3, + "resetDuraition" : 28, + "name" : "Imp Cache", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 20, "type": "imp" }, + { "amount": 20, "type": "imp" }, + { "amount": 20, "type": "imp", "upgradeChance": 50 }, + { "amount": 20, "type": "imp" }, + { "amount": 20, "type": "imp" } + ], + "combat_value": 100, + "reward" : { + "value": 2000, + "resources": + { + "wood" : 0, + "mercury" : 2, + "ore" : 0, + "sulfur" : 0, + "crystal" : 0, + "gems" : 0, + "gold" : 1000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 30, "type": "imp" }, + { "amount": 30, "type": "imp" }, + { "amount": 30, "type": "imp", "upgradeChance": 50 }, + { "amount": 30, "type": "imp" }, + { "amount": 30, "type": "imp" } + ], + "combat_value": 150, + "reward" : { + "value": 3000, + "resources": + { + "mercury" : 3, + "gold" : 1500 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 40, "type": "imp" }, + { "amount": 40, "type": "imp" }, + { "amount": 40, "type": "imp", "upgradeChance": 50 }, + { "amount": 40, "type": "imp" }, + { "amount": 40, "type": "imp" } + ], + "combat_value": 200, + "reward" : { + "value": 4000, + "resources": + { + "mercury" : 4, + "gold" : 2000 + } + } + }, + { + "chance": 10, + "guards": [ + { "amount": 60, "type": "imp" }, + { "amount": 60, "type": "imp" }, + { "amount": 60, "type": "imp", "upgradeChance": 50 }, + { "amount": 60, "type": "imp" }, + { "amount": 60, "type": "imp" } + ], + "combat_value": 300, + "reward" : { + "value": 6000, + "resources": + { + "mercury" : 6, + "gold" : 3000 + } + } + } + ] + }, + "medusaStore" : { + "index" : 4, + "resetDuraition" : 28, + "name" : "Medusa Stores", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 4, "type": "medusa" }, + { "amount": 4, "type": "medusa" }, + { "amount": 4, "type": "medusa", "upgradeChance": 50 }, + { "amount": 4, "type": "medusa" }, + { "amount": 4, "type": "medusa" } + ], + "combat_value": 207, + "reward" : { + "value": 4500, + "resources": + { + "sulfur" : 5, + "gold" : 2000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 6, "type": "medusa" }, + { "amount": 6, "type": "medusa" }, + { "amount": 6, "type": "medusa", "upgradeChance": 50 }, + { "amount": 6, "type": "medusa" }, + { "amount": 6, "type": "medusa" } + ], + "combat_value": 310, + "reward" : { + "value": 6000, + "resources": + { + "sulfur" : 6, + "gold" : 3000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "medusa" }, + { "amount": 8, "type": "medusa" }, + { "amount": 8, "type": "medusa", "upgradeChance": 50 }, + { "amount": 8, "type": "medusa" }, + { "amount": 8, "type": "medusa" } + ], + "combat_value": 414, + "reward" : { + "value": 8000, + "resources": + { + "sulfur" : 8, + "gold" : 4000 + } + } + }, + { + "chance": 10, + "guards": [ + { "amount": 10, "type": "medusa" }, + { "amount": 10, "type": "medusa" }, + { "amount": 10, "type": "medusa", "upgradeChance": 50 }, + { "amount": 10, "type": "medusa" }, + { "amount": 10, "type": "medusa" } + ], + "combat_value": 517, + "reward" : { + "value": 10000, + "resources": + { + "sulfur" : 10, + "gold" : 5000 + } + } + } + ] + }, + "nagaBank" : { + "index" : 5, + "resetDuraition" : 28, + "name" : "Naga Bank", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 2, "type": "naga" }, + { "amount": 2, "type": "naga" }, + { "amount": 2, "type": "naga", "upgradeChance": 50 }, + { "amount": 2, "type": "naga" }, + { "amount": 2, "type": "naga" } + ], + "combat_value": 403, + "reward" : { + "value": 8000, + "resources": + { + "gems" : 8, + "gold" : 4000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 3, "type": "naga" }, + { "amount": 3, "type": "naga" }, + { "amount": 3, "type": "naga", "upgradeChance": 50 }, + { "amount": 3, "type": "naga" }, + { "amount": 3, "type": "naga" } + ], + "combat_value": 605, + "reward" : { + "value": 12000, + "resources": + { + "gems" : 12, + "gold" : 6000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 4, "type": "naga" }, + { "amount": 4, "type": "naga" }, + { "amount": 4, "type": "naga", "upgradeChance": 50 }, + { "amount": 4, "type": "naga" }, + { "amount": 4, "type": "naga" } + ], + "combat_value": 806, + "reward" : { + "value": 16000, + "resources": + { + "gems" : 16, + "gold" : 8000 + } + } + }, + { + "chance": 10, + "guards": [ + { "amount": 6, "type": "naga" }, + { "amount": 6, "type": "naga" }, + { "amount": 6, "type": "naga", "upgradeChance": 50 }, + { "amount": 6, "type": "naga" }, + { "amount": 6, "type": "naga" } + ], + "combat_value": 1210, + "reward" : { + "value": 24000, + "resources": + { + "gems" : 24, + "gold" : 12000 + } + } + } + ] + }, + "dragonflyHive" : { + "index" : 6, + "resetDuraition" : 28, + "name" : "Dragon Fly Hive", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 6, "type": "dragonFly" }, + { "amount": 6, "type": "dragonFly" }, + { "amount": 6, "type": "dragonFly" }, + { "amount": 6, "type": "dragonFly" }, + { "amount": 6, "type": "dragonFly" } + ], + "combat_value": 154, + "reward" : { + "value": 3200, + "creatures": [ { "amount": 4, "type": "vyvern" } ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 9, "type": "dragonFly" }, + { "amount": 9, "type": "dragonFly" }, + { "amount": 9, "type": "dragonFly" }, + { "amount": 9, "type": "dragonFly" }, + { "amount": 9, "type": "dragonFly" } + ], + "combat_value": 230, + "reward" : { + "value": 4800, + "creatures": [ { "amount": 6, "type": "vyvern" } ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 12, "type": "dragonFly" }, + { "amount": 12, "type": "dragonFly" }, + { "amount": 12, "type": "dragonFly" }, + { "amount": 12, "type": "dragonFly" }, + { "amount": 12, "type": "dragonFly" } + ], + "combat_value": 307, + "reward" : { + "value": 6400, + "creatures": [ { "amount": 8, "type": "vyvern" } ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 18, "type": "dragonFly" }, + { "amount": 18, "type": "dragonFly" }, + { "amount": 18, "type": "dragonFly" }, + { "amount": 18, "type": "dragonFly" }, + { "amount": 18, "type": "dragonFly" } + ], + "combat_value": 461, + "reward" : { + "value": 9600, + "creatures": [ { "amount": 12, "type": "vyvern" } ] + } + } + ] + } + } + }, + "shipwreck" : { + "index" :85, + "handler": "bank", + "types" : { + "shipwreck" : { + "index" : 0, + "resetDuraition" : 28, + "name" : "Shipwreck", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 2, "type": "wight" }, + { "amount": 2, "type": "wight" }, + { "amount": 2, "type": "wight" }, + { "amount": 2, "type": "wight" }, + { "amount": 2, "type": "wight" } + ], + "combat_value": 31, + "reward" : { + "value": 2000, + "resources": + { + "gold" : 2000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 3, "type": "wight" }, + { "amount": 3, "type": "wight" }, + { "amount": 3, "type": "wight" }, + { "amount": 3, "type": "wight" }, + { "amount": 3, "type": "wight" } + ], + "combat_value": 46, + "reward" : { + "value": 3000, + "resources": + { + "gold" : 3000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 5, "type": "wight" }, + { "amount": 5, "type": "wight" }, + { "amount": 5, "type": "wight" }, + { "amount": 5, "type": "wight" }, + { "amount": 5, "type": "wight" } + ], + "combat_value": 77, + "reward" : { + "value": 5000, + "resources": + { + "gold" : 4000 + }, + "artifacts": [ { "class" : "TREASURE" } ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 10, "type": "wight" }, + { "amount": 10, "type": "wight" }, + { "amount": 10, "type": "wight" }, + { "amount": 10, "type": "wight" }, + { "amount": 10, "type": "wight" } + ], + "combat_value": 154, + "reward" : { + "value": 7000, + "resources": + { + "gold" : 5000 + }, + "artifacts": [ { "class" : "MINOR" } ] + } + } + ] + } + } + } + "derelictShip" : { + "index" :24, + "handler": "bank", + "types" : { + "derelictShip" : { + "index" : 0, + "resetDuraition" : 28, + "name" : "Derelict Ship", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 4, "type": "waterElemental" }, + { "amount": 4, "type": "waterElemental" }, + { "amount": 4, "type": "waterElemental" }, + { "amount": 4, "type": "waterElemental" }, + { "amount": 4, "type": "waterElemental" } + ], + "combat_value": 138, + "reward" : { + "value": 3000, + "resources": + { + "gold" : 3000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 6, "type": "waterElemental" }, + { "amount": 6, "type": "waterElemental" }, + { "amount": 6, "type": "waterElemental" }, + { "amount": 6, "type": "waterElemental" }, + { "amount": 6, "type": "waterElemental" } + ], + "combat_value": 207, + "reward" : { + "value": 4000, + "resources": + { + "gold" : 3000 + }, + "artifacts": [ { "class" : "TREASURE" } ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "waterElemental" }, + { "amount": 8, "type": "waterElemental" }, + { "amount": 8, "type": "waterElemental" }, + { "amount": 8, "type": "waterElemental" }, + { "amount": 8, "type": "waterElemental" } + ], + "combat_value": 276, + "reward" : { + "value": 5000, + "resources": + { + "gold" : 4000 + }, + "artifacts": [ { "class" : "TREASURE" } ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 12, "type": "waterElemental" }, + { "amount": 12, "type": "waterElemental" }, + { "amount": 12, "type": "waterElemental" }, + { "amount": 12, "type": "waterElemental" }, + { "amount": 12, "type": "waterElemental" } + ], + "combat_value": 414, + "reward" : { + "value": 8000, + "resources": + { + "gold" : 6000 + }, + "artifacts": [ { "class" : "MINOR" } ] + } + } + ] + } + } + }, + "crypt" : { + "index" :84, + "handler": "bank", + "types" : { + "crypt" : { + "index" : 0, + "resetDuraition" : 28, + "name" : "Crypt", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 10, "type": "skeleton" }, + { "amount": 10, "type": "walkingDead" }, + { "amount": 10, "type": "walkingDead" }, + { "amount": 10, "type": "skeleton" }, + { "amount": 10, "type": "skeleton" } + ], + "combat_value": 75, + "reward" : { + "value": 1500, + "resources": + { + "gold" : 1500 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 13, "type": "skeleton" }, + { "amount": 10, "type": "walkingDead" }, + { "amount": 5, "type": "wight" }, + { "amount": 10, "type": "walkingDead" }, + { "amount": 12, "type": "skeleton" } + ], + "combat_value": 94, + "reward" : { + "value": 2000, + "resources": + { + "gold" : 2000 + } + } + }, + { + "chance": 30, + "guards": [ + { "amount": 20, "type": "skeleton" }, + { "amount": 20, "type": "walkingDead" }, + { "amount": 10, "type": "wight" }, + { "amount": 5, "type": "vampire" } + ], + "combat_value": 169, + "reward" : { + "value": 3500, + "resources": + { + "gold" : 2500 + }, + "artifacts": [ { "class" : "TREASURE" } ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 20, "type": "skeleton" }, + { "amount": 20, "type": "walkingDead" }, + { "amount": 10, "type": "wight" }, + { "amount": 10, "type": "vampire" } + ], + "combat_value": 225, + "reward" : { + "value": 6000, + "resources": + { + "gold" : 5000 + }, + "artifacts": [ { "class" : "TREASURE" } ] + } + } + ] + } + } + }, + "dragonUtopia" : { + "index" :25, + "handler": "bank", + "types" : { + "dragonUtopia" : { + "index" : 0, + "resetDuraition" : 28, + "name" : "Dragon Utopia", + "levels": [ + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "greenDragon" }, + { "amount": 5, "type": "redDragon" }, + { "amount": 2, "type": "goldDragon" }, + { "amount": 1, "type": "blackDragon" } + ], + "combat_value": 769, + "reward" : { + "value": 38000, + "resources": + { + "gold" : 20000 + }, + "artifacts": [ + { "class" : "TREASURE" } + { "class" : "MINOR" } + { "class" : "MAJOR" } + { "class" : "RELIC" } + ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "greenDragon" }, + { "amount": 6, "type": "redDragon" }, + { "amount": 3, "type": "goldDragon" }, + { "amount": 2, "type": "blackDragon" } + ], + "combat_value": 209, + "reward" : { + "value": 57000, + "resources": + { + "gold" : 30000 + }, + "artifacts": [ + { "class" : "MINOR" } + { "class" : "MAJOR" } + { "class" : "RELIC" } + { "class" : "RELIC" } + ] + } + }, + { + "chance": 30, + "guards": [ + { "amount": 8, "type": "greenDragon" }, + { "amount": 6, "type": "redDragon" }, + { "amount": 4, "type": "goldDragon" }, + { "amount": 3, "type": "blackDragon" } + ], + "combat_value": 556, + "reward" : { + "value": 75000, + "resources": + { + "gold" : 40000 + }, + "artifacts": [ 0, 0, 1, 3 ] + "artifacts": [ + { "class" : "MAJOR" } + { "class" : "RELIC" } + { "class" : "RELIC" } + { "class" : "RELIC" } + ] + } + }, + { + "chance": 10, + "guards": [ + { "amount": 8, "type": "greenDragon" }, + { "amount": 7, "type": "redDragon" }, + { "amount": 6, "type": "goldDragon" }, + { "amount": 5, "type": "blackDragon" } + ], + "combat_value": 343, + "reward" : { + "value": 90000, + "resources": + { + "gold" : 50000 + }, + "artifacts": [ 0, 0, 0, 4 ] + "artifacts": [ + { "class" : "RELIC" } + { "class" : "RELIC" } + { "class" : "RELIC" } + { "class" : "RELIC" } + ] + } + } + ] + } + } + }, + "pyramid" : { + "index" :63, + "handler": "bank", + "types" : { + "pyramid" : { + "index" : 0, + "resetDuraition" : 28, + "name" : "Pyramid", + "levels": [ + { + "chance": 100, + "guards": [ + { "amount": 20, "type": "goldGolem" }, + { "amount": 10, "type": "diamondGolem" }, + { "amount": 20, "type": "goldGolem" }, + { "amount": 10, "type": "diamondGolem" } + ], + "combat_value": 786, + "reward" : { + "value": 15000, + "spells" : [ { "level" : 5 } ] + } + } + ] + } + } + } +} diff --git a/config/objects/dwellings.json b/config/objects/dwellings.json index 8b5c83f52..0f8150163 100644 --- a/config/objects/dwellings.json +++ b/config/objects/dwellings.json @@ -104,9 +104,9 @@ [ "fireElemental" ], [ "earthElemental" ] ], - "guards" : { - "earthElemental" : 12 - } + "guards" : [ + { "amount" : 12, "type" : "earthElemental" } + ] }, "golemFactory" : { "index" : 1, @@ -116,10 +116,10 @@ [ "goldGolem" ], [ "diamondGolem" ] ], - "guards" : { - "goldGolem" : 9, - "diamondGolem" : 6 - } + "guards" : [ + { "amount" : 9, "type" : "goldGolem" }, + { "amount" : 6, "type" : "diamondGolem" } + ] } } }, diff --git a/config/objects/generic.json b/config/objects/generic.json index 759214acd..bc1e1dcf8 100644 --- a/config/objects/generic.json +++ b/config/objects/generic.json @@ -16,11 +16,6 @@ "blackMarket" : { "index" :7, "handler": "blackMarket" }, - "crypt" : { "index" :84, "handler": "bank" }, - "shipwreck" : { "index" :85, "handler": "bank" }, - "derelictShip" : { "index" :24, "handler": "bank" }, - "dragonUtopia" : { "index" :25, "handler": "bank" }, - "pandoraBox" : { "index" :6, "handler": "pandora" }, "event" : { "index" :26, "handler": "event" }, @@ -45,7 +40,6 @@ "magicWell" : { "index" :49, "handler": "magicWell" }, "obelisk" : { "index" :57, "handler": "obelisk" }, "oceanBottle" : { "index" :59, "handler": "sign" }, - "pyramid" : { "index" :63, "handler": "pyramid" }, "scholar" : { "index" :81, "handler": "scholar" }, "shipyard" : { "index" :87, "handler": "shipyard" }, "sign" : { "index" :91, "handler": "sign" }, diff --git a/config/objects/moddables.json b/config/objects/moddables.json index 195e4989d..b03c5f6a5 100644 --- a/config/objects/moddables.json +++ b/config/objects/moddables.json @@ -184,9 +184,6 @@ } }, - // subtype: different content - "creatureBank" : { "index" :16, "handler": "bank" }, - // subtype: 0 = normal, 1 = anti-magic "garrisonHorizontal" : { "index" :33, "handler": "garrison" }, "garrisonVertical" : { "index" :219, "handler": "garrison" }, diff --git a/lib/CArtHandler.cpp b/lib/CArtHandler.cpp index 72e9f5065..561300a7b 100644 --- a/lib/CArtHandler.cpp +++ b/lib/CArtHandler.cpp @@ -262,12 +262,21 @@ CArtifact * CArtHandler::loadFromJson(const JsonNode & node) return art; } -void CArtHandler::addSlot(CArtifact * art, const std::string & slotID) +ArtifactPosition CArtHandler::stringToSlot(std::string slotName) { #define ART_POS(x) ( #x, ArtifactPosition::x ) static const std::map artifactPositionMap = boost::assign::map_list_of ART_POS_LIST; #undef ART_POS + auto it = artifactPositionMap.find (slotName); + if (it != artifactPositionMap.end()) + return it->second; + logGlobal->warnStream() << "Warning! Artifact slot " << slotName << " not recognized!"; + return ArtifactPosition::PRE_FIRST; +} + +void CArtHandler::addSlot(CArtifact * art, const std::string & slotID) +{ if (slotID == "MISC") { art->possibleSlots[ArtBearer::HERO] += ArtifactPosition::MISC1, ArtifactPosition::MISC2, ArtifactPosition::MISC3, ArtifactPosition::MISC4, ArtifactPosition::MISC5; @@ -278,14 +287,9 @@ void CArtHandler::addSlot(CArtifact * art, const std::string & slotID) } else { - auto it = artifactPositionMap.find (slotID); - if (it != artifactPositionMap.end()) - { - auto slot = it->second; + auto slot = stringToSlot(slotID); + if (slot != ArtifactPosition::PRE_FIRST) art->possibleSlots[ArtBearer::HERO].push_back (slot); - } - else - logGlobal->warnStream() << "Warning! Artifact slot " << slotID << " not recognized!"; } } @@ -303,7 +307,7 @@ void CArtHandler::loadSlots(CArtifact * art, const JsonNode & node) } } -void CArtHandler::loadClass(CArtifact * art, const JsonNode & node) +CArtifact::EartClass CArtHandler::stringToClass(std::string className) { static const std::map artifactClassMap = boost::assign::map_list_of ("TREASURE", CArtifact::ART_TREASURE) @@ -312,16 +316,17 @@ void CArtHandler::loadClass(CArtifact * art, const JsonNode & node) ("RELIC", CArtifact::ART_RELIC) ("SPECIAL", CArtifact::ART_SPECIAL); - auto it = artifactClassMap.find (node["class"].String()); + auto it = artifactClassMap.find (className); if (it != artifactClassMap.end()) - { - art->aClass = it->second; - } - else - { - logGlobal->warnStream() << "Warning! Artifact rarity " << node["class"].String() << " not recognized!"; - art->aClass = CArtifact::ART_SPECIAL; - } + return it->second; + + logGlobal->warnStream() << "Warning! Artifact rarity " << className << " not recognized!"; + return CArtifact::ART_SPECIAL; +} + +void CArtHandler::loadClass(CArtifact * art, const JsonNode & node) +{ + art->aClass = stringToClass(node["class"].String()); } void CArtHandler::loadType(CArtifact * art, const JsonNode & node) @@ -424,7 +429,7 @@ CreatureID CArtHandler::machineIDToCreature(ArtifactID id) return CreatureID::NONE; //this artifact is not a creature } -ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags) +ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags, std::function accepts) { auto getAllowedArts = [&](std::vector > &out, std::vector *arts, CArtifact::EartClass flag) { @@ -433,8 +438,11 @@ ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags) for (auto & arts_i : *arts) { - CArtifact *art = arts_i; - out.push_back(art); + if (accepts(arts_i->id)) + { + CArtifact *art = arts_i; + out.push_back(art); + } } }; @@ -469,6 +477,16 @@ ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags) return artID; } +ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, std::function accepts) +{ + return pickRandomArtifact(rand, 0xff, accepts); +} + +ArtifactID CArtHandler::pickRandomArtifact(CRandomGenerator & rand, int flags) +{ + return pickRandomArtifact(rand, flags, [](ArtifactID){ return true;}); +} + Bonus *createBonus(Bonus::BonusType type, int val, int subtype, Bonus::ValueType valType, shared_ptr limiter = shared_ptr(), int additionalInfo = 0) { auto added = new Bonus(Bonus::PERMANENT,type,Bonus::ARTIFACT,val,-1,subtype); diff --git a/lib/CArtHandler.h b/lib/CArtHandler.h index 1105f9d4a..592e1de58 100644 --- a/lib/CArtHandler.h +++ b/lib/CArtHandler.h @@ -203,11 +203,17 @@ public: boost::optional&> listFromClass(CArtifact::EartClass artifactClass); + ArtifactPosition stringToSlot(std::string slotName); + CArtifact::EartClass stringToClass(std::string className); + /// Gets a artifact ID randomly and removes the selected artifact from this handler. ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags); + ArtifactID pickRandomArtifact(CRandomGenerator & rand, std::function accepts); + ArtifactID pickRandomArtifact(CRandomGenerator & rand, int flags, std::function accepts); + bool legalArtifact(ArtifactID id); - void getAllowedArts(std::vector > &out, std::vector *arts, int flag); - void getAllowed(std::vector > &out, int flags); + //void getAllowedArts(std::vector > &out, std::vector *arts, int flag); + //void getAllowed(std::vector > &out, int flags); bool isBigArtifact (ArtifactID artID) const {return bigArtifacts.find(artID) != bigArtifacts.end();} void initAllowedArtifactsList(const std::vector &allowed); //allowed[art_id] -> 0 if not allowed, 1 if allowed static ArtifactID creatureToMachineID(CreatureID id); diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index 6ef239348..89ae588cb 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -31,6 +31,7 @@ set(lib_SRCS mapObjects/CQuest.cpp mapObjects/CRewardableConstructor.cpp mapObjects/CRewardableObject.cpp + mapObjects/JsonRandom.cpp mapObjects/MiscObjects.cpp mapObjects/ObjectTemplate.cpp diff --git a/lib/NetPacks.h b/lib/NetPacks.h index 62ff3ce37..5f8c0d666 100644 --- a/lib/NetPacks.h +++ b/lib/NetPacks.h @@ -1021,8 +1021,7 @@ namespace ObjProperty BONUS_VALUE_FIRST, BONUS_VALUE_SECOND, //used in Rampart for special building that generates resources (storing resource type and quantity) //creature-bank specific - BANK_DAYCOUNTER, BANK_CLEAR_ARTIFACTS, BANK_ADD_ARTIFACT, BANK_MULTIPLIER, BANK_CONFIG_PRESET, - BANK_CLEAR_CONFIG, BANK_INIT_ARMY, BANK_RESET, + BANK_DAYCOUNTER, BANK_RESET, BANK_CLEAR, //object with reward REWARD_RESET, REWARD_SELECT diff --git a/lib/VCMI_Lib.cpp b/lib/VCMI_Lib.cpp index 706c21886..620663058 100644 --- a/lib/VCMI_Lib.cpp +++ b/lib/VCMI_Lib.cpp @@ -45,11 +45,11 @@ DLL_LINKAGE void preinitDLL(CConsoleHandler *Console) DLL_LINKAGE void loadDLLClasses() { - try + //try { VLC->init(); } - HANDLE_EXCEPTION; + //HANDLE_EXCEPTION; } const IBonusTypeHandler * LibClasses::getBth() const diff --git a/lib/mapObjects/CBank.cpp b/lib/mapObjects/CBank.cpp index 0302310d9..8f4f52582 100644 --- a/lib/mapObjects/CBank.cpp +++ b/lib/mapObjects/CBank.cpp @@ -14,6 +14,8 @@ #include "../NetPacks.h" #include "../CGeneralTextHandler.h" #include "../CSoundBase.h" +#include "CommonConstructors.h" +#include "../CSpellHandler.h" using namespace boost::assign; @@ -24,134 +26,51 @@ static std::string & visitedTxt(const bool visited) return VLC->generaltexth->allTexts[id]; } +CBank::CBank() +{ +} + +CBank::~CBank() +{ +} + void CBank::initObj() { - index = VLC->objh->bankObjToIndex(this); - bc = nullptr; daycounter = 0; - multiplier = 1; + resetDuration = 0; + VLC->objtypeh->getHandlerFor(ID, subID)->configureObject(this, cb->gameState()->getRandomGenerator()); } + const std::string & CBank::getHoverText() const { bool visited = (bc == nullptr); - hoverName = VLC->objh->creBanksNames[index] + " " + visitedTxt(visited); + hoverName = visitedTxt(visited); // FIXME: USE BANK_SPECIFIC NAMES return hoverName; } -void CBank::reset(ui16 var1) //prevents desync + +void CBank::setConfig(const BankConfig & config) { - ui8 chance = 0; - for (auto & elem : VLC->objh->banksInfo[index]) - { - if (var1 < (chance += elem->chance)) - { - bc = elem; - break; - } - } - artifacts.clear(); + bc.reset(new BankConfig(config)); + clear(); // remove all stacks, if any + + for (auto & stack : config.guards) + setCreature (SlotID(stacksCount()), stack.type->idNumber, stack.count); } -void CBank::initialize() const -{ - cb->setObjProperty(id, ObjProperty::BANK_RESET, cb->gameState()->getRandomGenerator().nextInt()); //synchronous reset - - for (ui8 i = 0; i <= 3; i++) - { - for (ui8 n = 0; n < bc->artifacts[i]; n++) - { - CArtifact::EartClass artClass; - switch(i) - { - case 0: artClass = CArtifact::ART_TREASURE; break; - case 1: artClass = CArtifact::ART_MINOR; break; - case 2: artClass = CArtifact::ART_MAJOR; break; - case 3: artClass = CArtifact::ART_RELIC; break; - default: assert(0); continue; - } - - int artID = VLC->arth->pickRandomArtifact(cb->gameState()->getRandomGenerator(), artClass); - cb->setObjProperty(id, ObjProperty::BANK_ADD_ARTIFACT, artID); - } - } - - cb->setObjProperty(id, ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //get army -} void CBank::setPropertyDer (ui8 what, ui32 val) -/// random values are passed as arguments and processed identically on all clients { switch (what) { case ObjProperty::BANK_DAYCOUNTER: //daycounter - if (val == 0) - daycounter = 1; //yes, 1 - else - daycounter++; - break; - case ObjProperty::BANK_MULTIPLIER: //multiplier, in percent - multiplier = val / 100.0; - break; - case 13: //bank preset - bc = VLC->objh->banksInfo[index][val]; + daycounter+=val; break; case ObjProperty::BANK_RESET: - reset (val%100); + initObj(); + daycounter = 1; //yes, 1 since "today" daycounter won't be incremented break; - case ObjProperty::BANK_CLEAR_CONFIG: - bc = nullptr; + case ObjProperty::BANK_CLEAR: + bc.reset(); break; - case ObjProperty::BANK_CLEAR_ARTIFACTS: //remove rewards from Derelict Ship - artifacts.clear(); - break; - case ObjProperty::BANK_INIT_ARMY: //set ArmedInstance army - { - int upgraded = 0; - if (val%100 < bc->upgradeChance) //once again anti-desync - upgraded = 1; - switch (bc->guards.size()) - { - case 1: - for (int i = 0; i < 4; ++i) - setCreature (SlotID(i), bc->guards[0].first, bc->guards[0].second / 5 ); - setCreature (SlotID(4), CreatureID(bc->guards[0].first + upgraded), bc->guards[0].second / 5 ); - break; - case 4: - { - if (bc->guards.back().second) //all stacks are present - { - for (auto & elem : bc->guards) - { - setCreature (SlotID(stacksCount()), elem.first, elem.second); - } - } - else if (bc->guards[2].second)//Wraiths are present, split two stacks in Crypt - { - setCreature (SlotID(0), bc->guards[0].first, bc->guards[0].second / 2 ); - setCreature (SlotID(1), bc->guards[1].first, bc->guards[1].second / 2); - setCreature (SlotID(2), CreatureID(bc->guards[2].first + upgraded), bc->guards[2].second); - setCreature (SlotID(3), bc->guards[1].first, bc->guards[1].second / 2 ); - setCreature (SlotID(4), bc->guards[0].first, bc->guards[0].second - (bc->guards[0].second / 2) ); - - } - else //split both stacks - { - for (int i = 0; i < 3; ++i) //skellies - setCreature (SlotID(2*i), bc->guards[0].first, bc->guards[0].second / 3); - for (int i = 0; i < 2; ++i) //zombies - setCreature (SlotID(2*i+1), bc->guards[1].first, bc->guards[1].second / 2); - } - } - break; - default: - logGlobal->warnStream() << "Error: Unexpected army data: " << bc->guards.size() <<" items found"; - return; - } - } - break; - case ObjProperty::BANK_ADD_ARTIFACT: //add Artifact - { - artifacts.push_back (val); - break; - } } } @@ -159,25 +78,19 @@ void CBank::newTurn() const { if (bc == nullptr) { - if (cb->getDate() == 1) - initialize(); //initialize on first day - else if (daycounter >= 28 && (subID < 13 || subID > 16)) //no reset for Emissaries + if (resetDuration != 0) { - initialize(); - cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 0); //daycounter 0 - if (ID == Obj::DERELICT_SHIP && cb->getDate() > 1) - { - cb->setObjProperty (id, ObjProperty::BANK_MULTIPLIER, 0);//ugly hack to make derelict ships usable only once - cb->setObjProperty (id, ObjProperty::BANK_CLEAR_ARTIFACTS, 0); - } + if (daycounter >= resetDuration) + cb->setObjProperty (id, ObjProperty::BANK_RESET, 0); //daycounter 0 + else + cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++ } - else - cb->setObjProperty (id, ObjProperty::BANK_DAYCOUNTER, 1); //daycounter++ } } + bool CBank::wasVisited (PlayerColor player) const { - return !bc; + return !bc; //FIXME: player A should not know about visit done by player B } void CBank::onHeroVisit (const CGHeroInstance * h) const @@ -185,6 +98,7 @@ void CBank::onHeroVisit (const CGHeroInstance * h) const if (bc) { int banktext = 0; + ui16 soundID = soundBase::ROGUE; switch (ID) { case Obj::CREATURE_BANK: @@ -202,13 +116,17 @@ void CBank::onHeroVisit (const CGHeroInstance * h) const case Obj::SHIPWRECK: banktext = 122; break; + case Obj::PYRAMID: + soundID = soundBase::MYSTERY; + banktext = 105; + break; } BlockingDialog bd (true, false); bd.player = h->getOwner(); - bd.soundID = soundBase::ROGUE; - bd.text.addTxt(MetaString::ADVOB_TXT,banktext); - if (ID == Obj::CREATURE_BANK) - bd.text.addReplacement(VLC->objh->creBanksNames[index]); + bd.soundID = soundID; + bd.text.addTxt(MetaString::ADVOB_TXT, banktext); + //if (ID == Obj::CREATURE_BANK) + // bd.text.addReplacement(VLC->objh->creBanksNames[index]); // FIXME: USE BANK SPECIFIC NAMES cb->showBlockingDialog (&bd); } else @@ -216,105 +134,100 @@ void CBank::onHeroVisit (const CGHeroInstance * h) const InfoWindow iw; iw.soundID = soundBase::GRAVEYARD; iw.player = h->getOwner(); - if (ID == Obj::CRYPT) //morale penalty for empty Crypt + if (ID == Obj::PYRAMID) // You come upon the pyramid ... pyramid is completely empty. { - GiveBonus gbonus; - gbonus.id = h->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[98]; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = -1; - cb->giveHeroBonus(&gbonus); - iw.text << VLC->generaltexth->advobtxt[120]; - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); + iw.text << VLC->generaltexth->advobtxt[107]; + iw.components.push_back (Component (Component::LUCK, 0 , -2, 0)); + GiveBonus gb; + gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,-2,id.getNum(),VLC->generaltexth->arraytxt[70]); + gb.id = h->id.getNum(); + cb->giveHeroBonus(&gb); } else { - iw.text << VLC->generaltexth->advobtxt[33]; - iw.text.addReplacement(VLC->objh->creBanksNames[index]); + iw.text << VLC->generaltexth->advobtxt[33];// This was X, now is completely empty + //iw.text.addReplacement(VLC->objh->creBanksNames[index]); // FIXME: USE BANK SPECIFIC NAMES } cb->showInfoDialog(&iw); } } -void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +void CBank::doVisit(const CGHeroInstance * hero) const { - if (result.winner == 0) + int textID = -1; + InfoWindow iw; + iw.player = hero->getOwner(); + MetaString loot; + + switch (ID) { - int textID = -1; - InfoWindow iw; - iw.player = hero->getOwner(); - MetaString loot; - - switch (ID) + case Obj::CREATURE_BANK: + case Obj::DRAGON_UTOPIA: + textID = 34; + break; + case Obj::DERELICT_SHIP: + if (!bc) + textID = 43; + else { - case Obj::CREATURE_BANK: case Obj::DRAGON_UTOPIA: - textID = 34; - break; - case Obj::DERELICT_SHIP: - if (multiplier) - textID = 43; - else - { - GiveBonus gbonus; - gbonus.id = hero->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[101]; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = -1; - cb->giveHeroBonus(&gbonus); - textID = 42; - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - } - break; - case Obj::CRYPT: - if (bc->resources.size() != 0) - textID = 121; - else - { - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - GiveBonus gbonus; - gbonus.id = hero->id.getNum(); - gbonus.bonus.duration = Bonus::ONE_BATTLE; - gbonus.bonus.source = Bonus::OBJECT; - gbonus.bonus.sid = ID; - gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[ID]; - gbonus.bonus.type = Bonus::MORALE; - gbonus.bonus.val = -1; - cb->giveHeroBonus(&gbonus); - textID = 120; - iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); - } - break; - case Obj::SHIPWRECK: - if (bc->resources.size()) - textID = 124; - else - textID = 123; - break; + GiveBonus gbonus; + gbonus.id = hero->id.getNum(); + gbonus.bonus.duration = Bonus::ONE_BATTLE; + gbonus.bonus.source = Bonus::OBJECT; + gbonus.bonus.sid = ID; + gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[101]; + gbonus.bonus.type = Bonus::MORALE; + gbonus.bonus.val = -1; + cb->giveHeroBonus(&gbonus); + textID = 42; + iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); } - - //grant resources - if (textID != 42) //empty derelict ship gives no cash + break; + case Obj::CRYPT: + if (bc) + textID = 121; + else { - for (int it = 0; it < bc->resources.size(); it++) + iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); + GiveBonus gbonus; + gbonus.id = hero->id.getNum(); + gbonus.bonus.duration = Bonus::ONE_BATTLE; + gbonus.bonus.source = Bonus::OBJECT; + gbonus.bonus.sid = ID; + gbonus.bdescr << "\n" << VLC->generaltexth->arraytxt[ID]; + gbonus.bonus.type = Bonus::MORALE; + gbonus.bonus.val = -1; + cb->giveHeroBonus(&gbonus); + textID = 120; + iw.components.push_back (Component (Component::MORALE, 0 , -1, 0)); + } + break; + case Obj::SHIPWRECK: + if (bc) + textID = 124; + else + textID = 123; + break; + case Obj::PYRAMID: + textID = 106; + } + + //grant resources + if (bc) + { + for (int it = 0; it < bc->resources.size(); it++) + { + if (bc->resources[it] != 0) { - if (bc->resources[it] != 0) - { - iw.components.push_back (Component (Component::RESOURCE, it, bc->resources[it], 0)); - loot << "%d %s"; - loot.addReplacement(iw.components.back().val); - loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype); - cb->giveResource (hero->getOwner(), static_cast(it), bc->resources[it]); - } + iw.components.push_back (Component (Component::RESOURCE, it, bc->resources[it], 0)); + loot << "%d %s"; + loot.addReplacement(iw.components.back().val); + loot.addReplacement(MetaString::RES_NAMES, iw.components.back().subtype); + cb->giveResource (hero->getOwner(), static_cast(it), bc->resources[it]); } } //grant artifacts - for (auto & elem : artifacts) + for (auto & elem : bc->artifacts) { iw.components.push_back (Component (Component::ARTIFACT, elem, 0, 0)); loot << "%s"; @@ -327,22 +240,52 @@ void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &resul iw.text.addTxt (MetaString::ADVOB_TXT, textID); if (textID == 34) { - iw.text.addReplacement(MetaString::CRE_PL_NAMES, result.casualties[1].begin()->first); + const CCreature * strongest = boost::range::max_element(bc->guards, [](const CStackBasicDescriptor & a, const CStackBasicDescriptor & b) + { + return a.type->fightValue < b.type->fightValue; + })->type; + + iw.text.addReplacement(MetaString::CRE_PL_NAMES, strongest->idNumber); iw.text.addReplacement(loot.buildList()); } cb->showInfoDialog(&iw); } + + if (!bc->spells.empty()) + { + std::set spells; + + bool noWisdom = false; + for (SpellID spell : bc->spells) + { + iw.text.addTxt (MetaString::SPELL_NAME, spell); + if (VLC->spellh->objects[spell]->level <= hero->getSecSkillLevel(SecondarySkill::WISDOM) + 2) + { + spells.insert(spell); + iw.components.push_back(Component (Component::SPELL, spell, 0, 0)); + } + else + noWisdom = true; + } + + if (!hero->getArt(ArtifactPosition::SPELLBOOK)) + iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook + else if (noWisdom) + iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom + if (spells.empty()) + cb->changeSpells (hero, true, spells); + } loot.clear(); iw.components.clear(); iw.text.clear(); //grant creatures CCreatureSet ourArmy; - for (auto it = bc->creatures.cbegin(); it != bc->creatures.cend(); it++) + for (auto slot : bc->creatures) { - SlotID slot = ourArmy.getSlotFor(it->first); - ourArmy.addToSlot(slot, it->first, it->second); + ourArmy.addToSlot(ourArmy.getSlotFor(slot.type->idNumber), slot.type->idNumber, slot.count); } + for (auto & elem : ourArmy.Slots()) { iw.components.push_back(Component(*elem.second)); @@ -362,7 +305,15 @@ void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &resul cb->showInfoDialog(&iw); cb->giveCreatures(this, hero, ourArmy, false); } - cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); //bc = nullptr + cb->setObjProperty (id, ObjProperty::BANK_CLEAR, 0); //bc = nullptr + } +} + +void CBank::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const +{ + if (result.winner == 0) + { + doVisit(hero); } } @@ -370,74 +321,9 @@ void CBank::blockingDialogAnswered(const CGHeroInstance *hero, ui32 answer) cons { if (answer) { - cb->startBattleI(hero, this, true); - } -} - -void CGPyramid::initObj() -{ - std::vector available; - cb->getAllowedSpells (available, 5); - if (available.size()) - { - bc = VLC->objh->banksInfo[21].front(); //TODO: remove hardcoded value? - spell = *RandomGeneratorUtil::nextItem(available, cb->gameState()->getRandomGenerator()); - } - else - { - logGlobal->errorStream() <<"No spells available for Pyramid! Object set to empty."; - } - setPropertyDer(ObjProperty::BANK_INIT_ARMY, cb->gameState()->getRandomGenerator().nextInt()); //set guards at game start -} -const std::string & CGPyramid::getHoverText() const -{ - hoverName = VLC->objh->creBanksNames[21]+ " " + visitedTxt((bc==nullptr)); - return hoverName; -} -void CGPyramid::onHeroVisit (const CGHeroInstance * h) const -{ - if (bc) - { - BlockingDialog bd (true, false); - bd.player = h->getOwner(); - bd.soundID = soundBase::MYSTERY; - bd.text << VLC->generaltexth->advobtxt[105]; - cb->showBlockingDialog(&bd); - } - else - { - InfoWindow iw; - iw.player = h->getOwner(); - iw.text << VLC->generaltexth->advobtxt[107]; - iw.components.push_back (Component (Component::LUCK, 0 , -2, 0)); - GiveBonus gb; - gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::LUCK,Bonus::OBJECT,-2,id.getNum(),VLC->generaltexth->arraytxt[70]); - gb.id = h->id.getNum(); - cb->giveHeroBonus(&gb); - cb->showInfoDialog(&iw); - } -} - -void CGPyramid::battleFinished(const CGHeroInstance *hero, const BattleResult &result) const -{ - if (result.winner == 0) - { - InfoWindow iw; - iw.player = hero->getOwner(); - iw.text.addTxt (MetaString::ADVOB_TXT, 106); - iw.text.addTxt (MetaString::SPELL_NAME, spell); - if (!hero->getArt(ArtifactPosition::SPELLBOOK)) - iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook - else if (hero->getSecSkillLevel(SecondarySkill::WISDOM) < 3) - iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom + if (bc) // not looted bank + cb->startBattleI(hero, this, true); else - { - std::set spells; - spells.insert (SpellID(spell)); - cb->changeSpells (hero, true, spells); - iw.components.push_back(Component (Component::SPELL, spell, 0, 0)); - } - cb->showInfoDialog(&iw); - cb->setObjProperty (id, ObjProperty::BANK_CLEAR_CONFIG, 0); + doVisit(hero); } } diff --git a/lib/mapObjects/CBank.h b/lib/mapObjects/CBank.h index 71e1bc00d..c8f8471aa 100644 --- a/lib/mapObjects/CBank.h +++ b/lib/mapObjects/CBank.h @@ -13,19 +13,26 @@ * */ +class BankConfig; +class CBankInstanceConstructor; + class DLL_LINKAGE CBank : public CArmedInstance { - public: - int index; //banks have unusal numbering - see ZCRBANK.txt and initObj() - BankConfig *bc; - double multiplier; //for improved banks script - std::vector artifacts; //fixed and deterministic + std::unique_ptr bc; ui32 daycounter; + ui32 resetDuration; + + void setPropertyDer(ui8 what, ui32 val) override; + void doVisit(const CGHeroInstance * hero) const; + +public: + CBank(); + ~CBank(); + + void setConfig(const BankConfig & bc); void initObj() override; const std::string & getHoverText() const override; - void initialize() const; - void reset(ui16 var1); void newTurn() const override; bool wasVisited (PlayerColor player) const override; void onHeroVisit(const CGHeroInstance * h) const override; @@ -35,25 +42,8 @@ class DLL_LINKAGE CBank : public CArmedInstance template void serialize(Handler &h, const int version) { h & static_cast(*this); - h & index & multiplier & artifacts & daycounter & bc; + h & daycounter & bc & resetDuration; } -protected: - void setPropertyDer(ui8 what, ui32 val) override; -}; -class DLL_LINKAGE CGPyramid : public CBank -{ -public: - ui16 spell; - void initObj() override; - const std::string & getHoverText() const override; - void newTurn() const override {}; //empty, no reset - void onHeroVisit(const CGHeroInstance * h) const override; - void battleFinished(const CGHeroInstance *hero, const BattleResult &result) const override; - - template void serialize(Handler &h, const int version) - { - h & static_cast(*this); - h & spell; - } + friend class CBankInstanceConstructor; }; diff --git a/lib/mapObjects/CObjectClassesHandler.cpp b/lib/mapObjects/CObjectClassesHandler.cpp index 553a40a15..122068eb6 100644 --- a/lib/mapObjects/CObjectClassesHandler.cpp +++ b/lib/mapObjects/CObjectClassesHandler.cpp @@ -35,13 +35,13 @@ CObjectClassesHandler::CObjectClassesHandler() SET_HANDLER_CLASS("dwelling", CDwellingInstanceConstructor); SET_HANDLER_CLASS("hero", CHeroInstanceConstructor); SET_HANDLER_CLASS("town", CTownInstanceConstructor); + SET_HANDLER_CLASS("bank", CBankInstanceConstructor); SET_HANDLER_CLASS("static", CObstacleConstructor); SET_HANDLER_CLASS("", CObstacleConstructor); SET_HANDLER("generic", CGObjectInstance); SET_HANDLER("market", CGMarket); - SET_HANDLER("bank", CBank); SET_HANDLER("cartographer", CCartographer); SET_HANDLER("artifact", CGArtifact); SET_HANDLER("blackMarket", CGBlackMarket); @@ -67,7 +67,6 @@ CObjectClassesHandler::CObjectClassesHandler() SET_HANDLER("pandora", CGPandoraBox); SET_HANDLER("pickable", CGPickable); SET_HANDLER("prison", CGHeroInstance); - SET_HANDLER("pyramid", CGPyramid); SET_HANDLER("questGuard", CGQuestGuard); SET_HANDLER("resource", CGResource); SET_HANDLER("scholar", CGScholar); @@ -130,6 +129,11 @@ si32 selectNextID(const JsonNode & fixedID, const Map & map, si32 defaultID) void CObjectClassesHandler::loadObjectEntry(const JsonNode & entry, ObjectContainter * obj) { + if (!handlerConstructors.count(obj->handlerName)) + { + logGlobal->errorStream() << "Handler with name " << obj->handlerName << " was not found!"; + return; + } auto handler = handlerConstructors.at(obj->handlerName)(); handler->init(entry); diff --git a/lib/mapObjects/CObjectClassesHandler.h b/lib/mapObjects/CObjectClassesHandler.h index 441f57a26..413deea9e 100644 --- a/lib/mapObjects/CObjectClassesHandler.h +++ b/lib/mapObjects/CObjectClassesHandler.h @@ -50,20 +50,45 @@ struct RandomMapInfo class IObjectInfo { public: - virtual bool givesResources() const = 0; + struct CArmyStructure + { + ui32 totalStrength; + ui32 shootersStrength; + ui32 flyersStrength; + ui32 walkersStrength; - virtual bool givesExperience() const = 0; - virtual bool givesMana() const = 0; - virtual bool givesMovement() const = 0; + CArmyStructure() : + totalStrength(0), + shootersStrength(0), + flyersStrength(0), + walkersStrength(0) + {} - virtual bool givesPrimarySkills() const = 0; - virtual bool givesSecondarySkills() const = 0; + bool operator <(const CArmyStructure & other) + { + return this->totalStrength < other.totalStrength; + } + }; - virtual bool givesArtifacts() const = 0; - virtual bool givesCreatures() const = 0; - virtual bool givesSpells() const = 0; + /// Returns possible composition of guards. Actual guards would be + /// somewhere between these two values + virtual CArmyStructure minGuards() const { return CArmyStructure(); } + virtual CArmyStructure maxGuards() const { return CArmyStructure(); } - virtual bool givesBonuses() const = 0; + virtual bool givesResources() const { return false; } + + virtual bool givesExperience() const { return false; } + virtual bool givesMana() const { return false; } + virtual bool givesMovement() const { return false; } + + virtual bool givesPrimarySkills() const { return false; } + virtual bool givesSecondarySkills() const { return false; } + + virtual bool givesArtifacts() const { return false; } + virtual bool givesCreatures() const { return false; } + virtual bool givesSpells() const { return false; } + + virtual bool givesBonuses() const { return false; } }; class CGObjectInstance; @@ -118,7 +143,7 @@ public: virtual void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const = 0; /// Returns object configuration, if available. Othervice returns NULL - virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const = 0; + virtual std::unique_ptr getObjectInfo(ObjectTemplate tmpl) const = 0; template void serialize(Handler &h, const int version) { diff --git a/lib/mapObjects/CObjectHandler.cpp b/lib/mapObjects/CObjectHandler.cpp index 828a7ce9a..bdc06a286 100644 --- a/lib/mapObjects/CObjectHandler.cpp +++ b/lib/mapObjects/CObjectHandler.cpp @@ -108,53 +108,6 @@ void IObjectInterface::garrisonDialogClosed(const CGHeroInstance *hero) const void IObjectInterface::heroLevelUpDone(const CGHeroInstance *hero) const {} -// Bank helper. Find the creature ID and their number, and store the -// result in storage (either guards or reward creatures). -static void readCreatures(const JsonNode &creature, std::vector< std::pair > &storage) -{ - std::pair creInfo = std::make_pair(CreatureID::NONE, 0); - - //TODO: replace numeric id's with mod-friendly string id's - creInfo.second = creature["number"].Float(); - creInfo.first = CreatureID((si32)creature["id"].Float()); - storage.push_back(creInfo); -} - -// Bank helper. Process a bank level. -static void readBankLevel(const JsonNode &level, BankConfig &bc) -{ - int idx; - - bc.chance = level["chance"].Float(); - - for(const JsonNode &creature : level["guards"].Vector()) - { - readCreatures(creature, bc.guards); - } - - bc.upgradeChance = level["upgrade_chance"].Float(); - bc.combatValue = level["combat_value"].Float(); - - bc.resources = Res::ResourceSet(level["reward_resources"]); - - for(const JsonNode &creature : level["reward_creatures"].Vector()) - { - readCreatures(creature, bc.creatures); - } - - bc.artifacts.resize(4); - idx = 0; - for(const JsonNode &artifact : level["reward_artifacts"].Vector()) - { - bc.artifacts[idx] = artifact.Float(); - idx ++; - } - - bc.value = level["value"].Float(); - bc.rewardDifficulty = level["profitability"].Float(); - bc.easiest = level["easiest"].Float(); -} - CObjectHandler::CObjectHandler() { logGlobal->traceStream() << "\t\tReading resources prices "; @@ -164,62 +117,8 @@ CObjectHandler::CObjectHandler() resVals.push_back(price.Float()); } logGlobal->traceStream() << "\t\tDone loading resource prices!"; - - logGlobal->traceStream() << "\t\tReading banks configs"; - const JsonNode config3(ResourceID("config/bankconfig.json")); - int bank_num = 0; - for(const JsonNode &bank : config3["banks"].Vector()) - { - creBanksNames[bank_num] = bank["name"].String(); - - int level_num = 0; - for(const JsonNode &level : bank["levels"].Vector()) - { - banksInfo[bank_num].push_back(new BankConfig); - BankConfig &bc = *banksInfo[bank_num].back(); - bc.level = level_num; - - readBankLevel(level, bc); - level_num ++; - } - - bank_num ++; - } - logGlobal->traceStream() << "\t\tDone loading banks configs"; } -CObjectHandler::~CObjectHandler() -{ - for(auto & mapEntry : banksInfo) - { - for(auto & vecEntry : mapEntry.second) - { - vecEntry.dellNull(); - } - } -} - -int CObjectHandler::bankObjToIndex (const CGObjectInstance * obj) -{ - switch (obj->ID) //find appriopriate key - { - case Obj::CREATURE_BANK: - return obj->subID; - case Obj::DERELICT_SHIP: - return 8; - case Obj::DRAGON_UTOPIA: - return 10; - case Obj::CRYPT: - return 9; - case Obj::SHIPWRECK: - return 7; - case Obj::PYRAMID: - return 21; - default: - logGlobal->warnStream() << "Unrecognized Bank indetifier!"; - return 0; - } -} PlayerColor CGObjectInstance::getOwner() const { //if (state) diff --git a/lib/mapObjects/CObjectHandler.h b/lib/mapObjects/CObjectHandler.h index a9ab899b0..e1031a123 100644 --- a/lib/mapObjects/CObjectHandler.h +++ b/lib/mapObjects/CObjectHandler.h @@ -163,41 +163,15 @@ private: CGObjectInstance * obj; }; -struct BankConfig -{ - BankConfig() {level = chance = upgradeChance = combatValue = value = rewardDifficulty = easiest = 0; }; - ui8 level; //1 - 4, how hard the battle will be - ui8 chance; //chance for this level being chosen - ui8 upgradeChance; //chance for creatures to be in upgraded versions - std::vector< std::pair > guards; //creature ID, amount - ui32 combatValue; //how hard are guards of this level - Res::ResourceSet resources; //resources given in case of victory - std::vector< std::pair > creatures; //creatures granted in case of victory (creature ID, amount) - std::vector artifacts; //number of artifacts given in case of victory [0] -> treasure, [1] -> minor [2] -> major [3] -> relic - ui32 value; //overall value of given things - ui32 rewardDifficulty; //proportion of reward value to difficulty of guards; how profitable is this creature Bank config - ui16 easiest; //?!? - - template void serialize(Handler &h, const int version) - { - h & level & chance & upgradeChance & guards & combatValue & resources & creatures & artifacts & value & rewardDifficulty & easiest; - } -}; - class DLL_LINKAGE CObjectHandler { public: - std::map > > banksInfo; //[index][preset] - std::map creBanksNames; //[crebank index] -> name of this creature bank std::vector resVals; //default values of resources in gold CObjectHandler(); - ~CObjectHandler(); - - int bankObjToIndex (const CGObjectInstance * obj); template void serialize(Handler &h, const int version) { - h & banksInfo & creBanksNames & resVals; + h & resVals; } }; diff --git a/lib/mapObjects/CRewardableConstructor.cpp b/lib/mapObjects/CRewardableConstructor.cpp index d2abb8336..e5b02e0c9 100644 --- a/lib/mapObjects/CRewardableConstructor.cpp +++ b/lib/mapObjects/CRewardableConstructor.cpp @@ -4,6 +4,7 @@ #include "../CRandomGenerator.h" #include "../StringConstants.h" #include "../CCreatureHandler.h" +#include "JsonRandom.h" /* * CRewardableConstructor.cpp, part of VCMI engine @@ -16,100 +17,6 @@ */ namespace { - si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0) - { - if (value.isNull()) - return defaultValue; - if (value.getType() == JsonNode::DATA_FLOAT) - return value.Float(); - si32 min = value["min"].Float(); - si32 max = value["max"].Float(); - return rng.getIntRange(min, max)(); - } - - TResources loadResources(const JsonNode & value, CRandomGenerator & rng) - { - TResources ret; - for (size_t i=0; i loadPrimary(const JsonNode & value, CRandomGenerator & rng) - { - std::vector ret; - for (auto & name : PrimarySkill::names) - { - ret.push_back(loadValue(value[name], rng)); - } - return ret; - } - - std::map loadSecondary(const JsonNode & value, CRandomGenerator & rng) - { - std::map ret; - for (auto & pair : value.Struct()) - { - SecondarySkill id(VLC->modh->identifiers.getIdentifier(pair.second.meta, "skill", pair.first).get()); - ret[id] = loadValue(pair.second, rng); - } - return ret; - } - - std::vector loadArtifacts(const JsonNode & value, CRandomGenerator & rng) - { - std::vector ret; - for (const JsonNode & entry : value.Vector()) - { - ArtifactID art(VLC->modh->identifiers.getIdentifier("artifact", entry).get()); - ret.push_back(art); - } - return ret; - } - - std::vector loadSpells(const JsonNode & value, CRandomGenerator & rng) - { - std::vector ret; - for (const JsonNode & entry : value.Vector()) - { - SpellID spell(VLC->modh->identifiers.getIdentifier("spell", entry).get()); - ret.push_back(spell); - } - return ret; - } - - std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng) - { - std::vector ret; - for (auto & pair : value.Struct()) - { - CStackBasicDescriptor stack; - stack.type = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier(pair.second.meta, "creature", pair.first).get()]; - stack.count = loadValue(pair.second, rng); - ret.push_back(stack); - } - return ret; - } - - std::vector loadBonuses(const JsonNode & value) - { - std::vector ret; - for (const JsonNode & entry : value.Vector()) - { - Bonus * bonus = JsonUtils::parseBonus(entry); - ret.push_back(*bonus); - delete bonus; - } - return ret; - } - - std::vector loadComponents(const JsonNode & value) - { - //TODO - } - MetaString loadMessage(const JsonNode & value) { MetaString ret; @@ -167,38 +74,42 @@ void CRandomRewardObjectInfo::configureObject(CRewardableObject * object, CRando const JsonNode & limiter = reward["limiter"]; CVisitInfo info; // load limiter - info.limiter.numOfGrants = loadValue(limiter["numOfGrants"], rng); - info.limiter.dayOfWeek = loadValue(limiter["dayOfWeek"], rng); - info.limiter.minLevel = loadValue(limiter["minLevel"], rng); - info.limiter.resources = loadResources(limiter["resources"], rng); + info.limiter.numOfGrants = JsonRandom::loadValue(limiter["numOfGrants"], rng); + info.limiter.dayOfWeek = JsonRandom::loadValue(limiter["dayOfWeek"], rng); + info.limiter.minLevel = JsonRandom::loadValue(limiter["minLevel"], rng); + info.limiter.resources = JsonRandom::loadResources(limiter["resources"], rng); - info.limiter.primary = loadPrimary(limiter["primary"], rng); - info.limiter.secondary = loadSecondary(limiter["secondary"], rng); - info.limiter.artifacts = loadArtifacts(limiter["artifacts"], rng); - info.limiter.creatures = loadCreatures(limiter["creatures"], rng); + info.limiter.primary = JsonRandom::loadPrimary(limiter["primary"], rng); + info.limiter.secondary = JsonRandom::loadSecondary(limiter["secondary"], rng); + info.limiter.artifacts = JsonRandom::loadArtifacts(limiter["artifacts"], rng); + info.limiter.creatures = JsonRandom::loadCreatures(limiter["creatures"], rng); - info.reward.resources = loadResources(reward["resources"], rng); + info.reward.resources = JsonRandom::loadResources(reward["resources"], rng); - info.reward.gainedExp = loadValue(reward["gainedExp"], rng); - info.reward.gainedLevels = loadValue(reward["gainedLevels"], rng); + info.reward.gainedExp = JsonRandom::loadValue(reward["gainedExp"], rng); + info.reward.gainedLevels = JsonRandom::loadValue(reward["gainedLevels"], rng); - info.reward.manaDiff = loadValue(reward["manaPoints"], rng); - info.reward.manaPercentage = loadValue(reward["manaPercentage"], rng, -1); + info.reward.manaDiff = JsonRandom::loadValue(reward["manaPoints"], rng); + info.reward.manaPercentage = JsonRandom::loadValue(reward["manaPercentage"], rng, -1); - info.reward.movePoints = loadValue(reward["movePoints"], rng); - info.reward.movePercentage = loadValue(reward["movePercentage"], rng, -1); + info.reward.movePoints = JsonRandom::loadValue(reward["movePoints"], rng); + info.reward.movePercentage = JsonRandom::loadValue(reward["movePercentage"], rng, -1); - info.reward.bonuses = loadBonuses(reward["bonuses"]); + info.reward.bonuses = JsonRandom::loadBonuses(reward["bonuses"]); - info.reward.primary = loadPrimary(reward["primary"], rng); - info.reward.secondary = loadSecondary(reward["secondary"], rng); + info.reward.primary = JsonRandom::loadPrimary(reward["primary"], rng); + info.reward.secondary = JsonRandom::loadSecondary(reward["secondary"], rng); - info.reward.artifacts = loadArtifacts(reward["artifacts"], rng); - info.reward.spells = loadSpells(reward["spells"], rng); - info.reward.creatures = loadCreatures(reward["creatures"], rng); + std::vector spells; + for (size_t i=0; i<6; i++) + IObjectInterface::cb->getAllowedSpells(spells, i); + + info.reward.artifacts = JsonRandom::loadArtifacts(reward["artifacts"], rng); + info.reward.spells = JsonRandom::loadSpells(reward["spells"], rng, spells); + info.reward.creatures = JsonRandom::loadCreatures(reward["creatures"], rng); info.message = loadMessage(reward["message"]); - info.selectChance = loadValue(reward["selectChance"], rng); + info.selectChance = JsonRandom::loadValue(reward["selectChance"], rng); } object->onSelect = loadMessage(parameters["onSelectMessage"]); @@ -284,7 +195,7 @@ void CRewardableConstructor::configureObject(CGObjectInstance * object, CRandomG objectInfo.configureObject(dynamic_cast(object), rng); } -const IObjectInfo * CRewardableConstructor::getObjectInfo(ObjectTemplate tmpl) const +std::unique_ptr CRewardableConstructor::getObjectInfo(ObjectTemplate tmpl) const { - return &objectInfo; + return std::unique_ptr(new CRandomRewardObjectInfo(objectInfo)); } diff --git a/lib/mapObjects/CRewardableConstructor.h b/lib/mapObjects/CRewardableConstructor.h index a22679167..171b5a6ca 100644 --- a/lib/mapObjects/CRewardableConstructor.h +++ b/lib/mapObjects/CRewardableConstructor.h @@ -54,5 +54,5 @@ public: void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const override; - const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const override; + std::unique_ptr getObjectInfo(ObjectTemplate tmpl) const override; }; diff --git a/lib/mapObjects/CommonConstructors.cpp b/lib/mapObjects/CommonConstructors.cpp index 792088726..14f45fd9c 100644 --- a/lib/mapObjects/CommonConstructors.cpp +++ b/lib/mapObjects/CommonConstructors.cpp @@ -3,9 +3,11 @@ #include "CGTownInstance.h" #include "CGHeroInstance.h" +#include "CBank.h" #include "../mapping/CMap.h" #include "../CHeroHandler.h" #include "../CCreatureHandler.h" +#include "JsonRandom.h" /* * CommonConstructors.cpp, part of VCMI engine @@ -176,33 +178,6 @@ CGObjectInstance * CDwellingInstanceConstructor::create(ObjectTemplate tmpl) con return obj; } -namespace -{ - si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0) - { - if (value.isNull()) - return defaultValue; - if (value.getType() == JsonNode::DATA_FLOAT) - return value.Float(); - si32 min = value["min"].Float(); - si32 max = value["max"].Float(); - return rng.getIntRange(min, max)(); - } - - std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng) - { - std::vector ret; - for (auto & pair : value.Struct()) - { - CStackBasicDescriptor stack; - stack.type = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier(pair.second.meta, "creature", pair.first).get()]; - stack.count = loadValue(pair.second, rng); - ret.push_back(stack); - } - return ret; - } -} - void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator &rng) const { CGDwelling * dwelling = dynamic_cast(object); @@ -221,7 +196,7 @@ void CDwellingInstanceConstructor::configureObject(CGObjectInstance * object, CR const CCreature * crea = availableCreatures.at(0).at(0); dwelling->putStack(SlotID(0), new CStackInstance(crea->idNumber, crea->growth * 3 )); } - else for (auto & stack : loadCreatures(guards, rng)) + else for (auto & stack : JsonRandom::loadCreatures(guards, rng)) { dwelling->putStack(SlotID(dwelling->stacksCount()), new CStackInstance(stack.type->idNumber, stack.count)); } @@ -237,3 +212,175 @@ bool CDwellingInstanceConstructor::producesCreature(const CCreature * crea) cons } return false; } + +CBankInstanceConstructor::CBankInstanceConstructor() +{ +} + +void CBankInstanceConstructor::initTypeData(const JsonNode & input) +{ + //TODO: name = input["name"].String(); + levels = input["levels"].Vector(); + bankResetDuration = input["resetDuration"].Float(); +} + +CGObjectInstance *CBankInstanceConstructor::create(ObjectTemplate tmpl) const +{ + return createTyped(tmpl); +} + +BankConfig CBankInstanceConstructor::generateConfig(const JsonNode & level, CRandomGenerator & rng) const +{ + BankConfig bc; + + bc.chance = level["chance"].Float(); + + bc.guards = JsonRandom::loadCreatures(level["guards"], rng); + bc.upgradeChance = level["upgrade_chance"].Float(); + bc.combatValue = level["combat_value"].Float(); + + std::vector spells; + for (size_t i=0; i<6; i++) + IObjectInterface::cb->getAllowedSpells(spells, i); + + bc.resources = Res::ResourceSet(level["reward"]["resources"]); + bc.creatures = JsonRandom::loadCreatures(level["reward"]["creatures"], rng); + bc.artifacts = JsonRandom::loadArtifacts(level["reward"]["artifacts"], rng); + bc.spells = JsonRandom::loadSpells(level["reward"]["spells"], rng, spells); + + bc.value = level["value"].Float(); + + return bc; +} + +void CBankInstanceConstructor::configureObject(CGObjectInstance * object, CRandomGenerator & rng) const +{ + auto bank = dynamic_cast(object); + + bank->resetDuration = bankResetDuration; + + si32 totalChance = 0; + for (auto & node : levels) + totalChance += node["chance"].Float(); + + assert(totalChance != 0); + + si32 selectedChance = rng.nextInt(totalChance - 1); + + for (auto & node : levels) + { + if (selectedChance < node["chance"].Float()) + { + bank->setConfig(generateConfig(node, rng)); + } + else + { + selectedChance -= node["chance"].Float(); + } + + } +} + +CBankInfo::CBankInfo(JsonVector config): + config(config) +{ +} + +static void addStackToArmy(IObjectInfo::CArmyStructure & army, const CCreature * crea, si32 amount) +{ + army.totalStrength += crea->fightValue * amount; + + bool walker = true; + if (crea->hasBonusOfType(Bonus::SHOOTER)) + { + army.shootersStrength += crea->fightValue * amount; + walker = false; + } + if (crea->hasBonusOfType(Bonus::FLYING)) + { + army.flyersStrength += crea->fightValue * amount; + walker = false; + } + if (walker) + army.walkersStrength += crea->fightValue * amount; +} + +IObjectInfo::CArmyStructure CBankInfo::minGuards() const +{ + std::vector armies; + for (auto configEntry : config) + { + auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]); + IObjectInfo::CArmyStructure army; + for (auto & stack : stacks) + { + assert(!stack.allowedCreatures.empty()); + auto weakest = boost::range::min_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b) + { + return a->fightValue < b->fightValue; + }); + addStackToArmy(army, *weakest, stack.minAmount); + } + armies.push_back(army); + } + return *boost::range::min_element(armies); +} + +IObjectInfo::CArmyStructure CBankInfo::maxGuards() const +{ + std::vector armies; + for (auto configEntry : config) + { + auto stacks = JsonRandom::evaluateCreatures(configEntry["guards"]); + IObjectInfo::CArmyStructure army; + for (auto & stack : stacks) + { + assert(!stack.allowedCreatures.empty()); + auto strongest = boost::range::max_element(stack.allowedCreatures, [](const CCreature * a, const CCreature * b) + { + return a->fightValue < b->fightValue; + }); + addStackToArmy(army, *strongest, stack.maxAmount); + } + armies.push_back(army); + } + return *boost::range::max_element(armies); +} + +bool CBankInfo::givesResources() const +{ + for (const JsonNode & node : config) + if (!node["reward"]["resources"].isNull()) + return true; + return false; +} + +bool CBankInfo::givesArtifacts() const +{ + for (const JsonNode & node : config) + if (!node["reward"]["artifacts"].isNull()) + return true; + return false; +} + +bool CBankInfo::givesCreatures() const +{ + for (const JsonNode & node : config) + if (!node["reward"]["creatures"].isNull()) + return true; + return false; +} + +bool CBankInfo::givesSpells() const +{ + for (const JsonNode & node : config) + if (!node["reward"]["spells"].isNull()) + return true; + return false; +} + + +std::unique_ptr CBankInstanceConstructor::getObjectInfo(ObjectTemplate tmpl) const +{ + return std::unique_ptr(new CBankInfo(levels)); +} diff --git a/lib/mapObjects/CommonConstructors.h b/lib/mapObjects/CommonConstructors.h index da3db805c..241673d12 100644 --- a/lib/mapObjects/CommonConstructors.h +++ b/lib/mapObjects/CommonConstructors.h @@ -19,6 +19,8 @@ class CGDwelling; //class CGArtifact; //class CGCreature; class CHeroClass; +class CBank; +class CStackBasicDescriptor; /// Class that is used for objects that do not have dedicated handler template @@ -45,7 +47,7 @@ public: { } - virtual const IObjectInfo * getObjectInfo(ObjectTemplate tmpl) const + virtual std::unique_ptr getObjectInfo(ObjectTemplate tmpl) const { return nullptr; } @@ -63,6 +65,7 @@ class CTownInstanceConstructor : public CDefaultObjectTypeHandler&>(*this); } }; + +struct BankConfig +{ + BankConfig() { chance = upgradeChance = combatValue = value = 0; }; + ui32 value; //overall value of given things + ui32 chance; //chance for this level being chosen + ui32 upgradeChance; //chance for creatures to be in upgraded versions + ui32 combatValue; //how hard are guards of this level + std::vector guards; //creature ID, amount + Res::ResourceSet resources; //resources given in case of victory + std::vector creatures; //creatures granted in case of victory (creature ID, amount) + std::vector artifacts; //artifacts given in case of victory + std::vector spells; // granted spell(s), for Pyramid + + template void serialize(Handler &h, const int version) + { + h & chance & upgradeChance & guards & combatValue & resources & creatures & artifacts & value & spells; + } +}; + +class CBankInfo : public IObjectInfo +{ + JsonVector config; +public: + CBankInfo(JsonVector config); + + CArmyStructure minGuards() const; + CArmyStructure maxGuards() const; + bool givesResources() const; + bool givesArtifacts() const; + bool givesCreatures() const; + bool givesSpells() const; +}; + +class CBankInstanceConstructor : public CDefaultObjectTypeHandler +{ + BankConfig generateConfig(const JsonNode & conf, CRandomGenerator & rng) const; + + JsonVector levels; +protected: + void initTypeData(const JsonNode & input); + +public: + // all banks of this type will be reset N days after clearing, + si32 bankResetDuration; + + CBankInstanceConstructor(); + + CGObjectInstance *create(ObjectTemplate tmpl) const; + void configureObject(CGObjectInstance * object, CRandomGenerator & rng) const; + + std::unique_ptr getObjectInfo(ObjectTemplate tmpl) const; +}; diff --git a/lib/mapObjects/JsonRandom.cpp b/lib/mapObjects/JsonRandom.cpp new file mode 100644 index 000000000..ac001a589 --- /dev/null +++ b/lib/mapObjects/JsonRandom.cpp @@ -0,0 +1,225 @@ +/* + * + * CRewardableObject.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 "JsonRandom.h" + +#include "../JsonNode.h" +#include "../CRandomGenerator.h" +#include "../StringConstants.h" +#include "../VCMI_Lib.h" +#include "../CModHandler.h" +#include "../CArtHandler.h" +#include "../CCreatureHandler.h" +#include "../CCreatureSet.h" +#include "../CSpellHandler.h" + +namespace JsonRandom +{ + si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue) + { + if (value.isNull()) + return defaultValue; + if (value.getType() == JsonNode::DATA_FLOAT) + return value.Float(); + if (!value["amount"].isNull()) + return value["amount"].Float(); + si32 min = value["min"].Float(); + si32 max = value["max"].Float(); + return rng.getIntRange(min, max)(); + } + + TResources loadResources(const JsonNode & value, CRandomGenerator & rng) + { + TResources ret; + for (size_t i=0; i loadPrimary(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (auto & name : PrimarySkill::names) + { + ret.push_back(loadValue(value[name], rng)); + } + return ret; + } + + std::map loadSecondary(const JsonNode & value, CRandomGenerator & rng) + { + std::map ret; + for (auto & pair : value.Struct()) + { + SecondarySkill id(VLC->modh->identifiers.getIdentifier(pair.second.meta, "skill", pair.first).get()); + ret[id] = loadValue(pair.second, rng); + } + return ret; + } + + ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng) + { + if (value.getType() == JsonNode::DATA_STRING) + return ArtifactID(VLC->modh->identifiers.getIdentifier("artifact", value).get()); + + std::set allowedClasses; + std::set allowedPositions; + ui32 minValue = 0; + ui32 maxValue = std::numeric_limits::max(); + + if (value["class"].getType() == JsonNode::DATA_STRING) + allowedClasses.insert(VLC->arth->stringToClass(value["class"].String())); + else + for (auto & entry : value["class"].Vector()) + allowedClasses.insert(VLC->arth->stringToClass(entry.String())); + + if (value["slot"].getType() == JsonNode::DATA_STRING) + allowedPositions.insert(VLC->arth->stringToSlot(value["class"].String())); + else + for (auto & entry : value["slot"].Vector()) + allowedPositions.insert(VLC->arth->stringToSlot(entry.String())); + + if (value["minValue"].isNull()) minValue = value["minValue"].Float(); + if (value["maxValue"].isNull()) maxValue = value["maxValue"].Float(); + + return VLC->arth->pickRandomArtifact(rng, [=](ArtifactID artID) -> bool + { + CArtifact * art = VLC->arth->artifacts[artID]; + + if (!vstd::iswithin(art->price, minValue, maxValue)) + return false; + + if (!allowedClasses.empty() && !allowedClasses.count(art->aClass)) + return false; + + if (!allowedPositions.empty()) + { + for (auto pos : art->possibleSlots[ArtBearer::HERO]) + { + if (allowedPositions.count(pos)) + return true; + } + return false; + } + return true; + }); + } + + std::vector loadArtifacts(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (const JsonNode & entry : value.Vector()) + { + ret.push_back(loadArtifact(entry, rng)); + } + return ret; + } + + SpellID loadSpell(const JsonNode & value, CRandomGenerator & rng, std::vector spells) + { + if (value.getType() == JsonNode::DATA_STRING) + return SpellID(VLC->modh->identifiers.getIdentifier("spell", value).get()); + if (value["type"].getType() == JsonNode::DATA_STRING) + return SpellID(VLC->modh->identifiers.getIdentifier("spell", value["type"]).get()); + + spells.erase(std::remove_if(spells.begin(), spells.end(), [=](SpellID spell) + { + return VLC->spellh->objects[spell]->level != si32(value["level"].Float()); + }), spells.end()); + + return SpellID(*RandomGeneratorUtil::nextItem(spells, rng)); + } + + std::vector loadSpells(const JsonNode & value, CRandomGenerator & rng, std::vector spells) + { + // possible extensions: (taken from spell json config) + // "type": "adventure",//"adventure", "combat", "ability" + // "school": {"air":true, "earth":true, "fire":true, "water":true}, + // "level": 1, + + std::vector ret; + for (const JsonNode & entry : value.Vector()) + { + ret.push_back(loadSpell(entry, rng, spells)); + } + return ret; + } + + CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng) + { + CStackBasicDescriptor stack; + stack.type = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier("creature", value["type"]).get()]; + stack.count = loadValue(value, rng); + if (!value["upgradeChance"].isNull() && !stack.type->upgrades.empty()) + { + if (int(value["upgradeChance"].Float()) > rng.nextInt(99)) // select random upgrade + { + stack.type = VLC->creh->creatures[*RandomGeneratorUtil::nextItem(stack.type->upgrades, rng)]; + } + } + return stack; + } + + std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng) + { + std::vector ret; + for (const JsonNode & node : value.Vector()) + { + ret.push_back(loadCreature(node, rng)); + } + return ret; + } + + std::vector evaluateCreatures(const JsonNode & value) + { + std::vector ret; + for (const JsonNode & node : value.Vector()) + { + RandomStackInfo info; + + if (!node["amount"].isNull()) + info.minAmount = info.maxAmount = node["amount"].Float(); + else + { + info.minAmount = node["min"].Float(); + info.maxAmount = node["max"].Float(); + } + const CCreature * crea = VLC->creh->creatures[VLC->modh->identifiers.getIdentifier("creature", node["type"]).get()]; + info.allowedCreatures.push_back(crea); + if (!node["upgradeChance"].Float() > 0) + { + for (auto creaID : crea->upgrades) + info.allowedCreatures.push_back(VLC->creh->creatures[creaID]); + } + } + return ret; + } + + std::vector loadBonuses(const JsonNode & value) + { + std::vector ret; + for (const JsonNode & entry : value.Vector()) + { + Bonus * bonus = JsonUtils::parseBonus(entry); + ret.push_back(*bonus); + delete bonus; + } + return ret; + } + + std::vector loadComponents(const JsonNode & value) + { + //TODO + } +} diff --git a/lib/mapObjects/JsonRandom.h b/lib/mapObjects/JsonRandom.h new file mode 100644 index 000000000..6e44da040 --- /dev/null +++ b/lib/mapObjects/JsonRandom.h @@ -0,0 +1,50 @@ +#pragma once + +#include "../GameConstants.h" +#include "../ResourceSet.h" + +/* + * JsonRandom.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 JsonNode; +typedef std::vector JsonVector; +class CRandomGenerator; + +class Bonus; +class Component; +class CStackBasicDescriptor; + +namespace JsonRandom +{ + struct RandomStackInfo + { + std::vector allowedCreatures; + si32 minAmount; + si32 maxAmount; + }; + + si32 loadValue(const JsonNode & value, CRandomGenerator & rng, si32 defaultValue = 0); + TResources loadResources(const JsonNode & value, CRandomGenerator & rng); + std::vector loadPrimary(const JsonNode & value, CRandomGenerator & rng); + std::map loadSecondary(const JsonNode & value, CRandomGenerator & rng); + + ArtifactID loadArtifact(const JsonNode & value, CRandomGenerator & rng); + std::vector loadArtifacts(const JsonNode & value, CRandomGenerator & rng); + + SpellID loadSpell(const JsonNode & value, CRandomGenerator & rng, std::vector spells); + std::vector loadSpells(const JsonNode & value, CRandomGenerator & rng, std::vector spells); + + CStackBasicDescriptor loadCreature(const JsonNode & value, CRandomGenerator & rng); + std::vector loadCreatures(const JsonNode & value, CRandomGenerator & rng); + std::vector evaluateCreatures(const JsonNode & value); + + std::vector loadBonuses(const JsonNode & value); + std::vector loadComponents(const JsonNode & value); +} diff --git a/lib/mapping/MapFormatH3M.cpp b/lib/mapping/MapFormatH3M.cpp index 21ddf65ee..f064cc70a 100644 --- a/lib/mapping/MapFormatH3M.cpp +++ b/lib/mapping/MapFormatH3M.cpp @@ -1569,7 +1569,7 @@ void CMapLoaderH3M::readObjects() { if(objTempl.subid == 0) { - nobj = new CGPyramid(); + nobj = new CBank(); } else { diff --git a/lib/registerTypes/RegisterTypes.h b/lib/registerTypes/RegisterTypes.h index 6bc8ca0ba..dd7467108 100644 --- a/lib/registerTypes/RegisterTypes.h +++ b/lib/registerTypes/RegisterTypes.h @@ -69,7 +69,6 @@ void registerTypesMapObjects1(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); - s.template registerType(); s.template registerType(); s.template registerType(); s.template registerType(); } @@ -81,13 +80,13 @@ void registerTypesMapObjectTypes(Serializer &s) s.template registerType(); s.template registerType(); s.template registerType(); + s.template registerType(); s.template registerType(); #define REGISTER_GENERIC_HANDLER(TYPENAME) s.template registerType >() REGISTER_GENERIC_HANDLER(CGObjectInstance); REGISTER_GENERIC_HANDLER(CGMarket); - REGISTER_GENERIC_HANDLER(CBank); REGISTER_GENERIC_HANDLER(CCartographer); REGISTER_GENERIC_HANDLER(CGArtifact); REGISTER_GENERIC_HANDLER(CGBlackMarket); @@ -113,7 +112,6 @@ void registerTypesMapObjectTypes(Serializer &s) REGISTER_GENERIC_HANDLER(CGOnceVisitable); REGISTER_GENERIC_HANDLER(CGPandoraBox); REGISTER_GENERIC_HANDLER(CGPickable); - REGISTER_GENERIC_HANDLER(CGPyramid); REGISTER_GENERIC_HANDLER(CGQuestGuard); REGISTER_GENERIC_HANDLER(CGResource); REGISTER_GENERIC_HANDLER(CGScholar);