1
0
mirror of https://github.com/vcmi/vcmi.git synced 2024-12-24 22:14:36 +02:00

Win/loss conditions based on logical expressions, yet another large

changeset:
- victory/defeat will be detected using triggered events
- vcmi will convert h3 conditions into set of triggered events
- it is possible to either change number of days without towns or even
remove this loss condition completely
- possibility of custom win/loss text and icons in pregame (no longer
connected to win/loss conditions)

Still missing:
- No interface to pass custom events/victory conditions into the game 
- AI would benefit from improvemets (handle all victory conditions,
select best one to fulfill)
- You have X days till defeat message still hardcoded to 7 days
This commit is contained in:
Ivan Savenko 2013-12-29 11:27:38 +00:00
parent d2d1a2f544
commit 0c5be52a42
15 changed files with 827 additions and 542 deletions

View File

@ -135,27 +135,56 @@ namespace Goals
TSubgoal Win::whatToDoToAchieve() TSubgoal Win::whatToDoToAchieve()
{ {
const VictoryCondition &vc = cb->getMapHeader()->victoryCondition; auto toBool = [=](const EventCondition &)
EVictoryConditionType::EVictoryConditionType cond = vc.condition;
if(!vc.appliesToAI)
{ {
//TODO deduce victory from human loss condition // TODO: proper implementation
cond = EVictoryConditionType::WINSTANDARD; // Right now even already fulfilled goals will be included into generated list
// Proper check should test if event condition is already fulfilled
// Easiest way to do this is to call CGameState::checkForVictory but this function should not be
// used on client side or in AI code
return false;
};
std::vector<EventCondition> goals;
for (const TriggeredEvent & event : cb->getMapHeader()->triggeredEvents)
{
//TODO: try to eliminate human player(s) using loss conditions that have isHuman element
if (event.effect.type == EventEffect::VICTORY)
{
boost::range::copy(event.trigger.getFulfillmentCandidates(toBool), std::back_inserter(goals));
}
} }
switch(cond) //TODO: instead of returning first encountered goal AI should generate list of possible subgoals
for (const EventCondition & goal : goals)
{ {
case EVictoryConditionType::ARTIFACT: switch(goal.condition)
return sptr (Goals::GetArtOfType(vc.objectId)); {
case EVictoryConditionType::BEATHERO: case EventCondition::HAVE_ARTIFACT:
return sptr (Goals::GetObj(vc.obj->id.getNum())); return sptr (Goals::GetArtOfType(goal.objectType));
case EVictoryConditionType::BEATMONSTER: case EventCondition::DESTROY:
return sptr (Goals::GetObj(vc.obj->id.getNum())); {
case EVictoryConditionType::BUILDCITY: if (goal.object)
//TODO build castle/capitol {
return sptr (Goals::GetObj(goal.object->id.getNum()));
}
else
{
// TODO: destroy all objects of type goal.objectType
// This situation represents "kill all creatures" condition from H3
break; break;
case EVictoryConditionType::BUILDGRAIL: }
}
case EventCondition::HAVE_BUILDING:
{
// TODO build other buildings apart from Grail
// goal.objectType = buidingID to build
// goal.object = optional, town in which building should be built
// Represents "Improve town" condition from H3 (but unlike H3 it consists from 2 separate conditions)
if (goal.objectType == BuildingID::GRAIL)
{ {
if(auto h = ai->getHeroWithGrail()) if(auto h = ai->getHeroWithGrail())
{ {
@ -182,40 +211,65 @@ TSubgoal Win::whatToDoToAchieve()
} }
} }
double ratio = 0; double ratio = 0;
// maybe make this check a bit more complex? For example:
// 0.75 -> dig randomly withing 3 tiles radius
// 0.85 -> radius now 2 tiles
// 0.95 -> 1 tile radius, position is fully known
// AFAIK H3 AI does something like this
int3 grailPos = cb->getGrailPos(ratio); int3 grailPos = cb->getGrailPos(ratio);
if(ratio > 0.99) if(ratio > 0.99)
{ {
return sptr (Goals::DigAtTile(grailPos)); return sptr (Goals::DigAtTile(grailPos));
} //TODO: use FIND_OBJ } //TODO: use FIND_OBJ
else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks
{
return sptr (Goals::GetObj(obj->id.getNum())); return sptr (Goals::GetObj(obj->id.getNum()));
}
else else
return sptr (Goals::Explore()); return sptr (Goals::Explore());
} }
break; break;
case EVictoryConditionType::CAPTURECITY: }
return sptr (Goals::GetObj(vc.obj->id.getNum())); case EventCondition::CONTROL:
case EVictoryConditionType::GATHERRESOURCE: {
return sptr (Goals::CollectRes(static_cast<Res::ERes>(vc.objectId), vc.count)); if (goal.object)
{
return sptr (Goals::GetObj(goal.object->id.getNum()));
}
else
{
//TODO: control all objects of type "goal.objectType"
// Represents H3 condition "Flag all mines"
break;
}
}
case EventCondition::HAVE_RESOURCES:
//TODO mines? piles? marketplace? //TODO mines? piles? marketplace?
//save? //save?
return sptr (Goals::CollectRes(static_cast<Res::ERes>(goal.objectType), goal.value));
case EventCondition::HAVE_CREATURES:
return sptr (Goals::GatherTroops(goal.objectType, goal.value));
case EventCondition::TRANSPORT:
{
//TODO. merge with bring Grail to town? So AI will first dig grail, then transport it using this goal and builds it
// Represents "transport artifact" condition:
// goal.objectType = type of artifact
// goal.object = destination-town where artifact should be transported
break; break;
case EVictoryConditionType::GATHERTROOP: }
return sptr (Goals::GatherTroops(vc.objectId, vc.count)); case EventCondition::STANDARD_WIN:
break;
case EVictoryConditionType::TAKEDWELLINGS:
break;
case EVictoryConditionType::TAKEMINES:
break;
case EVictoryConditionType::TRANSPORTITEM:
break;
case EVictoryConditionType::WINSTANDARD:
return sptr (Goals::Conquer()); return sptr (Goals::Conquer());
// Conditions that likely don't need any implementation
case EventCondition::DAYS_PASSED:
break; // goal.value = number of days for condition to trigger
case EventCondition::DAYS_WITHOUT_TOWN:
break; // goal.value = number of days to trigger this
case EventCondition::IS_HUMAN:
break; // Should be only used in calculation of candidates (see toBool lambda)
default: default:
assert(0); assert(0);
} }
}
return sptr (Goals::Invalid()); return sptr (Goals::Invalid());
} }

View File

