1 #include "stdafx.h"
2 #include "model_builder.h"
3 #include "level.h"
4 #include "tribe.h"
5 #include "inventory.h"
6 #include "collective_builder.h"
7 #include "options.h"
8 #include "player_control.h"
9 #include "spectator.h"
10 #include "creature.h"
11 #include "square.h"
12 #include "progress_meter.h"
13 #include "collective.h"
14 #include "level_maker.h"
15 #include "model.h"
16 #include "level_builder.h"
17 #include "monster_ai.h"
18 #include "game.h"
19 #include "campaign.h"
20 #include "creature_name.h"
21 #include "villain_type.h"
22 #include "enemy_factory.h"
23 #include "view_object.h"
24 #include "item.h"
25 #include "furniture.h"
26 #include "sokoban_input.h"
27 #include "external_enemies.h"
28 #include "immigration.h"
29 #include "technology.h"
30 #include "keybinding.h"
31 #include "tutorial.h"
32 #include "settlement_info.h"
33 #include "enemy_info.h"
34
35 using namespace std::chrono;
36
ModelBuilder(ProgressMeter * m,RandomGen & r,Options * o,SokobanInput * sok)37 ModelBuilder::ModelBuilder(ProgressMeter* m, RandomGen& r, Options* o, SokobanInput* sok) : random(r), meter(m), options(o),
38 enemyFactory(EnemyFactory(random)), sokobanInput(sok) {
39 }
40
~ModelBuilder()41 ModelBuilder::~ModelBuilder() {
42 }
43
getPigstyPopulationIncrease()44 int ModelBuilder::getPigstyPopulationIncrease() {
45 return 4;
46 }
47
getStatuePopulationIncrease()48 int ModelBuilder::getStatuePopulationIncrease() {
49 return 1;
50 }
51
getThronePopulationIncrease()52 int ModelBuilder::getThronePopulationIncrease() {
53 return 10;
54 }
55
getKeeperConfig(RandomGen & random,bool fastImmigration,bool regenerateMana)56 static CollectiveConfig getKeeperConfig(RandomGen& random, bool fastImmigration, bool regenerateMana) {
57 return CollectiveConfig::keeper(
58 fastImmigration ? 10 : 140,
59 10,
60 regenerateMana,
61 {
62 CONSTRUCT(PopulationIncrease,
63 c.type = FurnitureType::PIGSTY;
64 c.increasePerSquare = 0.25;
65 c.maxIncrease = ModelBuilder::getPigstyPopulationIncrease();),
66 CONSTRUCT(PopulationIncrease,
67 c.type = FurnitureType::MINION_STATUE;
68 c.increasePerSquare = ModelBuilder::getStatuePopulationIncrease();
69 c.maxIncrease = 1000;),
70 CONSTRUCT(PopulationIncrease,
71 c.type = FurnitureType::THRONE;
72 c.increasePerSquare = ModelBuilder::getThronePopulationIncrease();
73 c.maxIncrease = c.increasePerSquare;),
74 },
75 {
76 ImmigrantInfo(CreatureId::IMP, {MinionTrait::WORKER, MinionTrait::NO_LIMIT, MinionTrait::NO_EQUIPMENT})
77 .setSpawnLocation(NearLeader{})
78 .setKeybinding(Keybinding::CREATE_IMP)
79 .setSound(Sound(SoundId::CREATE_IMP).setPitch(2))
80 .setNoAuto()
81 .setInitialRecruitment(4)
82 .addRequirement(ExponentialCost{ CostInfo(CollectiveResourceId::GOLD, 6), 5, 4 }),
83 ImmigrantInfo(CreatureId::GOBLIN, {MinionTrait::FIGHTER, MinionTrait::NO_EQUIPMENT})
84 .setFrequency(0.7)
85 .addRequirement(0.1, AttractionInfo{1, vector<AttractionType>(
86 {FurnitureType::FORGE, FurnitureType::WORKSHOP, FurnitureType::JEWELER})}),
87 ImmigrantInfo(CreatureId::ORC, {MinionTrait::FIGHTER})
88 .setFrequency(0.7)
89 .addRequirement(0.1, AttractionInfo{1, FurnitureType::TRAINING_WOOD}),
90 ImmigrantInfo(CreatureId::ORC_SHAMAN, {MinionTrait::FIGHTER})
91 .setFrequency(0.6)
92 .addRequirement(0.1, AttractionInfo{1, {FurnitureType::BOOKCASE_WOOD, FurnitureType::LABORATORY}}),
93 ImmigrantInfo(CreatureId::OGRE, {MinionTrait::FIGHTER})
94 .setFrequency(0.3)
95 .addRequirement(0.1, AttractionInfo{1, FurnitureType::TRAINING_IRON}),
96 ImmigrantInfo(CreatureId::HARPY, {MinionTrait::FIGHTER})
97 .setFrequency(0.3)
98 .addRequirement(0.1, AttractionInfo{1, FurnitureType::TRAINING_WOOD})
99 .addRequirement(0.3, AttractionInfo{1, ItemIndex::RANGED_WEAPON}),
100 ImmigrantInfo(CreatureId::ZOMBIE, {MinionTrait::FIGHTER})
101 .setFrequency(0.5)
102 .setSpawnLocation(FurnitureType::GRAVE)
103 .addRequirement(0.0, CostInfo(CollectiveResourceId::CORPSE, 1)),
104 ImmigrantInfo(CreatureId::SKELETON, {MinionTrait::FIGHTER})
105 .setFrequency(0.5)
106 .setSpawnLocation(FurnitureType::GRAVE)
107 .addRequirement(0.1, AttractionInfo{1, FurnitureType::TRAINING_IRON})
108 .addRequirement(0.0, CostInfo(CollectiveResourceId::CORPSE, 1)),
109 ImmigrantInfo(CreatureId::VAMPIRE, {MinionTrait::FIGHTER})
110 .setFrequency(0.2)
111 .setSpawnLocation(FurnitureType::GRAVE)
112 .addRequirement(0.1, AttractionInfo{1, FurnitureType::TRAINING_IRON})
113 .addRequirement(0.0, CostInfo(CollectiveResourceId::CORPSE, 1)),
114 ImmigrantInfo(CreatureId::LOST_SOUL, {MinionTrait::FIGHTER})
115 .setFrequency(0.3)
116 .setSpawnLocation(FurnitureType::DEMON_SHRINE)
117 .addRequirement(0.3, AttractionInfo{1, FurnitureType::DEMON_SHRINE})
118 .addRequirement(0.0, FurnitureType::DEMON_SHRINE),
119 ImmigrantInfo(CreatureId::SUCCUBUS, {MinionTrait::FIGHTER, MinionTrait::NO_EQUIPMENT})
120 .setFrequency(0.3)
121 .setSpawnLocation(FurnitureType::DEMON_SHRINE)
122 .addRequirement(0.3, AttractionInfo{2, FurnitureType::DEMON_SHRINE})
123 .addRequirement(0.0, FurnitureType::DEMON_SHRINE),
124 ImmigrantInfo(CreatureId::DOPPLEGANGER, {MinionTrait::FIGHTER})
125 .setFrequency(0.3)
126 .setSpawnLocation(FurnitureType::DEMON_SHRINE)
127 .addRequirement(0.3, AttractionInfo{3, FurnitureType::DEMON_SHRINE})
128 .addRequirement(0.0, FurnitureType::DEMON_SHRINE),
129 ImmigrantInfo(CreatureId::RAVEN, {MinionTrait::FIGHTER, MinionTrait::NO_RETURNING})
130 .setFrequency(0.5)
131 .addRequirement(0.0, FurnitureType::BEAST_CAGE)
132 .addRequirement(0.0, SunlightState::DAY),
133 ImmigrantInfo(CreatureId::BAT, {MinionTrait::FIGHTER, MinionTrait::NO_RETURNING})
134 .setFrequency(0.5)
135 .addRequirement(0.0, FurnitureType::BEAST_CAGE)
136 .addRequirement(0.0, SunlightState::NIGHT),
137 ImmigrantInfo(CreatureId::WOLF, {MinionTrait::FIGHTER, MinionTrait::NO_RETURNING})
138 .setFrequency(0.15)
139 .addRequirement(0.0, FurnitureType::BEAST_CAGE)
140 .setGroupSize(Range(3, 9))
141 .setAutoTeam()
142 .addRequirement(0.0, SunlightState::NIGHT),
143 ImmigrantInfo(CreatureId::CAVE_BEAR, {MinionTrait::FIGHTER, MinionTrait::NO_RETURNING})
144 .addRequirement(0.0, FurnitureType::BEAST_CAGE)
145 .setFrequency(0.1),
146 ImmigrantInfo(CreatureId::WEREWOLF, {MinionTrait::FIGHTER, MinionTrait::NO_RETURNING})
147 .setFrequency(0.1)
148 .addRequirement(0.1, AttractionInfo{2, FurnitureType::TRAINING_IRON}),
149 ImmigrantInfo(CreatureId::DARK_ELF_WARRIOR, {MinionTrait::FIGHTER})
150 .addRequirement(0.0, RecruitmentInfo{{EnemyId::DARK_ELVES}, 3, MinionTrait::FIGHTER})
151 .addRequirement(CostInfo(CollectiveResourceId::GOLD, 20)),
152 ImmigrantInfo(CreatureId::ORC, {MinionTrait::FIGHTER})
153 .addRequirement(0.0, RecruitmentInfo{{EnemyId::ORC_VILLAGE}, 3, MinionTrait::FIGHTER})
154 .addRequirement(CostInfo(CollectiveResourceId::GOLD, 5)),
155 ImmigrantInfo(CreatureId::HARPY, {MinionTrait::FIGHTER})
156 .addRequirement(0.0, RecruitmentInfo{{EnemyId::HARPY_CAVE}, 3, MinionTrait::FIGHTER})
157 .addRequirement(CostInfo(CollectiveResourceId::GOLD, 12)),
158 ImmigrantInfo(CreatureId::OGRE, {MinionTrait::FIGHTER})
159 .addRequirement(0.0, RecruitmentInfo{{EnemyId::OGRE_CAVE, EnemyId::ORC_VILLAGE}, 3, MinionTrait::FIGHTER})
160 .addRequirement(CostInfo(CollectiveResourceId::GOLD, 12)),
161 ImmigrantInfo(random.permutation({CreatureId::SPECIAL_HMBN, CreatureId::SPECIAL_HMBW,
162 CreatureId::SPECIAL_HMGN, CreatureId::SPECIAL_HMGW}), {MinionTrait::FIGHTER})
163 .addRequirement(0.0, TechId::HUMANOID_MUT)
164 .addRequirement(0.0, Pregnancy {})
165 .addRequirement(CostInfo(CollectiveResourceId::GOLD, 100))
166 .setSpawnLocation(Pregnancy {}),
167 ImmigrantInfo(random.permutation({CreatureId::SPECIAL_BMBN, CreatureId::SPECIAL_BMBW,
168 CreatureId::SPECIAL_BMGN, CreatureId::SPECIAL_BMGW}), {MinionTrait::FIGHTER})
169 .addRequirement(0.0, TechId::BEAST_MUT)
170 .addRequirement(0.0, Pregnancy {})
171 .addRequirement(CostInfo(CollectiveResourceId::GOLD, 100))
172 .setSpawnLocation(Pregnancy {})
173 });
174 }
175
makeExtraLevel(WModel model,EnemyInfo & enemy)176 SettlementInfo& ModelBuilder::makeExtraLevel(WModel model, EnemyInfo& enemy) {
177 const int towerHeight = random.get(7, 12);
178 const int gnomeHeight = random.get(3, 5);
179 SettlementInfo& mainSettlement = enemy.settlement;
180 SettlementInfo& extraSettlement = enemy.levelConnection->otherEnemy->settlement;
181 switch (enemy.levelConnection->type) {
182 case LevelConnection::TOWER: {
183 StairKey downLink = StairKey::getNew();
184 extraSettlement.upStairs = {downLink};
185 for (int i : Range(towerHeight - 1)) {
186 StairKey upLink = StairKey::getNew();
187 model->buildLevel(
188 LevelBuilder(meter, random, 4, 4, "Tower floor" + toString(i + 2)),
189 LevelMaker::towerLevel(random,
190 CONSTRUCT(SettlementInfo,
191 c.type = SettlementType::TOWER;
192 c.inhabitants.fighters = CreatureList(
193 random.get(1, 3),
194 random.choose(
195 CreatureId::WATER_ELEMENTAL, CreatureId::AIR_ELEMENTAL, CreatureId::FIRE_ELEMENTAL,
196 CreatureId::EARTH_ELEMENTAL));
197 c.tribe = enemy.settlement.tribe;
198 c.collective = new CollectiveBuilder(CollectiveConfig::noImmigrants(), c.tribe);
199 c.upStairs = {upLink};
200 c.downStairs = {downLink};
201 c.furniture = FurnitureFactory(TribeId::getHuman(), FurnitureType::GROUND_TORCH);
202 c.buildingId = BuildingId::BRICK;)));
203 downLink = upLink;
204 }
205 mainSettlement.downStairs = {downLink};
206 model->buildLevel(
207 LevelBuilder(meter, random, 5, 5, "Tower top"),
208 LevelMaker::towerLevel(random, mainSettlement));
209 return extraSettlement;
210 }
211 case LevelConnection::CRYPT: {
212 StairKey key = StairKey::getNew();
213 extraSettlement.downStairs = {key};
214 mainSettlement.upStairs = {key};
215 model->buildLevel(
216 LevelBuilder(meter, random, 40, 40, "Crypt"),
217 LevelMaker::cryptLevel(random, mainSettlement));
218 return extraSettlement;
219 }
220 case LevelConnection::MAZE: {
221 StairKey key = StairKey::getNew();
222 extraSettlement.upStairs = {key};
223 mainSettlement.downStairs = {key};
224 model->buildLevel(
225 LevelBuilder(meter, random, 40, 40, "Maze"),
226 LevelMaker::mazeLevel(random, extraSettlement));
227 return mainSettlement;
228 }
229 case LevelConnection::GNOMISH_MINES: {
230 StairKey upLink = StairKey::getNew();
231 extraSettlement.downStairs = {upLink};
232 for (int i : Range(gnomeHeight - 1)) {
233 StairKey downLink = StairKey::getNew();
234 model->buildLevel(
235 LevelBuilder(meter, random, 60, 40, "Mines lvl " + toString(i + 1)),
236 LevelMaker::roomLevel(random, CreatureFactory::gnomishMines(
237 mainSettlement.tribe, TribeId::getMonster(), 0),
238 CreatureFactory::waterCreatures(TribeId::getMonster()),
239 CreatureFactory::lavaCreatures(TribeId::getMonster()), {upLink}, {downLink},
240 FurnitureFactory::roomFurniture(TribeId::getPest())));
241 upLink = downLink;
242 }
243 mainSettlement.upStairs = {upLink};
244 model->buildLevel(
245 LevelBuilder(meter, random, 60, 40, "Mine Town"),
246 LevelMaker::mineTownLevel(random, mainSettlement));
247 return extraSettlement;
248 }
249 case LevelConnection::SOKOBAN:
250 StairKey key = StairKey::getNew();
251 extraSettlement.upStairs = {key};
252 mainSettlement.downStairs = {key};
253 Table<char> sokoLevel = sokobanInput->getNext();
254 model->buildLevel(
255 LevelBuilder(meter, random, sokoLevel.getBounds().width(), sokoLevel.getBounds().height(), "Sokoban"),
256 LevelMaker::sokobanFromFile(random, mainSettlement, sokoLevel));
257 return extraSettlement;
258 }
259 }
260
singleMapModel(const string & worldName)261 PModel ModelBuilder::singleMapModel(const string& worldName) {
262 return tryBuilding(10, [&] { return trySingleMapModel(worldName);});
263 }
264
trySingleMapModel(const string & worldName)265 PModel ModelBuilder::trySingleMapModel(const string& worldName) {
266 vector<EnemyInfo> enemies;
267 for (int i : Range(random.get(5, 9)))
268 enemies.push_back(enemyFactory->get(EnemyId::HUMAN_COTTAGE));
269 for (int i : Range(random.get(1, 3)))
270 enemies.push_back(enemyFactory->get(EnemyId::KOBOLD_CAVE));
271 for (int i : Range(random.get(1, 3)))
272 enemies.push_back(enemyFactory->get(EnemyId::BANDITS).setVillainType(VillainType::LESSER));
273 enemies.push_back(enemyFactory->get(random.choose(EnemyId::GNOMES, EnemyId::DARK_ELVES)).setVillainType(VillainType::ALLY));
274 append(enemies, enemyFactory->getVaults());
275 if (random.roll(4))
276 enemies.push_back(enemyFactory->get(EnemyId::ANTS_CLOSED).setVillainType(VillainType::LESSER));
277 enemies.push_back(enemyFactory->get(EnemyId::KNIGHTS).setVillainType(VillainType::MAIN));
278 enemies.push_back(enemyFactory->get(random.choose(EnemyId::OGRE_CAVE, EnemyId::HARPY_CAVE))
279 .setVillainType(VillainType::ALLY));
280 for (auto& enemy : random.chooseN(3, {
281 EnemyId::ELEMENTALIST,
282 EnemyId::WARRIORS,
283 EnemyId::ELVES,
284 EnemyId::DWARVES,
285 EnemyId::VILLAGE}))
286 enemies.push_back(enemyFactory->get(enemy).setVillainType(VillainType::MAIN));
287 for (auto& enemy : random.chooseN(3, {
288 EnemyId::GREEN_DRAGON,
289 EnemyId::SHELOB,
290 EnemyId::HYDRA,
291 EnemyId::RED_DRAGON,
292 EnemyId::CYCLOPS,
293 EnemyId::DRIADS,
294 EnemyId::ENTS}))
295 enemies.push_back(enemyFactory->get(enemy).setVillainType(VillainType::LESSER));
296 for (auto& enemy : random.chooseN(1, {
297 EnemyId::KRAKEN,
298 EnemyId::WITCH,
299 EnemyId::CEMETERY}))
300 enemies.push_back(enemyFactory->get(enemy));
301 return tryModel(360, worldName, enemies, true, BiomeId::GRASSLAND, {}, true);
302 }
303
addMapVillains(vector<EnemyInfo> & enemyInfo,BiomeId biomeId)304 void ModelBuilder::addMapVillains(vector<EnemyInfo>& enemyInfo, BiomeId biomeId) {
305 switch (biomeId) {
306 case BiomeId::GRASSLAND:
307 for (int i : Range(random.get(3, 5)))
308 enemyInfo.push_back(enemyFactory->get(EnemyId::HUMAN_COTTAGE));
309 break;
310 case BiomeId::MOUNTAIN:
311 for (int i : Range(random.get(1, 4)))
312 enemyInfo.push_back(enemyFactory->get(random.choose(EnemyId::DWARF_CAVE, EnemyId::KOBOLD_CAVE)));
313 for (int i : Range(random.get(0, 3)))
314 enemyInfo.push_back(enemyFactory->get(random.choose({EnemyId::BANDITS, EnemyId::NO_AGGRO_BANDITS}, {1, 4})));
315 break;
316 case BiomeId::FORREST:
317 for (int i : Range(random.get(3, 5)))
318 enemyInfo.push_back(enemyFactory->get(EnemyId::ELVEN_COTTAGE));
319 break;
320 }
321 }
322
tryCampaignBaseModel(const string & siteName,bool addExternalEnemies)323 PModel ModelBuilder::tryCampaignBaseModel(const string& siteName, bool addExternalEnemies) {
324 vector<EnemyInfo> enemyInfo;
325 BiomeId biome = BiomeId::MOUNTAIN;
326 enemyInfo.push_back(random.choose(enemyFactory->get(EnemyId::DWARF_CAVE), enemyFactory->get(EnemyId::KOBOLD_CAVE)));
327 enemyInfo.push_back(enemyFactory->get(EnemyId::BANDITS));
328 enemyInfo.push_back(enemyFactory->get(EnemyId::TUTORIAL_VILLAGE));
329 if (random.chance(0.3))
330 enemyInfo.push_back(enemyFactory->get(EnemyId::KRAKEN));
331 optional<ExternalEnemies> externalEnemies;
332 if (addExternalEnemies)
333 externalEnemies = ExternalEnemies(random, enemyFactory->getExternalEnemies());
334 return tryModel(230, siteName, enemyInfo, true, biome, std::move(externalEnemies), true);
335 }
336
tryTutorialModel(const string & siteName)337 PModel ModelBuilder::tryTutorialModel(const string& siteName) {
338 vector<EnemyInfo> enemyInfo;
339 BiomeId biome = BiomeId::MOUNTAIN;
340 enemyInfo.push_back(enemyFactory->get(EnemyId::TUTORIAL_VILLAGE).setVillainType(VillainType::LESSER));
341 return tryModel(230, siteName, enemyInfo, true, biome, {}, false);
342 }
343
getBiome(EnemyId enemyId,RandomGen & random)344 static optional<BiomeId> getBiome(EnemyId enemyId, RandomGen& random) {
345 switch (enemyId) {
346 case EnemyId::KNIGHTS:
347 case EnemyId::WARRIORS:
348 case EnemyId::ELEMENTALIST:
349 case EnemyId::LIZARDMEN:
350 case EnemyId::HYDRA:
351 case EnemyId::VILLAGE:
352 case EnemyId::ORC_VILLAGE: return BiomeId::GRASSLAND;
353 case EnemyId::RED_DRAGON:
354 case EnemyId::GREEN_DRAGON:
355 case EnemyId::DWARVES:
356 case EnemyId::DARK_ELVES:
357 case EnemyId::OGRE_CAVE:
358 case EnemyId::HARPY_CAVE:
359 case EnemyId::SOKOBAN:
360 case EnemyId::GNOMES:
361 case EnemyId::UNICORN_HERD:
362 case EnemyId::CYCLOPS:
363 case EnemyId::SHELOB:
364 case EnemyId::DEMON_DEN:
365 case EnemyId::ANTS_OPEN: return BiomeId::MOUNTAIN;
366 case EnemyId::ELVES:
367 case EnemyId::DRIADS:
368 case EnemyId::ENTS: return BiomeId::FORREST;
369 case EnemyId::BANDITS: return random.choose<BiomeId>();
370 case EnemyId::CEMETERY: return random.choose(BiomeId::GRASSLAND, BiomeId::FORREST);
371 default: return none;
372 }
373 }
374
tryCampaignSiteModel(const string & siteName,EnemyId enemyId,VillainType type)375 PModel ModelBuilder::tryCampaignSiteModel(const string& siteName, EnemyId enemyId, VillainType type) {
376 vector<EnemyInfo> enemyInfo { enemyFactory->get(enemyId).setVillainType(type)};
377 auto biomeId = getBiome(enemyId, random);
378 CHECK(biomeId) << "Unimplemented enemy in campaign " << EnumInfo<EnemyId>::getString(enemyId);
379 addMapVillains(enemyInfo, *biomeId);
380 return tryModel(170, siteName, enemyInfo, false, *biomeId, {}, true);
381 }
382
tryBuilding(int numTries,function<PModel ()> buildFun)383 PModel ModelBuilder::tryBuilding(int numTries, function<PModel()> buildFun) {
384 for (int i : Range(numTries)) {
385 try {
386 if (meter)
387 meter->reset();
388 return buildFun();
389 } catch (LevelGenException) {
390 INFO << "Retrying level gen";
391 }
392 }
393 FATAL << "Couldn't generate a level";
394 return nullptr;
395
396 }
397
campaignBaseModel(const string & siteName,bool externalEnemies)398 PModel ModelBuilder::campaignBaseModel(const string& siteName, bool externalEnemies) {
399 return tryBuilding(20, [=] { return tryCampaignBaseModel(siteName, externalEnemies); });
400 }
401
tutorialModel(const string & siteName)402 PModel ModelBuilder::tutorialModel(const string& siteName) {
403 return tryBuilding(20, [=] { return tryTutorialModel(siteName); });
404 }
405
campaignSiteModel(const string & siteName,EnemyId enemyId,VillainType type)406 PModel ModelBuilder::campaignSiteModel(const string& siteName, EnemyId enemyId, VillainType type) {
407 return tryBuilding(20, [&] { return tryCampaignSiteModel(siteName, enemyId, type); });
408 }
409
measureSiteGen(int numTries,vector<string> types)410 void ModelBuilder::measureSiteGen(int numTries, vector<string> types) {
411 if (types.empty()) {
412 types = {"single_map", "campaign_base"};
413 for (auto enemy : ENUM_ALL(EnemyId))
414 if (!!getBiome(enemy, random))
415 types.push_back(EnumInfo<EnemyId>::getString(enemy));
416 }
417 vector<function<void()>> tasks;
418 for (auto& type : types) {
419 if (type == "single_map")
420 tasks.push_back([=] { measureModelGen(type, numTries, [this] { trySingleMapModel("pok"); }); });
421 else if (type == "campaign_base")
422 tasks.push_back([=] { measureModelGen(type, numTries, [this] { tryCampaignBaseModel("pok", false); }); });
423 else if (auto id = EnumInfo<EnemyId>::fromStringSafe(type)) {
424 if (!!getBiome(*id, random))
425 tasks.push_back([=] { measureModelGen(type, numTries, [&] { tryCampaignSiteModel("", *id, VillainType::LESSER); }); });
426 } else {
427 std::cout << "Bad map type: " << type << std::endl;
428 return;
429 }
430 }
431 for (auto& t : tasks)
432 t();
433 }
434
measureModelGen(const string & name,int numTries,function<void ()> genFun)435 void ModelBuilder::measureModelGen(const string& name, int numTries, function<void()> genFun) {
436 int numSuccess = 0;
437 int maxT = 0;
438 int minT = 1000000;
439 double sumT = 0;
440 std::cout << name;
441 for (int i : Range(numTries)) {
442 #ifndef OSX // this triggers some compiler errors OSX, I don't need it there anyway.
443 auto time = steady_clock::now();
444 #endif
445 try {
446 genFun();
447 ++numSuccess;
448 std::cout << ".";
449 std::cout.flush();
450 } catch (LevelGenException) {
451 std::cout << "x";
452 std::cout.flush();
453 }
454 #ifndef OSX
455 int millis = duration_cast<milliseconds>(steady_clock::now() - time).count();
456 sumT += millis;
457 maxT = max(maxT, millis);
458 minT = min(minT, millis);
459 #endif
460 }
461 std::cout << std::endl << numSuccess << " / " << numTries << ". MinT: " <<
462 minT << ". MaxT: " << maxT << ". AvgT: " << sumT / numSuccess << std::endl;
463 }
464
spawnKeeper(WModel m,PCreature keeper,bool regenerateMana,vector<string> introText)465 WCollective ModelBuilder::spawnKeeper(WModel m, PCreature keeper, bool regenerateMana, vector<string> introText) {
466 WLevel level = m->getTopLevel();
467 WCreature keeperRef = keeper.get();
468 CHECK(level->landCreature(StairKey::keeperSpawn(), keeperRef)) << "Couldn't place keeper on level.";
469 m->addCreature(std::move(keeper));
470 m->collectives.push_back(CollectiveBuilder(
471 getKeeperConfig(random, options->getBoolValue(OptionId::FAST_IMMIGRATION), regenerateMana), TribeId::getKeeper())
472 .setLevel(level)
473 .addCreature(keeperRef, {MinionTrait::LEADER})
474 .build());
475 WCollective playerCollective = m->collectives.back().get();
476 playerCollective->setControl(PlayerControl::create(playerCollective, introText));
477 playerCollective->setVillainType(VillainType::PLAYER);
478 playerCollective->acquireInitialTech();
479 return playerCollective;
480 }
481
tryModel(int width,const string & levelName,vector<EnemyInfo> enemyInfo,bool keeperSpawn,BiomeId biomeId,optional<ExternalEnemies> externalEnemies,bool hasWildlife)482 PModel ModelBuilder::tryModel(int width, const string& levelName, vector<EnemyInfo> enemyInfo, bool keeperSpawn,
483 BiomeId biomeId, optional<ExternalEnemies> externalEnemies, bool hasWildlife) {
484 auto model = Model::create();
485 vector<SettlementInfo> topLevelSettlements;
486 vector<EnemyInfo> extraEnemies;
487 for (auto& elem : enemyInfo) {
488 elem.settlement.collective = new CollectiveBuilder(elem.config, elem.settlement.tribe);
489 if (elem.levelConnection) {
490 elem.levelConnection->otherEnemy->settlement.collective =
491 new CollectiveBuilder(elem.levelConnection->otherEnemy->config,
492 elem.levelConnection->otherEnemy->settlement.tribe);
493 topLevelSettlements.push_back(makeExtraLevel(model.get(), elem));
494 extraEnemies.push_back(*elem.levelConnection->otherEnemy);
495 } else
496 topLevelSettlements.push_back(elem.settlement);
497 }
498 append(enemyInfo, extraEnemies);
499 optional<CreatureFactory> wildlife;
500 if (hasWildlife)
501 wildlife = CreatureFactory::forrest(TribeId::getWildlife());
502 WLevel top = model->buildTopLevel(
503 LevelBuilder(meter, random, width, width, levelName, false),
504 LevelMaker::topLevel(random, wildlife, topLevelSettlements, width,
505 keeperSpawn, biomeId));
506 model->calculateStairNavigation();
507 for (auto& enemy : enemyInfo) {
508 if (enemy.settlement.locationName)
509 enemy.settlement.collective->setLocationName(*enemy.settlement.locationName);
510 if (auto race = enemy.settlement.race)
511 enemy.settlement.collective->setRaceName(*race);
512 if (enemy.discoverable)
513 enemy.settlement.collective->setDiscoverable();
514 PCollective collective = enemy.settlement.collective->build();
515 auto control = VillageControl::create(collective.get(), enemy.villain);
516 if (enemy.villainType)
517 collective->setVillainType(*enemy.villainType);
518 if (enemy.id)
519 collective->setEnemyId(*enemy.id);
520 collective->setControl(std::move(control));
521 model->collectives.push_back(std::move(collective));
522 }
523 if (externalEnemies)
524 model->addExternalEnemies(std::move(*externalEnemies));
525 return model;
526 }
527
splashModel(const FilePath & splashPath)528 PModel ModelBuilder::splashModel(const FilePath& splashPath) {
529 auto m = Model::create();
530 WLevel l = m->buildTopLevel(
531 LevelBuilder(meter, Random, Level::getSplashBounds().width(), Level::getSplashBounds().height(), "Splash",
532 true, 1.0),
533 LevelMaker::splashLevel(
534 CreatureFactory::splashLeader(TribeId::getHuman()),
535 CreatureFactory::splashHeroes(TribeId::getHuman()),
536 CreatureFactory::splashMonsters(TribeId::getKeeper()),
537 CreatureFactory::singleType(TribeId::getKeeper(), CreatureId::IMP), splashPath));
538 m->topLevel = l;
539 return m;
540 }
541
battleModel(const FilePath & levelPath,CreatureList allies,CreatureList enemies)542 PModel ModelBuilder::battleModel(const FilePath& levelPath, CreatureList allies, CreatureList enemies) {
543 auto m = Model::create();
544 ifstream stream(levelPath.getPath());
545 Table<char> level = *SokobanInput::readTable(stream);
546 WLevel l = m->buildTopLevel(
547 LevelBuilder(meter, Random, level.getBounds().width(), level.getBounds().height(), "Battle", true, 1.0),
548 LevelMaker::battleLevel(level, allies, enemies));
549 m->topLevel = l;
550 return m;
551 }
552