1 #include "stdafx.h"
2 #include "campaign_builder.h"
3 #include "options.h"
4 #include "campaign_type.h"
5 #include "player_role.h"
6 #include "view.h"
7 #include "enemy_factory.h"
8 #include "villain_type.h"
9 #include "creature.h"
10 #include "creature_name.h"
11 #include "retired_games.h"
12 #include "view_object.h"
13 #include "name_generator.h"
14 #include "creature_factory.h"
15 
16 
considerStaticPlayerPos(const Campaign & campaign)17 optional<Vec2> CampaignBuilder::considerStaticPlayerPos(const Campaign& campaign) {
18   switch (campaign.type) {
19     case CampaignType::CAMPAIGN:
20     case CampaignType::QUICK_MAP:
21     case CampaignType::SINGLE_KEEPER:
22       return campaign.sites.getBounds().middle();
23     default:
24       return none;
25   }
26 }
27 
setCountLimits(Options * options)28 static void setCountLimits(Options* options) {
29 #ifdef RELEASE
30   options->setLimits(OptionId::MAIN_VILLAINS, 1, 4);
31 #else
32   options->setLimits(OptionId::MAIN_VILLAINS, 0, 4);
33 #endif
34   options->setLimits(OptionId::LESSER_VILLAINS, 0, 6);
35   options->setLimits(OptionId::ALLIES, 0, 4);
36   options->setLimits(OptionId::INFLUENCE_SIZE, 3, 6);
37 }
38 
getSecondaryOptions(CampaignType type) const39 vector<OptionId> CampaignBuilder::getSecondaryOptions(CampaignType type) const {
40   switch (type) {
41     case CampaignType::QUICK_MAP:
42     case CampaignType::CAMPAIGN:
43       return {};
44     case CampaignType::ENDLESS:
45       return {OptionId::LESSER_VILLAINS, OptionId::ALLIES};
46     case CampaignType::FREE_PLAY:
47       if (playerRole == PlayerRole::KEEPER)
48         return {OptionId::MAIN_VILLAINS, OptionId::LESSER_VILLAINS, OptionId::ALLIES, OptionId::GENERATE_MANA};
49       else
50         return {OptionId::MAIN_VILLAINS, OptionId::LESSER_VILLAINS, OptionId::ALLIES};
51     case CampaignType::SINGLE_KEEPER:
52       return {};
53   }
54 }
55 
getPlayerNameOptionId() const56 OptionId CampaignBuilder::getPlayerNameOptionId() const {
57   switch (playerRole) {
58     case PlayerRole::KEEPER:
59       return OptionId::KEEPER_NAME;
60     case PlayerRole::ADVENTURER:
61       return OptionId::ADVENTURER_NAME;
62   }
63 }
64 
getPlayerTypeOptionId() const65 OptionId CampaignBuilder::getPlayerTypeOptionId() const {
66   switch (playerRole) {
67     case PlayerRole::KEEPER:
68       return OptionId::KEEPER_TYPE;
69     case PlayerRole::ADVENTURER:
70       return OptionId::ADVENTURER_TYPE;
71   }
72 }
73 
getPlayerTribeId() const74 TribeId CampaignBuilder::getPlayerTribeId() const {
75   switch (playerRole) {
76     case PlayerRole::KEEPER:
77       return TribeId::getKeeper();
78     case PlayerRole::ADVENTURER:
79       return TribeId::getAdventurer();
80   }
81 }
82 
getPrimaryOptions() const83 vector<OptionId> CampaignBuilder::getPrimaryOptions() const {
84   return {getPlayerTypeOptionId(), getPlayerNameOptionId()};
85 }
86 
getCampaignTypeDescription(CampaignType type)87 static vector<string> getCampaignTypeDescription(CampaignType type) {
88   switch (type) {
89     case CampaignType::CAMPAIGN:
90       return {
91         "the main competitive mode"
92       };
93     case CampaignType::FREE_PLAY:
94       return {
95         "retired dungeons of other players",
96         "custom starting point and villains",
97         "highscores not recorded",
98       };
99     case CampaignType::SINGLE_KEEPER:
100       return {
101         "everyone on one big map",
102         "retiring not possible"
103       };
104     case CampaignType::ENDLESS:
105       return {
106         "conquest not mandatory",
107         "recurring enemy waves",
108         "survive as long as possible"
109       };
110     default:
111       return {};
112   }
113 }
114 
getAvailableTypes() const115 vector<CampaignType> CampaignBuilder::getAvailableTypes() const {
116   switch (playerRole) {
117     case PlayerRole::KEEPER:
118       return {
119         CampaignType::CAMPAIGN,
120         CampaignType::FREE_PLAY,
121         CampaignType::SINGLE_KEEPER,
122         CampaignType::ENDLESS,
123 #ifndef RELEASE
124         CampaignType::QUICK_MAP,
125 #endif
126       };
127     case PlayerRole::ADVENTURER:
128       return {
129         CampaignType::CAMPAIGN,
130         CampaignType::FREE_PLAY,
131       };
132   }
133 }
134 
getSiteChoiceTitle(CampaignType type) const135 optional<string> CampaignBuilder::getSiteChoiceTitle(CampaignType type) const {
136   switch (type) {
137     case CampaignType::FREE_PLAY:
138     case CampaignType::ENDLESS:
139       switch (playerRole) {
140         case PlayerRole::KEEPER: return "Choose the location of your base:"_s;
141         case PlayerRole::ADVENTURER: return "Choose a location to start your adventure:"_s;
142       }
143       break;
144     default:
145       return none;
146   }
147 }
148 
getMainVillains()149 vector<Campaign::VillainInfo> CampaignBuilder::getMainVillains() {
150   switch (playerRole) {
151     case PlayerRole::KEEPER:
152       return {
153         {ViewId::DUKE, EnemyId::KNIGHTS, "Knights", VillainType::MAIN},
154         {ViewId::ELF_LORD, EnemyId::ELVES, "Elves", VillainType::MAIN},
155         {ViewId::DWARF_BARON, EnemyId::DWARVES, "Dwarves", VillainType::MAIN},
156         {ViewId::RED_DRAGON, EnemyId::RED_DRAGON, "Red dragon", VillainType::MAIN},
157         {ViewId::ELEMENTALIST, EnemyId::ELEMENTALIST, "Elementalist", VillainType::MAIN},
158         {ViewId::GREEN_DRAGON, EnemyId::GREEN_DRAGON, "Green dragon", VillainType::MAIN},
159         {ViewId::LIZARDLORD, EnemyId::LIZARDMEN, "Lizardmen", VillainType::MAIN},
160         {ViewId::SHAMAN, EnemyId::WARRIORS, "Warriors", VillainType::MAIN},
161         {ViewId::DEMON_LORD, EnemyId::DEMON_DEN, "Demon den", VillainType::MAIN},
162       };
163     case PlayerRole::ADVENTURER:
164       return {
165         {ViewId::RED_DRAGON, EnemyId::RED_DRAGON, "Red dragon", VillainType::MAIN},
166         {ViewId::GREEN_DRAGON, EnemyId::GREEN_DRAGON, "Green dragon", VillainType::MAIN},
167         {ViewId::SHELOB, EnemyId::SHELOB, "Giant spider", VillainType::MAIN},
168         {ViewId::ANT_QUEEN, EnemyId::ANTS_OPEN, "Ants", VillainType::MAIN},
169         {ViewId::DARK_ELF_LORD, EnemyId::DARK_ELVES, "Dark elves", VillainType::MAIN},
170         {ViewId::ORC_CAPTAIN, EnemyId::ORC_VILLAGE, "Greenskin village", VillainType::MAIN},
171         {ViewId::DEMON_LORD, EnemyId::DEMON_DEN, "Demon den", VillainType::MAIN},
172       };
173   }
174 }
175 
getLesserVillains()176 vector<Campaign::VillainInfo> CampaignBuilder::getLesserVillains() {
177   switch (playerRole) {
178     case PlayerRole::KEEPER:
179       return {
180         {ViewId::ENT, EnemyId::ENTS, "Tree spirits", VillainType::LESSER},
181         {ViewId::DRIAD, EnemyId::DRIADS, "Driads", VillainType::LESSER},
182         {ViewId::CYCLOPS, EnemyId::CYCLOPS, "Cyclops", VillainType::LESSER},
183         {ViewId::SHELOB, EnemyId::SHELOB, "Giant spider", VillainType::LESSER},
184         {ViewId::HYDRA, EnemyId::HYDRA, "Hydra", VillainType::LESSER},
185         {ViewId::UNICORN, EnemyId::UNICORN_HERD, "Unicorn herd", VillainType::LESSER},
186         {ViewId::ANT_QUEEN, EnemyId::ANTS_OPEN, "Ants", VillainType::LESSER},
187         {ViewId::ZOMBIE, EnemyId::CEMETERY, "Zombies", VillainType::LESSER},
188       };
189     case PlayerRole::ADVENTURER:
190       return {
191         {ViewId::BANDIT, EnemyId::BANDITS, "Bandits", VillainType::LESSER},
192         {ViewId::CYCLOPS, EnemyId::CYCLOPS, "Cyclops", VillainType::LESSER},
193         {ViewId::HYDRA, EnemyId::HYDRA, "Hydra", VillainType::LESSER},
194         {ViewId::ZOMBIE, EnemyId::CEMETERY, "Zombies", VillainType::LESSER},
195         {ViewId::OGRE, EnemyId::OGRE_CAVE, "Ogres", VillainType::LESSER},
196         {ViewId::HARPY, EnemyId::HARPY_CAVE, "Harpies", VillainType::LESSER},
197       };
198   }
199 }
200 
getAllies()201 vector<Campaign::VillainInfo> CampaignBuilder::getAllies() {
202   switch (playerRole) {
203     case PlayerRole::KEEPER:
204       return {
205 //        {ViewId::UNKNOWN_MONSTER, EnemyId::OGRE_CAVE, "Unknown", VillainType::ALLY},
206 //        {ViewId::UNKNOWN_MONSTER, EnemyId::HARPY_CAVE, "Unknown", VillainType::ALLY},
207         {ViewId::UNKNOWN_MONSTER, EnemyId::SOKOBAN, "Unknown", VillainType::ALLY},
208         {ViewId::DARK_ELF_LORD, EnemyId::DARK_ELVES, "Dark elves", VillainType::ALLY},
209         {ViewId::GNOME_BOSS, EnemyId::GNOMES, "Gnomes", VillainType::ALLY},
210         {ViewId::ORC_CAPTAIN, EnemyId::ORC_VILLAGE, "Greenskin village", VillainType::ALLY},
211       };
212     case PlayerRole::ADVENTURER:
213       return {
214         {ViewId::DUKE, EnemyId::KNIGHTS, "Knights", VillainType::ALLY},
215         {ViewId::ELF_LORD, EnemyId::ELVES, "Elves", VillainType::ALLY},
216         {ViewId::DWARF_BARON, EnemyId::DWARVES, "Dwarves", VillainType::ALLY},
217         {ViewId::LIZARDLORD, EnemyId::LIZARDMEN, "Lizardmen", VillainType::ALLY},
218       };
219   }
220 }
221 
getIntroText() const222 const char* CampaignBuilder::getIntroText() const {
223    switch (playerRole) {
224     case PlayerRole::KEEPER:
225       return
226         "Welcome to the campaign mode! "
227         "The world, which you see below, is made up of smaller maps. You will build your base on one of them. "
228         "There are hostile and friendly tribes around you. You have to conquer all villains marked as \"main\" "
229         "to win the game."
230         "You can travel to other sites by creating a team and using the travel command.\n\n"
231         "The highlighted tribes are in your influence zone, which means that you can currently interact with them "
232         "(trade, recruit, attack or be attacked). "
233         "As you conquer more enemies, your influence zone grows.\n\n";
234     case PlayerRole::ADVENTURER:
235       return
236         "Welcome to the campaign mode! "
237         "The world, which you see below, is made up of smaller maps. Your adventure will start on one of them. "
238         "There are hostile and friendly tribes around you. You have to conquer all villains marked as \"main\" "
239         "to win the game."
240         "You can travel to other sites by using the travel command.\n\n"
241         "The highlighted tribes are in your influence zone, which means that you can currently travel there. "
242         "As you conquer more enemies, your influence zone grows.\n\n";
243    }
244 }
245 
setPlayerPos(Campaign & campaign,Vec2 pos,WConstCreature player)246 void CampaignBuilder::setPlayerPos(Campaign& campaign, Vec2 pos, WConstCreature player) {
247   switch (playerRole) {
248     case PlayerRole::KEEPER:
249       if (campaign.playerPos)
250         campaign.clearSite(*campaign.playerPos);
251       campaign.playerPos = pos;
252       campaign.sites[*campaign.playerPos].dweller =
253           Campaign::SiteInfo::Dweller(Campaign::KeeperInfo{player->getViewObject().id()});
254       break;
255     case PlayerRole:: ADVENTURER:
256       campaign.playerPos = pos;
257       break;
258   }
259 
260 }
261 
getPlayerCreature()262 PCreature CampaignBuilder::getPlayerCreature() {
263   PCreature ret = CreatureFactory::fromId(options->getCreatureId(getPlayerTypeOptionId()), getPlayerTribeId());
264   auto name = options->getStringValue(getPlayerNameOptionId());
265   if (!name.empty())
266     ret->getName().setFirst(name);
267   ret->getName().useFullTitle();
268   return ret;
269 }
270 
271 
getTerrain(RandomGen & random,Vec2 size,int numBlocked)272 static Table<Campaign::SiteInfo> getTerrain(RandomGen& random, Vec2 size, int numBlocked) {
273   Table<Campaign::SiteInfo> ret(size, {});
274   for (Vec2 v : ret.getBounds())
275     ret[v].viewId.push_back(ViewId::GRASS);
276   vector<Vec2> freePos = ret.getBounds().getAllSquares();
277   for (int i : Range(numBlocked)) {
278     Vec2 pos = random.choose(freePos);
279     freePos.removeElement(pos);
280     ret[pos].setBlocked();
281   }
282   return ret;
283 }
284 
285 struct VillainCounts {
286   int numMain;
287   int numLesser;
288   int numAllies;
289   int maxRetired;
290 };
291 
getVillainCounts(CampaignType type,Options * options)292 static VillainCounts getVillainCounts(CampaignType type, Options* options) {
293   switch (type) {
294     case CampaignType::FREE_PLAY: {
295       return {
296         options->getIntValue(OptionId::MAIN_VILLAINS),
297         options->getIntValue(OptionId::LESSER_VILLAINS),
298         options->getIntValue(OptionId::ALLIES),
299         10000
300       };
301     }
302     case CampaignType::CAMPAIGN:
303       return {4, 6, 2, 1};
304     case CampaignType::ENDLESS:
305       return {
306         0,
307         options->getIntValue(OptionId::LESSER_VILLAINS),
308         options->getIntValue(OptionId::ALLIES),
309         0
310       };
311     case CampaignType::QUICK_MAP:
312     case CampaignType::SINGLE_KEEPER:
313       return {0, 0, 0, 0};
314   }
315 }
316 
CampaignBuilder(View * v,RandomGen & rand,Options * o,PlayerRole r)317 CampaignBuilder::CampaignBuilder(View* v, RandomGen& rand, Options* o, PlayerRole r)
318     : view(v), random(rand), playerRole(r), options(o) {
319 }
320 
getNewIdSuffix()321 static string getNewIdSuffix() {
322   vector<char> chars;
323   for (char c : Range(128))
324     if (isalnum(c))
325       chars.push_back(c);
326   string ret;
327   for (int i : Range(4))
328     ret += Random.choose(chars);
329   return ret;
330 }
331 
332 struct VillainPlacement {
333   function<bool(int)> xPredicate;
334   optional<Vec2> firstLocation;
335 };
336 
placeVillains(Campaign & campaign,vector<Campaign::SiteInfo::Dweller> villains,const VillainPlacement & placement,int count)337 void CampaignBuilder::placeVillains(Campaign& campaign, vector<Campaign::SiteInfo::Dweller> villains,
338     const VillainPlacement& placement, int count) {
339   random.shuffle(villains.begin(), villains.end());
340   if (villains.size() > count)
341     villains.resize(count);
342   vector<Vec2> freePos;
343   for (Vec2 v : campaign.sites.getBounds())
344     if (!campaign.sites[v].blocked && campaign.sites[v].isEmpty() && placement.xPredicate(v.x))
345       freePos.push_back(v);
346   freePos = random.permutation(freePos);
347   if (auto& pos = placement.firstLocation)
348     freePos = concat({*pos}, freePos);
349   for (int i : All(villains))
350     campaign.sites[freePos[i]].dweller = villains[i];
351 }
352 
getVillainPlacement(const Campaign & campaign,VillainType type)353 VillainPlacement CampaignBuilder::getVillainPlacement(const Campaign& campaign, VillainType type) {
354   VillainPlacement ret { [&campaign](int x) { return campaign.sites.getBounds().getXRange().contains(x);}, none };
355   switch (campaign.getType()) {
356     case CampaignType::CAMPAIGN:
357       switch (type) {
358         case VillainType::LESSER:
359           ret.xPredicate = [](int x) { return x >= 5 && x < 12; };
360           break;
361         case VillainType::MAIN:
362           ret.xPredicate = [](int x) { return (x >= 1 && x < 5) || (x >= 12 && x < 16) ; };
363           break;
364         case VillainType::ALLY:
365           if (campaign.getPlayerRole() == PlayerRole::ADVENTURER)
366             ret.firstLocation = *considerStaticPlayerPos(campaign);
367           break;
368         default:
369           break;
370       }
371       break;
372     default:
373       break;
374   }
375   return ret;
376 }
377 
378 using Dweller = Campaign::SiteInfo::Dweller;
379 
380 template <typename T>
shuffle(RandomGen & random,vector<T> v)381 vector<Dweller> shuffle(RandomGen& random, vector<T> v) {
382   random.shuffle(v.begin(), v.end());
383   return v.transform([](const T& t) { return Dweller(t); });
384 }
385 
placeVillains(Campaign & campaign,const VillainCounts & counts,const optional<RetiredGames> & retired)386 void CampaignBuilder::placeVillains(Campaign& campaign, const VillainCounts& counts,
387     const optional<RetiredGames>& retired) {
388   int numRetired = retired ? min(retired->getNumActive(), min(counts.numMain, counts.maxRetired)) : 0;
389   placeVillains(campaign, shuffle(random, getMainVillains()), getVillainPlacement(campaign, VillainType::MAIN),
390       counts.numMain - numRetired);
391   placeVillains(campaign, shuffle(random, getLesserVillains()), getVillainPlacement(campaign, VillainType::LESSER),
392       counts.numLesser);
393   placeVillains(campaign, shuffle(random, getAllies()), getVillainPlacement(campaign, VillainType::ALLY),
394       counts.numAllies);
395   if (retired) {
396     placeVillains(campaign, retired->getActiveGames().transform(
397         [](const RetiredGames::RetiredGame& game) -> Dweller {
398           return Campaign::RetiredInfo{game.gameInfo, game.fileInfo};
399         }), getVillainPlacement(campaign, VillainType::MAIN), numRetired);
400   }
401 }
402 
getMenuWarning(CampaignType type)403 static optional<View::CampaignOptions::WarningType> getMenuWarning(CampaignType type) {
404   switch (type) {
405     case CampaignType::CAMPAIGN:
406       return View::CampaignOptions::OTHER_MODES;
407     case CampaignType::SINGLE_KEEPER:
408       return View::CampaignOptions::NO_RETIRE;
409     default:
410       return none;
411   }
412 }
413 
autoConfirm(CampaignType type)414 static bool autoConfirm(CampaignType type) {
415   switch (type) {
416     case CampaignType::QUICK_MAP:
417       return true;
418     default:
419       return false;
420   }
421 }
422 
getIntroMessages(CampaignType type,string worldName)423 static vector<string> getIntroMessages(CampaignType type, string worldName) {
424   vector<string> ret = {
425     "Welcome to KeeperRL Alpha23! A lot of gameplay changes have arrived with this update. Below is a very short "
426     "summary, and we encourage you to check out the full change log at www.keeperrl.com.\n \n"
427     "Mana is no longer generated at the library, and instead you only receive it for defeating enemies. "
428     "Many features, including construction and crafting costs, have been rebalanced to accommodate this change. "
429     "You will only need mana to research new technology, and increase the population limit.\n \n"
430     "If you miss the old ways, you can enable mana regeneration when playing the 'free play' game mode.\n \n"
431     "When commanding a team, you can choose to control every team member directly. "
432     "We encourage you to use this feature during combat, as it's extremely useful. You can toggle it using the "
433     "shortcut [G] or by going into the [Commands] menu.\n \n"
434     "From now on the vampire lord will be hostile when you wake him up, so be careful!"
435   };
436   if (type == CampaignType::ENDLESS)
437     ret.push_back(
438         "Welcome to the new endless mode! Your task here is to survive as long as possible, while "
439         "defending your dungeon from incoming enemy waves. The enemies don't come from any specific place and "
440         "will just appear at the edge of the map. You will get mana for defeating each wave. "
441         "Note that there are also traditional enemy villages scattered around and they may also attack you.\n \n"
442         "The endless mode is a completely new feature and we are very interested in your feedback on how "
443         "it can be developed further. Please drop by on the forums at keeperrl.com or on Steam and let us know!"
444     );
445 
446   return ret;
447 }
448 
prepareCampaign(function<optional<RetiredGames> (CampaignType)> genRetired,CampaignType type)449 optional<CampaignSetup> CampaignBuilder::prepareCampaign(function<optional<RetiredGames>(CampaignType)> genRetired,
450     CampaignType type) {
451   Vec2 size(17, 9);
452   int numBlocked = 0.6 * size.x * size.y;
453   Table<Campaign::SiteInfo> terrain = getTerrain(random, size, numBlocked);
454   auto retired = genRetired(type);
455   View::CampaignMenuState menuState { true, false};
456   setCountLimits(options);
457   options->setChoices(OptionId::KEEPER_TYPE, {CreatureId::KEEPER, CreatureId::KEEPER_F});
458   options->setChoices(OptionId::ADVENTURER_TYPE, {CreatureId::ADVENTURER, CreatureId::ADVENTURER_F});
459   while (1) {
460     PCreature player = getPlayerCreature();
461     Campaign campaign(terrain, type, playerRole, NameGenerator::get(NameGeneratorId::WORLD)->getNext());
462     if (auto pos = considerStaticPlayerPos(campaign)) {
463       campaign.clearSite(*pos);
464       setPlayerPos(campaign, *pos, player.get());
465     }
466     placeVillains(campaign, getVillainCounts(type, options), retired);
467     while (1) {
468       bool updateMap = false;
469       campaign.influenceSize = options->getIntValue(OptionId::INFLUENCE_SIZE);
470       campaign.refreshInfluencePos();
471       CampaignAction action = autoConfirm(type) ? CampaignActionId::CONFIRM
472           : view->prepareCampaign({
473               campaign,
474               (retired && type == CampaignType::FREE_PLAY) ? optional<RetiredGames&>(*retired) : none,
475               player.get(),
476               getPrimaryOptions(),
477               getSecondaryOptions(type),
478               getSiteChoiceTitle(type),
479               getIntroText(),
480               getAvailableTypes().transform([](CampaignType t) -> View::CampaignOptions::CampaignTypeInfo {
481                   return {t, getCampaignTypeDescription(t)};}),
482               getMenuWarning(type)
483               }, options, menuState);
484       switch (action.getId()) {
485         case CampaignActionId::REROLL_MAP:
486             terrain = getTerrain(random, size, numBlocked);
487             updateMap = true;
488             break;
489         case CampaignActionId::UPDATE_MAP:
490             updateMap = true;
491             break;
492         case CampaignActionId::CHANGE_TYPE:
493             type = action.get<CampaignType>();
494             retired = genRetired(type);
495             updateMap = true;
496             break;
497         case CampaignActionId::UPDATE_OPTION:
498             switch (action.get<OptionId>()) {
499               case OptionId::KEEPER_NAME:
500               case OptionId::ADVENTURER_NAME:
501               case OptionId::KEEPER_TYPE:
502               case OptionId::ADVENTURER_TYPE:
503                 player = getPlayerCreature();
504                 if (campaign.playerPos) {
505                   setPlayerPos(campaign, *campaign.playerPos, player.get());
506                 }
507                 break;
508               case OptionId::GENERATE_MANA:
509               case OptionId::INFLUENCE_SIZE: break;
510               default: updateMap = true; break;
511             }
512             break;
513         case CampaignActionId::CANCEL:
514             return none;
515         case CampaignActionId::CHOOSE_SITE:
516             if (!considerStaticPlayerPos(campaign))
517               setPlayerPos(campaign, action.get<Vec2>(), player.get());
518             break;
519         case CampaignActionId::CONFIRM:
520             if (!retired || retired->getNumActive() > 0 || playerRole != PlayerRole::KEEPER ||
521                 retired->getAllGames().empty() ||
522                 view->yesOrNoPrompt("The imps are going to be sad if you don't add any retired dungeons. Continue?")) {
523               string name = *player->getName().first();
524               string gameIdentifier = name + "_" + campaign.worldName + getNewIdSuffix();
525               string gameDisplayName = name + " of " + campaign.worldName;
526               return CampaignSetup{campaign, std::move(player), gameIdentifier, gameDisplayName,
527                   options->getBoolValue(OptionId::GENERATE_MANA) &&
528                   getSecondaryOptions(type).contains(OptionId::GENERATE_MANA),
529                   getIntroMessages(type, campaign.getWorldName())};
530             }
531       }
532       if (updateMap)
533         break;
534     }
535   }
536 }
537 
getEmptyCampaign()538 CampaignSetup CampaignBuilder::getEmptyCampaign() {
539   Campaign ret(Table<Campaign::SiteInfo>(1, 1), CampaignType::SINGLE_KEEPER, PlayerRole::KEEPER, "");
540   return CampaignSetup{ret, PCreature(nullptr), "", ""};
541 }
542