@ -2107,7 +2107,8 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
if(player == playerID) if(player == playerID)
{ {
if(victoryLossCheckResult.loss()) showInfoDialog(CGI->generaltexth->allTexts[95]); if(victoryLossCheckResult.loss())
showInfoDialog(CGI->generaltexth->allTexts[95]);
if(LOCPLINT == this) if(LOCPLINT == this)
{ {
@ -2154,18 +2155,9 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
{ {
if(victoryLossCheckResult.loss() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME) //enemy has lost if(victoryLossCheckResult.loss() && cb->getPlayerStatus(playerID) == EPlayerStatus::INGAME) //enemy has lost
{ {
std::string txt; std::string str = victoryLossCheckResult.messageToSelf;
if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER) boost::algorithm::replace_first(str, "%s", CGI->generaltexth->capColors[player.getNum()]);
{ showInfoDialog(str, std::vector<CComponent*>(1, new CComponent(CComponent::flag, player.getNum(), 0)));
txt = CGI->generaltexth->allTexts[8]; // %s's heroes have abandoned him, and he is banished from this land.
}
else
{
txt = CGI->generaltexth->allTexts[5]; // %s has been vanquished!
}
boost::algorithm::replace_first(txt, "%s", CGI->generaltexth->capColors[player.getNum()]);
showInfoDialog(txt,std::vector<CComponent*>(1, new CComponent(CComponent::flag, player.getNum(), 0)));
} }
} }
} }

View File

@ -1437,18 +1437,10 @@ void SelectionTab::printMaps(SDL_Surface *to)
blitAtLoc(format->ourImages[temp].bitmap, 88, 117 + line * 25, to); blitAtLoc(format->ourImages[temp].bitmap, 88, 117 + line * 25, to);
//victory conditions //victory conditions
if (currentItem->mapHeader->victoryCondition.condition == EVictoryConditionType::WINSTANDARD) blitAtLoc(CGP->victory->ourImages[currentItem->mapHeader->victoryIconIndex].bitmap, 306, 117 + line * 25, to);
temp = 11;
else
temp = currentItem->mapHeader->victoryCondition.condition;
blitAtLoc(CGP->victory->ourImages[temp].bitmap, 306, 117 + line * 25, to);
//loss conditions //loss conditions
if (currentItem->mapHeader->lossCondition.typeOfLossCon == ELossConditionType::LOSSSTANDARD) blitAtLoc(CGP->loss->ourImages[currentItem->mapHeader->defeatIconIndex].bitmap, 339, 117 + line * 25, to);
temp=3;
else
temp=currentItem->mapHeader->lossCondition.typeOfLossCon;
blitAtLoc(CGP->loss->ourImages[temp].bitmap, 339, 117 + line * 25, to);
} }
else //if campaign else //if campaign
{ {
@ -2023,26 +2015,14 @@ void InfoCard::showAll(SDL_Surface * to)
CDefHandler * loss = CGP ? CGP->loss : CDefHandler::giveDef("SCNRLOSS.DEF"); CDefHandler * loss = CGP ? CGP->loss : CDefHandler::giveDef("SCNRLOSS.DEF");
CDefHandler * victory = CGP ? CGP->victory : CDefHandler::giveDef("SCNRVICT.DEF"); CDefHandler * victory = CGP ? CGP->victory : CDefHandler::giveDef("SCNRVICT.DEF");
CMapHeader * header = SEL->current->mapHeader.get();
//victory conditions //victory conditions
temp = SEL->current->mapHeader->victoryCondition.condition+1; printAtLoc(header->victoryMessage, 60, 307, FONT_SMALL, Colors::WHITE, to);
if (temp>20) temp=0; blitAtLoc(victory->ourImages[header->victoryIconIndex].bitmap, 24, 302, to); //victory cond descr
std::string sss = CGI->generaltexth->victoryConditions[temp];
if (temp && SEL->current->mapHeader->victoryCondition.allowNormalVictory) sss+= "/" + CGI->generaltexth->victoryConditions[0];
printAtLoc(sss, 60, 307, FONT_SMALL, Colors::WHITE, to);
temp = SEL->current->mapHeader->victoryCondition.condition;
if (temp>12) temp=11;
blitAtLoc(victory->ourImages[temp].bitmap, 24, 302, to); //victory cond descr
//loss conditoins //loss conditoins
temp = SEL->current->mapHeader->lossCondition.typeOfLossCon+1; printAtLoc(header->defeatMessage, 60, 366, FONT_SMALL, Colors::WHITE, to);
if (temp>20) temp=0; blitAtLoc(loss->ourImages[header->defeatIconIndex].bitmap, 24, 359, to); //loss cond
sss = CGI->generaltexth->lossCondtions[temp];
printAtLoc(sss, 60, 366, FONT_SMALL, Colors::WHITE, to);
temp=SEL->current->mapHeader->lossCondition.typeOfLossCon;
if (temp>12) temp=3;
blitAtLoc(loss->ourImages[temp].bitmap, 24, 359, to); //loss cond
if (!CGP) if (!CGP)
{ {
@ -2987,7 +2967,7 @@ bool mapSorter::operator()(const CMapInfo *aaa, const CMapInfo *bbb)
return (a->version<b->version); return (a->version<b->version);
break; break;
case _loscon: //by loss conditions case _loscon: //by loss conditions
return (a->lossCondition.typeOfLossCon<b->lossCondition.typeOfLossCon); return (a->defeatMessage < b->defeatMessage);
break; break;
case _playerAm: //by player amount case _playerAm: //by player amount
int playerAmntB,humenPlayersB,playerAmntA,humenPlayersA; int playerAmntB,humenPlayersB,playerAmntA,humenPlayersA;
@ -3008,7 +2988,7 @@ bool mapSorter::operator()(const CMapInfo *aaa, const CMapInfo *bbb)
return (a->width<b->width); return (a->width<b->width);
break; break;
case _viccon: //by victory conditions case _viccon: //by victory conditions
return (a->victoryCondition.condition < b->victoryCondition.condition); return (a->victoryMessage < b->victoryMessage);
break; break;
case _name: //by name case _name: //by name
return boost::ilexicographical_compare(a->name, b->name); return boost::ilexicographical_compare(a->name, b->name);

View File

@ -2225,42 +2225,61 @@ bool CGameState::checkForVisitableDir( const int3 & src, const TerrainTile *pom,
EVictoryLossCheckResult CGameState::checkForVictoryAndLoss(PlayerColor player) const EVictoryLossCheckResult CGameState::checkForVictoryAndLoss(PlayerColor player) const
{ {
auto result = checkForVictory(player); const std::string & messageWonSelf = VLC->generaltexth->allTexts[659];
if (result == EVictoryLossCheckResult::NO_VICTORY_OR_LOSS) result = checkForLoss(player); const std::string & messageWonOther = VLC->generaltexth->allTexts[5];
return result; const std::string & messageLostSelf = VLC->generaltexth->allTexts[7];
const std::string & messageLostOther = VLC->generaltexth->allTexts[8];
auto evaluateEvent = [=](const EventCondition & condition)
{
return this->checkForVictory(player, condition);
};
const PlayerState *p = CGameInfoCallback::getPlayer(player);
//cheater or tester, but has entered the code...
if (p->enteredWinningCheatCode)
return EVictoryLossCheckResult::victory(messageWonSelf, messageWonOther);
if (p->enteredLosingCheatCode)
return EVictoryLossCheckResult::defeat(messageLostSelf, messageLostOther);
for (const TriggeredEvent & event : map->triggeredEvents)
{
if ((event.trigger.test(evaluateEvent)))
{
if (event.effect.type == EventEffect::VICTORY)
return EVictoryLossCheckResult::victory(event.onFulfill, event.effect.toOtherMessage);
if (event.effect.type == EventEffect::DEFEAT)
return EVictoryLossCheckResult::defeat(event.onFulfill, event.effect.toOtherMessage);
}
}
if (checkForStandardLoss(player))
{
return EVictoryLossCheckResult::defeat(messageLostSelf, messageLostOther);
}
return EVictoryLossCheckResult();
} }
EVictoryLossCheckResult CGameState::checkForVictory( PlayerColor player ) const bool CGameState::checkForVictory( PlayerColor player, const EventCondition & condition ) const
{ {
const PlayerState *p = CGameInfoCallback::getPlayer(player); const PlayerState *p = CGameInfoCallback::getPlayer(player);
if(map->victoryCondition.condition == EVictoryConditionType::WINSTANDARD || map->victoryCondition.allowNormalVictory switch (condition.condition)
|| (!p->human && !map->victoryCondition.appliesToAI)) //if the special victory condition applies only to human, AI has the standard)
{ {
if(player == checkForStandardWin()) case EventCondition::STANDARD_WIN:
return EVictoryLossCheckResult::VICTORY_STANDARD; {
return player == checkForStandardWin();
} }
case EventCondition::HAVE_ARTIFACT: //check if any hero has winning artifact
if (p->enteredWinningCheatCode)
{ //cheater or tester, but has entered the code...
if(map->victoryCondition.condition == EVictoryConditionType::WINSTANDARD)
return EVictoryLossCheckResult::VICTORY_STANDARD;
else
return EVictoryLossCheckResult::VICTORY_SPECIAL;
}
if(p->human || map->victoryCondition.appliesToAI)
{ {
switch(map->victoryCondition.condition)
{
case EVictoryConditionType::ARTIFACT:
//check if any hero has winning artifact
for(auto & elem : p->heroes) for(auto & elem : p->heroes)
if(elem->hasArt(map->victoryCondition.objectId)) if(elem->hasArt(condition.objectType))
return EVictoryLossCheckResult::VICTORY_SPECIAL; return true;
return false;
break; }
case EventCondition::HAVE_CREATURES:
case EVictoryConditionType::GATHERTROOP:
{ {
//check if in players armies there is enough creatures //check if in players armies there is enough creatures
int total = 0; //creature counter int total = 0; //creature counter
@ -2272,94 +2291,85 @@ EVictoryLossCheckResult CGameState::checkForVictory( PlayerColor player ) const
&& (ai = dynamic_cast<const CArmedInstance*>(map->objects[i].get()))) //contains army && (ai = dynamic_cast<const CArmedInstance*>(map->objects[i].get()))) //contains army
{ {
for(auto & elem : ai->Slots()) //iterate through army for(auto & elem : ai->Slots()) //iterate through army
if(elem.second->type->idNumber == map->victoryCondition.objectId) //it's searched creature if(elem.second->type->idNumber == condition.objectType) //it's searched creature
total += elem.second->count; total += elem.second->count;
} }
} }
return total >= condition.value;
if(total >= map->victoryCondition.count)
return EVictoryLossCheckResult::VICTORY_SPECIAL;
} }
break; case EventCondition::HAVE_RESOURCES:
case EVictoryConditionType::GATHERRESOURCE:
if(p->resources[map->victoryCondition.objectId] >= map->victoryCondition.count)
return EVictoryLossCheckResult::VICTORY_SPECIAL;
break;
case EVictoryConditionType::BUILDCITY:
{ {
const CGTownInstance *t = static_cast<const CGTownInstance *>(map->victoryCondition.obj); return p->resources[condition.objectType] >= condition.value;
if(t->tempOwner == player && t->fortLevel()-1 >= map->victoryCondition.objectId && t->hallLevel()-1 >= map->victoryCondition.count)
return EVictoryLossCheckResult::VICTORY_SPECIAL;
} }
break; case EventCondition::HAVE_BUILDING:
case EVictoryConditionType::BUILDGRAIL:
for(const CGTownInstance *t : map->towns)
if((t == map->victoryCondition.obj || !map->victoryCondition.obj)
&& t->tempOwner == player
&& t->hasBuilt(BuildingID::GRAIL))
return EVictoryLossCheckResult::VICTORY_SPECIAL;
break;
case EVictoryConditionType::BEATHERO:
if(map->victoryCondition.obj->tempOwner >= PlayerColor::PLAYER_LIMIT) //target hero not present on map
return EVictoryLossCheckResult::VICTORY_SPECIAL;
break;
case EVictoryConditionType::CAPTURECITY:
{ {
if(map->victoryCondition.obj->tempOwner == player) const CGTownInstance *t = static_cast<const CGTownInstance *>(condition.object);
return EVictoryLossCheckResult::VICTORY_SPECIAL; return (t->tempOwner == player && t->hasBuilt(BuildingID(condition.objectType)));
} }
break; case EventCondition::DESTROY:
case EVictoryConditionType::BEATMONSTER:
if(!getObj(map->victoryCondition.obj->id)) //target monster not present on map
return EVictoryLossCheckResult::VICTORY_SPECIAL;
break;
case EVictoryConditionType::TAKEDWELLINGS:
for(auto & elem : map->objects)
{ {
if(elem && elem->tempOwner != player) //check not flagged objs if (condition.object) // mode A - destroy specific object of this type
{ {
switch(elem->ID) if (auto hero = dynamic_cast<const CGHeroInstance*>(condition.object))
return boost::range::find(gs->map->heroesOnMap, hero) == gs->map->heroesOnMap.end();
else
return getObj(condition.object->id) == nullptr;
}
else
{ {
case Obj::CREATURE_GENERATOR1: case Obj::CREATURE_GENERATOR2: for(auto & elem : map->objects) // mode B - destroy all objects of this type
case Obj::CREATURE_GENERATOR3: case Obj::CREATURE_GENERATOR4:
case Obj::RANDOM_DWELLING: case Obj::RANDOM_DWELLING_LVL: case Obj::RANDOM_DWELLING_FACTION:
return EVictoryLossCheckResult::NO_VICTORY_OR_LOSS; //found not flagged dwelling - player not won
}
}
}
return EVictoryLossCheckResult::VICTORY_SPECIAL;
case EVictoryConditionType::TAKEMINES:
for(auto & elem : map->objects)
{ {
if(elem && elem->tempOwner != player) //check not flagged objs if(elem && elem->ID == condition.objectType)
return false;
}
return true;
}
}
case EventCondition::CONTROL:
{ {
switch(elem->ID) if (condition.object) // mode A - flag one specific object, like town
{ {
case Obj::MINE: case Obj::ABANDONED_MINE: return condition.object->tempOwner == player;
return EVictoryLossCheckResult::NO_VICTORY_OR_LOSS; //found not flagged mine - player not won
} }
} else
}
return EVictoryLossCheckResult::VICTORY_SPECIAL;
case EVictoryConditionType::TRANSPORTITEM:
{ {
const CGTownInstance *t = static_cast<const CGTownInstance *>(map->victoryCondition.obj); for(auto & elem : map->objects) // mode B - flag all objects of this type
if((t->visitingHero && t->visitingHero->hasArt(map->victoryCondition.objectId))
|| (t->garrisonHero && t->garrisonHero->hasArt(map->victoryCondition.objectId)))
{ {
return EVictoryLossCheckResult::VICTORY_SPECIAL; //check not flagged objs
if(elem && elem->tempOwner != player && elem->ID == condition.objectType)
return false;
}
return true;
} }
} }
break; case EventCondition::TRANSPORT:
{
const CGTownInstance *t = static_cast<const CGTownInstance *>(condition.object);
if((t->visitingHero && t->visitingHero->hasArt(condition.objectType))
|| (t->garrisonHero && t->garrisonHero->hasArt(condition.objectType)))
{
return true;
}
return false;
}
case EventCondition::DAYS_PASSED:
{
return gs->day > condition.value;
}
case EventCondition::IS_HUMAN:
{
return p->human ? condition.value == 1 : condition.value == 0;
}
case EventCondition::DAYS_WITHOUT_TOWN:
{
if (p->daysWithoutCastle)
return p->daysWithoutCastle.get() >= condition.value;
else
return false;
} }
} }
assert(0);
return EVictoryLossCheckResult::NO_VICTORY_OR_LOSS; return false;
} }
PlayerColor CGameState::checkForStandardWin() const PlayerColor CGameState::checkForStandardWin() const
@ -2597,46 +2607,6 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
#undef FILL_FIELD #undef FILL_FIELD
} }
EVictoryLossCheckResult CGameState::checkForLoss( PlayerColor player ) const
{
const PlayerState *p = CGameInfoCallback::getPlayer(player);
//if(map->lossCondition.typeOfLossCon == lossStandard)
if(checkForStandardLoss(player))
return EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS;
if (p->enteredLosingCheatCode)
{
return EVictoryLossCheckResult::LOSS_SPECIAL;
}
if(p->human) //special loss condition applies only to human player
{
switch(map->lossCondition.typeOfLossCon)
{
case ELossConditionType::LOSSCASTLE:
case ELossConditionType::LOSSHERO:
{
const CGObjectInstance *obj = map->lossCondition.obj;
assert(obj);
if(obj->tempOwner != player)
return EVictoryLossCheckResult::LOSS_SPECIAL;
}
break;
case ELossConditionType::TIMEEXPIRES:
if(map->lossCondition.timeLimit < day)
return EVictoryLossCheckResult::LOSS_SPECIAL;
break;
}
}
if(p->towns.empty() && p->daysWithoutCastle && *p->daysWithoutCastle >= 6 && currentPlayer != player)
{
return EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER;
}
return EVictoryLossCheckResult::NO_VICTORY_OR_LOSS;
}
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > CGameState::unusedHeroesFromPool() std::map<ui32, ConstTransitivePtr<CGHeroInstance> > CGameState::unusedHeroesFromPool()
{ {
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = hpool.heroesPool; std::map<ui32, ConstTransitivePtr<CGHeroInstance> > pool = hpool.heroesPool;
@ -3479,21 +3449,16 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance
allowEmbarkAndDisembark = true; allowEmbarkAndDisembark = true;
} }
const EVictoryLossCheckResult EVictoryLossCheckResult::NO_VICTORY_OR_LOSS = EVictoryLossCheckResult(0); EVictoryLossCheckResult::EVictoryLossCheckResult() :
const EVictoryLossCheckResult EVictoryLossCheckResult::VICTORY_STANDARD = EVictoryLossCheckResult(1); intValue(0)
const EVictoryLossCheckResult EVictoryLossCheckResult::VICTORY_SPECIAL = EVictoryLossCheckResult(2);
const EVictoryLossCheckResult EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS = EVictoryLossCheckResult(3);
const EVictoryLossCheckResult EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER = EVictoryLossCheckResult(4);
const EVictoryLossCheckResult EVictoryLossCheckResult::LOSS_SPECIAL = EVictoryLossCheckResult(5);
EVictoryLossCheckResult::EVictoryLossCheckResult() : intValue(0)
{ {
} }
EVictoryLossCheckResult::EVictoryLossCheckResult(si32 intValue) : intValue(intValue) EVictoryLossCheckResult::EVictoryLossCheckResult(si32 intValue, std::string toSelf, std::string toOthers) :
messageToSelf(toSelf),
messageToOthers(toOthers),
intValue(intValue)
{ {
} }
bool EVictoryLossCheckResult::operator==(EVictoryLossCheckResult const & other) const bool EVictoryLossCheckResult::operator==(EVictoryLossCheckResult const & other) const
@ -3508,27 +3473,31 @@ bool EVictoryLossCheckResult::operator!=(EVictoryLossCheckResult const & other)
bool EVictoryLossCheckResult::victory() const bool EVictoryLossCheckResult::victory() const
{ {
return *this == VICTORY_STANDARD || *this == VICTORY_SPECIAL; return intValue == VICTORY;
} }
bool EVictoryLossCheckResult::loss() const bool EVictoryLossCheckResult::loss() const
{ {
return !victory(); return intValue == DEFEAT;
} }
std::string EVictoryLossCheckResult::toString() const EVictoryLossCheckResult EVictoryLossCheckResult::invert()
{ {
if(*this == EVictoryLossCheckResult::NO_VICTORY_OR_LOSS) return "No victory or loss"; return EVictoryLossCheckResult(-intValue, messageToOthers, messageToSelf);
else if(*this == EVictoryLossCheckResult::VICTORY_STANDARD) return "Victory standard"; }
else if(*this == EVictoryLossCheckResult::VICTORY_SPECIAL) return "Victory special";
else if(*this == EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS) return "Loss standard heroes and towns"; EVictoryLossCheckResult EVictoryLossCheckResult::victory(std::string toSelf, std::string toOthers)
else if(*this == EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER) return "Loss standard towns and time over"; {
else if(*this == EVictoryLossCheckResult::LOSS_SPECIAL) return "Loss special"; return EVictoryLossCheckResult(VICTORY, toSelf, toOthers);
else return "Unknown type"; }
EVictoryLossCheckResult EVictoryLossCheckResult::defeat(std::string toSelf, std::string toOthers)
{
return EVictoryLossCheckResult(DEFEAT, toSelf, toOthers);
} }
std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult) std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult)
{ {
os << victoryLossCheckResult.toString(); os << victoryLossCheckResult.messageToSelf;
return os; return os;
} }

