mirror of
https://github.com/vcmi/vcmi.git
synced 2025-01-14 02:33:51 +02:00
617e1f962e
* lib/ERMScriptModule.cpp * lib/ERMScriptModule.h * lib/CObstacleInstance.h More jugglery with callbacks. Moving stuff from CGameState to CGameInfoCallback. Work on unified game events interface for player (AI or GUI) and script module. Directing events to ERM interpretetr, first attempts of calling some triggers. Crashy, if there any scripts. Some other changes, including fighting amount of includes in includes and tracking of hero visits (need further work).
7184 lines
185 KiB
C++
7184 lines
185 KiB
C++
#define VCMI_DLL
|
|
#include "../stdafx.h"
|
|
#include "CObjectHandler.h"
|
|
#include "CDefObjInfoHandler.h"
|
|
#include "CLodHandler.h"
|
|
#include "CGeneralTextHandler.h"
|
|
#include "CDefObjInfoHandler.h"
|
|
#include "CHeroHandler.h"
|
|
#include "CSpellHandler.h"
|
|
#include "../client/CSoundBase.h"
|
|
#include <boost/bind.hpp>
|
|
#include <boost/algorithm/string/replace.hpp>
|
|
#include <boost/assign/std/vector.hpp>
|
|
#include <boost/lexical_cast.hpp>
|
|
#include <boost/random/linear_congruential.hpp>
|
|
#include "CTownHandler.h"
|
|
#include "CArtHandler.h"
|
|
#include "CCreatureHandler.h"
|
|
#include "VCMI_Lib.h"
|
|
#include "IGameCallback.h"
|
|
#include "CGameState.h"
|
|
#include "NetPacks.h"
|
|
#include "../StartInfo.h"
|
|
#include "map.h"
|
|
#include <sstream>
|
|
#include <SDL_stdinc.h>
|
|
#include <boost/foreach.hpp>
|
|
#include <boost/format.hpp>
|
|
#include <boost/algorithm/string/trim.hpp>
|
|
|
|
using namespace boost::assign;
|
|
|
|
/*
|
|
* 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
|
|
*
|
|
*/
|
|
|
|
std::map<int,std::map<int, std::vector<int> > > CGTeleport::objs;
|
|
std::vector<std::pair<int, int> > CGTeleport::gates;
|
|
IGameCallback * IObjectInterface::cb = NULL;
|
|
DLL_EXPORT void loadToIt(std::string &dest, const std::string &src, int &iter, int mode);
|
|
extern CLodHandler * bitmaph;
|
|
extern boost::rand48 ran;
|
|
std::map <ui8, std::set <ui8> > CGKeys::playerKeyMap;
|
|
std::map <si32, std::vector<si32> > CGMagi::eyelist;
|
|
ui8 CGObelisk::obeliskCount; //how many obelisks are on map
|
|
std::map<ui8, ui8> CGObelisk::visited; //map: team_id => how many obelisks has been visited
|
|
|
|
std::vector<const CArtifact *> CGTownInstance::merchantArtifacts;
|
|
std::vector<int> CGTownInstance::universitySkills;
|
|
|
|
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 )
|
|
{}
|
|
|
|
void IObjectInterface::postInit()
|
|
{}
|
|
|
|
void IObjectInterface::preInit()
|
|
{}
|
|
|
|
void CPlayersVisited::setPropertyDer( ui8 what, ui32 val )
|
|
{
|
|
if(what == 10)
|
|
players.insert((ui8)val);
|
|
}
|
|
|
|
bool CPlayersVisited::hasVisited( ui8 player ) const
|
|
{
|
|
return vstd::contains(players,player);
|
|
}
|
|
|
|
static void readCreatures(std::istream & is, BankConfig & bc, bool guards) //helper function for void CObjectHandler::loadObjects()
|
|
{
|
|
const int MAX_BUF = 5000;
|
|
char buffer[MAX_BUF + 1];
|
|
std::pair<si16, si32> guardInfo = std::make_pair(0, 0);
|
|
std::string creName;
|
|
|
|
is >> guardInfo.second;
|
|
//one getline just does not work... probably a kind of left whitespace
|
|
is.getline(buffer, MAX_BUF, '\t');
|
|
is.getline(buffer, MAX_BUF, '\t');
|
|
creName = buffer;
|
|
|
|
if( std::string(buffer) == "None" ) //no creature to be added
|
|
return;
|
|
|
|
//look for the best creature that is described by given name
|
|
if( vstd::contains(VLC->creh->nameToID, creName) )
|
|
{
|
|
guardInfo.first = VLC->creh->nameToID[creName];
|
|
}
|
|
else
|
|
{
|
|
for(int g=0; g<VLC->creh->creatures.size(); ++g)
|
|
{
|
|
if(VLC->creh->creatures[g]->namePl == creName
|
|
|| VLC->creh->creatures[g]->nameRef == creName
|
|
|| VLC->creh->creatures[g]->nameSing == creName)
|
|
{
|
|
guardInfo.first = VLC->creh->creatures[g]->idNumber;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(guards)
|
|
bc.guards.push_back(guardInfo);
|
|
else //given creatures
|
|
bc.creatures.push_back(guardInfo);
|
|
}
|
|
void CObjectHandler::readConfigLine(std::ifstream &istr, int g)
|
|
{
|
|
banksInfo[g].push_back(new BankConfig);
|
|
|
|
BankConfig &bc = *banksInfo[g].back();
|
|
std::string buf;
|
|
//bc.level is of type char and thus we cannot read directly to it; same for some othre variables
|
|
istr >> buf;
|
|
bc.level = atoi(buf.c_str());
|
|
|
|
istr >> buf;
|
|
bc.chance = atoi(buf.c_str());
|
|
|
|
readCreatures(istr, bc, true);
|
|
istr >> buf;
|
|
bc.upgradeChance = atoi(buf.c_str());
|
|
|
|
for(int b=0; b<3; ++b)
|
|
readCreatures(istr, bc, true);
|
|
|
|
istr >> bc.combatValue;
|
|
bc.resources.resize(RESOURCE_QUANTITY);
|
|
|
|
//a dirty trick to make it work if there is no 0 for 0 quantity (like in grotto - last entry)
|
|
char buft[52];
|
|
istr.getline(buft, 50, '\t');
|
|
for(int h=0; h<7; ++h)
|
|
{
|
|
istr.getline(buft, 50, '\t');
|
|
if(buft[0] == '\0')
|
|
bc.resources[h] = 0;
|
|
else
|
|
bc.resources[h] = SDL_atoi(buft);
|
|
}
|
|
readCreatures(istr, bc, false);
|
|
|
|
bc.artifacts.resize(4);
|
|
for(int b=0; b<4; ++b)
|
|
{
|
|
istr >> bc.artifacts[b];
|
|
}
|
|
|
|
istr >> bc.value;
|
|
istr >> bc.rewardDifficulty;
|
|
istr >> buf;
|
|
bc.easiest = atoi(buf.c_str());
|
|
}
|
|
|
|
void CObjectHandler::loadObjects()
|
|
{
|
|
{
|
|
tlog5 << "\t\tReading cregens \n";
|
|
cregens.resize(110); //TODO: hardcoded value - change
|
|
for(size_t i=0; i < cregens.size(); ++i)
|
|
{
|
|
cregens[i]=-1;
|
|
}
|
|
std::ifstream ifs(DATA_DIR "/config/cregens.txt");
|
|
while(!ifs.eof())
|
|
{
|
|
int dw, cr;
|
|
ifs >> dw >> cr;
|
|
cregens[dw]=cr;
|
|
}
|
|
tlog5 << "\t\tDone loading objects!\n";
|
|
|
|
ifs.close();
|
|
ifs.clear();
|
|
|
|
int k = -1;
|
|
ifs.open(DATA_DIR "/config/resources.txt");
|
|
ifs >> k;
|
|
int pom;
|
|
for(int i=0;i<k;i++)
|
|
{
|
|
ifs >> pom;
|
|
resVals.push_back(pom);
|
|
}
|
|
tlog5 << "\t\tDone loading resource prices!\n";
|
|
}
|
|
|
|
std::ifstream istr;
|
|
istr.open(DATA_DIR "/config/bankconfig.txt", std::ios_base::binary);
|
|
if(!istr.is_open())
|
|
{
|
|
tlog1 << "No config/bankconfig.txt file !!!\n";
|
|
}
|
|
|
|
const int MAX_BUF = 5000;
|
|
char buffer[MAX_BUF + 1];
|
|
|
|
//omitting unnecessary lines
|
|
istr.getline(buffer, MAX_BUF);
|
|
istr.getline(buffer, MAX_BUF);
|
|
|
|
for(int g=0; g<21; ++g) //TODO: remove hardcoded value
|
|
{
|
|
//reading name
|
|
istr.getline(buffer, MAX_BUF, '\t');
|
|
creBanksNames[g] = std::string(buffer);
|
|
//remove trailing new line characters
|
|
while(creBanksNames[g][0] == 10 || creBanksNames[g][0] == 13)
|
|
creBanksNames[g].erase(creBanksNames[g].begin());
|
|
|
|
for(int i=0; i<4; ++i) //reading levels
|
|
{
|
|
readConfigLine(istr,g);
|
|
}
|
|
}
|
|
//reading name
|
|
istr.getline(buffer, MAX_BUF, '\t');
|
|
creBanksNames[21] = std::string(buffer);
|
|
while(creBanksNames[21][0] == 10 || creBanksNames[21][0] == 13)
|
|
creBanksNames[21].erase(creBanksNames[21].begin());
|
|
readConfigLine(istr,21); //pyramid
|
|
}
|
|
|
|
int CGObjectInstance::getOwner() const
|
|
{
|
|
//if (state)
|
|
// return state->owner;
|
|
//else
|
|
return tempOwner; //won't have owner
|
|
}
|
|
|
|
CGObjectInstance::CGObjectInstance(): animPhaseShift(rand()%0xff)
|
|
{
|
|
pos = int3(-1,-1,-1);
|
|
//std::cout << "Tworze obiekt "<<this<<std::endl;
|
|
//state = new CLuaObjectScript();
|
|
ID = subID = id = -1;
|
|
defInfo = NULL;
|
|
tempOwner = 254;
|
|
blockVisit = false;
|
|
}
|
|
CGObjectInstance::~CGObjectInstance()
|
|
{
|
|
//std::cout << "Usuwam obiekt "<<this<<std::endl;
|
|
//if (state)
|
|
// delete state;
|
|
//state=NULL;
|
|
}
|
|
|
|
const std::string & CGObjectInstance::getHoverText() const
|
|
{
|
|
return hoverName;
|
|
}
|
|
void CGObjectInstance::setOwner(int ow)
|
|
{
|
|
//if (state)
|
|
// state->owner = ow;
|
|
//else
|
|
tempOwner = ow;
|
|
}
|
|
int CGObjectInstance::getWidth() const//returns width of object graphic in tiles
|
|
{
|
|
return defInfo->width;
|
|
}
|
|
int CGObjectInstance::getHeight() const //returns height of object graphic in tiles
|
|
{
|
|
return defInfo->height;
|
|
}
|
|
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)
|
|
{
|
|
if(defInfo==NULL)
|
|
{
|
|
tlog2 << "Warning: VisitableAt for obj "<<id<<": NULL defInfo!\n";
|
|
return false;
|
|
}
|
|
|
|
if((defInfo->visitMap[y] >> (7-x) ) & 1)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
bool CGObjectInstance::blockingAt(int x, int y) const
|
|
{
|
|
if(x<0 || y<0 || x>=getWidth() || y>=getHeight() || defInfo==NULL)
|
|
return false;
|
|
if((defInfo->blockMap[y+6-getHeight()] >> (7-(8-getWidth()+x) )) & 1)
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
bool CGObjectInstance::coveringAt(int x, int y) const
|
|
{
|
|
if((defInfo->coverageMap[y] >> (7-(x) )) & 1
|
|
|| (defInfo->shadowCoverage[y] >> (7-(x) )) & 1)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
std::set<int3> CGObjectInstance::getBlockedPos() const
|
|
{
|
|
std::set<int3> ret;
|
|
for(int w=0; w<getWidth(); ++w)
|
|
{
|
|
for(int h=0; h<getHeight(); ++h)
|
|
{
|
|
if(blockingAt(w, h))
|
|
ret.insert(int3(pos.x - getWidth() + w + 1, pos.y - getHeight() + h + 1, pos.z));
|
|
}
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
bool CGObjectInstance::operator<(const CGObjectInstance & cmp) const //screen printing priority comparing
|
|
{
|
|
if(defInfo->printPriority==1 && cmp.defInfo->printPriority==0)
|
|
return true;
|
|
if(cmp.defInfo->printPriority==1 && defInfo->printPriority==0)
|
|
return false;
|
|
if(this->pos.y<cmp.pos.y)
|
|
return true;
|
|
if(this->pos.y>cmp.pos.y)
|
|
return false;
|
|
if(cmp.ID==HEROI_TYPE && ID!=HEROI_TYPE)
|
|
return true;
|
|
if(cmp.ID!=HEROI_TYPE && ID==HEROI_TYPE)
|
|
return false;
|
|
if(!defInfo->isVisitable() && cmp.defInfo->isVisitable())
|
|
return true;
|
|
if(!cmp.defInfo->isVisitable() && defInfo->isVisitable())
|
|
return false;
|
|
if(this->pos.x<cmp.pos.x)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
void CGObjectInstance::initObj()
|
|
{
|
|
switch(ID)
|
|
{
|
|
case 95:
|
|
blockVisit = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CGObjectInstance::setProperty( ui8 what, ui32 val )
|
|
{
|
|
switch(what)
|
|
{
|
|
case ObjProperty::OWNER:
|
|
tempOwner = val;
|
|
break;
|
|
case ObjProperty::BLOCKVIS:
|
|
blockVisit = val;
|
|
break;
|
|
case ObjProperty::ID:
|
|
ID = 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(boost::unordered_set<int3, ShashInt3> &tiles) const //returns reference to the set
|
|
{
|
|
cb->getTilesInRange(tiles, getSightCenter(), getSightRadious(), tempOwner, 1);
|
|
}
|
|
void CGObjectInstance::hideTiles(int ourplayer, int radius) const
|
|
{
|
|
for (std::map<ui8, TeamState>::iterator i = cb->gameState()->teams.begin(); i != cb->gameState()->teams.end(); i++)
|
|
{
|
|
if ( !vstd::contains(i->second.players, ourplayer ))//another team
|
|
{
|
|
for (std::set<ui8>::iterator j = i->second.players.begin(); j != i->second.players.end(); j++)
|
|
if ( cb->getPlayer(*j)->status == PlayerState::INGAME )//seek for living player (if any)
|
|
{
|
|
FoWChange fw;
|
|
fw.mode = 0;
|
|
fw.player = *j;
|
|
cb->getTilesInRange (fw.tiles, pos, radius, (*j), -1);
|
|
cb->sendAndApply (&fw);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
int3 CGObjectInstance::getVisitableOffset() const
|
|
{
|
|
for(int y = 0; y < 6; y++)
|
|
for (int x = 0; x < 8; x++)
|
|
if((defInfo->visitMap[5-y] >> x) & 1)
|
|
return int3(x,y,0);
|
|
|
|
tlog2 << "Warning: getVisitableOffset called on non-visitable obj!\n";
|
|
return int3(-1,-1,-1);
|
|
}
|
|
|
|
void CGObjectInstance::getNameVis( std::string &hname ) const
|
|
{
|
|
const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer());
|
|
hname = VLC->generaltexth->names[ID];
|
|
if(h)
|
|
{
|
|
if(!h->hasBonusFrom(Bonus::OBJECT,ID))
|
|
hname += " " + VLC->generaltexth->allTexts[353]; //not visited
|
|
else
|
|
hname += " " + VLC->generaltexth->allTexts[352]; //visited
|
|
}
|
|
}
|
|
|
|
void CGObjectInstance::giveDummyBonus(int heroID, ui8 duration) const
|
|
{
|
|
GiveBonus gbonus;
|
|
gbonus.bonus.type = Bonus::NONE;
|
|
gbonus.id = heroID;
|
|
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 35: //Hill fort
|
|
{
|
|
OpenWindow ow;
|
|
ow.window = OpenWindow::HILL_FORT_WINDOW;
|
|
ow.id1 = id;
|
|
ow.id2 = h->id;
|
|
cb->sendAndApply(&ow);
|
|
}
|
|
break;
|
|
case 80: //Sanctuary
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.soundID = soundBase::GETPROTECTION;
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, 114); //You enter the sanctuary and immediately feel as if a great weight has been lifted off your shoulders. You feel safe here.
|
|
cb->sendAndApply(&iw);
|
|
}
|
|
break;
|
|
case 95: //Tavern
|
|
{
|
|
OpenWindow ow;
|
|
ow.window = OpenWindow::TAVERN_WINDOW;
|
|
ow.id1 = h->id;
|
|
ow.id2 = id;
|
|
cb->sendAndApply(&ow);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
ui8 CGObjectInstance::getPassableness() const
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
bool CGObjectInstance::hasShadowAt( int x, int y ) const
|
|
{
|
|
if( (defInfo->shadowCoverage[y] >> (7-(x) )) & 1 )
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
int3 CGObjectInstance::visitablePos() const
|
|
{
|
|
return pos - getVisitableOffset();
|
|
}
|
|
|
|
bool CGObjectInstance::isVisitable() const
|
|
{
|
|
for(int g=0; g<ARRAY_COUNT(defInfo->visitMap); ++g)
|
|
{
|
|
if(defInfo->visitMap[g] != 0)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static int lowestSpeed(const CGHeroInstance * chi)
|
|
{
|
|
if(!chi->Slots().size())
|
|
{
|
|
tlog1 << "Error! Hero " << chi->id << " ("<<chi->name<<") has no army!\n";
|
|
return 20;
|
|
}
|
|
TSlots::const_iterator 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;
|
|
}
|
|
|
|
unsigned int CGHeroInstance::getTileCost(const TerrainTile &dest, const TerrainTile &from) const
|
|
{
|
|
//TODO: check if all creatures are on its native terrain and change cost appropriately
|
|
|
|
//base move cost
|
|
unsigned ret = 100;
|
|
|
|
//if there is road both on dest and src tiles - use road movement cost
|
|
if(dest.malle && from.malle)
|
|
{
|
|
int road = std::min(dest.malle,from.malle); //used road ID
|
|
switch(road)
|
|
{
|
|
case TerrainTile::dirtRoad:
|
|
ret = 75;
|
|
break;
|
|
case TerrainTile::grazvelRoad:
|
|
ret = 65;
|
|
break;
|
|
case TerrainTile::cobblestoneRoad:
|
|
ret = 50;
|
|
break;
|
|
default:
|
|
tlog1 << "Unknown road type: " << road << "... Something wrong!\n";
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ret = type->heroClass->terrCosts[from.tertype];
|
|
ret = std::max(ret - 25*unsigned(getSecSkillLevel(CGHeroInstance::PATHFINDING)), 100u); //reduce 25% of terrain penalty for each pathfinding level
|
|
}
|
|
return ret;
|
|
}
|
|
#if 0
|
|
// Unused and buggy method.
|
|
// - for loop is wrong. will not find all creatures. must use iterator instead.
|
|
// - -> is the slot number. use second->first for creature index
|
|
// Is lowestSpeed() the correct equivalent ?
|
|
unsigned int CGHeroInstance::getLowestCreatureSpeed() const
|
|
{
|
|
unsigned int sl = 100;
|
|
for(size_t h=0; h < stacksCount(); ++h)
|
|
{
|
|
if(VLC->creh->creatures[Slots().find(h)->first]->speed<sl)
|
|
sl = VLC->creh->creatures[Slots().find(h)->first]->speed;
|
|
}
|
|
return sl;
|
|
}
|
|
#endif
|
|
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(size_t i=0; i < secSkills.size(); ++i)
|
|
if(secSkills[i].first == skill)
|
|
return secSkills[i].second;
|
|
return 0;
|
|
}
|
|
|
|
void CGHeroInstance::setSecSkillLevel(SecondarySkill which, int val, bool abs)
|
|
{
|
|
if(getSecSkillLevel(which) == 0)
|
|
{
|
|
secSkills.push_back(std::pair<int,int>(which, val));
|
|
updateSkill(which, val);
|
|
}
|
|
else
|
|
{
|
|
for (unsigned i=0; i<secSkills.size(); i++)
|
|
{
|
|
if(secSkills[i].first == which)
|
|
{
|
|
if(abs)
|
|
secSkills[i].second = val;
|
|
else
|
|
secSkills[i].second += val;
|
|
|
|
if(secSkills[i].second > 3) //workaround to avoid crashes when same sec skill is given more than once
|
|
{
|
|
tlog1 << "Warning: Skill " << which << " increased over limit! Decreasing to Expert.\n";
|
|
secSkills[i].second = 3;
|
|
}
|
|
updateSkill(which, secSkills[i].second); //when we know final value
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int CGHeroInstance::maxMovePoints(bool onLand) const
|
|
{
|
|
int base = -1;
|
|
if(onLand)
|
|
{
|
|
static const int moveForSpeed[] = { 1500, 1560, 1630, 1700, 1760, 1830, 1900, 1960, 2000 }; //first element for 3 and lower; last for 11 and more
|
|
int index = lowestSpeed(this) - 3;
|
|
amin(index, ARRAY_COUNT(moveForSpeed)-1);
|
|
amax(index, 0);
|
|
base = moveForSpeed[index];
|
|
}
|
|
else
|
|
{
|
|
base = 1500; //on water base movement is always 1500 (speed of army doesn't matter)
|
|
}
|
|
|
|
int bonus = valOfBonuses(Bonus::MOVEMENT) + (onLand ? valOfBonuses(Bonus::LAND_MOVEMENT) : valOfBonuses(Bonus::SEA_MOVEMENT));
|
|
|
|
double modifier = 0;
|
|
if(onLand)
|
|
{
|
|
modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, LOGISTICS) / 100.0f;
|
|
}
|
|
else
|
|
{
|
|
modifier = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, NAVIGATION) / 100.0f;
|
|
}
|
|
return int(base + base*modifier) + bonus;
|
|
}
|
|
|
|
// int CGHeroInstance::getSpellSecLevel(int spell) const
|
|
// {
|
|
// int bestslvl = 0;
|
|
// if(VLC->spellh->spells[spell].air)
|
|
// if(getSecSkillLevel(15) >= bestslvl)
|
|
// {
|
|
// bestslvl = getSecSkillLevel(15);
|
|
// }
|
|
// if(VLC->spellh->spells[spell].fire)
|
|
// if(getSecSkillLevel(14) >= bestslvl)
|
|
// {
|
|
// bestslvl = getSecSkillLevel(14);
|
|
// }
|
|
// if(VLC->spellh->spells[spell].water)
|
|
// if(getSecSkillLevel(16) >= bestslvl)
|
|
// {
|
|
// bestslvl = getSecSkillLevel(16);
|
|
// }
|
|
// if(VLC->spellh->spells[spell].earth)
|
|
// if(getSecSkillLevel(17) >= bestslvl)
|
|
// {
|
|
// bestslvl = getSecSkillLevel(17);
|
|
// }
|
|
// return bestslvl;
|
|
// }
|
|
|
|
CGHeroInstance::CGHeroInstance()
|
|
: IBoatGenerator(this)
|
|
{
|
|
nodeType = HERO;
|
|
ID = HEROI_TYPE;
|
|
tacticFormationEnabled = inTownGarrison = false;
|
|
mana = movement = portrait = level = -1;
|
|
isStanding = true;
|
|
moveDir = 4;
|
|
exp = 0xffffffff;
|
|
visitedTown = NULL;
|
|
type = NULL;
|
|
boat = NULL;
|
|
secSkills.push_back(std::make_pair(-1, -1));
|
|
speciality.nodeType = CBonusSystemNode::SPECIALITY;
|
|
attachTo(&speciality); //do we evert need to detach it?
|
|
}
|
|
|
|
void CGHeroInstance::initHero(int SUBID)
|
|
{
|
|
subID = SUBID;
|
|
initHero();
|
|
}
|
|
|
|
void CGHeroInstance::initHero()
|
|
{
|
|
assert(validTypes(true));
|
|
if(ID == HEROI_TYPE)
|
|
initHeroDefInfo();
|
|
if(!type)
|
|
type = VLC->heroh->heroes[subID];
|
|
if(!vstd::contains(spells, 0xffffffff) && type->startingSpell >= 0) //hero starts with a spell
|
|
spells.insert(type->startingSpell);
|
|
else //remove placeholder
|
|
spells -= 0xffffffff;
|
|
|
|
if(!getArt(Arts::MACH4) && type->startingSpell >= 0) //no catapult means we haven't read pre-existant set -> use default rules for spellbook
|
|
putArtifact(Arts::SPELLBOOK, CArtifactInstance::createNewArtifactInstance(0));
|
|
|
|
if(!getArt(Arts::MACH4))
|
|
putArtifact(Arts::MACH4, CArtifactInstance::createNewArtifactInstance(3)); //everyone has a catapult
|
|
|
|
if(portrait < 0 || portrait == 255)
|
|
portrait = subID;
|
|
if(!hasBonus(Selector::sourceType(Bonus::HERO_BASE_SKILL)))
|
|
{
|
|
pushPrimSkill(PrimarySkill::ATTACK, type->heroClass->initialAttack);
|
|
pushPrimSkill(PrimarySkill::DEFENSE, type->heroClass->initialDefence);
|
|
pushPrimSkill(PrimarySkill::SPELL_POWER, type->heroClass->initialPower);
|
|
pushPrimSkill(PrimarySkill::KNOWLEDGE, type->heroClass->initialKnowledge);
|
|
}
|
|
if(secSkills.size() == 1 && secSkills[0] == std::pair<ui8,ui8>(-1, -1)) //set secondary skills to default
|
|
secSkills = type->secSkillsInit;
|
|
if (!name.length())
|
|
name = type->name;
|
|
if (exp == 0xffffffff)
|
|
{
|
|
initExp();
|
|
}
|
|
else
|
|
{
|
|
level = VLC->heroh->level(exp);
|
|
}
|
|
|
|
if (sex == 0xFF)//sex is default
|
|
sex = type->sex;
|
|
|
|
setFormation(false);
|
|
if (!stacksCount()) //standard army//initial army
|
|
{
|
|
initArmy();
|
|
}
|
|
assert(validTypes());
|
|
|
|
hoverName = VLC->generaltexth->allTexts[15];
|
|
boost::algorithm::replace_first(hoverName,"%s",name);
|
|
boost::algorithm::replace_first(hoverName,"%s", type->heroClass->name);
|
|
|
|
if (mana < 0)
|
|
mana = manaLimit();
|
|
}
|
|
|
|
void CGHeroInstance::initArmy(IArmyDescriptor *dst /*= NULL*/)
|
|
{
|
|
if(!dst)
|
|
dst = this;
|
|
|
|
int howManyStacks = 0; //how many stacks will hero receives <1 - 3>
|
|
int pom = ran()%100;
|
|
int warMachinesGiven = 0;
|
|
|
|
if(pom < 9)
|
|
howManyStacks = 1;
|
|
else if(pom < 79)
|
|
howManyStacks = 2;
|
|
else
|
|
howManyStacks = 3;
|
|
|
|
for(int stackNo=0; stackNo < howManyStacks; stackNo++)
|
|
{
|
|
int creID = (VLC->creh->nameToID[type->refTypeStack[stackNo]]);
|
|
int range = type->highStack[stackNo] - type->lowStack[stackNo];
|
|
int count = ran()%(range+1) + type->lowStack[stackNo];
|
|
|
|
if(creID>=145 && creID<=149) //war machine
|
|
{
|
|
warMachinesGiven++;
|
|
if(dst != this)
|
|
continue;
|
|
|
|
int slot = -1, aid = -1;
|
|
switch (creID)
|
|
{
|
|
case 145: //catapult
|
|
slot = Arts::MACH4;
|
|
aid = 3;
|
|
break;
|
|
default:
|
|
aid = CArtHandler::convertMachineID(creID,true);
|
|
slot = 9 + aid;
|
|
break;
|
|
}
|
|
|
|
if(!getArt(slot))
|
|
putArtifact(slot, CArtifactInstance::createNewArtifactInstance(aid));
|
|
else
|
|
tlog3 << "Hero " << name << " already has artifact at " << slot << ", ommiting giving " << aid << std::endl;
|
|
}
|
|
else
|
|
dst->setCreature(stackNo-warMachinesGiven, creID, count);
|
|
}
|
|
}
|
|
void CGHeroInstance::initHeroDefInfo()
|
|
{
|
|
if(!defInfo || defInfo->id != HEROI_TYPE)
|
|
{
|
|
defInfo = new CGDefInfo();
|
|
defInfo->id = HEROI_TYPE;
|
|
defInfo->subid = subID;
|
|
defInfo->printPriority = 0;
|
|
defInfo->visitDir = 0xff;
|
|
}
|
|
for(int i=0;i<6;i++)
|
|
{
|
|
defInfo->blockMap[i] = 255;
|
|
defInfo->visitMap[i] = 0;
|
|
defInfo->coverageMap[i] = 0;
|
|
defInfo->shadowCoverage[i] = 0;
|
|
}
|
|
defInfo->blockMap[5] = 253;
|
|
defInfo->visitMap[5] = 2;
|
|
defInfo->coverageMap[4] = defInfo->coverageMap[5] = 224;
|
|
}
|
|
CGHeroInstance::~CGHeroInstance()
|
|
{
|
|
}
|
|
|
|
bool CGHeroInstance::needsLastStack() const
|
|
{
|
|
return true;
|
|
}
|
|
void CGHeroInstance::onHeroVisit(const CGHeroInstance * h) const
|
|
{
|
|
if(h == this) return; //exclude potential self-visiting
|
|
|
|
if (ID == HEROI_TYPE) //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 == 62) //prison
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.soundID = soundBase::ROGUE;
|
|
|
|
if(cb->getHeroCount(h->tempOwner,false) < 8) //free hero slot
|
|
{
|
|
cb->changeObjPos(id,pos+int3(1,0,0),0);
|
|
cb->setObjProperty(id, ObjProperty::ID, HEROI_TYPE); //set ID to 34
|
|
cb->giveHero(id,h->tempOwner); //recreates def and adds hero to player
|
|
|
|
iw.text << std::pair<ui8,ui32>(11,102);
|
|
}
|
|
else //already 8 wandering heroes
|
|
{
|
|
iw.text << std::pair<ui8,ui32>(11,103);
|
|
}
|
|
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
}
|
|
|
|
const std::string & CGHeroInstance::getBiography() const
|
|
{
|
|
if (biography.length())
|
|
return biography;
|
|
else
|
|
return VLC->generaltexth->hTxts[subID].biography;
|
|
}
|
|
void CGHeroInstance::initObj()
|
|
{
|
|
blockVisit = true;
|
|
speciality.growthsWithLevel = false;
|
|
|
|
if(!type)
|
|
return; //TODO support prison
|
|
|
|
for (std::vector<SSpecialtyInfo>::const_iterator it = type->spec.begin(); it != type->spec.end(); it++)
|
|
{
|
|
Bonus *bonus = new Bonus();
|
|
bonus->val = it->val;
|
|
bonus->sid = id; //from the hero, speciality has no unique id
|
|
bonus->duration = Bonus::PERMANENT;
|
|
bonus->source = Bonus::HERO_SPECIAL;
|
|
switch (it->type)
|
|
{
|
|
case 1:// creature speciality
|
|
{
|
|
speciality.growthsWithLevel = true;
|
|
|
|
const CCreature &specCreature = *VLC->creh->creatures[it->additionalinfo]; //creature in which we have specialty
|
|
|
|
int creLevel = specCreature.level;
|
|
if(!creLevel) //TODO: set fixed level for War Machines
|
|
{
|
|
if(it->additionalinfo == 146)
|
|
creLevel = 5; //treat ballista as 5-level
|
|
else
|
|
{
|
|
tlog2 << "Warning: unknown level of " << specCreature.namePl << std::endl;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
bonus->limiter.reset(new CCreatureTypeLimiter (specCreature, true)); //with upgrades
|
|
bonus->type = Bonus::PRIMARY_SKILL;
|
|
bonus->additionalInfo = it->additionalinfo;
|
|
bonus->valType = Bonus::ADDITIVE_VALUE;
|
|
|
|
bonus->subtype = PrimarySkill::ATTACK;
|
|
speciality.addNewBonus(bonus);
|
|
|
|
bonus = new Bonus(*bonus);
|
|
bonus->subtype = PrimarySkill::DEFENSE;
|
|
speciality.addNewBonus(bonus);
|
|
//values will be calculated later
|
|
|
|
bonus = new Bonus(*bonus);
|
|
bonus->type = Bonus::STACKS_SPEED;
|
|
bonus->val = 1; //+1 speed
|
|
speciality.addNewBonus(bonus);
|
|
}
|
|
break;
|
|
case 2://secondary skill
|
|
speciality.growthsWithLevel = 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 = it->subtype; //skill id
|
|
bonus->val = it->val; //value per level, in percent
|
|
speciality.addNewBonus(bonus);
|
|
bonus = new Bonus(*bonus);
|
|
|
|
switch (it->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
|
|
speciality.addNewBonus(bonus);
|
|
break;
|
|
case 3://spell damage bonus, level dependant but calculated elsehwere
|
|
bonus->type = Bonus::SPECIAL_SPELL_LEV;
|
|
bonus->subtype = it->subtype;
|
|
speciality.addNewBonus(bonus);
|
|
break;
|
|
case 4://creature stat boost
|
|
switch (it->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->valType = Bonus::ADDITIVE_VALUE;
|
|
bonus->limiter.reset(new CCreatureTypeLimiter (*VLC->creh->creatures[it->additionalinfo], true));
|
|
speciality.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 = it->subtype; //spell id
|
|
speciality.addNewBonus(bonus);
|
|
break;
|
|
case 6://damage bonus for bless (Adela)
|
|
bonus->type = Bonus::SPECIAL_BLESS_DAMAGE;
|
|
bonus->subtype = it->subtype; //spell id if you ever wanted to use it otherwise
|
|
bonus->additionalInfo = it->additionalinfo; //damage factor
|
|
speciality.addNewBonus(bonus);
|
|
break;
|
|
case 7://maxed mastery for spell
|
|
bonus->type = Bonus::MAXED_SPELL;
|
|
bonus->subtype = it->subtype; //spell i
|
|
speciality.addNewBonus(bonus);
|
|
break;
|
|
case 8://peculiar spells - enchantments
|
|
bonus->type = Bonus::SPECIAL_PECULIAR_ENCHANT;
|
|
bonus->subtype = it->subtype; //spell id
|
|
bonus->additionalInfo = it->additionalinfo;//0, 1 for Coronius
|
|
speciality.addNewBonus(bonus);
|
|
break;
|
|
case 9://upgrade creatures
|
|
{
|
|
std::vector< ConstTransitivePtr<CCreature> >* creatures = &VLC->creh->creatures;
|
|
bonus->type = Bonus::SPECIAL_UPGRADE;
|
|
bonus->subtype = it->subtype; //base id
|
|
bonus->additionalInfo = it->additionalinfo; //target id
|
|
speciality.addNewBonus(bonus);
|
|
bonus = new Bonus(*bonus);
|
|
|
|
for (std::set<ui32>::iterator i = (*creatures)[it->subtype]->upgrades.begin();
|
|
i != (*creatures)[it->subtype]->upgrades.end(); i++)
|
|
{
|
|
bonus->subtype = *i; //propagate for regular upgrades of base creature
|
|
speciality.addNewBonus(bonus);
|
|
bonus = new Bonus(*bonus);
|
|
}
|
|
delNull(bonus);
|
|
break;
|
|
}
|
|
case 10://resource generation
|
|
bonus->type = Bonus::GENERATE_RESOURCE;
|
|
bonus->subtype = it->subtype;
|
|
speciality.addNewBonus(bonus);
|
|
break;
|
|
case 11://starting skill with mastery (Adrienne)
|
|
cb->changeSecSkill(id, it->val, it->additionalinfo); //simply give it and forget
|
|
break;
|
|
case 12://army speed
|
|
bonus->type = Bonus::STACKS_SPEED;
|
|
speciality.addNewBonus(bonus);
|
|
break;
|
|
case 13://Dragon bonuses (Mutare)
|
|
bonus->type = Bonus::PRIMARY_SKILL;
|
|
bonus->valType = Bonus::ADDITIVE_VALUE;
|
|
switch (it->subtype)
|
|
{
|
|
case 1:
|
|
bonus->subtype = PrimarySkill::ATTACK;
|
|
break;
|
|
case 2:
|
|
bonus->subtype = PrimarySkill::DEFENSE;
|
|
break;
|
|
}
|
|
bonus->limiter.reset(new HasAnotherBonusLimiter(Bonus::DRAGON_NATURE));
|
|
speciality.addNewBonus(bonus);
|
|
break;
|
|
default:
|
|
tlog2 << "Unexpected hero speciality " << type <<'\n';
|
|
}
|
|
}
|
|
//initialize bonuses
|
|
for (std::vector<std::pair<ui8,ui8> >::iterator it = secSkills.begin(); it != secSkills.end(); it++)
|
|
updateSkill(it->first, it->second);
|
|
UpdateSpeciality();
|
|
|
|
mana = manaLimit(); //after all bonuses are taken into account, make sure this line is the last one
|
|
type->name = name;
|
|
}
|
|
void CGHeroInstance::UpdateSpeciality()
|
|
{
|
|
if (speciality.growthsWithLevel)
|
|
{
|
|
std::vector< ConstTransitivePtr<CCreature> > & creatures = VLC->creh->creatures;
|
|
|
|
BOOST_FOREACH(Bonus *it, speciality.bonuses)
|
|
{
|
|
switch (it->type)
|
|
{
|
|
case Bonus::SECONDARY_SKILL_PREMY:
|
|
it->val = (speciality.valOfBonuses(Bonus::SPECIAL_SECONDARY_SKILL, it->subtype) * level);
|
|
break; //use only hero skills as bonuses to avoid feedback loop
|
|
case Bonus::PRIMARY_SKILL: //for crearures, that is
|
|
int creLevel = creatures[it->additionalInfo]->level;
|
|
if(!creLevel)
|
|
{
|
|
if(it->additionalInfo == 146)
|
|
creLevel = 5; //treat ballista as 5-level
|
|
else
|
|
{
|
|
tlog2 << "Warning: unknown level of " << creatures[it->additionalInfo]->namePl << std::endl;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
double primSkillModifier = (int)(level / creLevel) / 20.0;
|
|
int param;
|
|
switch (it->subtype)
|
|
{
|
|
case PrimarySkill::ATTACK:
|
|
param = creatures[it->additionalInfo]->attack;
|
|
break;
|
|
case PrimarySkill::DEFENSE:
|
|
param = creatures[it->additionalInfo]->defence;
|
|
break;
|
|
}
|
|
it->val = ceil(param * (1 + primSkillModifier)) - param; //yep, overcomplicated but matches original
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
void CGHeroInstance::updateSkill(int which, int val)
|
|
{
|
|
if(which == LEADERSHIP || which == LUCK)
|
|
{ //luck-> VLC->generaltexth->arraytxt[73+luckSkill]; VLC->generaltexth->arraytxt[104+moraleSkill]
|
|
bool luck = which == LUCK;
|
|
Bonus::BonusType type[] = {Bonus::MORALE, Bonus::LUCK};
|
|
|
|
Bonus *b = getBonus(Selector::type(type[luck]) && 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 == DIPLOMACY) //surrender discount: 20% per level
|
|
{
|
|
|
|
if(Bonus *b = getBonus(Selector::type(Bonus::SURRENDER_DISCOUNT) && 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 ARCHERY:
|
|
switch (val)
|
|
{
|
|
case 1:
|
|
skillVal = 10; break;
|
|
case 2:
|
|
skillVal = 25; break;
|
|
case 3:
|
|
skillVal = 50; break;
|
|
}
|
|
break;
|
|
case LOGISTICS:
|
|
skillVal = 10 * val; break;
|
|
case NAVIGATION:
|
|
skillVal = 50 * val; break;
|
|
case MYSTICISM:
|
|
skillVal = val; break;
|
|
case EAGLE_EYE:
|
|
skillVal = 30 + 10 * val; break;
|
|
case NECROMANCY:
|
|
skillVal = 10 * val; break;
|
|
case LEARNING:
|
|
skillVal = 5 * val; break;
|
|
case OFFENCE:
|
|
skillVal = 10 * val; break;
|
|
case ARMORER:
|
|
skillVal = 5 * val; break;
|
|
case INTELLIGENCE:
|
|
skillVal = 25 << (val-1); break;
|
|
case SORCERY:
|
|
skillVal = 5 * val; break;
|
|
case RESISTANCE:
|
|
skillVal = 5 << (val-1); break;
|
|
case FIRST_AID:
|
|
skillVal = 25 + 25*val; break;
|
|
}
|
|
|
|
|
|
int skillValType = skillVal ? Bonus::BASE_NUMBER : Bonus::INDEPENDENT_MIN;
|
|
if(Bonus * b = bonuses.getFirst(Selector::typeSybtype(Bonus::SECONDARY_SKILL_PREMY, which) && Selector::sourceType(Bonus::SECONDARY_SKILL))) //only local hero bonus
|
|
{
|
|
b->val = skillVal;
|
|
b->valType = skillValType;
|
|
}
|
|
else
|
|
{
|
|
Bonus *bonus = new Bonus(Bonus::PERMANENT, Bonus::SECONDARY_SKILL_PREMY, id, skillVal, ID, which, skillValType);
|
|
bonus->source = Bonus::SECONDARY_SKILL;
|
|
addNewBonus(bonus);
|
|
}
|
|
|
|
}
|
|
void CGHeroInstance::setPropertyDer( ui8 what, ui32 val )
|
|
{
|
|
if(what == ObjProperty::PRIMARY_STACK_COUNT)
|
|
setStackCount(0, val);
|
|
}
|
|
|
|
double CGHeroInstance::getHeroStrength() const
|
|
{
|
|
return sqrt((1.0 + 0.05*getPrimSkillLevel(0)) * (1.0 + 0.05*getPrimSkillLevel(1)));
|
|
}
|
|
|
|
int CGHeroInstance::getTotalStrength() const
|
|
{
|
|
double ret = getHeroStrength() * getArmyStrength();
|
|
return (int) ret;
|
|
}
|
|
|
|
expType CGHeroInstance::calculateXp(expType exp) const
|
|
{
|
|
return exp * (100 + valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, CGHeroInstance::LEARNING))/100.0f;
|
|
}
|
|
|
|
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<int>(getSecSkillLevel( \
|
|
static_cast<CGHeroInstance::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
|
|
|
|
|
|
|
|
amax(skill, valOfBonuses(Bonus::MAGIC_SCHOOL_SKILL, 0)); //any school bonus
|
|
amax(skill, valOfBonuses(Bonus::SPELL, spell->id)); //given by artifact or other effect
|
|
if (hasBonusOfType(Bonus::MAXED_SPELL, spell->id))//hero speciality (Daremyth, Melodia)
|
|
skill = 3;
|
|
assert(skill >= 0 && skill <= 3);
|
|
return skill;
|
|
}
|
|
|
|
bool CGHeroInstance::canCastThisSpell(const CSpell * spell) const
|
|
{
|
|
if(!getArt(17)) //if hero has no spellbook
|
|
return false;
|
|
|
|
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(CGHeroInstance::NECROMANCY);
|
|
|
|
// Hero knows necromancy.
|
|
if (necromancyLevel > 0)
|
|
{
|
|
double necromancySkill = valOfBonuses(Bonus::SECONDARY_SKILL_PREMY, NECROMANCY)/100.0;
|
|
amin(necromancySkill, 1.0); //it's impossible to raise more creatures than all...
|
|
const std::map<ui32,si32> &casualties = battleResult.casualties[!battleResult.winner];
|
|
ui32 raisedUnits = 0;
|
|
|
|
// Figure out what to raise and how many.
|
|
const ui32 creatureTypes[] = {56, 58, 60, 64}; // IDs for Skeletons, Walking Dead, Wights and Liches respectively.
|
|
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 (std::map<ui32,si32>::const_iterator it = casualties.begin(); it != casualties.end(); it++)
|
|
{
|
|
// Get lost enemy hit points convertible to units.
|
|
const ui32 raisedHP = VLC->creh->creatures[it->first]->valOfBonuses(Bonus::STACK_HEALTH) * it->second * necromancySkill;
|
|
raisedUnits += std::min<ui32>(raisedHP / raisedUnitHP, it->second * necromancySkill); //limit to % of HP and % of original stack count
|
|
}
|
|
|
|
// Make room for new units.
|
|
int slot = getSlotFor(raisedUnitType->idNumber);
|
|
if (slot == -1)
|
|
{
|
|
// 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::GENIE;
|
|
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);
|
|
iw.text.addReplacement(MetaString::CRE_PL_NAMES, raisedStack.type->idNumber);
|
|
}
|
|
else // Practicing the dark arts of necromancy, ... (singular)
|
|
{
|
|
iw.text.addTxt(MetaString::GENERAL_TXT, 146);
|
|
iw.text.addReplacement(MetaString::CRE_SING_NAMES, raisedStack.type->idNumber);
|
|
}
|
|
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
int3 CGHeroInstance::getSightCenter() const
|
|
{
|
|
return getPosition(false);
|
|
}
|
|
|
|
int CGHeroInstance::getSightRadious() const
|
|
{
|
|
return 5 + getSecSkillLevel(CGHeroInstance::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
|
|
{
|
|
int alignment = type->heroType / 6;
|
|
switch(alignment)
|
|
{
|
|
case 0:
|
|
return 1; //good
|
|
case 1:
|
|
return 0; //evil
|
|
case 2:
|
|
return 2;
|
|
default:
|
|
throw std::string("Wrong alignment!");
|
|
}
|
|
}
|
|
|
|
void CGHeroInstance::getOutOffsets(std::vector<int3> &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 (size_t i = 0; i < ARRAY_COUNT(dirs); i++)
|
|
offsets += dirs[i];
|
|
}
|
|
|
|
int CGHeroInstance::getSpellCost(const CSpell *sp) const
|
|
{
|
|
return sp->costs[getSpellSchoolLevel(sp)];
|
|
}
|
|
|
|
void CGHeroInstance::pushPrimSkill(int which, int val)
|
|
{
|
|
addNewBonus(new Bonus(Bonus::PERMANENT, Bonus::PRIMARY_SKILL, Bonus::HERO_BASE_SKILL, val, id, which));
|
|
}
|
|
|
|
EAlignment CGHeroInstance::getAlignment() const
|
|
{
|
|
return type->heroClass->getAlignment();
|
|
}
|
|
|
|
void CGHeroInstance::initExp()
|
|
{
|
|
exp=40+ (ran()) % 50;
|
|
level = 1;
|
|
}
|
|
|
|
std::string CGHeroInstance::nodeName() const
|
|
{
|
|
return "Hero " + name;
|
|
}
|
|
|
|
void CGHeroInstance::putArtifact(ui16 pos, CArtifactInstance *art)
|
|
{
|
|
assert(!getArt(pos));
|
|
art->putAt(this, pos);
|
|
}
|
|
|
|
void CGHeroInstance::putInBackpack(CArtifactInstance *art)
|
|
{
|
|
putArtifact(art->firstBackpackSlot(this), art);
|
|
}
|
|
|
|
bool CGHeroInstance::hasSpellbook() const
|
|
{
|
|
return getArt(Arts::SPELLBOOK);
|
|
}
|
|
|
|
void CGHeroInstance::deserializationFix()
|
|
{
|
|
for(bmap<ui16, ArtSlotInfo>::iterator i = artifactsWorn.begin(); i != artifactsWorn.end(); i++)
|
|
if(i->second.artifact && !i->second.locked)
|
|
attachTo(i->second.artifact);
|
|
|
|
attachTo(&speciality);
|
|
}
|
|
|
|
CBonusSystemNode * CGHeroInstance::whereShouldBeAttached(CGameState *gs)
|
|
{
|
|
if(visitedTown)
|
|
{
|
|
if(inTownGarrison)
|
|
return visitedTown;
|
|
else
|
|
return &visitedTown->townAndVis;
|
|
}
|
|
else
|
|
return CArmedInstance::whereShouldBeAttached(gs);
|
|
}
|
|
|
|
void CGDwelling::initObj()
|
|
{
|
|
switch(ID)
|
|
{
|
|
case 17:
|
|
{
|
|
int crid = VLC->objh->cregens[subID];
|
|
const CCreature *crs = VLC->creh->creatures[crid];
|
|
|
|
creatures.resize(1);
|
|
creatures[0].second.push_back(crid);
|
|
hoverName = VLC->generaltexth->creGens[subID];
|
|
if(crs->level > 4)
|
|
putStack(0, new CStackInstance(crs, (crs->growth) * 3));
|
|
if (getOwner() != 255)
|
|
cb->gameState()->players[getOwner()].dwellings.push_back (this);
|
|
}
|
|
break;
|
|
|
|
case 20:
|
|
creatures.resize(4);
|
|
if(subID == 1) //Golem Factory
|
|
{
|
|
creatures[0].second.push_back(32); //Stone Golem
|
|
creatures[1].second.push_back(33); //Iron Golem
|
|
creatures[2].second.push_back(116); //Gold Golem
|
|
creatures[3].second.push_back(117); //Diamond Golem
|
|
//guards
|
|
putStack(0, new CStackInstance(116, 9));
|
|
putStack(1, new CStackInstance(117, 6));
|
|
}
|
|
else if(subID == 0) // Elemental Conflux
|
|
{
|
|
creatures[0].second.push_back(112); //Air Elemental
|
|
creatures[1].second.push_back(114); //Fire Elemental
|
|
creatures[2].second.push_back(113); //Earth Elemental
|
|
creatures[3].second.push_back(115); //Water Elemental
|
|
//guards
|
|
putStack(0, new CStackInstance(113, 12));
|
|
}
|
|
else
|
|
{
|
|
assert(0);
|
|
}
|
|
hoverName = VLC->generaltexth->creGens4[subID];
|
|
break;
|
|
|
|
case 78: //Refugee Camp
|
|
//is handled within newturn func
|
|
break;
|
|
|
|
case 106: //War Machine Factory
|
|
creatures.resize(3);
|
|
creatures[0].second.push_back(146); //Ballista
|
|
creatures[1].second.push_back(147); //First Aid Tent
|
|
creatures[2].second.push_back(148); //Ammo Cart
|
|
break;
|
|
|
|
default:
|
|
assert(0);
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CGDwelling::setProperty(ui8 what, ui32 val)
|
|
{
|
|
switch (what)
|
|
{
|
|
case ObjProperty::OWNER: //change owner
|
|
if (ID == 17) //single generators
|
|
{
|
|
if (tempOwner != NEUTRAL_PLAYER)
|
|
{
|
|
std::vector<ConstTransitivePtr<CGDwelling> >* dwellings = &cb->gameState()->players[tempOwner].dwellings;
|
|
dwellings->erase (std::find(dwellings->begin(), dwellings->end(), this));
|
|
}
|
|
if (val != NEUTRAL_PLAYER) //can new owner be neutral?
|
|
cb->gameState()->players[val].dwellings.push_back (this);
|
|
}
|
|
break;
|
|
case ObjProperty::AVAILABLE_CREATURE:
|
|
creatures.resize(1);
|
|
creatures[0].second.resize(1);
|
|
creatures[0].second[0] = val;
|
|
break;
|
|
}
|
|
CGObjectInstance::setProperty(what,val);
|
|
}
|
|
void CGDwelling::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
if(ID == 78 && !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;
|
|
}
|
|
|
|
int relations = cb->gameState()->getPlayerRelations( h->tempOwner, tempOwner );
|
|
|
|
if ( relations == 1 )//ally
|
|
return;//do not allow recruiting or capturing
|
|
|
|
if( !relations && stacksCount() > 0) //object is guarded, owned by enemy
|
|
{
|
|
BlockingDialog bd;
|
|
bd.player = h->tempOwner;
|
|
bd.flags = BlockingDialog::ALLOW_CANCEL;
|
|
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 == 17 ? 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, boost::bind(&CGDwelling::wantsFight, this, h, _1));
|
|
return;
|
|
}
|
|
|
|
if(!relations && ID != 106)
|
|
{
|
|
cb->setOwner(id, h->tempOwner);
|
|
}
|
|
|
|
BlockingDialog bd;
|
|
bd.player = h->tempOwner;
|
|
bd.flags = BlockingDialog::ALLOW_CANCEL;
|
|
if(ID == 17 || ID == 20)
|
|
{
|
|
bd.text.addTxt(MetaString::ADVOB_TXT, ID == 17 ? 35 : 36); //{%s} Would you like to recruit %s? / {%s} Would you like to recruit %s, %s, %s, or %s?
|
|
bd.text.addReplacement(ID == 17 ? MetaString::CREGENS : MetaString::CREGENS4, subID);
|
|
for(size_t i = 0; i < creatures.size(); i++)
|
|
bd.text.addReplacement(MetaString::CRE_PL_NAMES, creatures[i].second[0]);
|
|
}
|
|
else if(ID == 78)
|
|
{
|
|
bd.text.addTxt(MetaString::ADVOB_TXT, 35); //{%s} Would you like to recruit %s?
|
|
bd.text.addReplacement(MetaString::OBJ_NAMES, ID);
|
|
for(size_t i = 0; i < creatures.size(); i++)
|
|
bd.text.addReplacement(MetaString::CRE_PL_NAMES, creatures[i].second[0]);
|
|
}
|
|
else if(ID == 106)
|
|
bd.text.addTxt(MetaString::ADVOB_TXT, 157); //{War Machine Factory} Would you like to purchase War Machines?
|
|
else
|
|
throw std::string("Illegal dwelling!");
|
|
|
|
cb->showBlockingDialog(&bd, boost::bind(&CGDwelling::heroAcceptsCreatures, this, h, _1));
|
|
}
|
|
|
|
void CGDwelling::newTurn() const
|
|
{
|
|
if(cb->getDate(1) != 1) //not first day of week
|
|
return;
|
|
|
|
//town growths and War Machines Factories are handled separately
|
|
if(ID == TOWNI_TYPE || ID == 106)
|
|
return;
|
|
|
|
if(ID == 78) //if it's a refugee camp, we need to pick an available creature
|
|
{
|
|
cb->setObjProperty(id, ObjProperty::AVAILABLE_CREATURE, VLC->creh->pickRandomMonster());
|
|
}
|
|
|
|
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 (DWELLINGS_ACCUMULATE_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, ui32 answer ) const
|
|
{
|
|
if(!answer)
|
|
return;
|
|
|
|
int crid = creatures[0].second[0];
|
|
CCreature *crs = VLC->creh->creatures[crid];
|
|
TQuantity count = creatures[0].first;
|
|
|
|
if(crs->level == 1 && ID != 78) //first level - creatures are for free
|
|
{
|
|
if(count) //there are available creatures
|
|
{
|
|
int slot = h->getSlotFor(crid);
|
|
if(slot < 0) //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 == 106) //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(13); //ballista
|
|
sac.creatures[1].first = !h->getArt(15); //first aid tent
|
|
sac.creatures[2].first = !h->getArt(14); //ammo cart
|
|
cb->sendAndApply(&sac);
|
|
}
|
|
|
|
OpenWindow ow;
|
|
ow.id1 = id;
|
|
ow.id2 = h->id;
|
|
ow.window = (ID == 17 || ID == 78)
|
|
? OpenWindow::RECRUITMENT_FIRST
|
|
: OpenWindow::RECRUITMENT_ALL;
|
|
cb->sendAndApply(&ow);
|
|
}
|
|
}
|
|
|
|
void CGDwelling::wantsFight( const CGHeroInstance *h, ui32 answer ) const
|
|
{
|
|
if(answer)
|
|
cb->startBattleI(h, this, boost::bind(&CGDwelling::fightOver, this, h, _1));
|
|
}
|
|
|
|
void CGDwelling::fightOver(const CGHeroInstance *h, BattleResult *result) const
|
|
{
|
|
if (result->winner == 0)
|
|
{
|
|
onHeroVisit(h);
|
|
}
|
|
}
|
|
|
|
int CGTownInstance::getSightRadious() const //returns sight distance
|
|
{
|
|
if (subID == 2) //tower
|
|
{
|
|
if ((builtBuildings.find(26)) != builtBuildings.end()) //skyship
|
|
return -1; //entire map
|
|
else if ((builtBuildings.find(21)) != builtBuildings.end()) //lookout tower
|
|
return 20;
|
|
}
|
|
return 5;
|
|
}
|
|
|
|
void CGTownInstance::setPropertyDer(ui8 what, ui32 val)
|
|
{
|
|
///this is freakin' overcomplicated solution
|
|
switch (what)
|
|
{
|
|
case 11: //add visitor of town building
|
|
bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, visitingHero->id);
|
|
break;
|
|
case 12:
|
|
bonusingBuildings[val]->setProperty (12, 0);
|
|
break;
|
|
case 13: //add garrisoned hero to visitors
|
|
bonusingBuildings[val]->setProperty (ObjProperty::VISITORS, garrisonHero->id);
|
|
break;
|
|
case 14:
|
|
bonusValue.first = val;
|
|
break;
|
|
case 15:
|
|
bonusValue.second = val;
|
|
break;
|
|
}
|
|
}
|
|
int CGTownInstance::fortLevel() const //0 - none, 1 - fort, 2 - citadel, 3 - castle
|
|
{
|
|
if((builtBuildings.find(9))!=builtBuildings.end())
|
|
return 3;
|
|
if((builtBuildings.find(8))!=builtBuildings.end())
|
|
return 2;
|
|
if((builtBuildings.find(7))!=builtBuildings.end())
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int CGTownInstance::hallLevel() const // -1 - none, 0 - village, 1 - town, 2 - city, 3 - capitol
|
|
{
|
|
if ((builtBuildings.find(13))!=builtBuildings.end())
|
|
return 3;
|
|
if ((builtBuildings.find(12))!=builtBuildings.end())
|
|
return 2;
|
|
if ((builtBuildings.find(11))!=builtBuildings.end())
|
|
return 1;
|
|
if ((builtBuildings.find(10))!=builtBuildings.end())
|
|
return 0;
|
|
return -1;
|
|
}
|
|
int CGTownInstance::mageGuildLevel() const
|
|
{
|
|
if ((builtBuildings.find(4))!=builtBuildings.end())
|
|
return 5;
|
|
if ((builtBuildings.find(3))!=builtBuildings.end())
|
|
return 4;
|
|
if ((builtBuildings.find(2))!=builtBuildings.end())
|
|
return 3;
|
|
if ((builtBuildings.find(1))!=builtBuildings.end())
|
|
return 2;
|
|
if ((builtBuildings.find(0))!=builtBuildings.end())
|
|
return 1;
|
|
return 0;
|
|
}
|
|
bool CGTownInstance::creatureDwelling(const int & level, bool upgraded) const
|
|
{
|
|
if ( level<0 || level >= CREATURES_PER_TOWN )
|
|
return false;
|
|
return vstd::contains(builtBuildings, 30+level+upgraded*CREATURES_PER_TOWN);
|
|
}
|
|
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[HID];
|
|
}
|
|
int CGTownInstance::creatureGrowth(const int & level) const
|
|
{
|
|
if (level<0 || level >=CREATURES_PER_TOWN)
|
|
return 0;
|
|
if (!vstd::contains(builtBuildings, Buildings::DWELL_FIRST+level))
|
|
return 0; //no dwelling
|
|
TCreature creid = town->basicCreatures[level];
|
|
|
|
int ret = VLC->creh->creatures[creid]->growth;
|
|
switch(fortLevel())
|
|
{
|
|
case 3:
|
|
ret*=2;break;
|
|
case 2:
|
|
ret*=(1.5); break;
|
|
}
|
|
ret *= (1 + VLC->creh->creatures[creid]->valOfBonuses(Bonus::CREATURE_GROWTH_PERCENT)/100); // double growth or plague
|
|
if(tempOwner != NEUTRAL_PLAYER)
|
|
{
|
|
ret *= (100.0f + cb->gameState()->players[tempOwner].valOfBonuses
|
|
(Selector::type(Bonus::CREATURE_GROWTH_PERCENT) && Selector::sourceType(Bonus::ARTIFACT)))/100; //Statue of Legion
|
|
for (std::vector<ConstTransitivePtr<CGDwelling> >::const_iterator it = cb->gameState()->players[tempOwner].dwellings.begin(); it != cb->gameState()->players[tempOwner].dwellings.end(); ++it)
|
|
{ //+1 for each dwelling
|
|
if (VLC->creh->creatures[creid]->idNumber == (*it)->creatures[0].second[0])
|
|
++ret;
|
|
}
|
|
}
|
|
if(getHordeLevel(0)==level)
|
|
if((builtBuildings.find(18)!=builtBuildings.end()) || (builtBuildings.find(19)!=builtBuildings.end()))
|
|
ret+=VLC->creh->creatures[creid]->hordeGrowth;
|
|
if(getHordeLevel(1)==level)
|
|
if((builtBuildings.find(24)!=builtBuildings.end()) || (builtBuildings.find(25)!=builtBuildings.end()))
|
|
ret+=VLC->creh->creatures[creid]->hordeGrowth;
|
|
|
|
//support for legs of legion etc.
|
|
if(garrisonHero)
|
|
ret += garrisonHero->valOfBonuses(Bonus::CREATURE_GROWTH, level);
|
|
if(visitingHero)
|
|
ret += visitingHero->valOfBonuses(Bonus::CREATURE_GROWTH, level);
|
|
if(builtBuildings.find(26)!=builtBuildings.end()) //grail - +50% to ALL growth
|
|
ret*=1.5;
|
|
//spcecial week unaffected by grail (bug in original game?)
|
|
ret += VLC->creh->creatures[creid]->valOfBonuses(Bonus::CREATURE_GROWTH);
|
|
return ret;//check CCastleInterface.cpp->CCastleInterface::CCreaInfo::clickRight if this one will be modified
|
|
}
|
|
int CGTownInstance::dailyIncome() const
|
|
{
|
|
int ret = 0;
|
|
if ((builtBuildings.find(26))!=builtBuildings.end())
|
|
ret+=5000;
|
|
if ((builtBuildings.find(13))!=builtBuildings.end())
|
|
ret+=4000;
|
|
else if ((builtBuildings.find(12))!=builtBuildings.end())
|
|
ret+=2000;
|
|
else if ((builtBuildings.find(11))!=builtBuildings.end())
|
|
ret+=1000;
|
|
else if ((builtBuildings.find(10))!=builtBuildings.end())
|
|
ret+=500;
|
|
return ret;
|
|
}
|
|
bool CGTownInstance::hasFort() const
|
|
{
|
|
return (builtBuildings.find(7))!=builtBuildings.end();
|
|
}
|
|
bool CGTownInstance::hasCapitol() const
|
|
{
|
|
return (builtBuildings.find(13))!=builtBuildings.end();
|
|
}
|
|
CGTownInstance::CGTownInstance()
|
|
:IShipyard(this), IMarket(this)
|
|
{
|
|
builded=-1;
|
|
destroyed=-1;
|
|
town=NULL;
|
|
}
|
|
|
|
CGTownInstance::~CGTownInstance()
|
|
{
|
|
for (std::vector<CGTownBuilding*>::const_iterator i = bonusingBuildings.begin(); i != bonusingBuildings.end(); i++)
|
|
delete *i;
|
|
}
|
|
|
|
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(subID == 2 && vstd::contains(builtBuildings,22)) //magic library in 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 = NULL;
|
|
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->startBattleI(h, defendingArmy, getSightCenter(), h, defendingHero, false, boost::bind(&CGTownInstance::fightOver, this, h, _1), (outsideTown ? NULL : this));
|
|
}
|
|
else
|
|
{
|
|
cb->setOwner(id, h->tempOwner);
|
|
removeCapitols(h->getOwner());
|
|
cb->heroVisitCastle(id, h->id);
|
|
}
|
|
}
|
|
else
|
|
cb->heroVisitCastle(id, h->id);
|
|
}
|
|
|
|
void CGTownInstance::onHeroLeave(const CGHeroInstance * h) const
|
|
{
|
|
cb->stopHeroVisitCastle(id,h->id);
|
|
}
|
|
|
|
void CGTownInstance::initObj()
|
|
///initialize town structures
|
|
{
|
|
blockVisit = true;
|
|
hoverName = name + ", " + town->Name();
|
|
|
|
if (subID == 5)
|
|
creatures.resize(CREATURES_PER_TOWN+1);//extra dwelling for Dungeon
|
|
else
|
|
creatures.resize(CREATURES_PER_TOWN);
|
|
for (int i = 0; i < CREATURES_PER_TOWN; i++)
|
|
{
|
|
if(creatureDwelling(i,false))
|
|
creatures[i].second.push_back(town->basicCreatures[i]);
|
|
if(creatureDwelling(i,true))
|
|
creatures[i].second.push_back(town->upgradedCreatures[i]);
|
|
}
|
|
|
|
switch (subID)
|
|
{ //add new visitable objects
|
|
case 0:
|
|
bonusingBuildings.push_back (new COPWBonus(21, this)); //Stables
|
|
break;
|
|
case 5:
|
|
bonusingBuildings.push_back (new COPWBonus(21, this)); //Vortex
|
|
case 2: case 3: case 6:
|
|
bonusingBuildings.push_back (new CTownBonus(23, this));
|
|
break;
|
|
case 7:
|
|
bonusingBuildings.push_back (new CTownBonus(17, this));
|
|
break;
|
|
}
|
|
//add special bonuses from buildings
|
|
|
|
recreateBuildingsBonuses();
|
|
}
|
|
|
|
void CGTownInstance::newTurn() const
|
|
{
|
|
if (cb->getDate(1) == 1) //reset on new week
|
|
{
|
|
if (vstd::contains(builtBuildings,17) && subID == 1 && cb->getDate(0) != 1 && (tempOwner < PLAYER_LIMIT) )//give resources for Rampart, Mystic Pond
|
|
{
|
|
int resID = rand()%4+2;//bonus to random rare resource
|
|
resID = (resID==2)?1:resID;
|
|
int resVal = rand()%4+1;//with size 1..4
|
|
cb->giveResource(tempOwner, resID, resVal);
|
|
cb->setObjProperty (id, 14, resID);
|
|
cb->setObjProperty (id, 15, resVal);
|
|
}
|
|
|
|
if ( subID == 5 )
|
|
for (std::vector<CGTownBuilding*>::const_iterator i = bonusingBuildings.begin(); i!=bonusingBuildings.end(); i++)
|
|
{
|
|
if ((*i)->ID == 21)
|
|
cb->setObjProperty (id, 12, (*i)->id); //reset visitors for Mana Vortex
|
|
}
|
|
|
|
if (tempOwner == NEUTRAL_PLAYER) //garrison growth for neutral towns
|
|
{
|
|
std::vector<ui8> nativeCrits; //slots
|
|
for (TSlots::const_iterator it = Slots().begin(); it != Slots().end(); it++)
|
|
{
|
|
if (it->second->type->faction == subID) //native
|
|
{
|
|
nativeCrits.push_back(it->first); //collect matching slots
|
|
}
|
|
}
|
|
if (nativeCrits.size())
|
|
{
|
|
TSlot pos = nativeCrits[rand() % nativeCrits.size()];
|
|
StackLocation sl(this, pos);
|
|
|
|
const CCreature *c = getCreature(pos);
|
|
if (rand()%100 < 90 || c->upgrades.empty()) //increase number if no upgrade avaliable
|
|
{
|
|
cb->changeStackCount(sl, c->growth);
|
|
}
|
|
else //upgrade
|
|
{
|
|
cb->changeStackType(sl, VLC->creh->creatures[*c->upgrades.begin()]);
|
|
}
|
|
}
|
|
if ((stacksCount() < ARMY_SIZE && rand()%100 < 25) || Slots().empty()) //add new stack
|
|
{
|
|
int i = rand() % std::min (ARMY_SIZE, cb->getDate(3)<<1);
|
|
TCreature c = town->basicCreatures[i];
|
|
TSlot n = -1;
|
|
TQuantity count = creatureGrowth(i);
|
|
|
|
{//no lower tiers or above current month
|
|
|
|
if ((n = getSlotFor(c))>=0)
|
|
{
|
|
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 ALL_PLAYERS;
|
|
if ( tempOwner == 255 )//neutral guarded - noone can visit
|
|
return 0;
|
|
|
|
ui8 mask = 0;
|
|
TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner);
|
|
BOOST_FOREACH(ui8 it, ts->players)
|
|
mask |= 1<<it;//allies - add to possible visitors
|
|
|
|
return mask;
|
|
}
|
|
|
|
void CGTownInstance::getOutOffsets( std::vector<int3> &offsets ) const
|
|
{
|
|
offsets += int3(-1,2,0), int3(-3,2,0);
|
|
}
|
|
|
|
void CGTownInstance::fightOver( const CGHeroInstance *h, BattleResult *result ) const
|
|
{
|
|
if(result->winner == 0)
|
|
{
|
|
removeCapitols (h->getOwner());
|
|
cb->setOwner (id, h->tempOwner); //give control after checkout is done
|
|
FoWChange fw;
|
|
fw.player = h->tempOwner;
|
|
fw.mode = 1;
|
|
getSightTiles (fw.tiles); //update visibility for castle structures
|
|
cb->sendAndApply (&fw);
|
|
}
|
|
}
|
|
|
|
void CGTownInstance::removeCapitols (ui8 owner) const
|
|
{
|
|
if (hasCapitol()) // search if there's an older capitol
|
|
{
|
|
PlayerState* state = cb->gameState()->getPlayer (owner); //get all towns owned by player
|
|
for (std::vector<ConstTransitivePtr<CGTownInstance> >::const_iterator i = state->towns.begin(); i < state->towns.end(); ++i)
|
|
{
|
|
if (*i != this && (*i)->hasCapitol())
|
|
{
|
|
RazeStructures rs;
|
|
rs.tid = id;
|
|
rs.bid.insert(13);
|
|
rs.destroyed = destroyed;
|
|
cb->sendAndApply(&rs);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int CGTownInstance::getBoatType() const
|
|
{
|
|
const CCreature *c = VLC->creh->creatures[town->basicCreatures.front()];
|
|
if (c->isGood())
|
|
return 1;
|
|
else if (c->isEvil())
|
|
return 0;
|
|
else //neutral
|
|
return 2;
|
|
}
|
|
|
|
int CGTownInstance::getMarketEfficiency() const
|
|
{
|
|
if(!vstd::contains(builtBuildings, 14))
|
|
return 0;
|
|
|
|
const PlayerState *p = cb->getPlayer(tempOwner);
|
|
assert(p);
|
|
|
|
int marketCount = 0;
|
|
BOOST_FOREACH(const CGTownInstance *t, p->towns)
|
|
if(vstd::contains(t->builtBuildings, 14))
|
|
marketCount++;
|
|
|
|
return marketCount;
|
|
}
|
|
|
|
bool CGTownInstance::allowsTrade(EMarketMode mode) const
|
|
{
|
|
switch(mode)
|
|
{
|
|
case RESOURCE_RESOURCE:
|
|
case RESOURCE_PLAYER:
|
|
return vstd::contains(builtBuildings, 14); // marketplace
|
|
case ARTIFACT_RESOURCE:
|
|
case RESOURCE_ARTIFACT:
|
|
return (subID == 2 || subID == 5 || subID == 8) && vstd::contains(builtBuildings, 17);//artifact merchants
|
|
case CREATURE_RESOURCE:
|
|
return subID == 6 && vstd::contains(builtBuildings, 21); //Freelancer's guild
|
|
case CREATURE_UNDEAD:
|
|
return subID == 4 && vstd::contains(builtBuildings, 22);//Skeleton transformer
|
|
case RESOURCE_SKILL:
|
|
return subID == 8 && vstd::contains(builtBuildings, 21);//Magic University
|
|
default:
|
|
assert(0);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
std::vector<int> CGTownInstance::availableItemsIds(EMarketMode mode) const
|
|
{
|
|
if(mode == RESOURCE_ARTIFACT)
|
|
{
|
|
std::vector<int> ret;
|
|
BOOST_FOREACH(const CArtifact *a, merchantArtifacts)
|
|
if(a)
|
|
ret.push_back(a->id);
|
|
else
|
|
ret.push_back(-1);
|
|
return ret;
|
|
}
|
|
else if ( mode == RESOURCE_SKILL )
|
|
{
|
|
return universitySkills;
|
|
}
|
|
else
|
|
return IMarket::availableItemsIds(mode);
|
|
}
|
|
|
|
std::string CGTownInstance::nodeName() const
|
|
{
|
|
return "Town (" + (town ? town->Name() : "unknown") + ") of " + name;
|
|
}
|
|
|
|
void CGTownInstance::deserializationFix()
|
|
{
|
|
attachTo(&townAndVis);
|
|
if(visitingHero)
|
|
visitingHero->attachTo(&townAndVis);
|
|
if(garrisonHero)
|
|
garrisonHero->attachTo(this);
|
|
}
|
|
|
|
void CGTownInstance::recreateBuildingsBonuses()
|
|
{
|
|
BonusList bl;
|
|
exportedBonuses.getBonuses(bl, Selector::sourceType(Bonus::TOWN_STRUCTURE));
|
|
BOOST_FOREACH(Bonus *b, bl)
|
|
removeBonus(b);
|
|
|
|
|
|
if(subID != 0 || !addBonusIfBuilt(22, Bonus::MORALE, +2)) //tricky! -> checks tavern only if no bratherhood of sword or not a castle
|
|
addBonusIfBuilt(5, Bonus::MORALE, +1);
|
|
|
|
if(subID == 0) //castle
|
|
{
|
|
addBonusIfBuilt(17, Bonus::SEA_MOVEMENT, +500, new CPropagatorNodeType(PLAYER)); //lighthouses
|
|
addBonusIfBuilt(26, Bonus::MORALE, +2, new CPropagatorNodeType(PLAYER)); //colossus
|
|
}
|
|
else if(subID == 1) //rampart
|
|
{
|
|
addBonusIfBuilt(21, Bonus::LUCK, +2); //fountain of fortune
|
|
addBonusIfBuilt(21, Bonus::LUCK, +2, new CPropagatorNodeType(PLAYER)); //guardian spirit
|
|
}
|
|
else if(subID == 2) //tower
|
|
{
|
|
addBonusIfBuilt(26, Bonus::PRIMARY_SKILL, +15, PrimarySkill::KNOWLEDGE); //grail
|
|
}
|
|
else if(subID == 3) //Inferno
|
|
{
|
|
addBonusIfBuilt(21, Bonus::PRIMARY_SKILL, +2, PrimarySkill::SPELL_POWER); //Brimstone Clouds
|
|
}
|
|
else if(subID == 4) //necropolis
|
|
{
|
|
addBonusIfBuilt(17, Bonus::DARKNESS, +20);
|
|
addBonusIfBuilt(21, Bonus::SECONDARY_SKILL_PREMY, +10, new CPropagatorNodeType(PLAYER), CGHeroInstance::NECROMANCY); //necromancy amplifier
|
|
addBonusIfBuilt(26, Bonus::SECONDARY_SKILL_PREMY, +20, new CPropagatorNodeType(PLAYER), CGHeroInstance::NECROMANCY); //Soul prison
|
|
}
|
|
else if(subID == 5) //Dungeon
|
|
{
|
|
addBonusIfBuilt(26, Bonus::PRIMARY_SKILL, +12, PrimarySkill::SPELL_POWER); //grail
|
|
}
|
|
else if(subID == 6) //Stronghold
|
|
{
|
|
addBonusIfBuilt(26, Bonus::PRIMARY_SKILL, +20, PrimarySkill::ATTACK); //grail
|
|
}
|
|
else if(subID == 7) //Fortress
|
|
{
|
|
addBonusIfBuilt(21, Bonus::PRIMARY_SKILL, +2, PrimarySkill::DEFENSE); //Glyphs of Fear
|
|
addBonusIfBuilt(22, Bonus::PRIMARY_SKILL, +2, PrimarySkill::ATTACK); //Blood Obelisk
|
|
addBonusIfBuilt(26, Bonus::PRIMARY_SKILL, +10, PrimarySkill::ATTACK); //grail
|
|
addBonusIfBuilt(26, Bonus::PRIMARY_SKILL, +10, PrimarySkill::DEFENSE); //grail
|
|
}
|
|
else if(subID == 8)
|
|
{
|
|
|
|
}
|
|
}
|
|
|
|
bool CGTownInstance::addBonusIfBuilt(int building, int type, int val, int subtype /*= -1*/)
|
|
{
|
|
return addBonusIfBuilt(building, type, val, NULL, subtype);
|
|
}
|
|
|
|
bool CGTownInstance::addBonusIfBuilt(int building, int type, int val, IPropagator *prop, int subtype /*= -1*/)
|
|
{
|
|
if(vstd::contains(builtBuildings, building))
|
|
{
|
|
std::ostringstream descr;
|
|
descr << VLC->generaltexth->buildings[subID][building].first << " ";
|
|
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 = NULL;
|
|
visitingHero->detachFrom(&townAndVis);
|
|
visitingHero->attachTo(p);
|
|
visitingHero = NULL;
|
|
}
|
|
}
|
|
|
|
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 = NULL;
|
|
garrisonHero->inTownGarrison = false;
|
|
garrisonHero->detachFrom(this);
|
|
garrisonHero->attachTo(p);
|
|
garrisonHero = NULL;
|
|
}
|
|
}
|
|
|
|
bool CGTownInstance::armedGarrison() const
|
|
{
|
|
return stacksCount() || garrisonHero;
|
|
}
|
|
|
|
CBonusSystemNode * CGTownInstance::whatShouldBeAttached()
|
|
{
|
|
return &townAndVis;
|
|
}
|
|
|
|
const CArmedInstance * CGTownInstance::getUpperArmy() const
|
|
{
|
|
if(garrisonHero)
|
|
return garrisonHero;
|
|
return this;
|
|
}
|
|
|
|
void CGVisitableOPH::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
if(visitors.find(h->id)==visitors.end())
|
|
{
|
|
onNAHeroVisit(h->id, false);
|
|
switch(ID)
|
|
{
|
|
case 102: //tree
|
|
case 4: //arena
|
|
case 41://library
|
|
case 47: //School of Magic
|
|
case 107://School of War
|
|
break;
|
|
default:
|
|
cb->setObjProperty(id, ObjProperty::VISITORS, h->id); //add to the visitors
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
onNAHeroVisit(h->id, true);
|
|
}
|
|
}
|
|
|
|
void CGVisitableOPH::initObj()
|
|
{
|
|
if(ID==102)
|
|
ttype = ran()%3;
|
|
else
|
|
ttype = -1;
|
|
}
|
|
|
|
void CGVisitableOPH::treeSelected( int heroID, int resType, int resVal, expType expVal, ui32 result ) const
|
|
{
|
|
if(result) //player agreed to give res for exp
|
|
{
|
|
cb->giveResource(cb->getOwner(heroID),resType,-resVal); //take resource
|
|
cb->changePrimSkill(heroID,4,expVal); //give exp
|
|
cb->setObjProperty(id, ObjProperty::VISITORS, heroID); //add to the visitors
|
|
}
|
|
}
|
|
void CGVisitableOPH::onNAHeroVisit(int heroID, bool alreadyVisited) const
|
|
{
|
|
int id=0, subid=0, ot=0, sound = 0;
|
|
expType val=1;
|
|
switch(ID)
|
|
{
|
|
case 4: //arena
|
|
sound = soundBase::NOMAD;
|
|
ot = 0;
|
|
break;
|
|
case 51: //mercenary camp
|
|
sound = soundBase::NOMAD;
|
|
subid=0;
|
|
ot=80;
|
|
break;
|
|
case 23: //marletto tower
|
|
sound = soundBase::NOMAD;
|
|
subid=1;
|
|
ot=39;
|
|
break;
|
|
case 61:
|
|
sound = soundBase::gazebo;
|
|
subid=2;
|
|
ot=100;
|
|
break;
|
|
case 32:
|
|
sound = soundBase::GETPROTECTION;
|
|
subid=3;
|
|
ot=59;
|
|
break;
|
|
case 100:
|
|
sound = soundBase::gazebo;
|
|
id=5;
|
|
ot=143;
|
|
val=1000;
|
|
break;
|
|
case 102:
|
|
sound = soundBase::gazebo;
|
|
id = 5;
|
|
subid = 1;
|
|
ot = 146;
|
|
val = 1;
|
|
break;
|
|
case 41:
|
|
sound = soundBase::gazebo;
|
|
ot = 66;
|
|
break;
|
|
case 47: //School of Magic
|
|
sound = soundBase::faerie;
|
|
ot = 71;
|
|
break;
|
|
case 107://School of War
|
|
sound = soundBase::MILITARY;
|
|
ot = 158;
|
|
break;
|
|
}
|
|
if (!alreadyVisited)
|
|
{
|
|
switch (ID)
|
|
{
|
|
case 4: //arena
|
|
{
|
|
BlockingDialog sd(false,true);
|
|
sd.soundID = sound;
|
|
sd.text << std::pair<ui8,ui32>(11,ot);
|
|
sd.components.push_back(Component(0,0,2,0));
|
|
sd.components.push_back(Component(0,1,2,0));
|
|
sd.player = cb->getOwner(heroID);
|
|
cb->showBlockingDialog(&sd,boost::bind(&CGVisitableOPH::arenaSelected,this,heroID,_1));
|
|
return;
|
|
}
|
|
case 51:
|
|
case 23:
|
|
case 61:
|
|
case 32:
|
|
{
|
|
cb->changePrimSkill(heroID,subid,val);
|
|
InfoWindow iw;
|
|
iw.soundID = sound;
|
|
iw.components.push_back(Component(0,subid,val,0));
|
|
iw.text << std::pair<ui8,ui32>(11,ot);
|
|
iw.player = cb->getOwner(heroID);
|
|
cb->showInfoDialog(&iw);
|
|
break;
|
|
}
|
|
case 100: //give exp
|
|
{
|
|
const CGHeroInstance *h = cb->getHero(heroID);
|
|
val = h->calculateXp(val);
|
|
InfoWindow iw;
|
|
iw.soundID = sound;
|
|
iw.components.push_back(Component(id,subid,val,0));
|
|
iw.player = cb->getOwner(heroID);
|
|
iw.text << std::pair<ui8,ui32>(11,ot);
|
|
iw.soundID = soundBase::gazebo;
|
|
cb->showInfoDialog(&iw);
|
|
cb->changePrimSkill(heroID,4,val);
|
|
break;
|
|
}
|
|
case 102://tree
|
|
{
|
|
const CGHeroInstance *h = cb->getHero(heroID);
|
|
val = VLC->heroh->reqExp(h->level+val) - VLC->heroh->reqExp(h->level);
|
|
if(!ttype)
|
|
{
|
|
cb->setObjProperty(this->id, ObjProperty::VISITORS, heroID); //add to the visitors
|
|
InfoWindow iw;
|
|
iw.soundID = sound;
|
|
iw.components.push_back(Component(id,subid,1,0));
|
|
iw.player = cb->getOwner(heroID);
|
|
iw.text << std::pair<ui8,ui32>(11,148);
|
|
cb->showInfoDialog(&iw);
|
|
cb->changePrimSkill(heroID,4,val);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
ui32 res;
|
|
expType resval;
|
|
if(ttype==1)
|
|
{
|
|
res = 6;
|
|
resval = 2000;
|
|
ot = 149;
|
|
}
|
|
else
|
|
{
|
|
res = 5;
|
|
resval = 10;
|
|
ot = 151;
|
|
}
|
|
|
|
if(cb->getResource(h->tempOwner,res) < resval) //not enough resources
|
|
{
|
|
ot++;
|
|
InfoWindow iw;
|
|
iw.soundID = sound;
|
|
iw.player = h->tempOwner;
|
|
iw.text << std::pair<ui8,ui32>(11,ot);
|
|
cb->showInfoDialog(&iw);
|
|
return;
|
|
}
|
|
|
|
BlockingDialog sd (true, false);
|
|
sd.soundID = sound;
|
|
sd.player = cb->getOwner(heroID);
|
|
sd.text << std::pair<ui8,ui32>(11,ot);
|
|
sd.components.push_back (Component (Component::RESOURCE, res, resval, 0));
|
|
cb->showBlockingDialog(&sd,boost::bind(&CGVisitableOPH::treeSelected,this,heroID,res,resval,val,_1));
|
|
}
|
|
break;
|
|
}
|
|
case 41://library of enlightenment
|
|
{
|
|
const CGHeroInstance *h = cb->getHero(heroID);
|
|
if(h->level < 10 - 2*h->getSecSkillLevel(CGHeroInstance::DIPLOMACY)) //not enough level
|
|
{
|
|
InfoWindow iw;
|
|
iw.soundID = sound;
|
|
iw.player = cb->getOwner(heroID);
|
|
iw.text << std::pair<ui8,ui32>(11,68);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
else
|
|
{
|
|
cb->setObjProperty(this->id, ObjProperty::VISITORS, heroID); //add to the visitors
|
|
cb->changePrimSkill(heroID,0,2);
|
|
cb->changePrimSkill(heroID,1,2);
|
|
cb->changePrimSkill(heroID,2,2);
|
|
cb->changePrimSkill(heroID,3,2);
|
|
InfoWindow iw;
|
|
iw.soundID = sound;
|
|
iw.player = cb->getOwner(heroID);
|
|
iw.text << std::pair<ui8,ui32>(11,66);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
break;
|
|
}
|
|
case 47: //School of Magic
|
|
case 107://School of War
|
|
{
|
|
int skill = (ID==47 ? 2 : 0);
|
|
if(cb->getResource(cb->getOwner(heroID),6) < 1000) //not enough resources
|
|
{
|
|
InfoWindow iw;
|
|
iw.soundID = sound;
|
|
iw.player = cb->getOwner(heroID);
|
|
iw.text << std::pair<ui8,ui32>(MetaString::ADVOB_TXT,ot+2);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
else
|
|
{
|
|
BlockingDialog sd(true,true);
|
|
sd.soundID = sound;
|
|
sd.player = cb->getOwner(heroID);
|
|
sd.text << std::pair<ui8,ui32>(11,ot);
|
|
sd.components.push_back(Component(Component::PRIM_SKILL, skill, +1, 0));
|
|
sd.components.push_back(Component(Component::PRIM_SKILL, skill+1, +1, 0));
|
|
cb->showBlockingDialog(&sd,boost::bind(&CGVisitableOPH::schoolSelected,this,heroID,_1));
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
ot++;
|
|
InfoWindow iw;
|
|
iw.soundID = sound;
|
|
iw.player = cb->getOwner(heroID);
|
|
iw.text << std::pair<ui8,ui32>(11,ot);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
}
|
|
|
|
const std::string & CGVisitableOPH::getHoverText() const
|
|
{
|
|
int pom = -1;
|
|
switch(ID)
|
|
{
|
|
case 4:
|
|
pom = -1;
|
|
break;
|
|
case 51:
|
|
pom = 8;
|
|
break;
|
|
case 23:
|
|
pom = 7;
|
|
break;
|
|
case 61:
|
|
pom = 11;
|
|
break;
|
|
case 32:
|
|
pom = 4;
|
|
break;
|
|
case 100:
|
|
pom = 5;
|
|
break;
|
|
case 102:
|
|
pom = 18;
|
|
break;
|
|
case 41:
|
|
break;
|
|
case 47: //School of Magic
|
|
pom = 9;
|
|
break;
|
|
case 107://School of War
|
|
pom = 10;
|
|
break;
|
|
default:
|
|
throw std::string("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";
|
|
hoverName += (vstd::contains(visitors,h->id))
|
|
? (VLC->generaltexth->allTexts[352]) //visited
|
|
: ( VLC->generaltexth->allTexts[353]); //not visited
|
|
}
|
|
return hoverName;
|
|
}
|
|
|
|
void CGVisitableOPH::arenaSelected( int heroID, int primSkill ) const
|
|
{
|
|
cb->setObjProperty(id, ObjProperty::VISITORS, heroID); //add to the visitors
|
|
cb->changePrimSkill(heroID,primSkill-1,2);
|
|
}
|
|
|
|
void CGVisitableOPH::setPropertyDer( ui8 what, ui32 val )
|
|
{
|
|
if(what == ObjProperty::VISITORS)
|
|
visitors.insert(val);
|
|
}
|
|
|
|
void CGVisitableOPH::schoolSelected(int heroID, ui32 which) const
|
|
{
|
|
if(!which) //player refused to pay
|
|
return;
|
|
|
|
int base = (ID == 47 ? 2 : 0);
|
|
cb->setObjProperty(id, ObjProperty::VISITORS, heroID); //add to the visitors
|
|
cb->giveResource(cb->getOwner(heroID),6,-1000); //take 1000 gold
|
|
cb->changePrimSkill(heroID, base + which-1, +1); //give appropriate skill
|
|
}
|
|
|
|
COPWBonus::COPWBonus (int index, CGTownInstance *TOWN)
|
|
{
|
|
ID = index;
|
|
town = TOWN;
|
|
id = town->bonusingBuildings.size();
|
|
}
|
|
void COPWBonus::setProperty(ui8 what, ui32 val)
|
|
{
|
|
switch (what)
|
|
{
|
|
case 4:
|
|
visitors.insert(val);
|
|
break;
|
|
case 12:
|
|
visitors.clear();
|
|
break;
|
|
}
|
|
}
|
|
void COPWBonus::onHeroVisit (const CGHeroInstance * h) const
|
|
{
|
|
int heroID = h->id;
|
|
if (town->builtBuildings.find(ID) != town->builtBuildings.end())
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
switch (town->subID)
|
|
{
|
|
case 0: //Stables
|
|
if (!h->hasBonusFrom(Bonus::OBJECT, 94)) //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;
|
|
cb->giveHeroBonus(&gb);
|
|
iw.text << VLC->generaltexth->allTexts[580];
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
break;
|
|
case 5: //Mana Vortex
|
|
if (visitors.empty() && h->mana <= h->manaLimit() * 2)
|
|
{
|
|
cb->setManaPoints (heroID, 2 * h->manaLimit());
|
|
cb->setObjProperty (id, ObjProperty::VISITED, true);
|
|
iw.text << VLC->generaltexth->allTexts[579];
|
|
cb->showInfoDialog(&iw);
|
|
cb->setObjProperty (town->id, 11, id); //add to visitors
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
CTownBonus::CTownBonus (int index, CGTownInstance *TOWN)
|
|
{
|
|
ID = index;
|
|
town = TOWN;
|
|
id = town->bonusingBuildings.size();
|
|
}
|
|
void CTownBonus::setProperty (ui8 what, ui32 val)
|
|
{
|
|
if(what == 4)
|
|
visitors.insert(val);
|
|
}
|
|
void CTownBonus::onHeroVisit (const CGHeroInstance * h) const
|
|
{
|
|
int heroID = h->id;
|
|
if ((town->builtBuildings.find(ID) != town->builtBuildings.end()) && (visitors.find(heroID) == visitors.end()))
|
|
{
|
|
InfoWindow iw;
|
|
int what, val, mid;
|
|
switch (ID)
|
|
{
|
|
case 23:
|
|
switch(town->subID)
|
|
{
|
|
case 2: //wall
|
|
what = 3;
|
|
val = 1;
|
|
mid = 581;
|
|
iw.components.push_back (Component(Component::PRIM_SKILL, 3, 1, 0));
|
|
break;
|
|
case 3: //order of fire
|
|
what = 2;
|
|
val = 1;
|
|
mid = 582;
|
|
iw.components.push_back (Component(Component::PRIM_SKILL, 2, 1, 0));
|
|
break;
|
|
case 6://hall of valhalla
|
|
what = 0;
|
|
val = 1;
|
|
mid = 584;
|
|
iw.components.push_back (Component(Component::PRIM_SKILL, 0, 1, 0));
|
|
break;
|
|
case 5://academy of battle scholars
|
|
what = 4;
|
|
val = 1000*(100+h->getSecSkillLevel(CGHeroInstance::LEARNING)*5)/100.0f;
|
|
mid = 583;
|
|
iw.components.push_back (Component(Component::EXPERIENCE, 0, val, 0));
|
|
break;
|
|
}
|
|
break;
|
|
case 17:
|
|
switch(town->subID)
|
|
{
|
|
case 7: //cage of warlords
|
|
what = 1;
|
|
val = 1;
|
|
mid = 585;
|
|
iw.components.push_back (Component(Component::PRIM_SKILL, 1, 1, 0));
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
iw.player = cb->getOwner(heroID);
|
|
iw.text << VLC->generaltexth->allTexts[mid];
|
|
cb->showInfoDialog(&iw);
|
|
cb->changePrimSkill (heroID, what, val);
|
|
if (town->visitingHero == h)
|
|
cb->setObjProperty (town->id, 11, id); //add to visitors
|
|
else
|
|
cb->setObjProperty (town->id, 13, id); //then it must be garrisoned hero
|
|
}
|
|
}
|
|
const std::string & CGCreature::getHoverText() const
|
|
{
|
|
MetaString ms;
|
|
int pom = stacks.begin()->second->getQuantityID();
|
|
pom = 174 + 3*pom + 1;
|
|
ms << std::pair<ui8,ui32>(6,pom) << " " << std::pair<ui8,ui32>(7,subID);
|
|
ms.toString(hoverName);
|
|
|
|
if(const CGHeroInstance *selHero = cb->getSelectedHero(cb->getCurrentPlayer()))
|
|
{
|
|
std::vector<std::string> * texts = &VLC->generaltexth->threat;
|
|
hoverName += "\n\n ";
|
|
hoverName += (*texts)[0];
|
|
int choice;
|
|
float ratio = ((float)getArmyStrength() / selHero->getTotalStrength());
|
|
if (ratio < 0.1) choice = 1;
|
|
else if (ratio < 0.25) choice = 2;
|
|
else if (ratio < 0.6) choice = 3;
|
|
else if (ratio < 0.9) choice = 4;
|
|
else if (ratio < 1.1) choice = 5;
|
|
else if (ratio < 1.3) choice = 6;
|
|
else if (ratio < 1.8) choice = 7;
|
|
else if (ratio < 2.5) choice = 8;
|
|
else if (ratio < 4) choice = 9;
|
|
else if (ratio < 8) choice = 10;
|
|
else if (ratio < 20) choice = 11;
|
|
else choice = 12;
|
|
hoverName += (*texts)[choice];
|
|
}
|
|
return hoverName;
|
|
}
|
|
void CGCreature::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
int action = takenAction(h);
|
|
switch( action ) //decide what we do...
|
|
{
|
|
case -2: //fight
|
|
fight(h);
|
|
break;
|
|
case -1: //flee
|
|
{
|
|
flee(h);
|
|
break;
|
|
}
|
|
case 0: //join for free
|
|
{
|
|
BlockingDialog ynd(true,false);
|
|
ynd.player = h->tempOwner;
|
|
ynd.text << std::pair<ui8,ui32>(MetaString::ADVOB_TXT, 86);
|
|
ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID);
|
|
cb->showBlockingDialog(&ynd,boost::bind(&CGCreature::joinDecision,this,h,0,_1));
|
|
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<std::string>(getStackCount(0)));
|
|
boost::algorithm::replace_first(tmp,"%d",boost::lexical_cast<std::string>(action));
|
|
boost::algorithm::replace_first(tmp,"%s",VLC->creh->creatures[subID]->namePl);
|
|
ynd.text << tmp;
|
|
cb->showBlockingDialog(&ynd,boost::bind(&CGCreature::joinDecision,this,h,action,_1));
|
|
break;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
void CGCreature::endBattle( BattleResult *result ) const
|
|
{
|
|
if(result->winner==0)
|
|
{
|
|
cb->removeObject(id);
|
|
}
|
|
else
|
|
{
|
|
//int killedAmount=0;
|
|
//for(std::set<std::pair<ui32,si32> >::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<ui8,ui32>(6,pom) << " " << std::pair<ui8,ui32>(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[restore.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
|
|
}
|
|
}
|
|
while (stacks.size() > 1) //hopefully that's enough
|
|
{
|
|
i = stacks.end();
|
|
i--;
|
|
TSlot 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGCreature::initObj()
|
|
{
|
|
blockVisit = true;
|
|
switch(character)
|
|
{
|
|
case 0:
|
|
character = 0;
|
|
break;
|
|
case 1:
|
|
character = 1 + ran()%7;
|
|
break;
|
|
case 2:
|
|
character = 1 + ran()%10;
|
|
break;
|
|
case 3:
|
|
character = 4 + ran()%7;
|
|
break;
|
|
case 4:
|
|
character = 10;
|
|
break;
|
|
}
|
|
|
|
stacks[0]->setType(subID);
|
|
TQuantity &amount = stacks[0]->count;
|
|
CCreature &c = *VLC->creh->creatures[subID];
|
|
if(!amount)
|
|
{
|
|
if(c.ammMax == c.ammMin)
|
|
amount = c.ammMax;
|
|
else
|
|
amount = c.ammMin + (ran() % (c.ammMax - c.ammMin));
|
|
}
|
|
|
|
temppower = stacks[0]->count * 1000;
|
|
}
|
|
void CGCreature::newTurn() const
|
|
{//Works only for stacks of single type of size up to 2 millions
|
|
if (stacks.begin()->second->count < CREEP_SIZE && cb->getDate(1) == 1 && cb->getDate(0) > 1)
|
|
{
|
|
ui32 power = temppower * (100 + WEEKLY_GROWTH)/100;
|
|
cb->setObjProperty(id, 10, std::min (power/1000 , (ui32)CREEP_SIZE)); //set new amount
|
|
cb->setObjProperty(id, 11, power); //increase temppower
|
|
}
|
|
if (STACK_EXP)
|
|
cb->setObjProperty(id, 12, 10000); //for testing purpose
|
|
}
|
|
void CGCreature::setPropertyDer(ui8 what, ui32 val)
|
|
{
|
|
switch (what)
|
|
{
|
|
case 10:
|
|
stacks[0]->count = val;
|
|
break;
|
|
case 11:
|
|
temppower = val;
|
|
break;
|
|
case 12:
|
|
giveStackExp(val);
|
|
break;
|
|
case 13:
|
|
restore.basicType = val;
|
|
break;
|
|
}
|
|
}
|
|
|
|
int CGCreature::takenAction(const CGHeroInstance *h, bool allowJoin) const
|
|
{
|
|
double hlp = h->getTotalStrength() / getArmyStrength();
|
|
|
|
if(!character) //compliant creatures will always join
|
|
return 0;
|
|
else if(allowJoin)//test for joining
|
|
{
|
|
int factor;
|
|
if(hlp >= 7)
|
|
factor = 11;
|
|
else if(hlp >= 1)
|
|
factor = (int)(2*(hlp-1));
|
|
else if(hlp >= 0.5)
|
|
factor = -1;
|
|
else if(hlp >= 0.333)
|
|
factor = -2;
|
|
else
|
|
factor = -3;
|
|
|
|
int sympathy = 0;
|
|
|
|
std::set<ui32> myKindCres; //what creatures are the same kind as we
|
|
myKindCres.insert(subID); //we
|
|
myKindCres.insert(VLC->creh->creatures[subID]->upgrades.begin(),VLC->creh->creatures[subID]->upgrades.end()); //our upgrades
|
|
for(std::vector<ConstTransitivePtr<CCreature> >::iterator i=VLC->creh->creatures.begin(); i!=VLC->creh->creatures.end(); i++)
|
|
if(vstd::contains((*i)->upgrades, (ui32) id)) //it's our base creatures
|
|
myKindCres.insert((*i)->idNumber);
|
|
|
|
int count = 0, //how many creatures of our kind has hero
|
|
totalCount = 0;
|
|
|
|
for (TSlots::const_iterator i = h->Slots().begin(); i != h->Slots().end(); i++)
|
|
{
|
|
if(vstd::contains(myKindCres,i->second->type->idNumber))
|
|
count += i->second->count;
|
|
totalCount += i->second->count;
|
|
}
|
|
|
|
if(count*2 > totalCount)
|
|
sympathy++;
|
|
if(count)
|
|
sympathy++;
|
|
|
|
|
|
int charisma = factor + h->getSecSkillLevel(CGHeroInstance::DIPLOMACY) + sympathy;
|
|
if(charisma >= character) //creatures might join...
|
|
{
|
|
if(h->getSecSkillLevel(CGHeroInstance::DIPLOMACY) + sympathy + 1 >= character)
|
|
return 0; //join for free
|
|
else if(h->getSecSkillLevel(CGHeroInstance::DIPLOMACY) * 2 + sympathy + 1 >= character)
|
|
return VLC->creh->creatures[subID]->cost[6] * getStackCount(0); //join for gold
|
|
}
|
|
}
|
|
|
|
//we are still here - creatures not joined heroes, test for fleeing
|
|
|
|
//TODO: it's provisional formula, should be replaced with original one (or something closer to it)
|
|
//TODO: should be deterministic (will be needed for Vision spell)
|
|
int hlp2 = (int) (hlp - 2)*1000;
|
|
if(!neverFlees
|
|
&& hlp2 >= 0
|
|
&& rand()%2000 < hlp2
|
|
)
|
|
return -1; //flee
|
|
else
|
|
return -2; //fight
|
|
|
|
}
|
|
|
|
void CGCreature::fleeDecision(const CGHeroInstance *h, ui32 pursue) const
|
|
{
|
|
if(pursue)
|
|
{
|
|
fight(h);
|
|
}
|
|
else
|
|
{
|
|
cb->removeObject(id);
|
|
}
|
|
}
|
|
|
|
void CGCreature::joinDecision(const CGHeroInstance *h, int cost, ui32 accept) const
|
|
{
|
|
if(!accept)
|
|
{
|
|
if(takenAction(h,false) == -1) //they flee
|
|
{
|
|
flee(h);
|
|
}
|
|
else //they fight
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.text << std::pair<ui8,ui32>(11,87); //Insulted by your refusal of their offer, the monsters attack!
|
|
cb->showInfoDialog(&iw);
|
|
fight(h);
|
|
}
|
|
}
|
|
else //accepted
|
|
{
|
|
if (cb->getResource(h->tempOwner,6) < cost) //player don't have enough gold!
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.text << std::pair<ui8,ui32>(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,6,-cost);
|
|
|
|
cb->tryJoiningArmy(this, h, true, false);
|
|
}
|
|
}
|
|
|
|
void CGCreature::fight( const CGHeroInstance *h ) const
|
|
{
|
|
//split stacks
|
|
int totalCount; //TODO: multiple creature types in a stack?
|
|
int basicType = stacks.begin()->second->type->idNumber;
|
|
cb->setObjProperty(id, 13, basicType); //store info about creature stack
|
|
|
|
float relativePower = ((float)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;
|
|
}
|
|
int stackSize;
|
|
TSlot sourceSlot = stacks.begin()->first;
|
|
TSlot destSlot;
|
|
for (int stacksLeft = stacksCount; stacksLeft > 1; --stacksLeft)
|
|
{
|
|
stackSize = stacks.begin()->second->count / stacksLeft;
|
|
if (stackSize)
|
|
{
|
|
if ((destSlot = getFreeSlot()) > -1)
|
|
cb->moveStack(StackLocation(this, sourceSlot), StackLocation(this, destSlot), stackSize);
|
|
else
|
|
{
|
|
tlog2 <<"Warning! Not enough empty slots to split stack!";
|
|
break;
|
|
}
|
|
}
|
|
else break;
|
|
}
|
|
if (stacksCount > 1)
|
|
{
|
|
if (rand()%100 < 50) //upgrade
|
|
{
|
|
TSlot slotId = (stacks.size() / 2);
|
|
if(ui32 upgradesSize = getStack(slotId).type->upgrades.size())
|
|
{
|
|
std::set<TCreature>::const_iterator it = getStack(slotId).type->upgrades.begin(); //pick random in case there are more
|
|
std::advance (it, rand() % upgradesSize);
|
|
cb->changeStackType(StackLocation(this, slotId), VLC->creh->creatures[*it]);
|
|
}
|
|
}
|
|
}
|
|
|
|
cb->startBattleI(h, this, boost::bind(&CGCreature::endBattle,this,_1));
|
|
|
|
}
|
|
|
|
void CGCreature::flee( const CGHeroInstance * h ) const
|
|
{
|
|
BlockingDialog ynd(true,false);
|
|
ynd.player = h->tempOwner;
|
|
ynd.text << std::pair<ui8,ui32>(11,91);
|
|
ynd.text.addReplacement(MetaString::CRE_PL_NAMES, subID);
|
|
cb->showBlockingDialog(&ynd,boost::bind(&CGCreature::fleeDecision,this,h,_1));
|
|
}
|
|
|
|
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,0);
|
|
return;
|
|
}
|
|
else if (relations == 1)//ally
|
|
return;
|
|
|
|
if(stacksCount()) //Mine is guarded
|
|
{
|
|
BlockingDialog ynd(true,false);
|
|
ynd.player = h->tempOwner;
|
|
ynd.text << std::pair<ui8,ui32>(MetaString::ADVOB_TXT, subID == 7 ? 84 : 187);
|
|
cb->showBlockingDialog(&ynd,boost::bind(&CGMine::fight, this, _1, h));
|
|
return;
|
|
}
|
|
|
|
flagMine(h->tempOwner);
|
|
|
|
}
|
|
|
|
void CGMine::newTurn() const
|
|
{
|
|
if(cb->getDate() == 1)
|
|
return;
|
|
|
|
if (tempOwner == NEUTRAL_PLAYER)
|
|
return;
|
|
|
|
cb->giveResource(tempOwner, producedResource, producedQuantity);
|
|
}
|
|
|
|
void CGMine::initObj()
|
|
{
|
|
if(subID >= 7) //Abandoned Mine
|
|
{
|
|
//set guardians
|
|
int howManyTroglodytes = 100 + ran()%100;
|
|
CStackInstance *troglodytes = new CStackInstance(70, howManyTroglodytes);
|
|
putStack(0, troglodytes);
|
|
|
|
//after map reading tempOwner placeholds bitmask for allowed resources
|
|
std::vector<int> possibleResources;
|
|
for (int i = 0; i < 8; i++)
|
|
if(tempOwner & 1<<i)
|
|
possibleResources.push_back(i);
|
|
|
|
assert(possibleResources.size());
|
|
producedResource = possibleResources[ran()%possibleResources.size()];
|
|
tempOwner = NEUTRAL_PLAYER;
|
|
hoverName = VLC->generaltexth->mines[7].first + "\n" + VLC->generaltexth->allTexts[202] + " " + troglodytes->getQuantityTXT(false) + " " + troglodytes->type->namePl;
|
|
}
|
|
else
|
|
{
|
|
producedResource = subID;
|
|
|
|
MetaString ms;
|
|
ms << std::pair<ui8,ui32>(9,producedResource);
|
|
if(tempOwner >= PLAYER_LIMIT)
|
|
tempOwner = NEUTRAL_PLAYER;
|
|
else
|
|
ms << " (" << std::pair<ui8,ui32>(6,23+tempOwner) << ")";
|
|
ms.toString(hoverName);
|
|
}
|
|
|
|
producedQuantity = defaultResProduction();
|
|
}
|
|
|
|
void CGMine::fight(ui32 agreed, const CGHeroInstance *h) const
|
|
{
|
|
cb->startBattleI(h, this, boost::bind(&CGMine::endBattle, this, _1, h->tempOwner));
|
|
}
|
|
|
|
void CGMine::endBattle(BattleResult *result, ui8 attackingPlayer) const
|
|
{
|
|
if(result->winner == 0) //attacker won
|
|
{
|
|
if(subID == 7)
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = attackingPlayer;
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, 85);
|
|
cb->showInfoDialog(&iw);
|
|
|
|
}
|
|
flagMine(attackingPlayer);
|
|
}
|
|
}
|
|
|
|
void CGMine::flagMine(ui8 player) const
|
|
{
|
|
assert(tempOwner != player);
|
|
cb->setOwner(id,player); //not ours? flag it!
|
|
|
|
MetaString ms;
|
|
ms << std::pair<ui8,ui32>(9,subID) << "\n(" << std::pair<ui8,ui32>(6,23+player) << ")";
|
|
if(subID == 7)
|
|
{
|
|
ms << "(%s)";
|
|
ms.addReplacement(MetaString::RES_NAMES, producedResource);
|
|
}
|
|
cb->setHoverName(id,&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(2,producedResource,producedQuantity,-1));
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
ui32 CGMine::defaultResProduction()
|
|
{
|
|
switch(producedResource)
|
|
{
|
|
case 0: //wood
|
|
case 2: //ore
|
|
return 2;
|
|
case 6: //gold
|
|
return 1000;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
void CGResource::initObj()
|
|
{
|
|
blockVisit = true;
|
|
hoverName = VLC->generaltexth->restypes[subID];
|
|
|
|
if(!amount)
|
|
{
|
|
switch(subID)
|
|
{
|
|
case 6:
|
|
amount = 500 + (rand()%6)*100;
|
|
break;
|
|
case 0: case 2:
|
|
amount = 6 + (rand()%5);
|
|
break;
|
|
default:
|
|
amount = 3 + (rand()%3);
|
|
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,boost::bind(&CGResource::fightForRes,this,_1,h));
|
|
}
|
|
else
|
|
{
|
|
fightForRes(1,h);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(message.length())
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.text << message;
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
collectRes(h->getOwner());
|
|
}
|
|
}
|
|
|
|
void CGResource::collectRes( int player ) const
|
|
{
|
|
cb->giveResource(player,subID,amount);
|
|
ShowInInfobox sii;
|
|
sii.player = player;
|
|
sii.c = Component(2,subID,amount,0);
|
|
sii.text << std::pair<ui8,ui32>(11,113);
|
|
sii.text.addReplacement(MetaString::RES_NAMES, subID);
|
|
cb->showCompInfo(&sii);
|
|
cb->removeObject(id);
|
|
}
|
|
|
|
void CGResource::fightForRes(ui32 agreed, const CGHeroInstance *h) const
|
|
{
|
|
if(agreed)
|
|
cb->startBattleI(h, this, boost::bind(&CGResource::endBattle,this,_1,h));
|
|
}
|
|
|
|
void CGResource::endBattle( BattleResult *result, const CGHeroInstance *h ) const
|
|
{
|
|
if(result->winner == 0) //attacker won
|
|
collectRes(h->getOwner());
|
|
}
|
|
|
|
void CGVisitableOPW::newTurn() const
|
|
{
|
|
if (cb->getDate(1) == 1) //first day of week = 1
|
|
{
|
|
cb->setObjProperty(id, ObjProperty::VISITED, false);
|
|
MetaString ms; //set text to "not visited"
|
|
ms << std::pair<ui8,ui32>(3,ID) << " " << std::pair<ui8,ui32>(1,353);
|
|
cb->setHoverName(id,&ms);
|
|
}
|
|
}
|
|
|
|
void CGVisitableOPW::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
int mid, sound = 0;
|
|
switch (ID)
|
|
{
|
|
case 55: //mystical garden
|
|
sound = soundBase::experience;
|
|
mid = 92;
|
|
break;
|
|
case 112://windmill
|
|
sound = soundBase::GENIE;
|
|
mid = 170;
|
|
break;
|
|
case 109://waterwheel
|
|
sound = soundBase::GENIE;
|
|
mid = 164;
|
|
break;
|
|
}
|
|
if (visited)
|
|
{
|
|
if (ID!=112)
|
|
mid++;
|
|
else
|
|
mid--;
|
|
|
|
InfoWindow iw;
|
|
iw.soundID = sound;
|
|
iw.player = h->tempOwner;
|
|
iw.text << std::pair<ui8,ui32>(11,mid);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
else
|
|
{
|
|
int type, sub, val;
|
|
type = 2;
|
|
switch (ID)
|
|
{
|
|
case 55:
|
|
if (rand()%2)
|
|
{
|
|
sub = 5;
|
|
val = 5;
|
|
}
|
|
else
|
|
{
|
|
sub = 6;
|
|
val = 500;
|
|
}
|
|
break;
|
|
case 112:
|
|
mid = 170;
|
|
sub = (rand() % 5) + 1;
|
|
val = (rand() % 4) + 3;
|
|
break;
|
|
case 109:
|
|
mid = 164;
|
|
sub = 6;
|
|
if(cb->getDate(0)<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 << std::pair<ui8,ui32>(11,mid);
|
|
cb->showInfoDialog(&iw);
|
|
cb->setObjProperty(id, ObjProperty::VISITED, true);
|
|
MetaString ms; //set text to "visited"
|
|
ms << std::pair<ui8,ui32>(3,ID) << " " << std::pair<ui8,ui32>(1,352);
|
|
cb->setHoverName(id,&ms);
|
|
}
|
|
}
|
|
|
|
void CGVisitableOPW::setPropertyDer( ui8 what, ui32 val )
|
|
{
|
|
if(what == ObjProperty::VISITED)
|
|
visited = val;
|
|
}
|
|
|
|
void CGTeleport::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
int destinationid=-1;
|
|
switch(ID)
|
|
{
|
|
case 43: //one way - find corresponding exit monolith
|
|
if(vstd::contains(objs,44) && vstd::contains(objs[44],subID) && objs[44][subID].size())
|
|
destinationid = objs[44][subID][rand()%objs[44][subID].size()];
|
|
else
|
|
tlog2 << "Cannot find corresponding exit monolith for "<< id << std::endl;
|
|
break;
|
|
case 45://two way monolith - pick any other one
|
|
case 111: //Whirlpool
|
|
if(vstd::contains(objs,ID) && vstd::contains(objs[ID],subID) && objs[ID][subID].size()>1)
|
|
{
|
|
while ((destinationid = objs[ID][subID][rand()%objs[ID][subID].size()]) == id); //choose another exit
|
|
if (ID == 111)
|
|
{
|
|
if (!h->hasBonusOfType(Bonus::WHIRLPOOL_PROTECTION))
|
|
{
|
|
if (h->Slots().size() > 1 || h->Slots().begin()->second->count > 1)
|
|
{ //we can't remove last unit
|
|
TSlot targetstack = h->Slots().begin()->first; //slot numbers may vary
|
|
for(TSlots::const_reverse_iterator 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;
|
|
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
|
|
tlog2 << "Cannot find corresponding exit monolith for "<< id << std::endl;
|
|
break;
|
|
case 103: //find nearest subterranean gate on the other level
|
|
{
|
|
int i=0;
|
|
for(; i < gates.size(); i++)
|
|
{
|
|
if(gates[i].first == id)
|
|
{
|
|
destinationid = gates[i].second;
|
|
break;
|
|
}
|
|
else if(gates[i].second == id)
|
|
{
|
|
destinationid = gates[i].first;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(destinationid < 0 || i == gates.size()) //no exit
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, 153);//Just inside the entrance you find a large pile of rubble blocking the tunnel. You leave discouraged.
|
|
cb->sendAndApply(&iw);
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if(destinationid < 0)
|
|
{
|
|
tlog2 << "Cannot find exit... (obj at " << pos << ") :( \n";
|
|
return;
|
|
}
|
|
if (ID == 111)
|
|
{
|
|
std::set<int3> tiles = cb->getObj(destinationid)->getBlockedPos();
|
|
std::set<int3>::iterator it = tiles.begin();
|
|
std::advance (it, rand() % tiles.size()); //picking random element of set is tiring
|
|
cb->moveHero (h->id, *it + 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 103://ignore subterranean gates subid
|
|
case 111:
|
|
{
|
|
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<const CGObjectInstance *> gatesSplit[2]; //surface and underground gates
|
|
for(size_t i = 0; i < objs[103][0].size(); i++)
|
|
{
|
|
const CGObjectInstance *hlp = cb->getObj(objs[103][0][i]);
|
|
gatesSplit[hlp->pos.z].push_back(hlp);
|
|
}
|
|
|
|
//sort by position
|
|
std::sort(gatesSplit[0].begin(), gatesSplit[0].end(), boost::bind(&CGObjectInstance::pos, _1) < boost::bind(&CGObjectInstance::pos, _2));
|
|
|
|
for(size_t i = 0; i < gatesSplit[0].size(); i++)
|
|
{
|
|
const CGObjectInstance *cur = gatesSplit[0][i];
|
|
|
|
//find nearest underground exit
|
|
std::pair<int,double> best(-1,150000); //pair<pos_in_vector, distance>
|
|
for(int j = 0; j < gatesSplit[1].size(); j++)
|
|
{
|
|
const CGObjectInstance *checked = gatesSplit[1][j];
|
|
if(!checked)
|
|
continue;
|
|
double hlp = checked->pos.dist2d(cur->pos);
|
|
if(hlp < best.second)
|
|
{
|
|
best.first = j;
|
|
best.second = hlp;
|
|
}
|
|
}
|
|
|
|
if(best.first >= 0) //found pair
|
|
{
|
|
gates.push_back(std::pair<int, int>(cur->id, gatesSplit[1][best.first]->id));
|
|
gatesSplit[1][best.first] = NULL;
|
|
}
|
|
else
|
|
{
|
|
gates.push_back(std::pair<int, int>(cur->id, -1));
|
|
}
|
|
}
|
|
objs.erase(103);
|
|
}
|
|
|
|
void CGArtifact::initObj()
|
|
{
|
|
blockVisit = true;
|
|
if(ID == 5)
|
|
{
|
|
hoverName = VLC->arth->artifacts[subID]->Name();
|
|
if(!storedArtifact->artType)
|
|
storedArtifact->setType(VLC->arth->artifacts[subID]);
|
|
}
|
|
if(ID == 93)
|
|
subID = 1;
|
|
|
|
assert(storedArtifact->artType);
|
|
assert(storedArtifact->parents.size());
|
|
}
|
|
|
|
void CGArtifact::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
if(!stacksCount())
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
switch(ID)
|
|
{
|
|
case 5:
|
|
{
|
|
iw.soundID = soundBase::treasure; //play sound only for non-scroll arts
|
|
iw.components.push_back(Component(4,subID,0,0));
|
|
if(message.length())
|
|
iw.text << message;
|
|
else
|
|
iw.text << std::pair<ui8,ui32>(12,subID);
|
|
}
|
|
break;
|
|
case 93:
|
|
{
|
|
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,boost::bind(&CGArtifact::fightForArt,this,_1,h));
|
|
}
|
|
else
|
|
{
|
|
fightForArt(0,h);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGArtifact::pick(const CGHeroInstance * h) const
|
|
{
|
|
cb->giveHeroArtifact(h, storedArtifact, -2);
|
|
cb->removeObject(id);
|
|
}
|
|
|
|
void CGArtifact::fightForArt( ui32 agreed, const CGHeroInstance *h ) const
|
|
{
|
|
if(agreed)
|
|
cb->startBattleI(h, this, boost::bind(&CGArtifact::endBattle,this,_1,h));
|
|
}
|
|
|
|
void CGArtifact::endBattle( BattleResult *result, const CGHeroInstance *h ) const
|
|
{
|
|
if(result->winner == 0) //attacker won
|
|
pick(h);
|
|
}
|
|
void CGPickable::initObj()
|
|
{
|
|
blockVisit = true;
|
|
switch(ID)
|
|
{
|
|
case 12: //campfire
|
|
val2 = (ran()%3) + 4; //4 - 6
|
|
val1 = val2 * 100;
|
|
type = ran()%6; //given resource
|
|
break;
|
|
case 29: //floatsam
|
|
switch(type = ran()%4)
|
|
{
|
|
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 82: //sea chest
|
|
{
|
|
int hlp = ran()%100;
|
|
if(hlp < 20)
|
|
{
|
|
val1 = 0;
|
|
type = 0;
|
|
}
|
|
else if(hlp < 90)
|
|
{
|
|
val1 = 1500;
|
|
type = 2;
|
|
}
|
|
else
|
|
{
|
|
val1 = 1000;
|
|
val2 = cb->getRandomArt (CArtifact::ART_TREASURE);
|
|
type = 1;
|
|
}
|
|
}
|
|
break;
|
|
case 86: //Shipwreck Survivor
|
|
{
|
|
int hlp = ran()%100;
|
|
if(hlp < 55)
|
|
val1 = cb->getRandomArt (CArtifact::ART_TREASURE);
|
|
else if(hlp < 75)
|
|
val1 = cb->getRandomArt (CArtifact::ART_MINOR);
|
|
else if(hlp < 95)
|
|
val1 = cb->getRandomArt (CArtifact::ART_MAJOR);
|
|
else
|
|
val1 = cb->getRandomArt (CArtifact::ART_RELIC);
|
|
}
|
|
break;
|
|
case 101: //treasure chest
|
|
{
|
|
int hlp = ran()%100;
|
|
if(hlp >= 95)
|
|
{
|
|
type = 1;
|
|
val1 = cb->getRandomArt (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 12: //campfire
|
|
{
|
|
cb->giveResource(h->tempOwner,type,val2); //non-gold resource
|
|
cb->giveResource(h->tempOwner,6,val1);//gold
|
|
InfoWindow iw;
|
|
iw.soundID = soundBase::experience;
|
|
iw.player = h->tempOwner;
|
|
iw.components.push_back(Component(2,6,val1,0));
|
|
iw.components.push_back(Component(2,type,val2,0));
|
|
iw.text << std::pair<ui8,ui32>(11,23);
|
|
cb->showInfoDialog(&iw);
|
|
break;
|
|
}
|
|
case 29: //flotsam
|
|
{
|
|
cb->giveResource(h->tempOwner,0,val1); //wood
|
|
cb->giveResource(h->tempOwner,6,val2);//gold
|
|
InfoWindow iw;
|
|
iw.soundID = soundBase::GENIE;
|
|
iw.player = h->tempOwner;
|
|
if(val1)
|
|
iw.components.push_back(Component(2,0,val1,0));
|
|
if(val2)
|
|
iw.components.push_back(Component(2,6,val2,0));
|
|
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, 51+type);
|
|
cb->showInfoDialog(&iw);
|
|
break;
|
|
}
|
|
case 82: //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(2,6,val1,0));
|
|
cb->giveResource(h->tempOwner,6,val1);
|
|
}
|
|
if(type == 1) //art
|
|
{
|
|
//TODO: what if no space in backpack?
|
|
iw.components.push_back(Component(4, val2, 1, 0));
|
|
iw.text.addReplacement(MetaString::ART_NAMES, val2);
|
|
cb->giveHeroNewArtifact(h, VLC->arth->artifacts[val2],-2);
|
|
}
|
|
cb->showInfoDialog(&iw);
|
|
break;
|
|
}
|
|
case 86: //Shipwreck Survivor
|
|
{
|
|
//TODO: what if no space in backpack?
|
|
InfoWindow iw;
|
|
iw.soundID = soundBase::experience;
|
|
iw.player = h->tempOwner;
|
|
iw.components.push_back(Component(4,val1,1,0));
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, 125);
|
|
iw.text.addReplacement(MetaString::ART_NAMES, val1);
|
|
cb->giveHeroNewArtifact(h, VLC->arth->artifacts[val1],-2);
|
|
cb->showInfoDialog(&iw);
|
|
break;
|
|
}
|
|
case 101: //treasure chest
|
|
{
|
|
if (subID) //not OH3 treasure chest
|
|
{
|
|
tlog2 << "Not supported WoG treasure chest!\n";
|
|
return;
|
|
}
|
|
|
|
if(type) //there is an artifact
|
|
{
|
|
cb->giveHeroNewArtifact(h, VLC->arth->artifacts[val1],-2);
|
|
InfoWindow iw;
|
|
iw.soundID = soundBase::treasure;
|
|
iw.player = h->tempOwner;
|
|
iw.components.push_back(Component(4,val1,1,0));
|
|
iw.text << std::pair<ui8,ui32>(11,145);
|
|
iw.text.addReplacement(MetaString::ART_NAMES, val1);
|
|
cb->showInfoDialog(&iw);
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
BlockingDialog sd(false,true);
|
|
sd.player = h->tempOwner;
|
|
sd.text << std::pair<ui8,ui32>(11,146);
|
|
sd.components.push_back(Component(2,6,val1,0));
|
|
expType expVal = h->calculateXp(val2);
|
|
sd.components.push_back(Component(5,0,expVal, 0));
|
|
sd.soundID = soundBase::chest;
|
|
boost::function<void(ui32)> fun = boost::bind(&CGPickable::chosen,this,_1,h->id);
|
|
cb->showBlockingDialog(&sd,fun);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
cb->removeObject(id);
|
|
}
|
|
|
|
void CGPickable::chosen( int which, int heroID ) const
|
|
{
|
|
const CGHeroInstance *h = cb->getHero(heroID);
|
|
switch(which)
|
|
{
|
|
case 1: //player pick gold
|
|
cb->giveResource(cb->getOwner(heroID),6,val1);
|
|
break;
|
|
case 2: //player pick exp
|
|
cb->changePrimSkill(heroID, 4, h->calculateXp(val2));
|
|
break;
|
|
default:
|
|
throw std::string("Unhandled treasure choice");
|
|
}
|
|
cb->removeObject(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 < 4; ++i)
|
|
{
|
|
if (h->getPrimSkillLevel(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 (int i = 0; i < m5arts.size(); ++i)
|
|
{
|
|
if (h->hasArt(m5arts[i]))
|
|
continue;
|
|
return false; //if the artifact was not found
|
|
}
|
|
return true;
|
|
case MISSION_ARMY:
|
|
{
|
|
std::vector<CStackBasicDescriptor>::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 (int i = 0; i < 7; ++i) //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)
|
|
return true;
|
|
return false;
|
|
case MISSION_PLAYER:
|
|
if (m13489val == h->getOwner())
|
|
return true;
|
|
return false;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
void CGSeerHut::initObj()
|
|
{
|
|
seerName = VLC->generaltexth->seerNames[ran()%VLC->generaltexth->seerNames.size()];
|
|
textOption = ran()%3;
|
|
progress = 0;
|
|
if (missionType)
|
|
{
|
|
if (!isCustom)
|
|
{
|
|
|
|
firstVisitText = VLC->generaltexth->quests[missionType-1][0][textOption];
|
|
nextVisitText = VLC->generaltexth->quests[missionType-1][1][textOption];
|
|
completedText = VLC->generaltexth->quests[missionType-1][2][textOption];
|
|
}
|
|
|
|
if(missionType == MISSION_KILL_CREATURE)
|
|
{
|
|
stackToKill = getCreatureToKill(false)->getStack(0);
|
|
stackDirection = checkDirection();
|
|
}
|
|
else if(missionType == MISSION_KILL_HERO)
|
|
{
|
|
heroName = getHeroToKill(false)->name;
|
|
heroPortrait = getHeroToKill(false)->portrait;
|
|
}
|
|
}
|
|
else
|
|
firstVisitText = VLC->generaltexth->seerEmpty[textOption];
|
|
|
|
}
|
|
|
|
const std::string & CGSeerHut::getHoverText() const
|
|
{
|
|
switch (ID)
|
|
{
|
|
case 83:
|
|
hoverName = VLC->generaltexth->allTexts[347];
|
|
boost::algorithm::replace_first(hoverName,"%s", seerName);
|
|
break;
|
|
case 215:
|
|
hoverName = VLC->generaltexth->names[ID];
|
|
break;
|
|
default:
|
|
tlog5 << "unrecognized quest object\n";
|
|
}
|
|
if (progress & missionType) //rollover when the quest is active
|
|
{
|
|
MetaString ms;
|
|
ms << "\n\n" << VLC->generaltexth->quests[missionType-1][3][textOption];
|
|
std::string str;
|
|
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 (std::vector<ui16>::const_iterator it = m5arts.begin(); it != m5arts.end(); ++it)
|
|
{
|
|
loot << "%s";
|
|
loot.addReplacement(MetaString::ART_NAMES, *it);
|
|
}
|
|
ms.addReplacement(loot.buildList());
|
|
}
|
|
break;
|
|
case MISSION_ARMY:
|
|
{
|
|
MetaString loot;
|
|
for (std::vector<CStackBasicDescriptor>::const_iterator it = m6creatures.begin(); it != m6creatures.end(); ++it)
|
|
{
|
|
loot << "%s";
|
|
loot.addReplacement(*it);
|
|
}
|
|
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;
|
|
}
|
|
ms.toString(str);
|
|
hoverName += str;
|
|
}
|
|
return hoverName;
|
|
}
|
|
|
|
void CGSeerHut::setPropertyDer (ui8 what, ui32 val)
|
|
{
|
|
switch (what)
|
|
{
|
|
case 10:
|
|
progress = val;
|
|
break;
|
|
case 11:
|
|
missionType = CQuest::MISSION_NONE;
|
|
break;
|
|
}
|
|
}
|
|
void CGSeerHut::newTurn() const
|
|
{
|
|
if (lastDay >= 0 && lastDay <= cb->getDate(0)) //time is up
|
|
{
|
|
cb->setObjProperty (id,11,0);
|
|
cb->setObjProperty (id,10,0);
|
|
}
|
|
|
|
}
|
|
void CGSeerHut::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->getOwner();
|
|
if (missionType)
|
|
{
|
|
if (!progress) //propose quest
|
|
{
|
|
iw.text << firstVisitText;
|
|
switch (missionType)
|
|
{
|
|
case MISSION_LEVEL:
|
|
iw.components.push_back(Component (Component::EXPERIENCE, 1, m13489val, 0));
|
|
if (!isCustom)
|
|
iw.text.addReplacement(m13489val);
|
|
break;
|
|
case MISSION_PRIMARY_STAT:
|
|
{
|
|
MetaString loot;
|
|
for (int i = 0; i < 4; ++i)
|
|
{
|
|
if (m2stats[i])
|
|
{
|
|
iw.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)
|
|
iw.text.addReplacement(loot.buildList());
|
|
}
|
|
break;
|
|
case MISSION_KILL_HERO:
|
|
iw.components.push_back(Component(Component::HERO, heroPortrait, 0, 0));
|
|
if (!isCustom)
|
|
addReplacements(iw.text, firstVisitText);
|
|
break;
|
|
case MISSION_HERO:
|
|
iw.components.push_back(Component (Component::HERO, m13489val, 0, 0));
|
|
if (!isCustom)
|
|
iw.text.addReplacement(VLC->heroh->heroes[m13489val]->name);
|
|
break;
|
|
case MISSION_KILL_CREATURE:
|
|
{
|
|
iw.components.push_back(Component(stackToKill));
|
|
if (!isCustom)
|
|
addReplacements(iw.text, firstVisitText);
|
|
}
|
|
break;
|
|
case MISSION_ART:
|
|
{
|
|
MetaString loot;
|
|
for (std::vector<ui16>::const_iterator it = m5arts.begin(); it != m5arts.end(); ++it)
|
|
{
|
|
iw.components.push_back(Component (Component::ARTIFACT, *it, 0, 0));
|
|
loot << "%s";
|
|
loot.addReplacement(MetaString::ART_NAMES, *it);
|
|
}
|
|
if (!isCustom)
|
|
iw.text.addReplacement(loot.buildList());
|
|
}
|
|
break;
|
|
case MISSION_ARMY:
|
|
{
|
|
MetaString loot;
|
|
for (std::vector<CStackBasicDescriptor>::const_iterator it = m6creatures.begin(); it != m6creatures.end(); ++it)
|
|
{
|
|
iw.components.push_back(Component(*it));
|
|
loot << "%s";
|
|
loot.addReplacement(*it);
|
|
}
|
|
if (!isCustom)
|
|
iw.text.addReplacement(loot.buildList());
|
|
}
|
|
break;
|
|
case MISSION_RESOURCES:
|
|
{
|
|
MetaString loot;
|
|
for (int i = 0; i < 7; ++i)
|
|
{
|
|
if (m7resources[i])
|
|
{
|
|
iw.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)
|
|
iw.text.addReplacement(loot.buildList());
|
|
}
|
|
break;
|
|
case MISSION_PLAYER:
|
|
iw.components.push_back(Component (Component::FLAG, m13489val, 0, 0));
|
|
if (!isCustom)
|
|
iw.text.addReplacement(VLC->generaltexth->colors[m13489val]);
|
|
break;
|
|
}
|
|
cb->setObjProperty (id,10,1);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
else if (!checkQuest(h))
|
|
{
|
|
iw.text << nextVisitText;
|
|
if(!isCustom)
|
|
addReplacements(iw.text, nextVisitText);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
if (checkQuest(h)) // propose completion, also on first visit
|
|
{
|
|
BlockingDialog bd (true, false);
|
|
bd.player = h->getOwner();
|
|
bd.soundID = soundBase::QUEST;
|
|
bd.text << completedText;
|
|
switch (missionType)
|
|
{
|
|
case CQuest::MISSION_LEVEL:
|
|
if (!isCustom)
|
|
bd.text.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 (!isCustom)
|
|
bd.text.addReplacement(loot.buildList());
|
|
}
|
|
break;
|
|
case CQuest::MISSION_ART:
|
|
{
|
|
MetaString loot;
|
|
for (std::vector<ui16>::const_iterator it = m5arts.begin(); it != m5arts.end(); ++it)
|
|
{
|
|
loot << "%s";
|
|
loot.addReplacement(MetaString::ART_NAMES, *it);
|
|
}
|
|
if (!isCustom)
|
|
bd.text.addReplacement(loot.buildList());
|
|
}
|
|
break;
|
|
case CQuest::MISSION_ARMY:
|
|
{
|
|
MetaString loot;
|
|
for (std::vector<CStackBasicDescriptor>::const_iterator it = m6creatures.begin(); it != m6creatures.end(); ++it)
|
|
{
|
|
loot << "%s";
|
|
loot.addReplacement(*it);
|
|
}
|
|
if (!isCustom)
|
|
bd.text.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 (!isCustom)
|
|
bd.text.addReplacement(loot.buildList());
|
|
}
|
|
break;
|
|
case MISSION_KILL_HERO:
|
|
case MISSION_KILL_CREATURE:
|
|
if (!isCustom)
|
|
addReplacements(bd.text, completedText);
|
|
break;
|
|
case MISSION_HERO:
|
|
if (!isCustom)
|
|
bd.text.addReplacement(VLC->heroh->heroes[m13489val]->name);
|
|
break;
|
|
case MISSION_PLAYER:
|
|
if (!isCustom)
|
|
bd.text.addReplacement(VLC->generaltexth->colors[m13489val]);
|
|
break;
|
|
}
|
|
|
|
switch (rewardType)
|
|
{
|
|
case 1: bd.components.push_back(Component (Component::EXPERIENCE, 0, rVal*(100+h->getSecSkillLevel(CGHeroInstance::LEARNING)*5)/100.0f, 0));
|
|
break;
|
|
case 2: bd.components.push_back(Component (Component::PRIM_SKILL, 5, rVal, 0));
|
|
break;
|
|
case 3: bd.components.push_back(Component (Component::MORALE, 0, rVal, 0));
|
|
break;
|
|
case 4: bd.components.push_back(Component (Component::LUCK, 0, rVal, 0));
|
|
break;
|
|
case 5: bd.components.push_back(Component (Component::RESOURCE, rID, rVal, 0));
|
|
break;
|
|
case 6: bd.components.push_back(Component (Component::PRIM_SKILL, rID, rVal, 0));
|
|
break;
|
|
case 7: bd.components.push_back(Component (Component::SEC_SKILL, rID, rVal, 0));
|
|
break;
|
|
case 8: bd.components.push_back(Component (Component::ARTIFACT, rID, 0, 0));
|
|
break;
|
|
case 9: bd.components.push_back(Component (Component::SPELL, rID, 0, 0));
|
|
break;
|
|
case 10: bd.components.push_back(Component (Component::CREATURE, rID, rVal, 0));
|
|
break;
|
|
}
|
|
|
|
cb->showBlockingDialog (&bd, boost::bind (&CGSeerHut::finishQuest, this, h, _1));
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
iw.text << VLC->generaltexth->seerEmpty[textOption];
|
|
if (ID == 83)
|
|
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 (missionType)
|
|
{
|
|
case CQuest::MISSION_ART:
|
|
for (std::vector<ui16>::const_iterator it = m5arts.begin(); it != m5arts.end(); ++it)
|
|
{
|
|
cb->removeArtifact(ArtifactLocation(h, h->getArtPos(*it, false)));
|
|
}
|
|
break;
|
|
case CQuest::MISSION_ARMY:
|
|
cb->takeCreatures(h->id, m6creatures);
|
|
break;
|
|
case CQuest::MISSION_RESOURCES:
|
|
for (int i = 0; i < 7; ++i)
|
|
{
|
|
cb->giveResource(h->getOwner(), i, -m7resources[i]);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
cb->setObjProperty(id,11,0); //no more mission avaliable
|
|
completeQuest(h); //make sure to remove QuestQuard at the very end
|
|
}
|
|
}
|
|
void CGSeerHut::completeQuest (const CGHeroInstance * h) const //reward
|
|
{
|
|
switch (rewardType)
|
|
{
|
|
case 1: //experience
|
|
{
|
|
expType expVal = h->calculateXp(rVal);
|
|
cb->changePrimSkill(h->id, 4, expVal, false);
|
|
break;
|
|
}
|
|
case 2: //mana points
|
|
{
|
|
cb->setManaPoints(h->id, h->mana+rVal);
|
|
break;
|
|
}
|
|
case 3: case 4: //morale /luck
|
|
{
|
|
Bonus hb(Bonus::ONE_WEEK, (rewardType == 3 ? Bonus::MORALE : Bonus::LUCK),
|
|
Bonus::OBJECT, rVal, h->id, "", -1);
|
|
GiveBonus gb;
|
|
gb.id = h->id;
|
|
gb.bonus = hb;
|
|
cb->giveHeroBonus(&gb);
|
|
}
|
|
break;
|
|
case 5: //resources
|
|
cb->giveResource(h->getOwner(), rID, rVal);
|
|
break;
|
|
case 6: //main ability bonus (attak, defence etd.)
|
|
cb->changePrimSkill(h->id, rID, rVal, false);
|
|
break;
|
|
case 7: // secondary ability gain
|
|
cb->changeSecSkill(h->id, rID, rVal, false);
|
|
break;
|
|
case 8: // artifact
|
|
cb->giveHeroNewArtifact(h, VLC->arth->artifacts[rID],-2);
|
|
break;
|
|
case 9:// spell
|
|
{
|
|
std::set<ui32> spell;
|
|
spell.insert (rID);
|
|
cb->changeSpells(h->id, true, spell);
|
|
}
|
|
break;
|
|
case 10:// creature
|
|
{
|
|
CCreatureSet creatures;
|
|
creatures.setCreature(0, rID, rVal);
|
|
cb->giveCreatures(this, h, creatures, false);
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
const CGHeroInstance * CGSeerHut::getHeroToKill(bool allowNull) const
|
|
{
|
|
const CGObjectInstance *o = cb->getObjByQuestIdentifier(m13489val);
|
|
if(allowNull && !o)
|
|
return NULL;
|
|
assert(o && o->ID == HEROI_TYPE);
|
|
return static_cast<const CGHeroInstance*>(o);
|
|
}
|
|
|
|
const CGCreature * CGSeerHut::getCreatureToKill(bool allowNull) const
|
|
{
|
|
const CGObjectInstance *o = cb->getObjByQuestIdentifier(m13489val);
|
|
if(allowNull && !o)
|
|
return NULL;
|
|
assert(o && o->ID == 54);
|
|
return static_cast<const CGCreature*>(o);
|
|
}
|
|
|
|
void CGSeerHut::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;
|
|
}
|
|
}
|
|
|
|
void CGQuestGuard::initObj()
|
|
{
|
|
blockVisit = true;
|
|
progress = 0;
|
|
textOption = ran()%3 + 3; //3-5
|
|
if (missionType && !isCustom)
|
|
{
|
|
firstVisitText = VLC->generaltexth->quests[missionType-1][0][textOption];
|
|
nextVisitText = VLC->generaltexth->quests[missionType-1][1][textOption];
|
|
completedText = VLC->generaltexth->quests[missionType-1][2][textOption];
|
|
}
|
|
else
|
|
firstVisitText = VLC->generaltexth->seerEmpty[textOption];
|
|
}
|
|
void CGQuestGuard::completeQuest(const CGHeroInstance *h) const
|
|
{
|
|
cb->removeObject(id);
|
|
}
|
|
void CGWitchHut::initObj()
|
|
{
|
|
ability = allowedAbilities[ran()%allowedAbilities.size()];
|
|
}
|
|
|
|
void CGWitchHut::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
InfoWindow iw;
|
|
iw.soundID = soundBase::gazebo;
|
|
iw.player = h->getOwner();
|
|
if(!hasVisited(h->tempOwner))
|
|
cb->setObjProperty(id,10,h->tempOwner);
|
|
|
|
if(h->getSecSkillLevel(static_cast<CGHeroInstance::SecondarySkill>(ability))) //you alredy know this skill
|
|
{
|
|
iw.text << std::pair<ui8,ui32>(11,172);
|
|
iw.text.addReplacement(MetaString::SEC_SKILL_NAME, ability);
|
|
}
|
|
else if(h->secSkills.size() >= SKILL_PER_HERO) //already all skills slots used
|
|
{
|
|
iw.text << std::pair<ui8,ui32>(11,173);
|
|
iw.text.addReplacement(MetaString::SEC_SKILL_NAME, ability);
|
|
}
|
|
else //give sec skill
|
|
{
|
|
iw.components.push_back(Component(1, ability, 1, 0));
|
|
iw.text << std::pair<ui8,ui32>(11,171);
|
|
iw.text.addReplacement(MetaString::SEC_SKILL_NAME, ability);
|
|
cb->changeSecSkill(h->id,ability,1,true);
|
|
}
|
|
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
const std::string & CGWitchHut::getHoverText() const
|
|
{
|
|
hoverName = VLC->generaltexth->names[ID];
|
|
if(hasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current
|
|
{
|
|
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(static_cast<CGHeroInstance::SecondarySkill>(ability))) //hero knows that ability
|
|
hoverName += "\n\n" + VLC->generaltexth->allTexts[357]; // (Already learned)
|
|
}
|
|
return hoverName;
|
|
}
|
|
|
|
void CGBonusingObject::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
bool visited = h->hasBonusFrom(Bonus::OBJECT,ID);
|
|
int messageID;
|
|
int bonusMove = 0, sound = -1;
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
GiveBonus gbonus;
|
|
gbonus.id = h->id;
|
|
gbonus.bonus.duration = Bonus::ONE_BATTLE;
|
|
gbonus.bonus.source = Bonus::OBJECT;
|
|
gbonus.bonus.sid = ID;
|
|
|
|
bool second = false;
|
|
Bonus secondBonus;
|
|
|
|
switch(ID)
|
|
{
|
|
case 11: //buoy
|
|
messageID = 21;
|
|
sound = soundBase::MORALE;
|
|
gbonus.bonus.type = Bonus::MORALE;
|
|
gbonus.bonus.val = +1;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,94);
|
|
break;
|
|
case 14: //swan pond
|
|
messageID = 29;
|
|
sound = soundBase::LUCK;
|
|
gbonus.bonus.type = Bonus::LUCK;
|
|
gbonus.bonus.val = 2;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,67);
|
|
bonusMove = -h->movement;
|
|
break;
|
|
case 28: //Faerie Ring
|
|
messageID = 49;
|
|
sound = soundBase::LUCK;
|
|
gbonus.bonus.type = Bonus::LUCK;
|
|
gbonus.bonus.val = 1;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,71);
|
|
break;
|
|
case 30: //fountain of fortune
|
|
messageID = 55;
|
|
sound = soundBase::LUCK;
|
|
gbonus.bonus.type = Bonus::LUCK;
|
|
gbonus.bonus.val = rand()%5 - 1;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,69);
|
|
gbonus.bdescr.addReplacement((gbonus.bonus.val<0 ? "-" : "+") + boost::lexical_cast<std::string>(gbonus.bonus.val));
|
|
break;
|
|
case 38: //idol of fortune
|
|
messageID = 62;
|
|
sound = soundBase::experience;
|
|
|
|
gbonus.bonus.val = 1;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,68);
|
|
if(cb->getDate(1) == 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(1)%2) ? Bonus::LUCK : Bonus::MORALE;
|
|
}
|
|
break;
|
|
case 52: //Mermaid
|
|
messageID = 83;
|
|
sound = soundBase::LUCK;
|
|
gbonus.bonus.type = Bonus::LUCK;
|
|
gbonus.bonus.val = 1;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,72);
|
|
break;
|
|
case 64: //Rally Flag
|
|
sound = soundBase::MORALE;
|
|
messageID = 111;
|
|
gbonus.bonus.type = Bonus::MORALE;
|
|
gbonus.bonus.val = 1;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,102);
|
|
|
|
second = true;
|
|
secondBonus = gbonus.bonus;
|
|
secondBonus.type = Bonus::LUCK;
|
|
|
|
bonusMove = 400;
|
|
break;
|
|
case 56: //oasis
|
|
messageID = 95;
|
|
gbonus.bonus.type = Bonus::MORALE;
|
|
gbonus.bonus.val = 1;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,95);
|
|
bonusMove = 800;
|
|
break;
|
|
case 96: //temple
|
|
messageID = 140;
|
|
iw.soundID = soundBase::temple;
|
|
gbonus.bonus.type = Bonus::MORALE;
|
|
if(cb->getDate(1)==7) //sunday
|
|
{
|
|
gbonus.bonus.val = 2;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,97);
|
|
}
|
|
else
|
|
{
|
|
gbonus.bonus.val = 1;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,96);
|
|
}
|
|
break;
|
|
case 110://Watering Hole
|
|
sound = soundBase::MORALE;
|
|
messageID = 166;
|
|
gbonus.bonus.type = Bonus::MORALE;
|
|
gbonus.bonus.val = 1;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,100);
|
|
bonusMove = 400;
|
|
break;
|
|
case 31: //Fountain of Youth
|
|
sound = soundBase::MORALE;
|
|
messageID = 57;
|
|
gbonus.bonus.type = Bonus::MORALE;
|
|
gbonus.bonus.val = 1;
|
|
gbonus.bdescr << std::pair<ui8,ui32>(6,103);
|
|
bonusMove = 400;
|
|
break;
|
|
case 94: //Stables
|
|
sound = soundBase::horse20;
|
|
CCreatureSet creatures;
|
|
for (TSlots::const_iterator i = h->Slots().begin(); i != h->Slots().end(); ++i)
|
|
{
|
|
if(i->second->type->idNumber == 10)
|
|
creatures.stacks.insert(*i);
|
|
}
|
|
if (creatures.stacks.size())
|
|
{
|
|
messageID = 138;
|
|
iw.components.push_back(Component(Component::CREATURE,11,0,1));
|
|
for (TSlots::const_iterator i = creatures.stacks.begin(); i != creatures.stacks.end(); ++i)
|
|
{
|
|
cb->changeStackType(StackLocation(h, i->first), VLC->creh->creatures[11]);
|
|
}
|
|
}
|
|
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<ui8,ui32>(6, 100);
|
|
break;
|
|
}
|
|
if(visited)
|
|
{
|
|
if(ID==64 || ID==96 || ID==56 || ID==52 || ID==94)
|
|
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(8,0,gbonus.bonus.val,0));
|
|
if(gbonus.bonus.type == Bonus::LUCK || secondBonus.type == Bonus::LUCK)
|
|
iw.components.push_back(Component(9,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.soundID = sound;
|
|
iw.text << std::pair<ui8,ui32>(11,messageID);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
const std::string & CGBonusingObject::getHoverText() const
|
|
{
|
|
const CGHeroInstance *h = cb->getSelectedHero(cb->getCurrentPlayer());
|
|
hoverName = VLC->generaltexth->names[ID];
|
|
if(h)
|
|
{
|
|
if(!h->hasBonusFrom(Bonus::OBJECT,ID))
|
|
hoverName += " " + VLC->generaltexth->allTexts[353]; //not visited
|
|
else
|
|
hoverName += " " + VLC->generaltexth->allTexts[352]; //visited
|
|
}
|
|
return hoverName;
|
|
}
|
|
|
|
void CGBonusingObject::initObj()
|
|
{
|
|
if(ID == 11 || ID == 52) //Buoy / Mermaid
|
|
{
|
|
blockVisit = true;
|
|
}
|
|
}
|
|
|
|
void CGMagicSpring::onHeroVisit(const CGHeroInstance * h) const
|
|
{
|
|
int messageID;
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.soundID = soundBase::GENIE;
|
|
if (!visited)
|
|
{
|
|
if (h->mana > h->manaLimit())
|
|
messageID = 76;
|
|
else
|
|
{
|
|
messageID = 74;
|
|
cb->setManaPoints (h->id, 2 * h->manaLimit());
|
|
cb->setObjProperty (id, ObjProperty::VISITED, true);
|
|
}
|
|
}
|
|
else
|
|
messageID = 75;
|
|
iw.text << std::pair<ui8,ui32>(11,messageID);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
const std::string & CGMagicSpring::getHoverText() const
|
|
{
|
|
hoverName = VLC->generaltexth->names[ID];
|
|
if(!visited)
|
|
hoverName += " " + VLC->generaltexth->allTexts[353]; //not visited
|
|
else
|
|
hoverName += " " + VLC->generaltexth->allTexts[352]; //visited
|
|
return hoverName;
|
|
}
|
|
|
|
void CGMagicWell::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
int message;
|
|
InfoWindow iw;
|
|
iw.soundID = soundBase::faerie;
|
|
iw.player = h->tempOwner;
|
|
if(h->hasBonusFrom(Bonus::OBJECT,ID)) //has already visited Well today
|
|
{
|
|
message = 78;
|
|
}
|
|
else if(h->mana < h->manaLimit())
|
|
{
|
|
giveDummyBonus(h->id);
|
|
cb->setManaPoints(h->id,h->manaLimit());
|
|
message = 77;
|
|
}
|
|
else
|
|
{
|
|
message = 79;
|
|
}
|
|
iw.text << std::pair<ui8,ui32>(11,message); //"A second drink at the well in one day will not help you."
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
const std::string & CGMagicWell::getHoverText() const
|
|
{
|
|
getNameVis(hoverName);
|
|
return hoverName;
|
|
}
|
|
|
|
void CGPandoraBox::initObj()
|
|
{
|
|
blockVisit = (ID==6); //block only if it's really pandora's box (events also derive from that class)
|
|
}
|
|
|
|
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, boost::bind (&CGPandoraBox::open, this, h, _1));
|
|
}
|
|
|
|
void CGPandoraBox::open( const CGHeroInstance * h, ui32 accept ) const
|
|
{
|
|
if (accept)
|
|
{
|
|
if (stacksCount() > 0) //if pandora's box is protected by army
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, 16);
|
|
cb->showInfoDialog(&iw);
|
|
cb->startBattleI(h, this, boost::bind(&CGPandoraBox::endBattle, this, h, _1)); //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
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, 15);
|
|
cb->showInfoDialog(&iw);
|
|
|
|
}
|
|
else //if it gives something without battle
|
|
{
|
|
giveContents (h, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGPandoraBox::endBattle( const CGHeroInstance *h, BattleResult *result ) const
|
|
{
|
|
if(result->winner)
|
|
return;
|
|
|
|
giveContents(h,true);
|
|
}
|
|
|
|
void CGPandoraBox::giveContents( const CGHeroInstance *h, bool afterBattle ) const
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->getOwner();
|
|
std::string msg = message; //in case box is removed in the meantime
|
|
|
|
bool changesPrimSkill = false;
|
|
for (int i = 0; i < primskills.size(); i++)
|
|
{
|
|
if(primskills[i])
|
|
{
|
|
changesPrimSkill = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(gainedExp || changesPrimSkill || abilities.size())
|
|
{
|
|
expType expVal = h->calculateXp(gainedExp);
|
|
getText(iw,afterBattle,175,h);
|
|
|
|
if(expVal)
|
|
iw.components.push_back(Component(Component::EXPERIENCE,0,expVal,0));
|
|
for(int i=0; i<primskills.size(); i++)
|
|
if(primskills[i])
|
|
iw.components.push_back(Component(Component::PRIM_SKILL,i,primskills[i],0));
|
|
|
|
for(int i=0; i<abilities.size(); i++)
|
|
iw.components.push_back(Component(Component::SEC_SKILL,abilities[i],abilityLevels[i],0));
|
|
|
|
cb->showInfoDialog(&iw);
|
|
|
|
//give exp
|
|
if(expVal)
|
|
cb->changePrimSkill(h->id,4,expVal,false);
|
|
//give prim skills
|
|
for(int i=0; i<primskills.size(); i++)
|
|
if(primskills[i])
|
|
cb->changePrimSkill(h->id,i,primskills[i],false);
|
|
|
|
//give sec skills
|
|
for(int i=0; i<abilities.size(); i++)
|
|
{
|
|
int curLev = h->getSecSkillLevel(static_cast<CGHeroInstance::SecondarySkill>(abilities[i]));
|
|
|
|
if( (curLev && curLev < abilityLevels[i])
|
|
|| (h->secSkills.size() < SKILL_PER_HERO) )
|
|
{
|
|
cb->changeSecSkill(h->id,abilities[i],abilityLevels[i],true);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
if(spells.size())
|
|
{
|
|
std::set<ui32> spellsToGive;
|
|
iw.components.clear();
|
|
std::vector<ConstTransitivePtr<CSpell> > * sp = &VLC->spellh->spells;
|
|
for(std::vector<si32>::const_iterator i=spells.begin(); i != spells.end(); i++)
|
|
{
|
|
if ((*sp)[*i]->level <= h->getSecSkillLevel(CGHeroInstance::WISDOM) + 2) //enough wisdom
|
|
{
|
|
iw.components.push_back(Component(Component::SPELL,*i,0,0));
|
|
spellsToGive.insert(*i);
|
|
}
|
|
}
|
|
if(spellsToGive.size())
|
|
{
|
|
cb->changeSpells(h->id,true,spellsToGive);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
}
|
|
|
|
if(manaDiff)
|
|
{
|
|
getText(iw,afterBattle,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,afterBattle,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,"");
|
|
gb.id = h->id;
|
|
cb->giveHeroBonus(&gb);
|
|
}
|
|
|
|
if(luckDiff)
|
|
{
|
|
getText(iw,afterBattle,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,"");
|
|
gb.id = h->id;
|
|
cb->giveHeroBonus(&gb);
|
|
}
|
|
|
|
iw.components.clear();
|
|
iw.text.clear();
|
|
for(int i=0; i<resources.size(); i++)
|
|
{
|
|
if(resources[i] < 0)
|
|
iw.components.push_back(Component(Component::RESOURCE,i,resources[i],0));
|
|
}
|
|
if(iw.components.size())
|
|
{
|
|
getText(iw,afterBattle,182,h);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
iw.components.clear();
|
|
iw.text.clear();
|
|
for(int i=0; i<resources.size(); i++)
|
|
{
|
|
if(resources[i] > 0)
|
|
iw.components.push_back(Component(Component::RESOURCE,i,resources[i],0));
|
|
}
|
|
if(iw.components.size())
|
|
{
|
|
getText(iw,afterBattle,183,h);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
iw.components.clear();
|
|
// getText(iw,afterBattle,183,h);
|
|
for(int i=0; i<artifacts.size(); i++)
|
|
{
|
|
iw.components.push_back(Component(Component::ARTIFACT,artifacts[i],0,0));
|
|
if(iw.components.size() >= 14)
|
|
{
|
|
cb->showInfoDialog(&iw);
|
|
iw.components.clear();
|
|
}
|
|
}
|
|
if(iw.components.size())
|
|
{
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
for(int i=0; i<resources.size(); i++)
|
|
if(resources[i])
|
|
cb->giveResource(h->getOwner(),i,resources[i]);
|
|
|
|
for(int i=0; i<artifacts.size(); i++)
|
|
cb->giveHeroNewArtifact(h, VLC->arth->artifacts[artifacts[i]],-2);
|
|
|
|
iw.components.clear();
|
|
iw.text.clear();
|
|
|
|
if (creatures.Slots().size())
|
|
{ //this part is taken straight from creature bank
|
|
MetaString loot;
|
|
for(TSlots::const_iterator i = creatures.Slots().begin(); i != creatures.Slots().end(); i++)
|
|
{ //build list of joined creatures
|
|
iw.components.push_back(Component(*i->second));
|
|
loot << "%s";
|
|
loot.addReplacement(*i->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(!afterBattle && msg.size())
|
|
{
|
|
iw.text << msg;
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
if (!creatures.Slots().size())
|
|
cb->removeObject(id); //only when we don't need to display garrison window
|
|
}
|
|
|
|
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 CGEvent::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
if(!(availableFor & (1 << h->tempOwner)))
|
|
return;
|
|
if(cb->getPlayerSettings(h->tempOwner)->human)
|
|
{
|
|
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, boost::bind(&CGEvent::endBattle,this,h,_1));
|
|
}
|
|
else
|
|
{
|
|
giveContents(h,false);
|
|
}
|
|
}
|
|
|
|
void CGObservatory::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
switch (ID)
|
|
{
|
|
case 58://redwood observatory
|
|
case 60://pillar of fire
|
|
{
|
|
iw.soundID = soundBase::LIGHTHOUSE;
|
|
iw.text.addTxt(MetaString::ADVOB_TXT,98 + (ID==60));
|
|
|
|
FoWChange fw;
|
|
fw.player = h->tempOwner;
|
|
fw.mode = 1;
|
|
cb->getTilesInRange (fw.tiles, pos, 20, h->tempOwner, 1);
|
|
cb->sendAndApply (&fw);
|
|
break;
|
|
}
|
|
case 15://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 == 255)
|
|
{
|
|
tlog1 << "Not initialized shrine visited!\n";
|
|
return;
|
|
}
|
|
|
|
if(!hasVisited(h->tempOwner))
|
|
cb->setObjProperty(id,10,h->tempOwner);
|
|
|
|
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(17)) //no spellbook
|
|
{
|
|
iw.text.addTxt(MetaString::ADVOB_TXT,131);
|
|
}
|
|
else if(ID == 90 && !h->getSecSkillLevel(CGHeroInstance::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<ui32> spells;
|
|
spells.insert(spell);
|
|
cb->changeSpells(h->id, true, spells);
|
|
|
|
iw.components.push_back(Component(Component::SPELL,spell,0,0));
|
|
}
|
|
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
void CGShrine::initObj()
|
|
{
|
|
if(spell == 255) //spell not set
|
|
{
|
|
int level = ID-87;
|
|
std::vector<ui16> possibilities;
|
|
cb->getAllowedSpells (possibilities, level);
|
|
|
|
if(!possibilities.size())
|
|
{
|
|
tlog1 << "Error: cannot init shrine, no allowed spells!\n";
|
|
return;
|
|
}
|
|
|
|
spell = possibilities[ran() % possibilities.size()];
|
|
}
|
|
}
|
|
|
|
const std::string & CGShrine::getHoverText() const
|
|
{
|
|
hoverName = VLC->generaltexth->names[ID];
|
|
if(hasVisited(cb->getCurrentPlayer())) //TODO: use local player, not current
|
|
{
|
|
hoverName += "\n" + VLC->generaltexth->allTexts[355]; // + (learn %s)
|
|
boost::algorithm::replace_first(hoverName,"%s",VLC->spellh->spells[spell]->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.size())
|
|
message = VLC->generaltexth->randsign[ran()%VLC->generaltexth->randsign.size()];
|
|
|
|
if(ID == 59)
|
|
{
|
|
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 == 59)
|
|
cb->removeObject(id);
|
|
}
|
|
|
|
void CGScholar::giveAnyBonus( const CGHeroInstance * h ) const
|
|
{
|
|
|
|
}
|
|
|
|
void CGScholar::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
|
|
int type = bonusType;
|
|
int bid = bonusID;
|
|
//check if the bonus if applicable, if not - give primary skill (always possible)
|
|
int ssl = h->getSecSkillLevel(static_cast<CGHeroInstance::SecondarySkill>(bid)); //current sec skill level, used if bonusType == 1
|
|
if((type == 1
|
|
&& ((ssl == 3) || (!ssl && h->secSkills.size() == SKILL_PER_HERO))) ////hero already has expert level in the skill or (don't know skill and doesn't have free slot)
|
|
|| (type == 2 && (!h->getArt(17) || vstd::contains(h->spells, (ui32) bid)
|
|
|| (VLC->spellh->spells[bid]->level > h->getSecSkillLevel(CGHeroInstance::WISDOM) + 2)
|
|
))) //hero doesn't have a spellbook or already knows the spell or doesn't have Wisdom
|
|
{
|
|
type = 0;
|
|
bid = ran() % PRIMARY_SKILLS;
|
|
}
|
|
|
|
|
|
InfoWindow iw;
|
|
iw.soundID = soundBase::gazebo;
|
|
iw.player = h->getOwner();
|
|
iw.text.addTxt(MetaString::ADVOB_TXT,115);
|
|
|
|
switch (type)
|
|
{
|
|
case 0:
|
|
cb->changePrimSkill(h->id,bid,+1);
|
|
iw.components.push_back(Component(Component::PRIM_SKILL,bid,+1,0));
|
|
break;
|
|
case 1:
|
|
cb->changeSecSkill(h->id,bid,+1);
|
|
iw.components.push_back(Component(Component::SEC_SKILL,bid,ssl+1,0));
|
|
break;
|
|
case 2:
|
|
{
|
|
std::set<ui32> hlp;
|
|
hlp.insert(bid);
|
|
cb->changeSpells(h->id,true,hlp);
|
|
iw.components.push_back(Component(Component::SPELL,bid,0,0));
|
|
}
|
|
break;
|
|
default:
|
|
tlog1 << "Error: wrong bonustype (" << (int)type << ") for Scholar!\n";
|
|
return;
|
|
}
|
|
|
|
cb->showInfoDialog(&iw);
|
|
cb->removeObject(id);
|
|
}
|
|
|
|
void CGScholar::initObj()
|
|
{
|
|
blockVisit = true;
|
|
if(bonusType == 255)
|
|
{
|
|
bonusType = ran()%3;
|
|
switch(bonusType)
|
|
{
|
|
case 0:
|
|
bonusID = ran() % PRIMARY_SKILLS;
|
|
break;
|
|
case 1:
|
|
bonusID = ran() % SKILL_QUANTITY;
|
|
break;
|
|
case 2:
|
|
std::vector<ui16> possibilities;
|
|
for (int i = 1; i < 6; ++i)
|
|
cb->getAllowedSpells (possibilities, i);
|
|
bonusID = possibilities[ran() % possibilities.size()];
|
|
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, boost::bind(&CGGarrison::fightOver, this, h, _1));
|
|
return;
|
|
}
|
|
|
|
//New owner.
|
|
if (!ally)
|
|
cb->setOwner(id, h->tempOwner);
|
|
|
|
cb->showGarrisonDialog(id, h->id, removableUnits, 0);
|
|
}
|
|
|
|
void CGGarrison::fightOver (const CGHeroInstance *h, BattleResult *result) const
|
|
{
|
|
if (result->winner == 0)
|
|
onHeroVisit(h);
|
|
}
|
|
|
|
ui8 CGGarrison::getPassableness() const
|
|
{
|
|
if ( !stacksCount() )//empty - anyone can visit
|
|
return ALL_PLAYERS;
|
|
if ( tempOwner == 255 )//neutral guarded - noone can visit
|
|
return 0;
|
|
|
|
ui8 mask = 0;
|
|
TeamState * ts = cb->gameState()->getPlayerTeam(tempOwner);
|
|
BOOST_FOREACH(ui8 it, ts->players)
|
|
mask |= 1<<it;//allies - add to possible visitors
|
|
|
|
return mask;
|
|
}
|
|
|
|
void CGOnceVisitable::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
int sound = 0;
|
|
int txtid = -1;
|
|
switch(ID)
|
|
{
|
|
case 22: //Corpse
|
|
txtid = 37;
|
|
sound = soundBase::MYSTERY;
|
|
break;
|
|
case 39: //Lean To
|
|
sound = soundBase::GENIE;
|
|
txtid = 64;
|
|
break;
|
|
case 105://Wagon
|
|
sound = soundBase::GENIE;
|
|
txtid = 154;
|
|
break;
|
|
case 108:
|
|
break;
|
|
default:
|
|
tlog1 << "Error: Unknown object (" << ID <<") treated as CGOnceVisitable!\n";
|
|
return;
|
|
}
|
|
|
|
if(ID == 108)//Warrior's Tomb
|
|
{
|
|
//ask if player wants to search the Tomb
|
|
BlockingDialog bd(true, false);
|
|
sound = soundBase::GRAVEYARD;
|
|
bd.player = h->getOwner();
|
|
bd.text.addTxt(MetaString::ADVOB_TXT,161);
|
|
cb->showBlockingDialog(&bd,boost::bind(&CGOnceVisitable::searchTomb,this,h,_1));
|
|
return;
|
|
}
|
|
|
|
InfoWindow iw;
|
|
iw.soundID = sound;
|
|
iw.player = h->getOwner();
|
|
|
|
if(players.size()) //we have been already visited...
|
|
{
|
|
txtid++;
|
|
if(ID == 105) //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 == 22) //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],-2);
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, txtid);
|
|
if (ID == 22) //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(), bonusType, bonusVal);
|
|
break;
|
|
}
|
|
if(ID == 105 && artOrRes == 1)
|
|
{
|
|
iw.text.localStrings.back().second++;
|
|
iw.text.addReplacement(MetaString::ART_NAMES, bonusType);
|
|
}
|
|
}
|
|
|
|
cb->showInfoDialog(&iw);
|
|
cb->setObjProperty(id,10,h->getOwner());
|
|
}
|
|
|
|
const std::string & CGOnceVisitable::getHoverText() const
|
|
{
|
|
hoverName = VLC->generaltexth->names[ID] + " ";
|
|
|
|
hoverName += (hasVisited(cb->getCurrentPlayer())
|
|
? (VLC->generaltexth->allTexts[352]) //visited
|
|
: ( VLC->generaltexth->allTexts[353])); //not visited
|
|
|
|
return hoverName;
|
|
}
|
|
|
|
void CGOnceVisitable::initObj()
|
|
{
|
|
switch(ID)
|
|
{
|
|
case 22: //Corpse
|
|
{
|
|
blockVisit = true;
|
|
int hlp = ran()%100;
|
|
if(hlp < 20)
|
|
{
|
|
artOrRes = 1;
|
|
bonusType = cb->getRandomArt (CArtifact::ART_TREASURE | CArtifact::ART_MINOR | CArtifact::ART_MAJOR);
|
|
}
|
|
else
|
|
{
|
|
artOrRes = 0;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case 39: //Lean To
|
|
{
|
|
artOrRes = 2;
|
|
bonusType = ran()%6; //any basic resource without gold
|
|
bonusVal = ran()%4 + 1;
|
|
}
|
|
break;
|
|
|
|
case 108://Warrior's Tomb
|
|
{
|
|
artOrRes = 1;
|
|
|
|
int hlp = ran()%100;
|
|
if(hlp < 30)
|
|
bonusType = cb->getRandomArt (CArtifact::ART_TREASURE);
|
|
else if(hlp < 80)
|
|
bonusType = cb->getRandomArt (CArtifact::ART_MINOR);
|
|
else if(hlp < 95)
|
|
bonusType = cb->getRandomArt (CArtifact::ART_MAJOR);
|
|
else
|
|
bonusType = cb->getRandomArt (CArtifact::ART_RELIC);
|
|
}
|
|
break;
|
|
|
|
case 105://Wagon
|
|
{
|
|
int hlp = ran()%100;
|
|
|
|
if(hlp < 10)
|
|
{
|
|
artOrRes = 0; // nothing... :(
|
|
}
|
|
else if(hlp < 50) //minor or treasure art
|
|
{
|
|
artOrRes = 1;
|
|
bonusType = cb->getRandomArt (CArtifact::ART_TREASURE | CArtifact::ART_MINOR);
|
|
}
|
|
else //2 - 5 of non-gold resource
|
|
{
|
|
artOrRes = 2;
|
|
bonusType = ran()%6;
|
|
bonusVal = ran()%4 + 2;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CGOnceVisitable::searchTomb(const CGHeroInstance *h, ui32 accept) const
|
|
{
|
|
if(accept)
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->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(h, VLC->arth->artifacts[bonusType],-2);
|
|
}
|
|
|
|
if(!h->hasBonusFrom(Bonus::OBJECT,ID)) //we don't have modifier from this object yet
|
|
{
|
|
//ruin morale
|
|
GiveBonus gb;
|
|
gb.id = h->id;
|
|
gb.bonus = Bonus(Bonus::ONE_BATTLE,Bonus::MORALE,Bonus::OBJECT,-3,id,"");
|
|
gb.bdescr.addTxt(MetaString::ARRAY_TXT,104); //Warrior Tomb Visited -3
|
|
cb->giveHeroBonus(&gb);
|
|
}
|
|
cb->showInfoDialog(&iw);
|
|
cb->setObjProperty(id,10,h->getOwner());
|
|
}
|
|
}
|
|
|
|
void CBank::initObj()
|
|
{
|
|
switch (ID) //find apriopriate key
|
|
{
|
|
case 16: //bank
|
|
index = subID; break;
|
|
case 24: //derelict ship
|
|
index = 8; break;
|
|
case 25: //utopia
|
|
index = 10; break;
|
|
case 84: //crypt
|
|
index = 9; break;
|
|
case 85: //shipwreck
|
|
index = 7; break;
|
|
}
|
|
bc = NULL;
|
|
daycounter = 0;
|
|
multiplier = 1;
|
|
}
|
|
const std::string & CBank::getHoverText() const
|
|
{
|
|
hoverName = VLC->objh->creBanksNames[index];
|
|
if (bc == NULL)
|
|
hoverName += " " + VLC->generaltexth->allTexts[352];
|
|
else
|
|
hoverName += " " + VLC->generaltexth->allTexts[353];
|
|
return hoverName;
|
|
}
|
|
void CBank::reset(ui16 var1) //prevents desync
|
|
{
|
|
ui8 chance = 0;
|
|
for (ui8 i = 0; i < VLC->objh->banksInfo[index].size(); i++)
|
|
{
|
|
if (var1 < (chance += VLC->objh->banksInfo[index][i]->chance))
|
|
{
|
|
bc = VLC->objh->banksInfo[index][i];
|
|
break;
|
|
}
|
|
}
|
|
artifacts.clear();
|
|
}
|
|
|
|
void CBank::initialize() const
|
|
{
|
|
cb->setObjProperty (id, 14, ran()); //synchronous reset
|
|
for (ui8 i = 0; i <= 3; i++)
|
|
{
|
|
for (ui8 n = 0; n < bc->artifacts[i]; n++) //new function using proper randomization algorithm
|
|
{
|
|
cb->setObjProperty (id, 18 + i, ran()); //synchronic artifacts
|
|
}
|
|
}
|
|
cb->setObjProperty (id, 17, ran()); //get army
|
|
}
|
|
void CBank::setPropertyDer (ui8 what, ui32 val)
|
|
/// random values are passed as arguments and processed identically on all clients
|
|
{
|
|
switch (what)
|
|
{
|
|
case 11: //daycounter
|
|
if (val == 0)
|
|
daycounter = 1; //yes, 1
|
|
else
|
|
daycounter++;
|
|
break;
|
|
case 12: //multiplier, in percent
|
|
multiplier = ((float)val)/100;
|
|
break;
|
|
case 13: //bank preset
|
|
bc = VLC->objh->banksInfo[index][val];
|
|
break;
|
|
case 14:
|
|
reset (val%100);
|
|
break;
|
|
case 15:
|
|
bc = NULL;
|
|
break;
|
|
case 16: //remove rewards from Derelict Ship
|
|
artifacts.clear();
|
|
break;
|
|
case 17: //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 (i, bc->guards[0].first, bc->guards[0].second / 5 );
|
|
setCreature (4, bc->guards[0].first + upgraded, bc->guards[0].second / 5 );
|
|
break;
|
|
case 4:
|
|
{
|
|
std::vector< std::pair <ui16, ui32> >::const_iterator it;
|
|
if (bc->guards.back().second) //all stacks are present
|
|
{
|
|
for (it = bc->guards.begin(); it != bc->guards.end(); it++)
|
|
{
|
|
setCreature (stacksCount(), it->first, it->second);
|
|
}
|
|
}
|
|
else if (bc->guards[2].second)//Wraiths are present, split two stacks in Crypt
|
|
{
|
|
setCreature (0, bc->guards[0].first, bc->guards[0].second / 2 );
|
|
setCreature (1, bc->guards[1].first, bc->guards[1].second / 2);
|
|
setCreature (2, bc->guards[2].first + upgraded, bc->guards[2].second);
|
|
setCreature (3, bc->guards[1].first, bc->guards[1].second / 2 );
|
|
setCreature (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 (2*i, bc->guards[0].first, bc->guards[0].second / 3);
|
|
for (int i = 0; i < 2; ++i) //zombies
|
|
setCreature (2*i+1, bc->guards[1].first, bc->guards[1].second / 2);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
tlog2 << "Error: Unexpected army data: " << bc->guards.size() <<" items found";
|
|
return;
|
|
}
|
|
}
|
|
break;
|
|
case 18: //add Artifact
|
|
{
|
|
int id = cb->getArtSync(val, CArtifact::ART_TREASURE);
|
|
artifacts.push_back (id);
|
|
cb->erasePickedArt(id);
|
|
break;
|
|
}
|
|
case 19: //add Artifact
|
|
{
|
|
int id = cb->getArtSync(val, CArtifact::ART_MINOR);
|
|
artifacts.push_back (id);
|
|
cb->erasePickedArt(id);
|
|
break;
|
|
}
|
|
case 20: //add Artifact
|
|
{
|
|
int id = cb->getArtSync(val, CArtifact::ART_MAJOR);
|
|
artifacts.push_back (id);
|
|
cb->erasePickedArt(id);
|
|
break;
|
|
}
|
|
case 21: //add Artifact
|
|
{
|
|
int id = cb->getArtSync(val, CArtifact::ART_RELIC);
|
|
artifacts.push_back (id);
|
|
cb->erasePickedArt(id);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void CBank::newTurn() const
|
|
{
|
|
if (bc == NULL)
|
|
{
|
|
if (cb->getDate(0) == 1)
|
|
initialize(); //initialize on first day
|
|
else if (daycounter >= 28 && (subID < 13 || subID > 16)) //no reset for Emissaries
|
|
{
|
|
initialize();
|
|
cb->setObjProperty (id, 11, 0); //daycounter 0
|
|
if (ID == 24 && cb->getDate(0) > 1)
|
|
{
|
|
cb->setObjProperty (id, 12, 0);//ugly hack to make derelict ships usable only once
|
|
cb->setObjProperty (id, 16, 0);
|
|
}
|
|
}
|
|
else
|
|
cb->setObjProperty (id, 11, 1); //daycounter++
|
|
}
|
|
}
|
|
void CBank::onHeroVisit (const CGHeroInstance * h) const
|
|
{
|
|
if (bc)
|
|
{
|
|
int banktext = 0;
|
|
switch (ID)
|
|
{
|
|
case 16: //generic bank
|
|
banktext = 32;
|
|
break;
|
|
case 24:
|
|
banktext = 41;
|
|
break;
|
|
case 25: //utopia
|
|
banktext = 47;
|
|
break;
|
|
case 84: //crypt
|
|
banktext = 119;
|
|
break;
|
|
case 85: //shipwreck
|
|
banktext = 122;
|
|
break;
|
|
}
|
|
BlockingDialog bd (true, false);
|
|
bd.player = h->getOwner();
|
|
bd.soundID = soundBase::DANGER;
|
|
bd.text << VLC->generaltexth->advobtxt[banktext];
|
|
if (ID == 16)
|
|
bd.text.addReplacement(VLC->objh->creBanksNames[index]);
|
|
cb->showBlockingDialog (&bd, boost::bind (&CBank::fightGuards, this, h, _1));
|
|
}
|
|
else
|
|
{
|
|
InfoWindow iw;
|
|
iw.soundID = soundBase::GRAVEYARD;
|
|
iw.player = h->getOwner();
|
|
if (ID == 84) //morale penalty for empty Crypt
|
|
{
|
|
GiveBonus gbonus;
|
|
gbonus.id = h->id;
|
|
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::fightGuards (const CGHeroInstance * h, ui32 accept) const
|
|
{
|
|
if (accept)
|
|
{
|
|
cb->startBattleI (h, this, boost::bind (&CBank::endBattle, this, h, _1), true);
|
|
}
|
|
}
|
|
void CBank::endBattle (const CGHeroInstance *h, const BattleResult *result) const
|
|
{
|
|
if (result->winner == 0)
|
|
{
|
|
int textID = -1;
|
|
InfoWindow iw;
|
|
iw.player = h->getOwner();
|
|
MetaString loot;
|
|
|
|
switch (ID)
|
|
{
|
|
case 16: case 25:
|
|
textID = 34;
|
|
break;
|
|
case 24: //derelict ship
|
|
if (multiplier)
|
|
textID = 43;
|
|
else
|
|
{
|
|
GiveBonus gbonus;
|
|
gbonus.id = h->id;
|
|
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 84: //Crypt
|
|
if (bc->resources.size() != 0)
|
|
textID = 121;
|
|
else
|
|
{
|
|
iw.components.push_back (Component (Component::MORALE, 0 , -1, 0));
|
|
GiveBonus gbonus;
|
|
gbonus.id = h->id;
|
|
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 85: //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 (h->getOwner(), it, bc->resources[it]);
|
|
}
|
|
}
|
|
}
|
|
//grant artifacts
|
|
for (std::vector<ui32>::const_iterator it = artifacts.begin(); it != artifacts.end(); it++)
|
|
{
|
|
iw.components.push_back (Component (Component::ARTIFACT, *it, 0, 0));
|
|
loot << "%s";
|
|
loot.addReplacement(MetaString::ART_NAMES, *it);
|
|
cb->giveHeroNewArtifact (h, VLC->arth->artifacts[*it], -2);
|
|
}
|
|
//display loot
|
|
if (!iw.components.empty())
|
|
{
|
|
if (textID == 34)
|
|
{
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, 34);//Heaving defeated %s, you discover %s
|
|
iw.text.addReplacement(MetaString::CRE_PL_NAMES, result->casualties[1].begin()->first);
|
|
iw.text.addReplacement(loot.buildList());
|
|
}
|
|
else
|
|
iw.text.addTxt (MetaString::ADVOB_TXT, textID);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
loot.clear();
|
|
iw.components.clear();
|
|
iw.text.clear();
|
|
|
|
//grant creatures
|
|
CCreatureSet ourArmy;
|
|
for (std::vector< std::pair <ui16, ui32> >::const_iterator it = bc->creatures.begin(); it != bc->creatures.end(); it++)
|
|
{
|
|
int slot = ourArmy.getSlotFor(it->first);
|
|
ourArmy.addToSlot(slot, it->first, it->second);
|
|
}
|
|
for (TSlots::const_iterator i = ourArmy.Slots().begin(); i != ourArmy.Slots().end(); i++)
|
|
{
|
|
iw.components.push_back(Component(*i->second));
|
|
loot << "%s";
|
|
loot.addReplacement(*i->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(h->name);
|
|
cb->showInfoDialog(&iw);
|
|
cb->giveCreatures(this, h, ourArmy, false);
|
|
}
|
|
cb->setObjProperty (id, 15, 0); //bc = NULL
|
|
}
|
|
|
|
}
|
|
|
|
void CGPyramid::initObj()
|
|
{
|
|
std::vector<ui16> available;
|
|
cb->getAllowedSpells (available, 5);
|
|
if (available.size())
|
|
{
|
|
bc = VLC->objh->banksInfo[21].front(); //TODO: remove hardcoded value?
|
|
spell = (available[rand()%available.size()]);
|
|
}
|
|
else
|
|
{
|
|
tlog1 <<"No spells available for Pyramid! Object set to empty.\n";
|
|
}
|
|
setPropertyDer (17,ran()); //set guards at game start
|
|
}
|
|
const std::string & CGPyramid::getHoverText() const
|
|
{
|
|
hoverName = VLC->objh->creBanksNames[21];
|
|
if (bc == NULL)
|
|
hoverName += " " + VLC->generaltexth->allTexts[352];
|
|
else
|
|
hoverName += " " + VLC->generaltexth->allTexts[353];
|
|
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, boost::bind (&CBank::fightGuards, this, h, _1));
|
|
}
|
|
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,VLC->generaltexth->arraytxt[70]);
|
|
gb.id = h->id;
|
|
cb->giveHeroBonus(&gb);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
}
|
|
|
|
void CGPyramid::endBattle (const CGHeroInstance *h, const BattleResult *result) const
|
|
{
|
|
if (result->winner == 0)
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->getOwner();
|
|
iw.text.addTxt (MetaString::ADVOB_TXT, 106);
|
|
iw.text.addTxt (MetaString::SPELL_NAME, spell);
|
|
if (!h->getArt(17))
|
|
iw.text.addTxt (MetaString::ADVOB_TXT, 109); //no spellbook
|
|
else if (h->getSecSkillLevel(CGHeroInstance::WISDOM) < 3)
|
|
iw.text.addTxt (MetaString::ADVOB_TXT, 108); //no expert Wisdom
|
|
else
|
|
{
|
|
std::set<ui32> spells;
|
|
spells.insert (spell);
|
|
cb->changeSpells (h->id, true, spells);
|
|
iw.components.push_back(Component (Component::SPELL, spell, 0, 0));
|
|
}
|
|
cb->showInfoDialog(&iw);
|
|
cb->setObjProperty (id, 15, 0);
|
|
}
|
|
}
|
|
void CGKeys::setPropertyDer (ui8 what, ui32 val) //101-108 - enable key for player 1-8
|
|
{
|
|
if (what >= 101 && what <= (100 + PLAYER_LIMIT))
|
|
playerKeyMap.find(what-101)->second.insert((ui8)val);
|
|
}
|
|
|
|
bool CGKeys::wasMyColorVisited (int player) const
|
|
{
|
|
if (vstd::contains(playerKeyMap[player], subID)) //creates set if it's not there
|
|
return true;
|
|
else
|
|
return false;
|
|
}
|
|
|
|
const std::string & CGKeymasterTent::getHoverText() const
|
|
{
|
|
hoverName = VLC->generaltexth->names[ID];
|
|
if (wasMyColorVisited (cb->getCurrentPlayer()) )//TODO: use local player, not current
|
|
hoverName += "\n" + VLC->generaltexth->allTexts[352];
|
|
else
|
|
hoverName += "\n" + VLC->generaltexth->allTexts[353];
|
|
return hoverName;
|
|
}
|
|
|
|
void CGKeymasterTent::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
InfoWindow iw;
|
|
iw.soundID = soundBase::CAVEHEAD;
|
|
iw.player = h->getOwner();
|
|
if (!wasMyColorVisited (h->getOwner()) )
|
|
{
|
|
cb->setObjProperty(id, h->tempOwner+101, subID);
|
|
iw.text << std::pair<ui8,ui32>(11,19);
|
|
}
|
|
else
|
|
iw.text << std::pair<ui8,ui32>(11,20);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
void CGBorderGuard::initObj()
|
|
{
|
|
blockVisit = true;
|
|
}
|
|
|
|
const std::string & CGBorderGuard::getHoverText() const
|
|
{
|
|
hoverName = VLC->generaltexth->names[ID];
|
|
if (wasMyColorVisited (cb->getCurrentPlayer()) )//TODO: use local player, not current
|
|
hoverName += "\n" + VLC->generaltexth->allTexts[352];
|
|
else
|
|
hoverName += "\n" + VLC->generaltexth->allTexts[353];
|
|
return hoverName;
|
|
}
|
|
|
|
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, boost::bind (&CGBorderGuard::openGate, this, h, _1));
|
|
}
|
|
else
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->getOwner();
|
|
iw.soundID = soundBase::CAVEHEAD;
|
|
iw.text << std::pair<ui8,ui32>(11,18);
|
|
cb->showInfoDialog (&iw);
|
|
}
|
|
}
|
|
|
|
void CGBorderGuard::openGate(const CGHeroInstance *h, ui32 accept) const
|
|
{
|
|
if (accept)
|
|
cb->removeObject(id);
|
|
}
|
|
|
|
void CGBorderGate::onHeroVisit( const CGHeroInstance * h ) const //TODO: passability
|
|
{
|
|
if (!wasMyColorVisited (h->getOwner()) )
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->getOwner();
|
|
iw.text << std::pair<ui8,ui32>(11,18);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
}
|
|
|
|
ui8 CGBorderGate::getPassableness() const
|
|
{
|
|
ui8 ret = 0;
|
|
for (int i = 0; i < PLAYER_LIMIT; i++)
|
|
ret |= wasMyColorVisited(i)<<i;
|
|
return ret;
|
|
}
|
|
|
|
void CGMagi::initObj()
|
|
{
|
|
if (ID == 27)
|
|
{
|
|
blockVisit = true;
|
|
eyelist[subID].push_back(id);
|
|
}
|
|
}
|
|
void CGMagi::onHeroVisit(const CGHeroInstance * h) const
|
|
{
|
|
if (ID == 37)
|
|
{
|
|
InfoWindow iw;
|
|
CenterView cv;
|
|
FoWChange fw;
|
|
cv.player = iw.player = fw.player = h->tempOwner;
|
|
|
|
iw.soundID = soundBase::LIGHTHOUSE;
|
|
iw.player = h->tempOwner;
|
|
iw.text.addTxt (MetaString::ADVOB_TXT, 61);
|
|
cb->showInfoDialog(&iw);
|
|
|
|
fw.mode = 1;
|
|
std::vector<si32>::iterator it;
|
|
for (it = eyelist[subID].begin(); it < eyelist[subID].end(); it++)
|
|
{
|
|
const CGObjectInstance *eye = cb->getObj(*it);
|
|
|
|
cb->getTilesInRange (fw.tiles, eye->pos, 10, h->tempOwner, 1);
|
|
cb->sendAndApply(&fw);
|
|
cv.pos = eye->pos;
|
|
cv.focusTime = 2000;
|
|
cb->sendAndApply(&cv);
|
|
}
|
|
cv.pos = h->getPosition(false);
|
|
cb->sendAndApply(&cv);
|
|
}
|
|
else if (ID == 27)
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.text.addTxt (MetaString::ADVOB_TXT, 48);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
|
|
}
|
|
void CGBoat::initObj()
|
|
{
|
|
hero = NULL;
|
|
}
|
|
|
|
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(11,133);
|
|
}
|
|
else
|
|
{
|
|
giveDummyBonus(h->id, Bonus::ONE_BATTLE);
|
|
int xp = 0;
|
|
|
|
for (TSlots::const_iterator 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(11,132);
|
|
iw.text.addReplacement(xp);
|
|
cb->changePrimSkill(h->ID, 4, xp, false);
|
|
}
|
|
else
|
|
{
|
|
iw.text.addTxt(11,134);
|
|
}
|
|
}
|
|
cb->showInfoDialog(&iw);
|
|
|
|
}
|
|
|
|
//bool IShipyard::validLocation() const
|
|
//{
|
|
// std::vector<int3> 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<int3> offsets;
|
|
getOutOffsets(offsets);
|
|
|
|
for (int i = 0; i < offsets.size(); ++i)
|
|
{
|
|
const TerrainTile *tile = IObjectInterface::cb->getTile(o->pos + offsets[i]);
|
|
if (tile) //tile is in the map
|
|
{
|
|
if (tile->tertype == TerrainTile::water && (!tile->blocked || tile->blockingObjects.front()->ID == 8)) //and is water and is not blocked or is blocked by boat
|
|
return o->pos + offsets[i];
|
|
}
|
|
}
|
|
return int3 (-1,-1,-1);
|
|
}
|
|
|
|
int IBoatGenerator::state() const
|
|
{
|
|
int3 tile = bestLocation();
|
|
const TerrainTile *t = IObjectInterface::cb->getTile(tile);
|
|
if(!t)
|
|
return 2; //no available water
|
|
else if(!t->blockingObjects.size())
|
|
return 0; //OK
|
|
else if(t->blockingObjects.front()->ID == 8)
|
|
return 1; //blocked with boat
|
|
else
|
|
return 2; //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(state())
|
|
{
|
|
case 1:
|
|
out.addTxt(MetaString::GENERAL_TXT, 51);
|
|
break;
|
|
case 2:
|
|
if(visitor)
|
|
{
|
|
out.addTxt(MetaString::GENERAL_TXT, 134);
|
|
out.addReplacement(visitor->name);
|
|
}
|
|
else
|
|
out.addTxt(MetaString::ADVOB_TXT, 189);
|
|
break;
|
|
case 3:
|
|
tlog1 << "Shipyard without water!!! " << o->pos << "\t" << o->id << std::endl;
|
|
return;
|
|
}
|
|
}
|
|
|
|
void IShipyard::getBoatCost( std::vector<si32> &cost ) const
|
|
{
|
|
cost.resize(RESOURCE_QUANTITY);
|
|
cost[0] = 10;
|
|
cost[6] = 1000;
|
|
}
|
|
|
|
IShipyard::IShipyard(const CGObjectInstance *O)
|
|
: IBoatGenerator(O)
|
|
{
|
|
}
|
|
|
|
IShipyard * IShipyard::castFrom( CGObjectInstance *obj )
|
|
{
|
|
if(!obj)
|
|
return NULL;
|
|
|
|
if(obj->ID == TOWNI_TYPE)
|
|
{
|
|
return static_cast<CGTownInstance*>(obj);
|
|
}
|
|
else if(obj->ID == 87)
|
|
{
|
|
return static_cast<CGShipyard*>(obj);
|
|
}
|
|
else
|
|
{
|
|
//tlog1 << "Cannot cast to IShipyard object with ID " << obj->ID << std::endl;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
const IShipyard * IShipyard::castFrom( const CGObjectInstance *obj )
|
|
{
|
|
return castFrom(const_cast<CGObjectInstance*>(obj));
|
|
}
|
|
|
|
CGShipyard::CGShipyard()
|
|
:IShipyard(this)
|
|
{
|
|
}
|
|
|
|
void CGShipyard::getOutOffsets( std::vector<int3> &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(id, h->tempOwner);
|
|
|
|
int s = state();
|
|
if(s)
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = tempOwner;
|
|
getProblemText(iw.text, h);
|
|
cb->showInfoDialog(&iw);
|
|
}
|
|
else
|
|
{
|
|
OpenWindow ow;
|
|
ow.id1 = id;
|
|
ow.id2 = h->id;
|
|
ow.window = OpenWindow::SHIPYARD_WINDOW;
|
|
cb->sendAndApply(&ow);
|
|
}
|
|
}
|
|
|
|
void CCartographer::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
if (!hasVisited (h->getOwner()) ) //if hero has not visited yet this cartographer
|
|
{
|
|
if (cb->getResource(h->tempOwner, 6) >= 1000) //if he can afford a map
|
|
{
|
|
//ask if he wants to buy one
|
|
int text;
|
|
switch (subID)
|
|
{
|
|
case 0:
|
|
text = 25;
|
|
break;
|
|
case 1:
|
|
text = 26;
|
|
break;
|
|
case 2:
|
|
text = 27;
|
|
break;
|
|
default:
|
|
tlog2 << "Unrecognized subtype of cartographer" << std::endl;
|
|
}
|
|
BlockingDialog bd (true, false);
|
|
bd.player = h->getOwner();
|
|
bd.soundID = soundBase::LIGHTHOUSE;
|
|
bd.text.addTxt (MetaString::ADVOB_TXT, text);
|
|
cb->showBlockingDialog (&bd, boost::bind (&CCartographer::buyMap, this, h, _1));
|
|
}
|
|
else //if he cannot afford
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->getOwner();
|
|
iw.soundID = soundBase::CAVEHEAD;
|
|
iw.text << std::pair<ui8,ui32>(11,28);
|
|
cb->showInfoDialog (&iw);
|
|
}
|
|
}
|
|
else //if he already visited carographer
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->getOwner();
|
|
iw.soundID = soundBase::CAVEHEAD;
|
|
iw.text << std::pair<ui8,ui32>(11,24);
|
|
cb->showInfoDialog (&iw);
|
|
}
|
|
}
|
|
|
|
void CCartographer::buyMap (const CGHeroInstance *h, ui32 accept) const
|
|
{
|
|
if (accept) //if hero wants to buy map
|
|
{
|
|
cb->giveResource (h->tempOwner, 6, -1000);
|
|
FoWChange fw;
|
|
fw.mode = 1;
|
|
fw.player = h->tempOwner;
|
|
|
|
//subIDs of different types of cartographers:
|
|
//water = 0; land = 1; underground = 2;
|
|
cb->getAllTiles (fw.tiles, h->tempOwner, subID - 1, !subID + 1); //reveal appropriate tiles
|
|
cb->sendAndApply (&fw);
|
|
cb->setObjProperty (id, 10, h->tempOwner);
|
|
}
|
|
}
|
|
|
|
void CGDenOfthieves::onHeroVisit (const CGHeroInstance * h) const
|
|
{
|
|
cb->showThievesGuildWindow(id);
|
|
}
|
|
|
|
void CGObelisk::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
TeamState *ts = cb->gameState()->getPlayerTeam(h->tempOwner);
|
|
assert(ts);
|
|
int team = ts->id;
|
|
|
|
if(!hasVisited(team))
|
|
{
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, 96);
|
|
cb->sendAndApply(&iw);
|
|
|
|
cb->setObjProperty(id,20,team); //increment general visited obelisks counter
|
|
|
|
OpenWindow ow;
|
|
ow.id1 = h->tempOwner;
|
|
ow.window = OpenWindow::PUZZLE_MAP;
|
|
cb->sendAndApply(&ow);
|
|
|
|
cb->setObjProperty(id,10,team); //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
|
|
{
|
|
hoverName = VLC->generaltexth->names[ID];
|
|
if(hasVisited(cb->getCurrentPlayer()))
|
|
hoverName += " " + VLC->generaltexth->allTexts[352]; //not visited
|
|
else
|
|
hoverName += " " + VLC->generaltexth->allTexts[353]; //visited
|
|
return hoverName;
|
|
}
|
|
|
|
void CGObelisk::setPropertyDer( ui8 what, ui32 val )
|
|
{
|
|
CPlayersVisited::setPropertyDer(what, val);
|
|
switch(what)
|
|
{
|
|
case 20:
|
|
assert(val < PLAYER_LIMIT);
|
|
visited[val]++;
|
|
|
|
if(visited[val] > obeliskCount)
|
|
{
|
|
tlog0 << "Error: Visited " << visited[val] << "\t\t" << obeliskCount << std::endl;
|
|
assert(0);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
void CGLighthouse::onHeroVisit( const CGHeroInstance * h ) const
|
|
{
|
|
if(h->tempOwner != tempOwner)
|
|
{
|
|
ui8 oldOwner = tempOwner;
|
|
cb->setOwner(id,h->tempOwner); //not ours? flag it!
|
|
|
|
|
|
InfoWindow iw;
|
|
iw.player = h->tempOwner;
|
|
iw.text.addTxt(MetaString::ADVOB_TXT, 69);
|
|
iw.soundID = soundBase::LIGHTHOUSE;
|
|
cb->sendAndApply(&iw);
|
|
|
|
giveBonusTo(h->tempOwner);
|
|
|
|
if(oldOwner < PLAYER_LIMIT) //remove bonus from old owner
|
|
{
|
|
RemoveBonus rb(RemoveBonus::PLAYER);
|
|
rb.whoID = oldOwner;
|
|
rb.source = Bonus::OBJECT;
|
|
rb.id = id;
|
|
cb->sendAndApply(&rb);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CGLighthouse::initObj()
|
|
{
|
|
if(tempOwner < PLAYER_LIMIT)
|
|
{
|
|
giveBonusTo(tempOwner);
|
|
}
|
|
}
|
|
|
|
const std::string & CGLighthouse::getHoverText() const
|
|
{
|
|
hoverName = VLC->generaltexth->names[ID];
|
|
//TODO: owned by %s player
|
|
return hoverName;
|
|
}
|
|
|
|
void CGLighthouse::giveBonusTo( ui8 player ) const
|
|
{
|
|
GiveBonus gb(GiveBonus::PLAYER);
|
|
gb.bonus.type = Bonus::SEA_MOVEMENT;
|
|
gb.bonus.val = 500;
|
|
gb.id = player;
|
|
gb.bonus.duration = Bonus::PERMANENT;
|
|
gb.bonus.source = Bonus::OBJECT;
|
|
gb.bonus.sid = id;
|
|
cb->sendAndApply(&gb);
|
|
}
|
|
|
|
void CArmedInstance::randomizeArmy(int type)
|
|
{
|
|
int max = VLC->creh->creatures.size();
|
|
for (TSlots::iterator j = stacks.begin(); j != stacks.end();j++)
|
|
{
|
|
int randID = j->second->idRand;
|
|
if(randID > max)
|
|
{
|
|
if(randID % 2)
|
|
j->second->setType(VLC->townh->towns[type].basicCreatures[(randID-197) / 2 -1]);
|
|
else
|
|
j->second->setType(VLC->townh->towns[type].upgradedCreatures[(randID-197) / 2 -1]);
|
|
|
|
randID = -1;
|
|
}
|
|
|
|
assert(j->second->armyObj == this);
|
|
}
|
|
return;
|
|
}
|
|
|
|
CArmedInstance::CArmedInstance()
|
|
{
|
|
battle = NULL;
|
|
}
|
|
|
|
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 = bonuses.getFirst(Selector::sourceType(Bonus::ARMY) && 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
|
|
bool canMix = hasBonusOfType(Bonus::NONEVIL_ALIGNMENT_MIX);
|
|
std::set<si8> factions;
|
|
for(TSlots::const_iterator i=Slots().begin(); i!=Slots().end(); i++)
|
|
{
|
|
// Take Angelic Alliance troop-mixing freedom of non-evil, non-Conflux units into account.
|
|
const si8 faction = i->second->type->faction;
|
|
if (canMix
|
|
&& ((faction >= 0 && faction <= 2) || faction == 6 || faction == 7))
|
|
{
|
|
factions.insert(0); // Insert a single faction of the affected group, Castle will do.
|
|
}
|
|
else
|
|
{
|
|
factions.insert(faction);
|
|
}
|
|
}
|
|
|
|
if(factions.size() == 1)
|
|
{
|
|
b->val = +1;
|
|
b->description = VLC->generaltexth->arraytxt[115]; //All troops of one alignment +1
|
|
}
|
|
else
|
|
{
|
|
b->val = 2-factions.size();
|
|
b->description = boost::str(boost::format(VLC->generaltexth->arraytxt[114]) % factions.size() % b->val); //Troops of %d alignments %d
|
|
}
|
|
boost::algorithm::trim(b->description);
|
|
|
|
//-1 modifier for any Necropolis unit in army
|
|
const ui8 UNDEAD_MODIFIER_ID = -2;
|
|
Bonus *undeadModifier = bonuses.getFirst(Selector::source(Bonus::ARMY, UNDEAD_MODIFIER_ID));
|
|
if(vstd::contains(factions,4))
|
|
{
|
|
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 < 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 mode) const
|
|
{
|
|
switch(mode)
|
|
{
|
|
case RESOURCE_RESOURCE:
|
|
{
|
|
float effectiveness = std::min(((float)getMarketEfficiency()+1.0f) / 20.0f, 0.5f);
|
|
|
|
float 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.5f;
|
|
val2 = 1;
|
|
}
|
|
}
|
|
break;
|
|
case CREATURE_RESOURCE:
|
|
{
|
|
const float effectivenessArray[] = {0, 0.3, 0.45, 0.50, 0.65, 0.7, 0.85, 0.9, 1};
|
|
float effectiveness = effectivenessArray[std::min(getMarketEfficiency(), 8)];
|
|
|
|
float 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.5f;
|
|
val2 = 1;
|
|
}
|
|
}
|
|
break;
|
|
case RESOURCE_PLAYER:
|
|
val1 = 1;
|
|
val2 = 1;
|
|
break;
|
|
case RESOURCE_ARTIFACT:
|
|
{
|
|
float effectiveness = std::min(((float)getMarketEfficiency()+3.0f) / 20.0f, 0.6f);
|
|
float 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;
|
|
|
|
assert(g >= r); //should we allow artifacts cheaper than unit of resource?
|
|
val1 = (g / r) + 0.5f;
|
|
val2 = 1;
|
|
}
|
|
break;
|
|
case ARTIFACT_RESOURCE:
|
|
{
|
|
float effectiveness = std::min(((float)getMarketEfficiency()+3.0f) / 20.0f, 0.6f);
|
|
float 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 = (r / g) + 0.5f;
|
|
}
|
|
break;
|
|
case CREATURE_EXP:
|
|
{
|
|
val1 = 1;
|
|
val2 = (VLC->creh->creatures[id1]->AIValue / 40) * 5;
|
|
}
|
|
break;
|
|
case 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 mode) const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
int IMarket::availableUnits(EMarketMode mode, int marketItemSerial) const
|
|
{
|
|
switch(mode)
|
|
{
|
|
case RESOURCE_RESOURCE:
|
|
case ARTIFACT_RESOURCE:
|
|
case CREATURE_RESOURCE:
|
|
return -1;
|
|
default:
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
std::vector<int> IMarket::availableItemsIds(EMarketMode mode) const
|
|
{
|
|
std::vector<int> ret;
|
|
switch(mode)
|
|
{
|
|
case RESOURCE_RESOURCE:
|
|
case ARTIFACT_RESOURCE:
|
|
case CREATURE_RESOURCE:
|
|
for (int i = 0; i < 7; i++)
|
|
ret.push_back(i);
|
|
default:
|
|
break;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
const IMarket * IMarket::castFrom(const CGObjectInstance *obj)
|
|
{
|
|
switch(obj->ID)
|
|
{
|
|
case TOWNI_TYPE:
|
|
return static_cast<const CGTownInstance*>(obj);
|
|
case 2: //Altar of Sacrifice
|
|
case 7: //Black Market
|
|
case 99: //Trading Post
|
|
case 221: //Trading Post (snow)
|
|
case 213: //Freelancer's Guild
|
|
return static_cast<const CGMarket*>(obj);
|
|
case 104: //University
|
|
return static_cast<const CGUniversity*>(obj);
|
|
default:
|
|
tlog1 << "Cannot cast to IMarket object with ID " << obj->ID << std::endl;
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
IMarket::IMarket(const CGObjectInstance *O)
|
|
:o(O)
|
|
{
|
|
|
|
}
|
|
|
|
std::vector<EMarketMode> IMarket::availableModes() const
|
|
{
|
|
std::vector<EMarketMode> ret;
|
|
for (int i = 0; i < MARTKET_AFTER_LAST_PLACEHOLDER; i++)
|
|
if(allowsTrade((EMarketMode)i))
|
|
ret.push_back((EMarketMode)i);
|
|
|
|
return ret;
|
|
}
|
|
|
|
void CGMarket::onHeroVisit(const CGHeroInstance * h) const
|
|
{
|
|
OpenWindow ow;
|
|
ow.id1 = id;
|
|
ow.id2 = h->id;
|
|
ow.window = OpenWindow::MARKET_WINDOW;
|
|
cb->sendAndApply(&ow);
|
|
}
|
|
|
|
int CGMarket::getMarketEfficiency() const
|
|
{
|
|
return 5;
|
|
}
|
|
|
|
bool CGMarket::allowsTrade(EMarketMode mode) const
|
|
{
|
|
switch(mode)
|
|
{
|
|
case RESOURCE_RESOURCE:
|
|
case RESOURCE_PLAYER:
|
|
switch(ID)
|
|
{
|
|
case 99: //Trading Post
|
|
case 221: //Trading Post (snow)
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
case CREATURE_RESOURCE:
|
|
return ID == 213; //Freelancer's Guild
|
|
//case ARTIFACT_RESOURCE:
|
|
case RESOURCE_ARTIFACT:
|
|
return ID == 7; //Black Market
|
|
case ARTIFACT_EXP:
|
|
case CREATURE_EXP:
|
|
return ID == 2; //TODO? check here for alignment of visiting hero? - would not be coherent with other checks here
|
|
case RESOURCE_SKILL:
|
|
return ID == 104;//University
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
int CGMarket::availableUnits(EMarketMode mode, int marketItemSerial) const
|
|
{
|
|
return -1;
|
|
}
|
|
|
|
std::vector<int> CGMarket::availableItemsIds(EMarketMode mode) const
|
|
{
|
|
switch(mode)
|
|
{
|
|
case RESOURCE_RESOURCE:
|
|
case RESOURCE_PLAYER:
|
|
return IMarket::availableItemsIds(mode);
|
|
default:
|
|
return std::vector<int>();
|
|
}
|
|
}
|
|
|
|
CGMarket::CGMarket()
|
|
:IMarket(this)
|
|
{
|
|
}
|
|
|
|
std::vector<int> CGBlackMarket::availableItemsIds(EMarketMode mode) const
|
|
{
|
|
switch(mode)
|
|
{
|
|
case ARTIFACT_RESOURCE:
|
|
return IMarket::availableItemsIds(mode);
|
|
case RESOURCE_ARTIFACT:
|
|
{
|
|
std::vector<int> ret;
|
|
BOOST_FOREACH(const CArtifact *a, artifacts)
|
|
if(a)
|
|
ret.push_back(a->id);
|
|
else
|
|
ret.push_back(-1);
|
|
return ret;
|
|
}
|
|
default:
|
|
return std::vector<int>();
|
|
}
|
|
}
|
|
|
|
void CGBlackMarket::newTurn() const
|
|
{
|
|
if(cb->getDate(2) != 1)
|
|
return;
|
|
|
|
SetAvailableArtifacts saa;
|
|
saa.id = id;
|
|
cb->pickAllowedArtsSet(saa.arts);
|
|
cb->sendAndApply(&saa);
|
|
}
|
|
|
|
void CGUniversity::initObj()
|
|
{
|
|
std::vector <int> toChoose;
|
|
for (int i=0; i<SKILL_QUANTITY; i++)
|
|
if (cb->isAllowed(2,i))
|
|
toChoose.push_back(i);
|
|
if (toChoose.size() < 4)
|
|
{
|
|
tlog0<<"Warning: less then 4 available skills was found by University initializer!\n";
|
|
return;
|
|
}
|
|
|
|
for (int i=0; i<4; i++)//get 4 skills
|
|
{
|
|
int skillPos = ran()%toChoose.size();
|
|
skills.push_back(toChoose[skillPos]);//move it to selected
|
|
toChoose.erase(toChoose.begin()+skillPos);//remove from list
|
|
}
|
|
}
|
|
|
|
std::vector<int> CGUniversity::availableItemsIds(EMarketMode mode) const
|
|
{
|
|
switch (mode)
|
|
{
|
|
case RESOURCE_SKILL:
|
|
return skills;
|
|
|
|
default:
|
|
return std::vector <int> ();
|
|
}
|
|
}
|
|
|
|
void CGUniversity::onHeroVisit(const CGHeroInstance * h) const
|
|
{
|
|
OpenWindow ow;
|
|
ow.id1 = id;
|
|
ow.id2 = h->id;
|
|
ow.window = OpenWindow::UNIVERSITY_WINDOW;
|
|
cb->sendAndApply(&ow);
|
|
}
|
|
|
|
const CArtifactInstance* CArtifactSet::getArt(ui16 pos, bool excludeLocked /*= true*/) const
|
|
{
|
|
if(const ArtSlotInfo *si = getSlot(pos))
|
|
{
|
|
if(si->artifact && (!excludeLocked || !si->locked))
|
|
return si->artifact;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
CArtifactInstance* CArtifactSet::getArt(ui16 pos, bool excludeLocked /*= true*/)
|
|
{
|
|
return const_cast<CArtifactInstance*>((const_cast<const CArtifactSet*>(this))->getArt(pos, excludeLocked));
|
|
}
|
|
|
|
si32 CArtifactSet::getArtPos(int aid, bool onlyWorn /*= true*/) const
|
|
{
|
|
for(std::map<ui16, ArtSlotInfo>::const_iterator i = artifactsWorn.begin(); i != artifactsWorn.end(); i++)
|
|
if(i->second.artifact->artType->id == aid)
|
|
return i->first;
|
|
|
|
if(onlyWorn)
|
|
return -1;
|
|
|
|
for(int i = 0; i < artifactsInBackpack.size(); i++)
|
|
if(artifactsInBackpack[i].artifact->artType->id == aid)
|
|
return Arts::BACKPACK_START + i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
si32 CArtifactSet::getArtPos(const CArtifactInstance *art) const
|
|
{
|
|
for(std::map<ui16, ArtSlotInfo>::const_iterator i = artifactsWorn.begin(); i != artifactsWorn.end(); i++)
|
|
if(i->second.artifact == art)
|
|
return i->first;
|
|
|
|
for(int i = 0; i < artifactsInBackpack.size(); i++)
|
|
if(artifactsInBackpack[i].artifact == art)
|
|
return Arts::BACKPACK_START + i;
|
|
|
|
return -1;
|
|
}
|
|
|
|
const CArtifactInstance * CArtifactSet::getArtByInstanceId(int artInstId) const
|
|
{
|
|
for(std::map<ui16, ArtSlotInfo>::const_iterator i = artifactsWorn.begin(); i != artifactsWorn.end(); i++)
|
|
if(i->second.artifact->id == artInstId)
|
|
return i->second.artifact;
|
|
|
|
for(int i = 0; i < artifactsInBackpack.size(); i++)
|
|
if(artifactsInBackpack[i].artifact->id == artInstId)
|
|
return artifactsInBackpack[i].artifact;
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool CArtifactSet::hasArt(ui32 aid, bool onlyWorn /*= false*/) const
|
|
{
|
|
return getArtPos(aid, onlyWorn) != -1;
|
|
}
|
|
|
|
const ArtSlotInfo * CArtifactSet::getSlot(ui16 pos) const
|
|
{
|
|
if(vstd::contains(artifactsWorn, pos))
|
|
return &artifactsWorn[pos];
|
|
if(pos >= Arts::AFTER_LAST )
|
|
{
|
|
int backpackPos = (int)pos - Arts::BACKPACK_START;
|
|
if(backpackPos < 0 || backpackPos >= artifactsInBackpack.size())
|
|
return NULL;
|
|
else
|
|
return &artifactsInBackpack[backpackPos];
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
bool CArtifactSet::isPositionFree(ui16 pos, bool onlyLockCheck /*= false*/) const
|
|
{
|
|
if(const ArtSlotInfo *s = getSlot(pos))
|
|
return (onlyLockCheck || !s->artifact) && !s->locked;
|
|
|
|
return true; //no slot means not used
|
|
}
|
|
|
|
si32 CArtifactSet::getArtTypeId(ui16 pos) const
|
|
{
|
|
const CArtifactInstance * const a = getArt(pos);
|
|
if(!a)
|
|
{
|
|
tlog2 << (dynamic_cast<const CGHeroInstance*>(this))->name << " has no artifact at " << pos << " (getArtTypeId)\n";
|
|
return -1;
|
|
}
|
|
return a->artType->id;
|
|
}
|
|
|
|
CArtifactSet::~CArtifactSet()
|
|
{
|
|
|
|
}
|
|
|
|
ArtSlotInfo & CArtifactSet::retreiveNewArtSlot(ui16 slot)
|
|
{
|
|
assert(!vstd::contains(artifactsWorn, slot));
|
|
ArtSlotInfo &ret = slot < Arts::BACKPACK_START
|
|
? artifactsWorn[slot]
|
|
: *artifactsInBackpack.insert(artifactsInBackpack.begin() + (slot - Arts::BACKPACK_START), ArtSlotInfo());
|
|
|
|
return ret;
|
|
}
|
|
|
|
void CArtifactSet::setNewArtSlot(ui16 slot, CArtifactInstance *art, bool locked)
|
|
{
|
|
ArtSlotInfo &asi = retreiveNewArtSlot(slot);
|
|
asi.artifact = art;
|
|
asi.locked = locked;
|
|
}
|
|
|
|
void CArtifactSet::eraseArtSlot(ui16 slot)
|
|
{
|
|
if(slot < Arts::BACKPACK_START)
|
|
{
|
|
artifactsWorn.erase(slot);
|
|
}
|
|
else
|
|
{
|
|
slot -= Arts::BACKPACK_START;
|
|
artifactsInBackpack.erase(artifactsInBackpack.begin() + slot);
|
|
}
|
|
}
|