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:
parent
d2d1a2f544
commit
0c5be52a42
@ -135,86 +135,140 @@ namespace Goals
|
||||
|
||||
TSubgoal Win::whatToDoToAchieve()
|
||||
{
|
||||
const VictoryCondition &vc = cb->getMapHeader()->victoryCondition;
|
||||
EVictoryConditionType::EVictoryConditionType cond = vc.condition;
|
||||
|
||||
if(!vc.appliesToAI)
|
||||
auto toBool = [=](const EventCondition &)
|
||||
{
|
||||
//TODO deduce victory from human loss condition
|
||||
cond = EVictoryConditionType::WINSTANDARD;
|
||||
// TODO: proper implementation
|
||||
// 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:
|
||||
return sptr (Goals::GetArtOfType(vc.objectId));
|
||||
case EVictoryConditionType::BEATHERO:
|
||||
return sptr (Goals::GetObj(vc.obj->id.getNum()));
|
||||
case EVictoryConditionType::BEATMONSTER:
|
||||
return sptr (Goals::GetObj(vc.obj->id.getNum()));
|
||||
case EVictoryConditionType::BUILDCITY:
|
||||
//TODO build castle/capitol
|
||||
break;
|
||||
case EVictoryConditionType::BUILDGRAIL:
|
||||
switch(goal.condition)
|
||||
{
|
||||
if(auto h = ai->getHeroWithGrail())
|
||||
case EventCondition::HAVE_ARTIFACT:
|
||||
return sptr (Goals::GetArtOfType(goal.objectType));
|
||||
case EventCondition::DESTROY:
|
||||
{
|
||||
//hero is in a town that can host Grail
|
||||
if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL))
|
||||
if (goal.object)
|
||||
{
|
||||
const CGTownInstance *t = h->visitedTown;
|
||||
return sptr (Goals::BuildThis(BuildingID::GRAIL, t));
|
||||
return sptr (Goals::GetObj(goal.object->id.getNum()));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto towns = cb->getTownsInfo();
|
||||
towns.erase(boost::remove_if(towns,
|
||||
[](const CGTownInstance *t) -> bool
|
||||
{
|
||||
return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL);
|
||||
}),
|
||||
towns.end());
|
||||
boost::sort(towns, isCloser);
|
||||
if(towns.size())
|
||||
{
|
||||
return sptr (Goals::VisitTile(towns.front()->visitablePos()).sethero(h));
|
||||
}
|
||||
// TODO: destroy all objects of type goal.objectType
|
||||
// This situation represents "kill all creatures" condition from H3
|
||||
break;
|
||||
}
|
||||
}
|
||||
double ratio = 0;
|
||||
int3 grailPos = cb->getGrailPos(ratio);
|
||||
if(ratio > 0.99)
|
||||
case EventCondition::HAVE_BUILDING:
|
||||
{
|
||||
return sptr (Goals::DigAtTile(grailPos));
|
||||
} //TODO: use FIND_OBJ
|
||||
else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks
|
||||
{
|
||||
return sptr (Goals::GetObj(obj->id.getNum()));
|
||||
// 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())
|
||||
{
|
||||
//hero is in a town that can host Grail
|
||||
if(h->visitedTown && !vstd::contains(h->visitedTown->forbiddenBuildings, BuildingID::GRAIL))
|
||||
{
|
||||
const CGTownInstance *t = h->visitedTown;
|
||||
return sptr (Goals::BuildThis(BuildingID::GRAIL, t));
|
||||
}
|
||||
else
|
||||
{
|
||||
auto towns = cb->getTownsInfo();
|
||||
towns.erase(boost::remove_if(towns,
|
||||
[](const CGTownInstance *t) -> bool
|
||||
{
|
||||
return vstd::contains(t->forbiddenBuildings, BuildingID::GRAIL);
|
||||
}),
|
||||
towns.end());
|
||||
boost::sort(towns, isCloser);
|
||||
if(towns.size())
|
||||
{
|
||||
return sptr (Goals::VisitTile(towns.front()->visitablePos()).sethero(h));
|
||||
}
|
||||
}
|
||||
}
|
||||
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);
|
||||
if(ratio > 0.99)
|
||||
{
|
||||
return sptr (Goals::DigAtTile(grailPos));
|
||||
} //TODO: use FIND_OBJ
|
||||
else if(const CGObjectInstance * obj = ai->getUnvisitedObj(objWithID<Obj::OBELISK>)) //there are unvisited Obelisks
|
||||
return sptr (Goals::GetObj(obj->id.getNum()));
|
||||
else
|
||||
return sptr (Goals::Explore());
|
||||
}
|
||||
break;
|
||||
}
|
||||
else
|
||||
return sptr (Goals::Explore());
|
||||
case EventCondition::CONTROL:
|
||||
{
|
||||
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?
|
||||
//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;
|
||||
}
|
||||
case EventCondition::STANDARD_WIN:
|
||||
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:
|
||||
assert(0);
|
||||
}
|
||||
break;
|
||||
case EVictoryConditionType::CAPTURECITY:
|
||||
return sptr (Goals::GetObj(vc.obj->id.getNum()));
|
||||
case EVictoryConditionType::GATHERRESOURCE:
|
||||
return sptr (Goals::CollectRes(static_cast<Res::ERes>(vc.objectId), vc.count));
|
||||
//TODO mines? piles? marketplace?
|
||||
//save?
|
||||
break;
|
||||
case EVictoryConditionType::GATHERTROOP:
|
||||
return sptr (Goals::GatherTroops(vc.objectId, vc.count));
|
||||
break;
|
||||
case EVictoryConditionType::TAKEDWELLINGS:
|
||||
break;
|
||||
case EVictoryConditionType::TAKEMINES:
|
||||
break;
|
||||
case EVictoryConditionType::TRANSPORTITEM:
|
||||
break;
|
||||
case EVictoryConditionType::WINSTANDARD:
|
||||
return sptr (Goals::Conquer());
|
||||
default:
|
||||
assert(0);
|
||||
}
|
||||
return sptr (Goals::Invalid());
|
||||
}
|
||||
|
@ -2107,7 +2107,8 @@ void CPlayerInterface::gameOver(PlayerColor player, const EVictoryLossCheckResul
|
||||
|
||||
if(player == playerID)
|
||||
{
|
||||
if(victoryLossCheckResult.loss()) showInfoDialog(CGI->generaltexth->allTexts[95]);
|
||||
if(victoryLossCheckResult.loss())
|
||||
showInfoDialog(CGI->generaltexth->allTexts[95]);
|
||||
|
||||
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
|
||||
{
|
||||
std::string txt;
|
||||
if(victoryLossCheckResult == EVictoryLossCheckResult::LOSS_STANDARD_TOWNS_AND_TIME_OVER)
|
||||
{
|
||||
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)));
|
||||
std::string str = victoryLossCheckResult.messageToSelf;
|
||||
boost::algorithm::replace_first(str, "%s", CGI->generaltexth->capColors[player.getNum()]);
|
||||
showInfoDialog(str, std::vector<CComponent*>(1, new CComponent(CComponent::flag, player.getNum(), 0)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1437,18 +1437,10 @@ void SelectionTab::printMaps(SDL_Surface *to)
|
||||
blitAtLoc(format->ourImages[temp].bitmap, 88, 117 + line * 25, to);
|
||||
|
||||
//victory conditions
|
||||
if (currentItem->mapHeader->victoryCondition.condition == EVictoryConditionType::WINSTANDARD)
|
||||
temp = 11;
|
||||
else
|
||||
temp = currentItem->mapHeader->victoryCondition.condition;
|
||||
blitAtLoc(CGP->victory->ourImages[temp].bitmap, 306, 117 + line * 25, to);
|
||||
blitAtLoc(CGP->victory->ourImages[currentItem->mapHeader->victoryIconIndex].bitmap, 306, 117 + line * 25, to);
|
||||
|
||||
//loss conditions
|
||||
if (currentItem->mapHeader->lossCondition.typeOfLossCon == ELossConditionType::LOSSSTANDARD)
|
||||
temp=3;
|
||||
else
|
||||
temp=currentItem->mapHeader->lossCondition.typeOfLossCon;
|
||||
blitAtLoc(CGP->loss->ourImages[temp].bitmap, 339, 117 + line * 25, to);
|
||||
blitAtLoc(CGP->loss->ourImages[currentItem->mapHeader->defeatIconIndex].bitmap, 339, 117 + line * 25, to);
|
||||
}
|
||||
else //if campaign
|
||||
{
|
||||
@ -2023,26 +2015,14 @@ void InfoCard::showAll(SDL_Surface * to)
|
||||
CDefHandler * loss = CGP ? CGP->loss : CDefHandler::giveDef("SCNRLOSS.DEF");
|
||||
CDefHandler * victory = CGP ? CGP->victory : CDefHandler::giveDef("SCNRVICT.DEF");
|
||||
|
||||
CMapHeader * header = SEL->current->mapHeader.get();
|
||||
//victory conditions
|
||||
temp = SEL->current->mapHeader->victoryCondition.condition+1;
|
||||
if (temp>20) temp=0;
|
||||
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
|
||||
printAtLoc(header->victoryMessage, 60, 307, FONT_SMALL, Colors::WHITE, to);
|
||||
blitAtLoc(victory->ourImages[header->victoryIconIndex].bitmap, 24, 302, to); //victory cond descr
|
||||
|
||||
//loss conditoins
|
||||
temp = SEL->current->mapHeader->lossCondition.typeOfLossCon+1;
|
||||
if (temp>20) temp=0;
|
||||
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
|
||||
printAtLoc(header->defeatMessage, 60, 366, FONT_SMALL, Colors::WHITE, to);
|
||||
blitAtLoc(loss->ourImages[header->defeatIconIndex].bitmap, 24, 359, to); //loss cond
|
||||
|
||||
if (!CGP)
|
||||
{
|
||||
@ -2987,7 +2967,7 @@ bool mapSorter::operator()(const CMapInfo *aaa, const CMapInfo *bbb)
|
||||
return (a->version<b->version);
|
||||
break;
|
||||
case _loscon: //by loss conditions
|
||||
return (a->lossCondition.typeOfLossCon<b->lossCondition.typeOfLossCon);
|
||||
return (a->defeatMessage < b->defeatMessage);
|
||||
break;
|
||||
case _playerAm: //by player amount
|
||||
int playerAmntB,humenPlayersB,playerAmntA,humenPlayersA;
|
||||
@ -3008,7 +2988,7 @@ bool mapSorter::operator()(const CMapInfo *aaa, const CMapInfo *bbb)
|
||||
return (a->width<b->width);
|
||||
break;
|
||||
case _viccon: //by victory conditions
|
||||
return (a->victoryCondition.condition < b->victoryCondition.condition);
|
||||
return (a->victoryMessage < b->victoryMessage);
|
||||
break;
|
||||
case _name: //by name
|
||||
return boost::ilexicographical_compare(a->name, b->name);
|
||||
|
@ -2225,141 +2225,151 @@ bool CGameState::checkForVisitableDir( const int3 & src, const TerrainTile *pom,
|
||||
|
||||
EVictoryLossCheckResult CGameState::checkForVictoryAndLoss(PlayerColor player) const
|
||||
{
|
||||
auto result = checkForVictory(player);
|
||||
if (result == EVictoryLossCheckResult::NO_VICTORY_OR_LOSS) result = checkForLoss(player);
|
||||
return result;
|
||||
const std::string & messageWonSelf = VLC->generaltexth->allTexts[659];
|
||||
const std::string & messageWonOther = VLC->generaltexth->allTexts[5];
|
||||
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);
|
||||
if(map->victoryCondition.condition == EVictoryConditionType::WINSTANDARD || map->victoryCondition.allowNormalVictory
|
||||
|| (!p->human && !map->victoryCondition.appliesToAI)) //if the special victory condition applies only to human, AI has the standard)
|
||||
switch (condition.condition)
|
||||
{
|
||||
if(player == checkForStandardWin())
|
||||
return EVictoryLossCheckResult::VICTORY_STANDARD;
|
||||
}
|
||||
|
||||
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 EventCondition::STANDARD_WIN:
|
||||
{
|
||||
return player == checkForStandardWin();
|
||||
}
|
||||
case EventCondition::HAVE_ARTIFACT: //check if any hero has winning artifact
|
||||
{
|
||||
case EVictoryConditionType::ARTIFACT:
|
||||
//check if any hero has winning artifact
|
||||
for(auto & elem : p->heroes)
|
||||
if(elem->hasArt(map->victoryCondition.objectId))
|
||||
return EVictoryLossCheckResult::VICTORY_SPECIAL;
|
||||
|
||||
break;
|
||||
|
||||
case EVictoryConditionType::GATHERTROOP:
|
||||
if(elem->hasArt(condition.objectType))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
case EventCondition::HAVE_CREATURES:
|
||||
{
|
||||
//check if in players armies there is enough creatures
|
||||
int total = 0; //creature counter
|
||||
for(size_t i = 0; i < map->objects.size(); i++)
|
||||
{
|
||||
//check if in players armies there is enough creatures
|
||||
int total = 0; //creature counter
|
||||
for(size_t i = 0; i < map->objects.size(); i++)
|
||||
const CArmedInstance *ai = nullptr;
|
||||
if(map->objects[i]
|
||||
&& map->objects[i]->tempOwner == player //object controlled by player
|
||||
&& (ai = dynamic_cast<const CArmedInstance*>(map->objects[i].get()))) //contains army
|
||||
{
|
||||
const CArmedInstance *ai = nullptr;
|
||||
if(map->objects[i]
|
||||
&& map->objects[i]->tempOwner == player //object controlled by player
|
||||
&& (ai = dynamic_cast<const CArmedInstance*>(map->objects[i].get()))) //contains army
|
||||
{
|
||||
for(auto & elem : ai->Slots()) //iterate through army
|
||||
if(elem.second->type->idNumber == map->victoryCondition.objectId) //it's searched creature
|
||||
total += elem.second->count;
|
||||
}
|
||||
}
|
||||
|
||||
if(total >= map->victoryCondition.count)
|
||||
return EVictoryLossCheckResult::VICTORY_SPECIAL;
|
||||
}
|
||||
break;
|
||||
|
||||
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);
|
||||
if(t->tempOwner == player && t->fortLevel()-1 >= map->victoryCondition.objectId && t->hallLevel()-1 >= map->victoryCondition.count)
|
||||
return EVictoryLossCheckResult::VICTORY_SPECIAL;
|
||||
}
|
||||
break;
|
||||
|
||||
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)
|
||||
return EVictoryLossCheckResult::VICTORY_SPECIAL;
|
||||
}
|
||||
break;
|
||||
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
|
||||
{
|
||||
switch(elem->ID)
|
||||
{
|
||||
case Obj::CREATURE_GENERATOR1: case Obj::CREATURE_GENERATOR2:
|
||||
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
|
||||
}
|
||||
for(auto & elem : ai->Slots()) //iterate through army
|
||||
if(elem.second->type->idNumber == condition.objectType) //it's searched creature
|
||||
total += elem.second->count;
|
||||
}
|
||||
}
|
||||
return EVictoryLossCheckResult::VICTORY_SPECIAL;
|
||||
case EVictoryConditionType::TAKEMINES:
|
||||
for(auto & elem : map->objects)
|
||||
return total >= condition.value;
|
||||
}
|
||||
case EventCondition::HAVE_RESOURCES:
|
||||
{
|
||||
return p->resources[condition.objectType] >= condition.value;
|
||||
}
|
||||
case EventCondition::HAVE_BUILDING:
|
||||
{
|
||||
const CGTownInstance *t = static_cast<const CGTownInstance *>(condition.object);
|
||||
return (t->tempOwner == player && t->hasBuilt(BuildingID(condition.objectType)));
|
||||
}
|
||||
case EventCondition::DESTROY:
|
||||
{
|
||||
if (condition.object) // mode A - destroy specific object of this type
|
||||
{
|
||||
if(elem && elem->tempOwner != player) //check not flagged objs
|
||||
{
|
||||
switch(elem->ID)
|
||||
{
|
||||
case Obj::MINE: case Obj::ABANDONED_MINE:
|
||||
return EVictoryLossCheckResult::NO_VICTORY_OR_LOSS; //found not flagged mine - player not won
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
return EVictoryLossCheckResult::VICTORY_SPECIAL;
|
||||
case EVictoryConditionType::TRANSPORTITEM:
|
||||
else
|
||||
{
|
||||
const CGTownInstance *t = static_cast<const CGTownInstance *>(map->victoryCondition.obj);
|
||||
if((t->visitingHero && t->visitingHero->hasArt(map->victoryCondition.objectId))
|
||||
|| (t->garrisonHero && t->garrisonHero->hasArt(map->victoryCondition.objectId)))
|
||||
for(auto & elem : map->objects) // mode B - destroy all objects of this type
|
||||
{
|
||||
return EVictoryLossCheckResult::VICTORY_SPECIAL;
|
||||
if(elem && elem->ID == condition.objectType)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
case EventCondition::CONTROL:
|
||||
{
|
||||
if (condition.object) // mode A - flag one specific object, like town
|
||||
{
|
||||
return condition.object->tempOwner == player;
|
||||
}
|
||||
else
|
||||
{
|
||||
for(auto & elem : map->objects) // mode B - flag all objects of this type
|
||||
{
|
||||
//check not flagged objs
|
||||
if(elem && elem->tempOwner != player && elem->ID == condition.objectType)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
return EVictoryLossCheckResult::NO_VICTORY_OR_LOSS;
|
||||
assert(0);
|
||||
return false;
|
||||
}
|
||||
|
||||
PlayerColor CGameState::checkForStandardWin() const
|
||||
@ -2597,46 +2607,6 @@ void CGameState::obtainPlayersStats(SThievesGuildInfo & tgi, int level)
|
||||
#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> > pool = hpool.heroesPool;
|
||||
@ -3479,21 +3449,16 @@ CPathfinder::CPathfinder(CPathsInfo &_out, CGameState *_gs, const CGHeroInstance
|
||||
allowEmbarkAndDisembark = true;
|
||||
}
|
||||
|
||||
const EVictoryLossCheckResult EVictoryLossCheckResult::NO_VICTORY_OR_LOSS = EVictoryLossCheckResult(0);
|
||||
const EVictoryLossCheckResult EVictoryLossCheckResult::VICTORY_STANDARD = EVictoryLossCheckResult(1);
|
||||
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() :
|
||||
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
|
||||
@ -3508,27 +3473,31 @@ bool EVictoryLossCheckResult::operator!=(EVictoryLossCheckResult const & other)
|
||||
|
||||
bool EVictoryLossCheckResult::victory() const
|
||||
{
|
||||
return *this == VICTORY_STANDARD || *this == VICTORY_SPECIAL;
|
||||
return intValue == VICTORY;
|
||||
}
|
||||
|
||||
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";
|
||||
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";
|
||||
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";
|
||||
else return "Unknown type";
|
||||
return EVictoryLossCheckResult(-intValue, messageToOthers, messageToSelf);
|
||||
}
|
||||
|
||||
EVictoryLossCheckResult EVictoryLossCheckResult::victory(std::string toSelf, std::string toOthers)
|
||||
{
|
||||
return EVictoryLossCheckResult(VICTORY, toSelf, toOthers);
|
||||
}
|
||||
|
||||
EVictoryLossCheckResult EVictoryLossCheckResult::defeat(std::string toSelf, std::string toOthers)
|
||||
{
|
||||
return EVictoryLossCheckResult(DEFEAT, toSelf, toOthers);
|
||||
}
|
||||
|
||||
std::ostream & operator<<(std::ostream & os, const EVictoryLossCheckResult & victoryLossCheckResult)
|
||||
{
|
||||
os << victoryLossCheckResult.toString();
|
||||
os << victoryLossCheckResult.messageToSelf;
|
||||
return os;
|
||||
}
|
||||
|
@ -357,28 +357,34 @@ struct BattleInfo;
|
||||
class DLL_LINKAGE EVictoryLossCheckResult
|
||||
{
|
||||
public:
|
||||
static const EVictoryLossCheckResult NO_VICTORY_OR_LOSS;
|
||||
static const EVictoryLossCheckResult VICTORY_STANDARD;
|
||||
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;
|
||||
static EVictoryLossCheckResult victory(std::string toSelf, std::string toOthers);
|
||||
static EVictoryLossCheckResult defeat(std::string toSelf, std::string toOthers);
|
||||
|
||||
EVictoryLossCheckResult();
|
||||
bool operator==(EVictoryLossCheckResult const & other) const;
|
||||
bool operator!=(EVictoryLossCheckResult const & other) const;
|
||||
bool victory() 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)
|
||||
{
|
||||
h & intValue;
|
||||
h & intValue & messageToSelf & messageToOthers;
|
||||
}
|
||||
|
||||
private:
|
||||
EVictoryLossCheckResult(si32 intValue);
|
||||
si32 intValue;
|
||||
enum EResult
|
||||
{
|
||||
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);
|
||||
@ -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
|
||||
int3 guardingCreaturePosition (int3 pos) const;
|
||||
std::vector<CGObjectInstance*> guardingCreatures (int3 pos) const;
|
||||
|
||||
// ----- victory, loss condition checks -----
|
||||
|
||||
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
|
||||
std::map<ui32, ConstTransitivePtr<CGHeroInstance> > unusedHeroesFromPool(); //heroes pool without heroes that are available in taverns
|
||||
@ -476,13 +488,6 @@ private:
|
||||
void initMapObjects();
|
||||
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 -----
|
||||
|
||||
void buildBonusSystemTree();
|
||||
|
@ -3,6 +3,8 @@
|
||||
#include "CDefObjInfoHandler.h"
|
||||
#include "JsonNode.h"
|
||||
#include "filesystem/Filesystem.h"
|
||||
#include "filesystem/AdapterLoaders.h"
|
||||
#include "filesystem/CFilesystemLoader.h"
|
||||
|
||||
#include "CCreatureHandler.h"
|
||||
#include "CArtHandler.h"
|
||||
|
@ -290,17 +290,6 @@ public:
|
||||
|
||||
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
|
||||
{
|
||||
enum EAlignment { GOOD, EVIL, NEUTRAL };
|
||||
|
@ -9,6 +9,7 @@ namespace LogicalExpressionDetail
|
||||
template<typename ContainedClass>
|
||||
class ExpressionBase
|
||||
{
|
||||
public:
|
||||
/// Possible logical operations, mostly needed to create different types for boost::variant
|
||||
enum EOperations
|
||||
{
|
||||
@ -16,7 +17,6 @@ namespace LogicalExpressionDetail
|
||||
ALL_OF,
|
||||
NONE_OF
|
||||
};
|
||||
public:
|
||||
template<EOperations tag> class Element;
|
||||
|
||||
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
|
||||
template <typename ContainedClass>
|
||||
class Reader
|
||||
@ -289,6 +327,18 @@ public:
|
||||
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".
|
||||
/// Note: empty expressions always return true
|
||||
bool test(std::function<bool(const Value &)> toBool) const
|
||||
|
@ -1612,7 +1612,8 @@ DLL_LINKAGE void YourTurn::applyGs( CGameState *gs )
|
||||
auto & playerState = gs->players[player];
|
||||
if(playerState.towns.empty())
|
||||
{
|
||||
if(playerState.daysWithoutCastle) ++(*playerState.daysWithoutCastle);
|
||||
if(playerState.daysWithoutCastle)
|
||||
++(*playerState.daysWithoutCastle);
|
||||
else playerState.daysWithoutCastle = 0;
|
||||
}
|
||||
else
|
||||
|
@ -46,18 +46,18 @@ std::unordered_set<ResourceID> CMappedFileLoader::getFilteredFiles(std::function
|
||||
|
||||
CFilesystemList::CFilesystemList()
|
||||
{
|
||||
loaders = new std::vector<std::unique_ptr<ISimpleResourceLoader> >;
|
||||
//loaders = new std::vector<std::unique_ptr<ISimpleResourceLoader> >;
|
||||
}
|
||||
|
||||
CFilesystemList::~CFilesystemList()
|
||||
{
|
||||
delete loaders;
|
||||
//delete loaders;
|
||||
}
|
||||
|
||||
std::unique_ptr<CInputStream> CFilesystemList::load(const ResourceID & resourceName) const
|
||||
{
|
||||
// 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))
|
||||
return loader->load(resourceName);
|
||||
@ -69,7 +69,7 @@ std::unique_ptr<CInputStream> CFilesystemList::load(const ResourceID & resourceN
|
||||
|
||||
bool CFilesystemList::existsResource(const ResourceID & resourceName) const
|
||||
{
|
||||
for (auto & loader : *loaders)
|
||||
for (auto & loader : loaders)
|
||||
if (loader->existsResource(resourceName))
|
||||
return true;
|
||||
return false;
|
||||
@ -91,7 +91,7 @@ std::unordered_set<ResourceID> CFilesystemList::getFilteredFiles(std::function<b
|
||||
{
|
||||
std::unordered_set<ResourceID> ret;
|
||||
|
||||
for (auto & loader : *loaders)
|
||||
for (auto & loader : loaders)
|
||||
for (auto & entry : loader->getFilteredFiles(filter))
|
||||
ret.insert(entry);
|
||||
|
||||
@ -101,7 +101,7 @@ std::unordered_set<ResourceID> CFilesystemList::getFilteredFiles(std::function<b
|
||||
bool CFilesystemList::createResource(std::string filename, bool update)
|
||||
{
|
||||
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,
|
||||
&& loader->createResource(filename, update)) // successfully created
|
||||
@ -123,7 +123,7 @@ std::vector<const ISimpleResourceLoader *> CFilesystemList::getResourcesWithName
|
||||
{
|
||||
std::vector<const ISimpleResourceLoader *> ret;
|
||||
|
||||
for (auto & loader : *loaders)
|
||||
for (auto & loader : loaders)
|
||||
boost::range::copy(loader->getResourcesWithName(resourceName), std::back_inserter(ret));
|
||||
|
||||
return ret;
|
||||
@ -131,7 +131,7 @@ std::vector<const ISimpleResourceLoader *> CFilesystemList::getResourcesWithName
|
||||
|
||||
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)
|
||||
writeableLoaders.insert(loader);
|
||||
}
|
||||
|
@ -54,7 +54,7 @@ private:
|
||||
|
||||
class DLL_LINKAGE CFilesystemList : public ISimpleResourceLoader
|
||||
{
|
||||
std::vector<std::unique_ptr<ISimpleResourceLoader> >* loaders;
|
||||
std::vector<std::unique_ptr<ISimpleResourceLoader> > loaders;
|
||||
|
||||
std::set<ISimpleResourceLoader *> writeableLoaders;
|
||||
|
||||
|
@ -3,9 +3,11 @@
|
||||
|
||||
#include "../CArtHandler.h"
|
||||
#include "../VCMI_Lib.h"
|
||||
#include "../CCreatureHandler.h"
|
||||
#include "../CTownHandler.h"
|
||||
#include "../CHeroHandler.h"
|
||||
#include "../CDefObjInfoHandler.h"
|
||||
#include "../CGeneralTextHandler.h"
|
||||
#include "../CSpellHandler.h"
|
||||
#include "CMapEditManager.h"
|
||||
|
||||
@ -59,17 +61,13 @@ bool PlayerInfo::hasCustomMainHero() const
|
||||
return !mainCustomHeroName.empty() && mainCustomHeroPortrait != -1;
|
||||
}
|
||||
|
||||
LossCondition::LossCondition() : typeOfLossCon(ELossConditionType::LOSSSTANDARD),
|
||||
pos(int3(-1, -1, -1)), timeLimit(-1), obj(nullptr)
|
||||
EventCondition::EventCondition(EWinLoseType condition):
|
||||
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)
|
||||
@ -146,9 +144,44 @@ const int CMapHeader::MAP_SIZE_MIDDLE = 72;
|
||||
const int CMapHeader::MAP_SIZE_LARGE = 108;
|
||||
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),
|
||||
twoLevel(true), difficulty(1), levelLimit(0), howManyTeams(0), areAnyPlayers(false)
|
||||
{
|
||||
setupEvents();
|
||||
allowedHeroes = VLC->heroh->getDefaultAllowed();
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
assert(objs.size());
|
||||
if(objs.size() > 1 && lookForHero && objs.front()->ID != Obj::HERO)
|
||||
for (CGObjectInstance * object : getTile(pos).visitableObjects)
|
||||
{
|
||||
assert(objs.back()->ID == Obj::HERO);
|
||||
return objs.back();
|
||||
if (object->ID == type)
|
||||
return object;
|
||||
}
|
||||
else
|
||||
return objs.front();
|
||||
// possibly may trigger for empty placeholders in campaigns
|
||||
logGlobal->warnStream() << "Failed to find object of type " << int(type) << " at " << pos;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
if(isInTheMap(lossCondition.pos))
|
||||
{
|
||||
lossCondition.obj = getObjectiveObjectFrom(lossCondition.pos, lossCondition.typeOfLossCon == ELossConditionType::LOSSHERO);
|
||||
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);
|
||||
}
|
||||
|
||||
break; case EventCondition::DESTROY:
|
||||
if (isInTheMap(cond.position))
|
||||
cond.object = getObjectiveObjectFrom(cond.position, Obj::EObj(cond.objectType));
|
||||
|
||||
if (cond.object)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -16,6 +16,7 @@
|
||||
#include "../ResourceSet.h"
|
||||
#include "../int3.h"
|
||||
#include "../GameConstants.h"
|
||||
#include "../LogicalExpression.h"
|
||||
|
||||
class CArtifactInstance;
|
||||
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)
|
||||
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;
|
||||
int3 pos; /// the position of an object which mustn't be lost
|
||||
si32 timeLimit; /// time limit in days, -1 if not used
|
||||
const CGObjectInstance * obj;
|
||||
EventCondition(EWinLoseType condition = STANDARD_WIN);
|
||||
|
||||
const CGObjectInstance * object; // object that was at specified position on start
|
||||
si32 value;
|
||||
si32 objectType;
|
||||
int3 position;
|
||||
EWinLoseType condition;
|
||||
|
||||
template <typename Handler>
|
||||
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,
|
||||
/// receive a specific artifact, ...)
|
||||
struct DLL_LINKAGE VictoryCondition
|
||||
{
|
||||
VictoryCondition();
|
||||
typedef LogicalExpression<EventCondition> EventExpression;
|
||||
|
||||
EVictoryConditionType::EVictoryConditionType condition;
|
||||
bool allowNormalVictory; /// true if a normal victory is allowed (defeat all enemy towns, heroes)
|
||||
bool appliesToAI;
|
||||
/// 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)
|
||||
int3 pos;
|
||||
/// 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);
|
||||
si32 count;
|
||||
/// object of specific monster / city / hero instance (nullptr if not used); set during map parsing
|
||||
const CGObjectInstance * obj;
|
||||
struct EventEffect
|
||||
{
|
||||
enum EType
|
||||
{
|
||||
VICTORY,
|
||||
DEFEAT
|
||||
};
|
||||
|
||||
/// effect type, using EType enum
|
||||
si8 type;
|
||||
|
||||
/// message that will be sent to other players
|
||||
std::string toOtherMessage;
|
||||
|
||||
template <typename Handler>
|
||||
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
|
||||
AB = 0x15, // 21
|
||||
SOD = 0x1c, // 28
|
||||
// HOTA = 0x1e ... 0x20 // 28 ... 30
|
||||
WOG = 0x33 // 51
|
||||
};
|
||||
}
|
||||
@ -294,6 +333,7 @@ enum EMapFormat
|
||||
/// The map header holds information about loss/victory condition,map format, version, players, height, width,...
|
||||
class DLL_LINKAGE CMapHeader
|
||||
{
|
||||
void setupEvents();
|
||||
public:
|
||||
static const int MAP_SIZE_SMALL;
|
||||
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
|
||||
/// maximum level for heroes. This is the default value.
|
||||
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.
|
||||
ui8 howManyTeams;
|
||||
std::vector<bool> allowedHeroes;
|
||||
std::vector<ui16> placeholdedHeroes;
|
||||
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>
|
||||
void serialize(Handler & h, const int Version)
|
||||
{
|
||||
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 addQuest(CGObjectInstance * quest);
|
||||
|
||||
/// Gets the topmost object or the lowermost object depending on the flag lookForHero from the specified position.
|
||||
const CGObjectInstance * getObjectiveObjectFrom(int3 pos, bool lookForHero);
|
||||
/// Gets object of specified type on requested position
|
||||
const CGObjectInstance * getObjectiveObjectFrom(int3 pos, Obj::EObj type);
|
||||
CGHeroInstance * getHero(int heroId);
|
||||
|
||||
/// Sets the victory/loss condition objectives ??
|
||||
|
@ -20,6 +20,7 @@
|
||||
|
||||
#include "../CSpellHandler.h"
|
||||
#include "../CCreatureHandler.h"
|
||||
#include "../CGeneralTextHandler.h"
|
||||
#include "../CHeroHandler.h"
|
||||
#include "../CObjectHandler.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()
|
||||
{
|
||||
mapHeader->victoryCondition.obj = nullptr;
|
||||
mapHeader->victoryCondition.condition = (EVictoryConditionType::EVictoryConditionType)reader.readUInt8();
|
||||
mapHeader->triggeredEvents.clear();
|
||||
|
||||
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
|
||||
if(mapHeader->victoryCondition.condition != EVictoryConditionType::WINSTANDARD)
|
||||
if(vicCondition == EVictoryConditionType::WINSTANDARD)
|
||||
{
|
||||
mapHeader->victoryCondition.allowNormalVictory = reader.readBool();
|
||||
mapHeader->victoryCondition.appliesToAI = reader.readBool();
|
||||
// create normal condition
|
||||
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
|
||||
// int nr = 0;
|
||||
switch(mapHeader->victoryCondition.condition)
|
||||
mapHeader->victoryIconIndex = ui16(vicCondition);
|
||||
mapHeader->victoryMessage = VLC->generaltexth->victoryConditions[size_t(vicCondition) + 1];
|
||||
|
||||
bool allowNormalVictory = reader.readBool();
|
||||
bool appliesToAI = reader.readBool();
|
||||
|
||||
switch(vicCondition)
|
||||
{
|
||||
case EVictoryConditionType::ARTIFACT:
|
||||
{
|
||||
mapHeader->victoryCondition.objectId = reader.readUInt8();
|
||||
EventCondition cond(EventCondition::HAVE_ARTIFACT);
|
||||
cond.objectType = reader.readUInt8();
|
||||
if (mapHeader->version != EMapFormat::ROE)
|
||||
reader.skip(1);
|
||||
|
||||
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[281];
|
||||
specialVictory.onFulfill = VLC->generaltexth->allTexts[280];
|
||||
specialVictory.trigger = EventExpression(cond);
|
||||
break;
|
||||
}
|
||||
case EVictoryConditionType::GATHERTROOP:
|
||||
{
|
||||
mapHeader->victoryCondition.objectId = reader.readUInt8();
|
||||
EventCondition cond(EventCondition::HAVE_CREATURES);
|
||||
cond.objectType = reader.readUInt8();
|
||||
if (mapHeader->version != EMapFormat::ROE)
|
||||
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;
|
||||
}
|
||||
case EVictoryConditionType::GATHERRESOURCE:
|
||||
{
|
||||
mapHeader->victoryCondition.objectId = reader.readUInt8();
|
||||
mapHeader->victoryCondition.count = reader.readUInt32();
|
||||
EventCondition cond(EventCondition::HAVE_RESOURCES);
|
||||
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;
|
||||
}
|
||||
case EVictoryConditionType::BUILDCITY:
|
||||
{
|
||||
mapHeader->victoryCondition.pos = readInt3();
|
||||
mapHeader->victoryCondition.count = reader.readUInt8();
|
||||
mapHeader->victoryCondition.objectId = reader.readUInt8();
|
||||
EventExpression::OperatorAll oper;
|
||||
EventCondition cond(EventCondition::HAVE_BUILDING);
|
||||
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;
|
||||
}
|
||||
case EVictoryConditionType::BUILDGRAIL:
|
||||
{
|
||||
int3 p = readInt3();
|
||||
if(p.z > 2)
|
||||
{
|
||||
p = int3(-1,-1,-1);
|
||||
}
|
||||
mapHeader->victoryCondition.pos = p;
|
||||
EventCondition cond(EventCondition::HAVE_BUILDING);
|
||||
cond.objectType = BuildingID::GRAIL;
|
||||
cond.position = readInt3();
|
||||
if(cond.position.z > 2)
|
||||
cond.position = int3(-1,-1,-1);
|
||||
|
||||
specialVictory.effect.toOtherMessage = VLC->generaltexth->allTexts[285];
|
||||
specialVictory.onFulfill = VLC->generaltexth->allTexts[284];
|
||||
specialVictory.trigger = EventExpression(cond);
|
||||
break;
|
||||
}
|
||||
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:
|
||||
{
|
||||
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:
|
||||
{
|
||||
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;
|
||||
}
|
||||
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:
|
||||
{
|
||||
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;
|
||||
}
|
||||
case EVictoryConditionType::TRANSPORTITEM:
|
||||
{
|
||||
mapHeader->victoryCondition.objectId = reader.readUInt8();
|
||||
mapHeader->victoryCondition.pos = readInt3();
|
||||
EventCondition cond(EventCondition::TRANSPORT);
|
||||
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;
|
||||
}
|
||||
default:
|
||||
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
|
||||
mapHeader->lossCondition.typeOfLossCon = (ELossConditionType::ELossConditionType) reader.readUInt8();
|
||||
switch(mapHeader->lossCondition.typeOfLossCon)
|
||||
auto lossCond = (ELossConditionType::ELossConditionType)reader.readUInt8();
|
||||
if (lossCond == ELossConditionType::LOSSSTANDARD)
|
||||
{
|
||||
case ELossConditionType::LOSSCASTLE:
|
||||
case ELossConditionType::LOSSHERO:
|
||||
{
|
||||
mapHeader->lossCondition.pos = readInt3();
|
||||
break;
|
||||
}
|
||||
case ELossConditionType::TIMEEXPIRES:
|
||||
{
|
||||
mapHeader->lossCondition.timeLimit = reader.readUInt16();
|
||||
break;
|
||||
}
|
||||
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:
|
||||
{
|
||||
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:
|
||||
{
|
||||
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;
|
||||
}
|
||||
case ELossConditionType::TIMEEXPIRES:
|
||||
{
|
||||
EventCondition cond(EventCondition::DAYS_PASSED);
|
||||
cond.value = reader.readUInt16();
|
||||
|
||||
specialDefeat.onFulfill = VLC->generaltexth->allTexts[254];
|
||||
specialDefeat.trigger = EventExpression(cond);
|
||||
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()
|
||||
@ -470,10 +665,18 @@ void CMapLoaderH3M::readAllowedArtifacts()
|
||||
}
|
||||
|
||||
// Messy, but needed
|
||||
if(map->victoryCondition.condition == EVictoryConditionType::ARTIFACT
|
||||
|| map->victoryCondition.condition == EVictoryConditionType::TRANSPORTITEM)
|
||||
for (TriggeredEvent & event : map->triggeredEvents)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1923,8 +1923,7 @@ void CGameHandler::heroVisitCastle(const CGTownInstance * obj, const CGHeroInsta
|
||||
vistiCastleObjects (obj, hero);
|
||||
giveSpells (obj, hero);
|
||||
|
||||
if(gs->map->victoryCondition.condition == EVictoryConditionType::TRANSPORTITEM)
|
||||
checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact?
|
||||
checkVictoryLossConditionsForPlayer(hero->tempOwner); //transported artifact?
|
||||
}
|
||||
|
||||
void CGameHandler::vistiCastleObjects (const CGTownInstance *t, const CGHeroInstance *h)
|
||||
@ -2168,29 +2167,25 @@ void CGameHandler::applyAndSend(CPackForClient * info)
|
||||
void CGameHandler::sendAndApply(CGarrisonOperationPack * info)
|
||||
{
|
||||
sendAndApply(static_cast<CPackForClient*>(info));
|
||||
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERTROOP)
|
||||
checkVictoryLossConditionsForAll();
|
||||
checkVictoryLossConditionsForAll();
|
||||
}
|
||||
|
||||
void CGameHandler::sendAndApply( SetResource * info )
|
||||
{
|
||||
sendAndApply(static_cast<CPackForClient*>(info));
|
||||
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERRESOURCE)
|
||||
checkVictoryLossConditionsForPlayer(info->player);
|
||||
checkVictoryLossConditionsForPlayer(info->player);
|
||||
}
|
||||
|
||||
void CGameHandler::sendAndApply( SetResources * info )
|
||||
{
|
||||
sendAndApply(static_cast<CPackForClient*>(info));
|
||||
if(gs->map->victoryCondition.condition == EVictoryConditionType::GATHERRESOURCE)
|
||||
checkVictoryLossConditionsForPlayer(info->player);
|
||||
checkVictoryLossConditionsForPlayer(info->player);
|
||||
}
|
||||
|
||||
void CGameHandler::sendAndApply( NewStructures * info )
|
||||
{
|
||||
sendAndApply(static_cast<CPackForClient*>(info));
|
||||
if(gs->map->victoryCondition.condition == EVictoryConditionType::BUILDCITY)
|
||||
checkVictoryLossConditionsForPlayer(getTown(info->tid)->tempOwner);
|
||||
checkVictoryLossConditionsForPlayer(getTown(info->tid)->tempOwner);
|
||||
}
|
||||
|
||||
void CGameHandler::save(const std::string & filename )
|
||||
@ -5048,7 +5043,8 @@ void CGameHandler::checkVictoryLossConditions(const std::set<PlayerColor> & play
|
||||
{
|
||||
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);
|
||||
|
||||
if(victoryLossCheckResult != EVictoryLossCheckResult::NO_VICTORY_OR_LOSS)
|
||||
if(victoryLossCheckResult.victory() || victoryLossCheckResult.loss())
|
||||
{
|
||||
InfoWindow iw;
|
||||
getVictoryLossMessage(player, victoryLossCheckResult, iw);
|
||||
@ -5083,18 +5079,19 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
|
||||
if(victoryLossCheckResult.victory())
|
||||
{
|
||||
//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++)
|
||||
{
|
||||
if(i->first != player && gs->getPlayer(i->first)->status == EPlayerStatus::INGAME)
|
||||
{
|
||||
iw.player = i->first;
|
||||
sendAndApply(&iw);
|
||||
|
||||
peg.player = i->first;
|
||||
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);
|
||||
}
|
||||
}
|
||||
@ -5122,7 +5119,6 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
|
||||
connection->prepareForSendingHeroes();
|
||||
}
|
||||
|
||||
|
||||
UpdateCampaignState ucs;
|
||||
ucs.camp = gs->scenarioOps->campState;
|
||||
sendAndApply(&ucs);
|
||||
@ -5149,6 +5145,19 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
|
||||
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);
|
||||
}
|
||||
|
||||
@ -5162,110 +5171,14 @@ void CGameHandler::checkVictoryLossConditionsForPlayer(PlayerColor player)
|
||||
|
||||
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.text.clear();
|
||||
out.text << victoryLossCheckResult.messageToSelf;
|
||||
// hackish, insert one player-specific string, if applicable
|
||||
if (victoryLossCheckResult.messageToSelf.find("%s") != std::string::npos)
|
||||
out.text.addReplacement(MetaString::COLOR, player.getNum());
|
||||
|
||||
if(victoryLossCheckResult == EVictoryLossCheckResult::VICTORY_SPECIAL)
|
||||
{
|
||||
switch(gs->map->victoryCondition.condition)
|
||||
{
|
||||
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.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";
|
||||
}
|
||||
out.components.push_back(Component(Component::FLAG, player.getNum(), 0, 0));
|
||||
}
|
||||
|
||||
bool CGameHandler::dig( const CGHeroInstance *h )
|
||||
|
Loading…
Reference in New Issue
Block a user