View File

@ -357,28 +357,34 @@ struct BattleInfo;
class DLL_LINKAGE EVictoryLossCheckResult class DLL_LINKAGE EVictoryLossCheckResult
{ {
public: public:
static const EVictoryLossCheckResult NO_VICTORY_OR_LOSS; static EVictoryLossCheckResult victory(std::string toSelf, std::string toOthers);
static const EVictoryLossCheckResult VICTORY_STANDARD; static EVictoryLossCheckResult defeat(std::string toSelf, std::string toOthers);
static const EVictoryLossCheckResult VICTORY_SPECIAL;
static const EVictoryLossCheckResult LOSS_STANDARD_HEROES_AND_TOWNS;
static const EVictoryLossCheckResult LOSS_STANDARD_TOWNS_AND_TIME_OVER;
static const EVictoryLossCheckResult LOSS_SPECIAL;
EVictoryLossCheckResult(); EVictoryLossCheckResult();
bool operator==(EVictoryLossCheckResult const & other) const; bool operator==(EVictoryLossCheckResult const & other) const;
bool operator!=(EVictoryLossCheckResult const & other) const; bool operator!=(EVictoryLossCheckResult const & other) const;
bool victory() const; bool victory() const;
bool loss() const; bool loss() const;
std::string toString() const;
EVictoryLossCheckResult invert();
std::string messageToSelf;
std::string messageToOthers;
template <typename Handler> void serialize(Handler &h, const int version) template <typename Handler> void serialize(Handler &h, const int version)
{ {
h & intValue; h & intValue & messageToSelf & messageToOthers;
} }
private: private:
EVictoryLossCheckResult(si32 intValue); enum EResult
si32 intValue; {
DEFEAT = -1,
INGAME = 0,
VICTORY= +1
};
EVictoryLossCheckResult(si32 intValue, std::string toSelf, std::string toOthers);
si32 intValue; // uses EResult
}; };
DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult); DLL_LINKAGE std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult);
@ -426,7 +432,13 @@ public:
void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists void calculatePaths(const CGHeroInstance *hero, CPathsInfo &out, int3 src = int3(-1,-1,-1), int movement = -1); //calculates possible paths for hero, by default uses current hero position and movement left; returns pointer to newly allocated CPath or nullptr if path does not exists
int3 guardingCreaturePosition (int3 pos) const; int3 guardingCreaturePosition (int3 pos) const;
std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const; std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
// ----- victory, loss condition checks -----
EVictoryLossCheckResult checkForVictoryAndLoss(PlayerColor player) const; EVictoryLossCheckResult checkForVictoryAndLoss(PlayerColor player) const;
bool checkForVictory(PlayerColor player, const EventCondition & condition) const; //checks if given player is winner
PlayerColor checkForStandardWin() const; //returns color of player that accomplished standard victory conditions or 255 (NEUTRAL) if no winner
bool checkForStandardLoss(PlayerColor player) const; //checks if given player lost the game
void obtainPlayersStats(SThievesGuildInfo & tgi, int level); //fills tgi with info about other players that is available at given level of thieves' guild void obtainPlayersStats(SThievesGuildInfo & tgi, int level); //fills tgi with info about other players that is available at given level of thieves' guild
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns std::map<ui32, ConstTransitivePtr<CGHeroInstance> > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns
@ -476,13 +488,6 @@ private:
void initMapObjects(); void initMapObjects();
void initVisitingAndGarrisonedHeroes(); void initVisitingAndGarrisonedHeroes();
// ----- victory, loss condition checks -----
EVictoryLossCheckResult checkForVictory(PlayerColor player) const; //checks if given player is winner
EVictoryLossCheckResult checkForLoss(PlayerColor player) const; //checks if given player is loser
PlayerColor checkForStandardWin() const; //returns color of player that accomplished standard victory conditions or 255 (NEUTRAL) if no winner
bool checkForStandardLoss(PlayerColor player) const; //checks if given player lost the game
// ----- bonus system handling ----- // ----- bonus system handling -----
void buildBonusSystemTree(); void buildBonusSystemTree();

