From 43ba3d30eaa0e9af80cb4dfa06b0d0d42bbafbab Mon Sep 17 00:00:00 2001 From: Ivan Savenko Date: Sun, 6 Apr 2014 23:14:26 +0300 Subject: [PATCH] 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();