1
0
mirror of https://github.com/vcmi/vcmi.git synced 2025-11-06 09:09:40 +02:00

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)
This commit is contained in:
Ivan Savenko
2014-06-22 13:39:40 +03:00
parent 0a71e89f58
commit ab475195ac
28 changed files with 1822 additions and 1371 deletions

View File

@@ -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<Res::ERes>(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<Res::ERes>(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<SpellID> 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<SpellID> 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<SpellID> 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);
}
}