View File

@ -3,6 +3,8 @@
#include "CDefObjInfoHandler.h" #include "CDefObjInfoHandler.h"
#include "JsonNode.h" #include "JsonNode.h"
#include "filesystem/Filesystem.h" #include "filesystem/Filesystem.h"
#include "filesystem/AdapterLoaders.h"
#include "filesystem/CFilesystemLoader.h"
#include "CCreatureHandler.h" #include "CCreatureHandler.h"
#include "CArtHandler.h" #include "CArtHandler.h"

View File

@ -290,17 +290,6 @@ public:
ID_LIKE_OPERATORS_DECLS(SecondarySkill, SecondarySkill::ESecondarySkill) ID_LIKE_OPERATORS_DECLS(SecondarySkill, SecondarySkill::ESecondarySkill)
namespace EVictoryConditionType
{
enum EVictoryConditionType { ARTIFACT, GATHERTROOP, GATHERRESOURCE, BUILDCITY, BUILDGRAIL, BEATHERO,
CAPTURECITY, BEATMONSTER, TAKEDWELLINGS, TAKEMINES, TRANSPORTITEM, WINSTANDARD = 255 };
}
namespace ELossConditionType
{
enum ELossConditionType { LOSSCASTLE, LOSSHERO, TIMEEXPIRES, LOSSSTANDARD = 255 };
}
namespace EAlignment namespace EAlignment
{ {
enum EAlignment { GOOD, EVIL, NEUTRAL }; enum EAlignment { GOOD, EVIL, NEUTRAL };

View File

@ -9,6 +9,7 @@ namespace LogicalExpressionDetail
template<typename ContainedClass> template<typename ContainedClass>
class ExpressionBase class ExpressionBase
{ {
public:
/// Possible logical operations, mostly needed to create different types for boost::variant /// Possible logical operations, mostly needed to create different types for boost::variant
enum EOperations enum EOperations
{ {
@ -16,7 +17,6 @@ namespace LogicalExpressionDetail
ALL_OF, ALL_OF,
NONE_OF NONE_OF
}; };
public:
template<EOperations tag> class Element; template<EOperations tag> class Element;
typedef Element<ANY_OF> OperatorAny; typedef Element<ANY_OF> OperatorAny;
@ -145,6 +145,44 @@ namespace LogicalExpressionDetail
} }
}; };
/// Simple foreach visitor
template <typename ContainedClass>
class ForEachVisitor : public boost::static_visitor<void>
{
typedef ExpressionBase<ContainedClass> Base;
std::function<void(typename Base::Value &)> visitor;
public:
ForEachVisitor(std::function<void(typename Base::Value &)> visitor):
visitor(visitor)
{}
//FIXME: duplicated code
void operator()(typename Base::OperatorAny & element) const
{
for (auto & entry : element.expressions)
boost::apply_visitor(*this, entry);
}
void operator()(typename Base::OperatorAll & element) const
{
for (auto & entry : element.expressions)
boost::apply_visitor(*this, entry);
}
void operator()(typename Base::OperatorNone & element) const
{
for (auto & entry : element.expressions)
boost::apply_visitor(*this, entry);
}
void operator()(typename Base::Value & element) const
{
visitor(element);
}
};
/// Json parser for expressions /// Json parser for expressions
template <typename ContainedClass> template <typename ContainedClass>
class Reader class Reader
@ -289,6 +327,18 @@ public:
std::swap(data, expr.data); std::swap(data, expr.data);
} }
Variant get()
{
return data;
}
/// Simple visitor that visits all entries in expression
void forEach(std::function<void(Value &)> visitor)
{
LogicalExpressionDetail::ForEachVisitor<Value> testVisitor(visitor);
boost::apply_visitor(testVisitor, data);
}
/// calculates if expression evaluates to "true". /// calculates if expression evaluates to "true".
/// Note: empty expressions always return true /// Note: empty expressions always return true
bool test(std::function<bool(const Value &)> toBool) const bool test(std::function<bool(const Value &)> toBool) const

View File

@ -1612,7 +1612,8 @@ DLL_LINKAGE void YourTurn::applyGs( CGameState *gs )
auto & playerState = gs->players[player]; auto & playerState = gs->players[player];
if(playerState.towns.empty()) if(playerState.towns.empty())
{ {
if(playerState.daysWithoutCastle) ++(*playerState.daysWithoutCastle); if(playerState.daysWithoutCastle)
++(*playerState.daysWithoutCastle);
else playerState.daysWithoutCastle = 0; else playerState.daysWithoutCastle = 0;
} }
else else

View File

@ -46,18 +46,18 @@ std::unordered_set<ResourceID> CMappedFileLoader::getFilteredFiles(std::function
CFilesystemList::CFilesystemList() CFilesystemList::CFilesystemList()
{ {
loaders = new std::vector<std::unique_ptr<ISimpleResourceLoader> >; //loaders = new std::vector<std::unique_ptr<ISimpleResourceLoader> >;
} }
CFilesystemList::~CFilesystemList() CFilesystemList::~CFilesystemList()
{ {
delete loaders; //delete loaders;
} }
std::unique_ptr<CInputStream> CFilesystemList::load(const ResourceID & resourceName) const std::unique_ptr<CInputStream> CFilesystemList::load(const ResourceID & resourceName) const
{ {
// load resource from last loader that have it (last overriden version) // load resource from last loader that have it (last overriden version)
for (auto & loader : boost::adaptors::reverse(*loaders)) for (auto & loader : boost::adaptors::reverse(loaders))
{ {
if (loader->existsResource(resourceName)) if (loader->existsResource(resourceName))
return loader->load(resourceName); return loader->load(resourceName);
@ -69,7 +69,7 @@ std::unique_ptr<CInputStream> CFilesystemList::load(const ResourceID & resourceN
bool CFilesystemList::existsResource(const ResourceID & resourceName) const bool CFilesystemList::existsResource(const ResourceID & resourceName) const
{ {
for (auto & loader : *loaders) for (auto & loader : loaders)
if (loader->existsResource(resourceName)) if (loader->existsResource(resourceName))
return true; return true;
return false; return false;
@ -91,7 +91,7 @@ std::unordered_set<ResourceID> CFilesystemList::getFilteredFiles(std::function<b
{ {
std::unordered_set<ResourceID> ret; std::unordered_set<ResourceID> ret;
for (auto & loader : *loaders) for (auto & loader : loaders)
for (auto & entry : loader->getFilteredFiles(filter)) for (auto & entry : loader->getFilteredFiles(filter))
ret.insert(entry); ret.insert(entry);
@ -101,7 +101,7 @@ std::unordered_set<ResourceID> CFilesystemList::getFilteredFiles(std::function<b
bool CFilesystemList::createResource(std::string filename, bool update) bool CFilesystemList::createResource(std::string filename, bool update)
{ {
logGlobal->traceStream()<< "Creating " << filename; logGlobal->traceStream()<< "Creating " << filename;
for (auto & loader : boost::adaptors::reverse(*loaders)) for (auto & loader : boost::adaptors::reverse(loaders))
{ {
if (writeableLoaders.count(loader.get()) != 0 // writeable, if (writeableLoaders.count(loader.get()) != 0 // writeable,
&& loader->createResource(filename, update)) // successfully created && loader->createResource(filename, update)) // successfully created
@ -123,7 +123,7 @@ std::vector<const ISimpleResourceLoader *> CFilesystemList::getResourcesWithName
{ {
std::vector<const ISimpleResourceLoader *> ret; std::vector<const ISimpleResourceLoader *> ret;
for (auto & loader : *loaders) for (auto & loader : loaders)
boost::range::copy(loader->getResourcesWithName(resourceName), std::back_inserter(ret)); boost::range::copy(loader->getResourcesWithName(resourceName), std::back_inserter(ret));
return ret; return ret;
@ -131,7 +131,7 @@ std::vector<const ISimpleResourceLoader *> CFilesystemList::getResourcesWithName
void CFilesystemList::addLoader(ISimpleResourceLoader * loader, bool writeable) void CFilesystemList::addLoader(ISimpleResourceLoader * loader, bool writeable)
{ {
loaders->push_back(std::unique_ptr<ISimpleResourceLoader>(loader)); loaders.push_back(std::unique_ptr<ISimpleResourceLoader>(loader));
if (writeable) if (writeable)
writeableLoaders.insert(loader); writeableLoaders.insert(loader);
} }

View File

@ -54,7 +54,7 @@ private:
class DLL_LINKAGE CFilesystemList : public ISimpleResourceLoader class DLL_LINKAGE CFilesystemList : public ISimpleResourceLoader
{ {
std::vector<std::unique_ptr<ISimpleResourceLoader> >* loaders; std::vector<std::unique_ptr<ISimpleResourceLoader> > loaders;
std::set<ISimpleResourceLoader *> writeableLoaders; std::set<ISimpleResourceLoader *> writeableLoaders;

View File

@ -3,9 +3,11 @@
#include "../CArtHandler.h" #include "../CArtHandler.h"
#include "../VCMI_Lib.h" #include "../VCMI_Lib.h"
#include "../CCreatureHandler.h"
#include "../CTownHandler.h" #include "../CTownHandler.h"
#include "../CHeroHandler.h" #include "../CHeroHandler.h"
#include "../CDefObjInfoHandler.h" #include "../CDefObjInfoHandler.h"
#include "../CGeneralTextHandler.h"
#include "../CSpellHandler.h" #include "../CSpellHandler.h"
#include "CMapEditManager.h" #include "CMapEditManager.h"
@ -59,17 +61,13 @@ bool PlayerInfo::hasCustomMainHero() const
return !mainCustomHeroName.empty() && mainCustomHeroPortrait != -1; return !mainCustomHeroName.empty() && mainCustomHeroPortrait != -1;
} }
LossCondition::LossCondition() : typeOfLossCon(ELossConditionType::LOSSSTANDARD), EventCondition::EventCondition(EWinLoseType condition):
pos(int3(-1, -1, -1)), timeLimit(-1), obj(nullptr) object(nullptr),
value(-1),
objectType(-1),
position(-1, -1, -1),
condition(condition)
{ {
}
VictoryCondition::VictoryCondition() : condition(EVictoryConditionType::WINSTANDARD),
allowNormalVictory(false), appliesToAI(false), pos(int3(-1, -1, -1)), objectId(0),
count(0), obj(nullptr)
{
} }
DisposedHero::DisposedHero() : heroId(0), portrait(255), players(0) DisposedHero::DisposedHero() : heroId(0), portrait(255), players(0)
@ -146,9 +144,44 @@ const int CMapHeader::MAP_SIZE_MIDDLE = 72;
const int CMapHeader::MAP_SIZE_LARGE = 108; const int CMapHeader::MAP_SIZE_LARGE = 108;
const int CMapHeader::MAP_SIZE_XLARGE = 144; const int CMapHeader::MAP_SIZE_XLARGE = 144;
void CMapHeader::setupEvents()
{
EventCondition victoryCondition(EventCondition::STANDARD_WIN);
EventCondition defeatCondition(EventCondition::DAYS_WITHOUT_TOWN);
defeatCondition.value = 7;
//Victory condition - defeat all
TriggeredEvent standardVictory;
standardVictory.effect.type = EventEffect::VICTORY;
standardVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[5];
standardVictory.identifier = "standardVictory";
standardVictory.description = ""; // TODO: display in quest window
standardVictory.onFulfill = VLC->generaltexth->allTexts[659];
standardVictory.trigger = EventExpression(victoryCondition);
//Loss condition - 7 days without town
TriggeredEvent standardDefeat;
standardDefeat.effect.type = EventEffect::DEFEAT;
standardDefeat.effect.toOtherMessage = VLC->generaltexth->allTexts[8];
standardDefeat.identifier = "standardDefeat";
standardDefeat.description = ""; // TODO: display in quest window
standardDefeat.onFulfill = VLC->generaltexth->allTexts[7];
standardDefeat.trigger = EventExpression(defeatCondition);
triggeredEvents.push_back(standardVictory);
triggeredEvents.push_back(standardDefeat);
victoryIconIndex = 11;
victoryMessage = VLC->generaltexth->victoryConditions[0];
defeatIconIndex = 3;
defeatMessage = VLC->generaltexth->lossCondtions[0];
}
CMapHeader::CMapHeader() : version(EMapFormat::SOD), height(72), width(72), CMapHeader::CMapHeader() : version(EMapFormat::SOD), height(72), width(72),
twoLevel(true), difficulty(1), levelLimit(0), howManyTeams(0), areAnyPlayers(false) twoLevel(true), difficulty(1), levelLimit(0), howManyTeams(0), areAnyPlayers(false)
{ {
setupEvents();
allowedHeroes = VLC->heroh->getDefaultAllowed(); allowedHeroes = VLC->heroh->getDefaultAllowed();
players.resize(PlayerColor::PLAYER_LIMIT_I); players.resize(PlayerColor::PLAYER_LIMIT_I);
} }
@ -272,29 +305,75 @@ bool CMap::isWaterTile(const int3 &pos) const
return isInTheMap(pos) && getTile(pos).terType == ETerrainType::WATER; return isInTheMap(pos) && getTile(pos).terType == ETerrainType::WATER;
} }
const CGObjectInstance * CMap::getObjectiveObjectFrom(int3 pos, bool lookForHero) const CGObjectInstance * CMap::getObjectiveObjectFrom(int3 pos, Obj::EObj type)
{ {
const std::vector<CGObjectInstance *> & objs = getTile(pos).visitableObjects; for (CGObjectInstance * object : getTile(pos).visitableObjects)
assert(objs.size());
if(objs.size() > 1 && lookForHero && objs.front()->ID != Obj::HERO)
{ {
assert(objs.back()->ID == Obj::HERO); if (object->ID == type)
return objs.back(); return object;
} }
else // possibly may trigger for empty placeholders in campaigns
return objs.front(); logGlobal->warnStream() << "Failed to find object of type " << int(type) << " at " << pos;
return nullptr;
} }
void CMap::checkForObjectives() void CMap::checkForObjectives()
{ {
if(isInTheMap(victoryCondition.pos)) // NOTE: probably should be moved to MapFormatH3M.cpp
for (TriggeredEvent & event : triggeredEvents)
{ {
victoryCondition.obj = getObjectiveObjectFrom(victoryCondition.pos, victoryCondition.condition == EVictoryConditionType::BEATHERO); auto patcher = [&](EventCondition & cond)
{
switch (cond.condition)
{
break; case EventCondition::HAVE_ARTIFACT:
boost::algorithm::replace_first(event.onFulfill, "%s", VLC->arth->artifacts[cond.objectType]->Name());
break; case EventCondition::HAVE_CREATURES:
boost::algorithm::replace_first(event.onFulfill, "%s", VLC->creh->creatures[cond.objectType]->nameSing);
boost::algorithm::replace_first(event.onFulfill, "%d", boost::lexical_cast<std::string>(cond.value));
break; case EventCondition::HAVE_RESOURCES:
boost::algorithm::replace_first(event.onFulfill, "%s", VLC->generaltexth->restypes[cond.objectType]);
boost::algorithm::replace_first(event.onFulfill, "%d", boost::lexical_cast<std::string>(cond.value));
break; case EventCondition::HAVE_BUILDING:
if (isInTheMap(cond.position))
cond.object = getObjectiveObjectFrom(cond.position, Obj::TOWN);
break; case EventCondition::CONTROL:
if (isInTheMap(cond.position))
cond.object = getObjectiveObjectFrom(cond.position, Obj::EObj(cond.objectType));
if (cond.object)
{
const CGTownInstance *town = dynamic_cast<const CGTownInstance*>(cond.object);
if (town)
boost::algorithm::replace_first(event.onFulfill, "%s", town->name);
const CGHeroInstance *hero = dynamic_cast<const CGHeroInstance*>(cond.object);
if (hero)
boost::algorithm::replace_first(event.onFulfill, "%s", hero->name);
} }
if(isInTheMap(lossCondition.pos)) break; case EventCondition::DESTROY:
if (isInTheMap(cond.position))
cond.object = getObjectiveObjectFrom(cond.position, Obj::EObj(cond.objectType));
if (cond.object)
{ {
lossCondition.obj = getObjectiveObjectFrom(lossCondition.pos, lossCondition.typeOfLossCon == ELossConditionType::LOSSHERO); const CGHeroInstance *hero = dynamic_cast<const CGHeroInstance*>(cond.object);
if (hero)
boost::algorithm::replace_first(event.onFulfill, "%s", hero->name);
}
break; case EventCondition::TRANSPORT:
cond.object = getObjectiveObjectFrom(cond.position, Obj::TOWN);
//break; case EventCondition::DAYS_PASSED:
//break; case EventCondition::IS_HUMAN:
//break; case EventCondition::DAYS_WITHOUT_TOWN:
//break; case EventCondition::STANDARD_WIN:
}
};
event.trigger.forEach(patcher);
} }
} }

View File

@ -16,6 +16,7 @@
#include "../ResourceSet.h" #include "../ResourceSet.h"
#include "../int3.h" #include "../int3.h"
#include "../GameConstants.h" #include "../GameConstants.h"
#include "../LogicalExpression.h"
class CArtifactInstance; class CArtifactInstance;
class CGDefInfo; class CGDefInfo;
@ -104,45 +105,82 @@ struct DLL_LINKAGE PlayerInfo
}; };
/// The loss condition describes the condition to lose the game. (e.g. lose all own heroes/castles) /// The loss condition describes the condition to lose the game. (e.g. lose all own heroes/castles)
struct DLL_LINKAGE LossCondition struct DLL_LINKAGE EventCondition
{ {
LossCondition(); enum EWinLoseType {
HAVE_ARTIFACT, // type - required artifact
HAVE_CREATURES, // type - creatures to collect, value - amount to collect
HAVE_RESOURCES, // type - resource ID, value - amount to collect
HAVE_BUILDING, // position - town, optional, type - building to build
CONTROL, // position - position of object, optional, type - type of object
DESTROY, // position - position of object, optional, type - type of object
TRANSPORT, // position - where artifact should be transported, type - type of artifact
DAYS_PASSED, // value - number of days from start of the game
IS_HUMAN, // value - 0 = player is AI, 1 = player is human
DAYS_WITHOUT_TOWN, // value - how long player can live without town, 0=instakill
STANDARD_WIN // normal defeat all enemies condition
};
ELossConditionType::ELossConditionType typeOfLossCon; EventCondition(EWinLoseType condition = STANDARD_WIN);
int3 pos; /// the position of an object which mustn't be lost
si32 timeLimit; /// time limit in days, -1 if not used const CGObjectInstance * object; // object that was at specified position on start
const CGObjectInstance * obj; si32 value;
si32 objectType;
int3 position;
EWinLoseType condition;
template <typename Handler> template <typename Handler>
void serialize(Handler & h, const int version) void serialize(Handler & h, const int version)
{ {
h & typeOfLossCon & pos & timeLimit & obj; h & object & value & objectType & position & condition;
} }
}; };
/// The victory condition describes the condition to win the game. (e.g. defeat all enemy heroes/castles, typedef LogicalExpression<EventCondition> EventExpression;
/// receive a specific artifact, ...)
struct DLL_LINKAGE VictoryCondition
{
VictoryCondition();
EVictoryConditionType::EVictoryConditionType condition; struct EventEffect
bool allowNormalVictory; /// true if a normal victory is allowed (defeat all enemy towns, heroes) {
bool appliesToAI; enum EType
/// pos of city to upgrade (3); pos of town to build grail, {-1,-1,-1} if not relevant (4); hero pos (5); town pos(6); {
/// monster pos (7); destination pos(8) VICTORY,
int3 pos; DEFEAT
/// artifact ID (0); monster ID (1); resource ID (2); needed fort level in upgraded town (3); artifact ID (8) };
si32 objectId;
/// needed count for creatures (1) / resource (2); upgraded town hall level (3); /// effect type, using EType enum
si32 count; si8 type;
/// object of specific monster / city / hero instance (nullptr if not used); set during map parsing
const CGObjectInstance * obj; /// message that will be sent to other players
std::string toOtherMessage;
template <typename Handler> template <typename Handler>
void serialize(Handler & h, const int version) void serialize(Handler & h, const int version)
{ {
h & condition & allowNormalVictory & appliesToAI & pos & objectId & count & obj; h & type & toOtherMessage;
}
};
struct TriggeredEvent
{
/// base condition that must be evaluated
EventExpression trigger;
/// string identifier read from config file (e.g. captureKreelah)
std::string identifier;
/// string-description, for use in UI (capture town to win)
std::string description;
/// Message that will be displayed when this event is triggered (You captured town. You won!)
std::string onFulfill;
/// Effect of this event. TODO: refactor into something more flexible
EventEffect effect;
template <typename Handler>
void serialize(Handler & h, const int version)
{
h & identifier & trigger & description & onFulfill & effect;
} }
}; };
@ -287,6 +325,7 @@ enum EMapFormat
ROE = 0x0e, // 14 ROE = 0x0e, // 14
AB = 0x15, // 21 AB = 0x15, // 21
SOD = 0x1c, // 28 SOD = 0x1c, // 28
// HOTA = 0x1e ... 0x20 // 28 ... 30
WOG = 0x33 // 51 WOG = 0x33 // 51
}; };
} }
@ -294,6 +333,7 @@ enum EMapFormat
/// The map header holds information about loss/victory condition,map format, version, players, height, width,... /// The map header holds information about loss/victory condition,map format, version, players, height, width,...
class DLL_LINKAGE CMapHeader class DLL_LINKAGE CMapHeader
{ {
void setupEvents();
public: public:
static const int MAP_SIZE_SMALL; static const int MAP_SIZE_SMALL;
static const int MAP_SIZE_MIDDLE; static const int MAP_SIZE_MIDDLE;
@ -313,19 +353,27 @@ public:
/// Specifies the maximum level to reach for a hero. A value of 0 states that there is no /// Specifies the maximum level to reach for a hero. A value of 0 states that there is no
/// maximum level for heroes. This is the default value. /// maximum level for heroes. This is the default value.
ui8 levelLimit; ui8 levelLimit;
LossCondition lossCondition; /// The default value is lose all your towns and heroes.
VictoryCondition victoryCondition; /// The default value is defeat all enemies. std::string victoryMessage;
std::string defeatMessage;
ui16 victoryIconIndex;
ui16 defeatIconIndex;
std::vector<PlayerInfo> players; /// The default size of the vector is PlayerColor::PLAYER_LIMIT. std::vector<PlayerInfo> players; /// The default size of the vector is PlayerColor::PLAYER_LIMIT.
ui8 howManyTeams; ui8 howManyTeams;
std::vector<bool> allowedHeroes; std::vector<bool> allowedHeroes;
std::vector<ui16> placeholdedHeroes; std::vector<ui16> placeholdedHeroes;
bool areAnyPlayers; /// Unused. True if there are any playable players on the map. bool areAnyPlayers; /// Unused. True if there are any playable players on the map.
/// "main quests" of the map that describe victory and loss conditions
std::vector<TriggeredEvent> triggeredEvents;
template <typename Handler> template <typename Handler>
void serialize(Handler & h, const int Version) void serialize(Handler & h, const int Version)
{ {
h & version & name & description & width & height & twoLevel & difficulty & levelLimit & areAnyPlayers; h & version & name & description & width & height & twoLevel & difficulty & levelLimit & areAnyPlayers;
h & players & lossCondition & victoryCondition & howManyTeams & allowedHeroes; h & players & howManyTeams & allowedHeroes & triggeredEvents;
h & victoryMessage & victoryIconIndex & defeatMessage & defeatIconIndex;
} }
}; };
@ -350,8 +398,8 @@ public:
void eraseArtifactInstance(CArtifactInstance * art); void eraseArtifactInstance(CArtifactInstance * art);
void addQuest(CGObjectInstance * quest); void addQuest(CGObjectInstance * quest);
/// Gets the topmost object or the lowermost object depending on the flag lookForHero from the specified position. /// Gets object of specified type on requested position
const CGObjectInstance * getObjectiveObjectFrom(int3 pos, bool lookForHero); const CGObjectInstance * getObjectiveObjectFrom(int3 pos, Obj::EObj type);
CGHeroInstance * getHero(int heroId); CGHeroInstance * getHero(int heroId);
/// Sets the victory/loss condition objectives ?? /// Sets the victory/loss condition objectives ??

View File

@ -20,6 +20,7 @@
#include "../CSpellHandler.h" #include "../CSpellHandler.h"
#include "../CCreatureHandler.h" #include "../CCreatureHandler.h"
#include "../CGeneralTextHandler.h"
#include "../CHeroHandler.h" #include "../CHeroHandler.h"
#include "../CObjectHandler.h" #include "../CObjectHandler.h"
#include "../CDefObjInfoHandler.h" #include "../CDefObjInfoHandler.h"
@ -281,98 +282,292 @@ void CMapLoaderH3M::readPlayerInfo()
} }
} }
namespace EVictoryConditionType
{
enum EVictoryConditionType { ARTIFACT, GATHERTROOP, GATHERRESOURCE, BUILDCITY, BUILDGRAIL, BEATHERO,
CAPTURECITY, BEATMONSTER, TAKEDWELLINGS, TAKEMINES, TRANSPORTITEM, WINSTANDARD = 255 };
}
namespace ELossConditionType
{
enum ELossConditionType { LOSSCASTLE, LOSSHERO, TIMEEXPIRES, LOSSSTANDARD = 255 };
}
void CMapLoaderH3M::readVictoryLossConditions() void CMapLoaderH3M::readVictoryLossConditions()
{ {
mapHeader->victoryCondition.obj = nullptr; mapHeader->triggeredEvents.clear();
mapHeader->victoryCondition.condition = (EVictoryConditionType::EVictoryConditionType)reader.readUInt8();
auto vicCondition = (EVictoryConditionType::EVictoryConditionType)reader.readUInt8();
EventCondition victoryCondition(EventCondition::STANDARD_WIN);
EventCondition defeatCondition(EventCondition::DAYS_WITHOUT_TOWN);
defeatCondition.value = 7;
TriggeredEvent standardVictory;
standardVictory.effect.type = EventEffect::VICTORY;
standardVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[5];
standardVictory.identifier = "standardVictory";
standardVictory.description = ""; // TODO: display in quest window
standardVictory.onFulfill = VLC->generaltexth->allTexts[659];
standardVictory.trigger = EventExpression(victoryCondition);
TriggeredEvent standardDefeat;
standardDefeat.effect.type = EventEffect::DEFEAT;
standardDefeat.effect.toOtherMessage = VLC->generaltexth->allTexts[8];
standardDefeat.identifier = "standardDefeat";
standardDefeat.description = ""; // TODO: display in quest window
standardDefeat.onFulfill = VLC->generaltexth->allTexts[7];
standardDefeat.trigger = EventExpression(defeatCondition);
// Specific victory conditions // Specific victory conditions
if(mapHeader->victoryCondition.condition != EVictoryConditionType::WINSTANDARD) if(vicCondition == EVictoryConditionType::WINSTANDARD)
{ {
mapHeader->victoryCondition.allowNormalVictory = reader.readBool(); // create normal condition
mapHeader->victoryCondition.appliesToAI = reader.readBool(); mapHeader->triggeredEvents.push_back(standardVictory);
mapHeader->victoryIconIndex = 11;
mapHeader->victoryMessage = VLC->generaltexth->victoryConditions[0];
}
else
{
TriggeredEvent specialVictory;
specialVictory.effect.type = EventEffect::VICTORY;
specialVictory.identifier = "specialVictory";
specialVictory.description = ""; // TODO: display in quest window
// Read victory conditions mapHeader->victoryIconIndex = ui16(vicCondition);
// int nr = 0; mapHeader->victoryMessage = VLC->generaltexth->victoryConditions[size_t(vicCondition) + 1];
switch(mapHeader->victoryCondition.condition)
bool allowNormalVictory = reader.readBool();
bool appliesToAI = reader.readBool();
switch(vicCondition)
{ {
case EVictoryConditionType::ARTIFACT: case EVictoryConditionType::ARTIFACT:
{ {
mapHeader->victoryCondition.objectId = reader.readUInt8(); EventCondition cond(EventCondition::HAVE_ARTIFACT);
cond.objectType = reader.readUInt8();
if (mapHeader->version != EMapFormat::ROE) if (mapHeader->version != EMapFormat::ROE)
reader.skip(1); reader.skip(1);
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[281];
specialVictory.onFulfill = VLC->generaltexth->allTexts[280];
specialVictory.trigger = EventExpression(cond);
break; break;
} }
case EVictoryConditionType::GATHERTROOP: case EVictoryConditionType::GATHERTROOP:
{ {
mapHeader->victoryCondition.objectId = reader.readUInt8(); EventCondition cond(EventCondition::HAVE_CREATURES);
cond.objectType = reader.readUInt8();
if (mapHeader->version != EMapFormat::ROE) if (mapHeader->version != EMapFormat::ROE)
reader.skip(1); reader.skip(1);
mapHeader->victoryCondition.count = reader.readUInt32(); cond.value = reader.readUInt32();
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[277];
specialVictory.onFulfill = VLC->generaltexth->allTexts[276];
specialVictory.trigger = EventExpression(cond);
break; break;
} }
case EVictoryConditionType::GATHERRESOURCE: case EVictoryConditionType::GATHERRESOURCE:
{ {
mapHeader->victoryCondition.objectId = reader.readUInt8(); EventCondition cond(EventCondition::HAVE_RESOURCES);
mapHeader->victoryCondition.count = reader.readUInt32(); cond.objectType = reader.readUInt8();
cond.value = reader.readUInt32();
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[279];
specialVictory.onFulfill = VLC->generaltexth->allTexts[278];
specialVictory.trigger = EventExpression(cond);
break; break;
} }
case EVictoryConditionType::BUILDCITY: case EVictoryConditionType::BUILDCITY:
{ {
mapHeader->victoryCondition.pos = readInt3(); EventExpression::OperatorAll oper;
mapHeader->victoryCondition.count = reader.readUInt8(); EventCondition cond(EventCondition::HAVE_BUILDING);
mapHeader->victoryCondition.objectId = reader.readUInt8(); cond.position = readInt3();
cond.objectType = BuildingID::VILLAGE_HALL + reader.readUInt8();
oper.expressions.push_back(cond);
cond.objectType = BuildingID::FORT + reader.readUInt8();
oper.expressions.push_back(cond);
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[283];
specialVictory.onFulfill = VLC->generaltexth->allTexts[282];
specialVictory.trigger = EventExpression(oper);
break; break;
} }
case EVictoryConditionType::BUILDGRAIL: case EVictoryConditionType::BUILDGRAIL:
{ {
int3 p = readInt3(); EventCondition cond(EventCondition::HAVE_BUILDING);
if(p.z > 2) cond.objectType = BuildingID::GRAIL;
{ cond.position = readInt3();
p = int3(-1,-1,-1); if(cond.position.z > 2)
} cond.position = int3(-1,-1,-1);
mapHeader->victoryCondition.pos = p;
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[285];
specialVictory.onFulfill = VLC->generaltexth->allTexts[284];
specialVictory.trigger = EventExpression(cond);
break; break;
} }
case EVictoryConditionType::BEATHERO: case EVictoryConditionType::BEATHERO:
{
EventCondition cond(EventCondition::DESTROY);
cond.objectType = Obj::HERO;
cond.position = readInt3();
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[253];
specialVictory.onFulfill = VLC->generaltexth->allTexts[252];
specialVictory.trigger = EventExpression(cond);
break;
}
case EVictoryConditionType::CAPTURECITY: case EVictoryConditionType::CAPTURECITY:
{
EventCondition cond(EventCondition::CONTROL);
cond.objectType = Obj::TOWN;
cond.position = readInt3();
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[250];
specialVictory.onFulfill = VLC->generaltexth->allTexts[249];
specialVictory.trigger = EventExpression(cond);
break;
}
case EVictoryConditionType::BEATMONSTER: case EVictoryConditionType::BEATMONSTER:
{ {
mapHeader->victoryCondition.pos = readInt3(); EventCondition cond(EventCondition::DESTROY);
cond.objectType = Obj::MONSTER;
cond.position = readInt3();
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[287];
specialVictory.onFulfill = VLC->generaltexth->allTexts[286];
specialVictory.trigger = EventExpression(cond);
break; break;
} }
case EVictoryConditionType::TAKEDWELLINGS: case EVictoryConditionType::TAKEDWELLINGS:
{
EventCondition cond(EventCondition::CONTROL);
cond.objectType = Obj::CREATURE_GENERATOR1; // FIXME: generators 2-4?
cond.position = readInt3();
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[289];
specialVictory.onFulfill = VLC->generaltexth->allTexts[288];
specialVictory.trigger = EventExpression(cond);
break;
}
case EVictoryConditionType::TAKEMINES: case EVictoryConditionType::TAKEMINES:
{ {
EventCondition cond(EventCondition::CONTROL);
cond.objectType = Obj::MINE;
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[291];
specialVictory.onFulfill = VLC->generaltexth->allTexts[290];
specialVictory.trigger = EventExpression(cond);
break; break;
} }
case EVictoryConditionType::TRANSPORTITEM: case EVictoryConditionType::TRANSPORTITEM:
{ {
mapHeader->victoryCondition.objectId = reader.readUInt8(); EventCondition cond(EventCondition::TRANSPORT);
mapHeader->victoryCondition.pos = readInt3(); cond.objectType = reader.readUInt8();
cond.position = readInt3();
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[293];
specialVictory.onFulfill = VLC->generaltexth->allTexts[292];
specialVictory.trigger = EventExpression(cond);
break; break;
} }
default: default:
assert(0); assert(0);
} }
//bool allowNormalVictory = reader.readBool();
// if condition is human-only turn it into following construction: AllOf(human, condition)
if (!appliesToAI)
{
EventExpression::OperatorAll oper;
EventCondition notAI(EventCondition::IS_HUMAN);
notAI.value = 1;
oper.expressions.push_back(notAI);
oper.expressions.push_back(specialVictory.trigger.get());
specialVictory.trigger = EventExpression(oper);
}
// if normal victory allowed - add one more quest
if (allowNormalVictory)
{
mapHeader->victoryMessage += " / ";
mapHeader->victoryMessage += VLC->generaltexth->victoryConditions[0];
mapHeader->triggeredEvents.push_back(standardVictory);
}
mapHeader->triggeredEvents.push_back(specialVictory);
} }
// Read loss conditions // Read loss conditions
mapHeader->lossCondition.typeOfLossCon = (ELossConditionType::ELossConditionType) reader.readUInt8(); auto lossCond = (ELossConditionType::ELossConditionType)reader.readUInt8();
switch(mapHeader->lossCondition.typeOfLossCon) if (lossCond == ELossConditionType::LOSSSTANDARD)
{
mapHeader->defeatIconIndex = 3;
mapHeader->defeatMessage = VLC->generaltexth->lossCondtions[0];
}
else
{
TriggeredEvent specialDefeat;
specialDefeat.effect.type = EventEffect::DEFEAT;
specialDefeat.effect.toOtherMessage = VLC->generaltexth->allTexts[5];
specialDefeat.identifier = "specialDefeat";
specialDefeat.description = ""; // TODO: display in quest window
mapHeader->defeatIconIndex = ui16(lossCond);
mapHeader->defeatMessage = VLC->generaltexth->lossCondtions[size_t(lossCond) + 1];
switch(lossCond)
{ {
case ELossConditionType::LOSSCASTLE: case ELossConditionType::LOSSCASTLE:
{
EventExpression::OperatorNone noneOf;
EventCondition cond(EventCondition::CONTROL);
cond.objectType = Obj::TOWN;
cond.position = readInt3();
noneOf.expressions.push_back(cond);
specialDefeat.onFulfill = VLC->generaltexth->allTexts[251];
specialDefeat.trigger = EventExpression(noneOf);
break;
}
case ELossConditionType::LOSSHERO: case ELossConditionType::LOSSHERO:
{ {
mapHeader->lossCondition.pos = readInt3(); EventExpression::OperatorNone noneOf;
EventCondition cond(EventCondition::CONTROL);
cond.objectType = Obj::HERO;
cond.position = readInt3();
noneOf.expressions.push_back(cond);
specialDefeat.onFulfill = VLC->generaltexth->allTexts[253];
specialDefeat.trigger = EventExpression(noneOf);
break; break;
} }
case ELossConditionType::TIMEEXPIRES: case ELossConditionType::TIMEEXPIRES:
{ {
mapHeader->lossCondition.timeLimit = reader.readUInt16(); EventCondition cond(EventCondition::DAYS_PASSED);
cond.value = reader.readUInt16();
specialDefeat.onFulfill = VLC->generaltexth->allTexts[254];
specialDefeat.trigger = EventExpression(cond);
break; break;
} }
} }
// turn simple loss condition into complete one that can be evaluated later:
// - any of :
// - days without town: 7
// - all of:
// - is human
// - (expression)
EventExpression::OperatorAll allOf;
EventCondition isHuman(EventCondition::IS_HUMAN);
isHuman.value = 1;
allOf.expressions.push_back(isHuman);
allOf.expressions.push_back(specialDefeat.trigger.get());
specialDefeat.trigger = EventExpression(allOf);
mapHeader->triggeredEvents.push_back(specialDefeat);
}
mapHeader->triggeredEvents.push_back(standardDefeat);
} }
void CMapLoaderH3M::readTeamInfo() void CMapLoaderH3M::readTeamInfo()
@ -470,10 +665,18 @@ void CMapLoaderH3M::readAllowedArtifacts()
} }
// Messy, but needed // Messy, but needed
if(map->victoryCondition.condition == EVictoryConditionType::ARTIFACT for (TriggeredEvent & event : map->triggeredEvents)
|| map->victoryCondition.condition == EVictoryConditionType::TRANSPORTITEM)
{ {
map->allowedArtifact[map->victoryCondition.objectId] = false; auto patcher = [&](EventCondition & cond)
{
if (cond.condition == EventCondition::HAVE_ARTIFACT ||
cond.condition == EventCondition::TRANSPORT)
{
map->allowedArtifact[cond.objectType] = false;
}
};
event.trigger.forEach(patcher);
} }
} }

View File

@ -1923,7 +1923,6 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
vistiCastleObjects (obj, hero); vistiCastleObjects (obj, hero);
giveSpells (obj, hero); giveSpells (obj, hero);
if(gs->map->victoryCondition.condition == EVictoryConditionType::TRANSPORTITEM)
checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact? checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact?
} }
@ -2168,28 +2167,24 @@ void CGameHandler::applyAndSend(CPackForClient * info)
void CGameHandler::sendAndApply(CGarrisonOperationPack * info) void CGameHandler::sendAndApply(CGarrisonOperationPack * info)
{ {
sendAndApply(static_cast<CPackForClient*>(info)); sendAndApply(static_cast<CPackForClient*>(info));
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERTROOP)
checkVictoryLossConditionsForAll(); checkVictoryLossConditionsForAll();
} }
void CGameHandler::sendAndApply( SetResource * info ) void CGameHandler::sendAndApply( SetResource * info )
{ {
sendAndApply(static_cast<CPackForClient*>(info)); sendAndApply(static_cast<CPackForClient*>(info));
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERRESOURCE)
checkVictoryLossConditionsForPlayer(info->player); checkVictoryLossConditionsForPlayer(info->player);
} }
void CGameHandler::sendAndApply( SetResources * info ) void CGameHandler::sendAndApply( SetResources * info )
{ {
sendAndApply(static_cast<CPackForClient*>(info)); sendAndApply(static_cast<CPackForClient*>(info));
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERRESOURCE)
checkVictoryLossConditionsForPlayer(info->player); checkVictoryLossConditionsForPlayer(info->player);
} }
void CGameHandler::sendAndApply( NewStructures * info ) void CGameHandler::sendAndApply( NewStructures * info )
{ {
sendAndApply(static_cast<CPackForClient*>(info)); sendAndApply(static_cast<CPackForClient*>(info));
if(gs->map->victoryCondition.condition == EVictoryConditionType::BUILDCITY)
checkVictoryLossConditionsForPlayer(getTown(info->tid)->tempOwner); checkVictoryLossConditionsForPlayer(getTown(info->tid)->tempOwner);
} }
@ -5048,7 +5043,8 @@ void CGameHandler::checkVictoryLossConditions(const std::set<PlayerColor> & play
{ {
for(auto playerColor : playerColors) for(auto playerColor : playerColors)
{ {
if(gs->getPlayer(playerColor)) checkVictoryLossConditionsForPlayer(playerColor); if(gs->getPlayer(playerColor))
checkVictoryLossConditionsForPlayer(playerColor);
} }
} }
@ -5069,7 +5065,7 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
auto victoryLossCheckResult = gs->checkForVictoryAndLoss(player); auto victoryLossCheckResult = gs->checkForVictoryAndLoss(player);
if(victoryLossCheckResult != EVictoryLossCheckResult::NO_VICTORY_OR_LOSS) if(victoryLossCheckResult.victory() || victoryLossCheckResult.loss())
{ {
InfoWindow iw; InfoWindow iw;
getVictoryLossMessage(player, victoryLossCheckResult, iw); getVictoryLossMessage(player, victoryLossCheckResult, iw);
@ -5083,18 +5079,19 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
if(victoryLossCheckResult.victory()) if(victoryLossCheckResult.victory())
{ {
//one player won -> all enemies lost //one player won -> all enemies lost
iw.text.localStrings.front().second++; //message about losing because enemy won first is just after victory message
for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++) for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++)
{ {
if(i->first != player && gs->getPlayer(i->first)->status == EPlayerStatus::INGAME) if(i->first != player && gs->getPlayer(i->first)->status == EPlayerStatus::INGAME)
{ {
iw.player = i->first;
sendAndApply(&iw);
peg.player = i->first; peg.player = i->first;
peg.victoryLossCheckResult = gameState()->getPlayerRelations(player, i->first) == PlayerRelations::ALLIES ? peg.victoryLossCheckResult = gameState()->getPlayerRelations(player, i->first) == PlayerRelations::ALLIES ?
victoryLossCheckResult : EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS; // ally of winner victoryLossCheckResult : victoryLossCheckResult.invert(); // ally of winner
InfoWindow iw;
getVictoryLossMessage(player, peg.victoryLossCheckResult, iw);
iw.player = i->first;
sendAndApply(&iw);
sendAndApply(&peg); sendAndApply(&peg);
} }
} }
@ -5122,7 +5119,6 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
connection->prepareForSendingHeroes(); connection->prepareForSendingHeroes();
} }
UpdateCampaignState ucs; UpdateCampaignState ucs;
ucs.camp = gs->scenarioOps->campState; ucs.camp = gs->scenarioOps->campState;
sendAndApply(&ucs); sendAndApply(&ucs);
@ -5149,6 +5145,19 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
if(player.getNum() != i) playerColors.insert(PlayerColor(i)); if(player.getNum() != i) playerColors.insert(PlayerColor(i));
} }
//notify all players
for (auto i = gs->players.cbegin(); i!=gs->players.cend(); i++)
{
if(i->first != player && gs->getPlayer(i->first)->status == EPlayerStatus::INGAME)
{
InfoWindow iw;
getVictoryLossMessage(player, victoryLossCheckResult.invert(), iw);
iw.player = i->first;
sendAndApply(&iw);
}
}
checkVictoryLossConditions(playerColors); checkVictoryLossConditions(playerColors);
} }
@ -5162,110 +5171,14 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
void CGameHandler::getVictoryLossMessage(PlayerColor player, EVictoryLossCheckResult victoryLossCheckResult, InfoWindow & out) const void CGameHandler::getVictoryLossMessage(PlayerColor player, EVictoryLossCheckResult victoryLossCheckResult, InfoWindow & out) const
{ {
// const PlayerState *p = gs->getPlayer(player);
// if(!p->human)
// return; //AI doesn't need text info of loss
out.player = player; out.player = player;
out.text.clear();
if(victoryLossCheckResult == EVictoryLossCheckResult::VICTORY_SPECIAL) out.text << victoryLossCheckResult.messageToSelf;
{ // hackish, insert one player-specific string, if applicable
switch(gs->map->victoryCondition.condition) if (victoryLossCheckResult.messageToSelf.find("%s") != std::string::npos)
{
case EVictoryConditionType::ARTIFACT:
out.text.addTxt(MetaString::GENERAL_TXT, 280); //Congratulations! You have found the %s, and can claim victory!
out.text.addReplacement(MetaString::ART_NAMES,gs->map->victoryCondition.objectId); //artifact name
break;
case EVictoryConditionType::GATHERTROOP:
out.text.addTxt(MetaString::GENERAL_TXT, 276); //Congratulations! You have over %d %s in your armies. Your enemies have no choice but to bow down before your power!
out.text.addReplacement(gs->map->victoryCondition.count);
out.text.addReplacement(MetaString::CRE_PL_NAMES, gs->map->victoryCondition.objectId);
break;
case EVictoryConditionType::GATHERRESOURCE:
out.text.addTxt(MetaString::GENERAL_TXT, 278); //Congratulations! You have collected over %d %s in your treasury. Victory is yours!
out.text.addReplacement(gs->map->victoryCondition.count);
out.text.addReplacement(MetaString::RES_NAMES, gs->map->victoryCondition.objectId);
break;
case EVictoryConditionType::BUILDCITY:
out.text.addTxt(MetaString::GENERAL_TXT, 282); //Congratulations! You have successfully upgraded your town, and can claim victory!
break;
case EVictoryConditionType::BUILDGRAIL:
out.text.addTxt(MetaString::GENERAL_TXT, 284); //Congratulations! You have constructed a permanent home for the Grail, and can claim victory!
break;
case EVictoryConditionType::BEATHERO:
{
out.text.addTxt(MetaString::GENERAL_TXT, 252); //Congratulations! You have completed your quest to defeat the enemy hero %s. Victory is yours!
const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(gs->map->victoryCondition.obj);
assert(h);
out.text.addReplacement(h->name);
}
break;
case EVictoryConditionType::CAPTURECITY:
{
out.text.addTxt(MetaString::GENERAL_TXT, 249); //Congratulations! You captured %s, and are victorious!
const CGTownInstance *t = dynamic_cast<const CGTownInstance*>(gs->map->victoryCondition.obj);
assert(t);
out.text.addReplacement(t->name);
}
break;
case EVictoryConditionType::BEATMONSTER:
out.text.addTxt(MetaString::GENERAL_TXT, 286); //Congratulations! You have completed your quest to kill the fearsome beast, and can claim victory!
break;
case EVictoryConditionType::TAKEDWELLINGS:
out.text.addTxt(MetaString::GENERAL_TXT, 288); //Congratulations! Your flag flies on the dwelling of every creature. Victory is yours!
break;
case EVictoryConditionType::TAKEMINES:
out.text.addTxt(MetaString::GENERAL_TXT, 290); //Congratulations! Your flag flies on every mine. Victory is yours!
break;
case EVictoryConditionType::TRANSPORTITEM:
out.text.addTxt(MetaString::GENERAL_TXT, 292); //Congratulations! You have reached your destination, precious cargo intact, and can claim victory!
break;
}
}
else if(victoryLossCheckResult == EVictoryLossCheckResult::VICTORY_STANDARD)
{
out.text.addTxt(MetaString::GENERAL_TXT, 659); // Congratulations! All your enemies have been defeated! Victory is yours!
}
else if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER)
{
out.text.addTxt(MetaString::GENERAL_TXT, 7);//%s, your heroes abandon you, and you are banished from this land.
out.text.addReplacement(MetaString::COLOR, player.getNum()); out.text.addReplacement(MetaString::COLOR, player.getNum());
out.components.push_back(Component(Component::FLAG, player.getNum(), 0, 0)); out.components.push_back(Component(Component::FLAG, player.getNum(), 0, 0));
}
else if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_SPECIAL)
{
switch(gs->map->lossCondition.typeOfLossCon)
{
case ELossConditionType::LOSSCASTLE:
{
out.text.addTxt(MetaString::GENERAL_TXT, 251); //The town of %s has fallen - all is lost!
const CGTownInstance *t = dynamic_cast<const CGTownInstance*>(gs->map->lossCondition.obj);
assert(t);
out.text.addReplacement(t->name);
}
break;
case ELossConditionType::LOSSHERO:
{
out.text.addTxt(MetaString::GENERAL_TXT, 253); //The hero, %s, has suffered defeat - your quest is over!
const CGHeroInstance *h = dynamic_cast<const CGHeroInstance*>(gs->map->lossCondition.obj);
assert(h);
out.text.addReplacement(h->name);
}
break;
case ELossConditionType::TIMEEXPIRES:
out.text.addTxt(MetaString::GENERAL_TXT, 254); //Alas, time has run out on your quest. All is lost.
break;
}
}
else if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_STANDARD_HEROES_AND_TOWNS)
{
out.text.addTxt(MetaString::GENERAL_TXT, 660); //All your forces have been defeated, and you are banished from this land!
}
else
{
assert(0);
logGlobal->warnStream() << "Unknown victory loss check result";
}
} }
bool CGameHandler::dig( const CGHeroInstance *h ) bool CGameHandler::dig( const CGHeroInstance *h )