diff --git a/tests/testRaid.cpp b/tests/testRaid.cpp index 03b9102..391edea 100644 --- a/tests/testRaid.cpp +++ b/tests/testRaid.cpp @@ -160,3 +160,162 @@ VF_TEST(everySpawnedRaiderStartsOnTheOutskirtsAndWalksTowardsTheBell) { startDistances.push_back(distance); } + raid.tick(world, villagers, pois, omen, schedule, clock, 200); + for (std::size_t i = 0; i < raid.raiders().size(); ++i) { + if (!raid.raiders()[i].alive) continue; + CHECK(distanceToCentre(raid.raiders()[i].position) < startDistances[i]); + } +} + +VF_TEST(theRaidRunsThroughEveryWaveAndEndsInVictory) { + World world = makeRaidWorld(); + PoiRegistry pois; + pois.scan(world, villageCenter, 12, 4); + std::vector villagers = makeVillagers(4); + VillagerSchedule schedule; + TimeOfDay clock(static_cast(0.35 * 24000.0)); + OmenState omen = omenAt(2); + + RaidManager raid(villageCenter, 555u, raidDifficultyNormal); + CHECK(raid.tryStart(omen, {0.5f, static_cast(walkY), 0.5f}, 32.0f)); + + int expectedSpawns = 0; + for (int index = 0; index < raid.waveCount(); ++index) + expectedSpawns += planRaidWave(index, 2, raidDifficultyNormal).total(); + + int guard = 0; + std::vector wavesSeen; + while (raid.active() && guard++ < 20000) { + raid.tick(world, villagers, pois, omen, schedule, clock, 1); + if (wavesSeen.empty() || wavesSeen.back() != raid.waveIndex()) + wavesSeen.push_back(raid.waveIndex()); + for (int i = 0; i < static_cast(raid.raiders().size()); ++i) + raid.damageRaider(i, 1000.0f); + } + + CHECK(raid.state() == RaidState::Victory); + CHECK_EQ(static_cast(wavesSeen.size()), raid.waveCount()); + CHECK_EQ(raid.waveIndex(), raid.waveCount() - 1); + CHECK_EQ(raid.totalSpawned(), expectedSpawns); + CHECK_EQ(raid.livingRaiders(), 0); + CHECK_EQ(raid.villagersLost(), 0); + CHECK_EQ(livingVillagerCount(villagers), 4); + CHECK(!raid.active()); + CHECK(raid.elapsedTicks() > raidWaveDelayTicks * (raid.waveCount() - 1)); +} + +VF_TEST(victoryLeavesMajorPositiveGossipAndHeroOfTheVillageWithEverySurvivor) { + World world = makeRaidWorld(); + PoiRegistry pois; + pois.scan(world, villageCenter, 12, 4); + std::vector villagers = makeVillagers(4); + VillagerSchedule schedule; + TimeOfDay clock(static_cast(0.35 * 24000.0)); + OmenState omen = omenAt(3); + + RaidManager raid(villageCenter, 909u, raidDifficultyEasy); + CHECK(raid.tryStart(omen, {0.5f, static_cast(walkY), 0.5f}, 32.0f)); + + int guard = 0; + while (raid.active() && guard++ < 20000) { + raid.tick(world, villagers, pois, omen, schedule, clock, 1); + for (int i = 0; i < static_cast(raid.raiders().size()); ++i) + raid.damageRaider(i, 1000.0f); + } + CHECK(raid.state() == RaidState::Victory); + + for (const Villager& villager : villagers) { + CHECK_EQ(villager.gossip.valueOf(GossipKind::MajorPositive), gossipMajorPositiveGain); + CHECK(villager.gossip.reputation() > 0); + } + CHECK(omen.isHero()); + CHECK_EQ(omen.heroLevel, 3); + CHECK_EQ(omen.heroTicksRemaining, heroDurationTicks); + CHECK(!omen.hasBadOmen()); + + tickOmen(omen, heroDurationTicks); + CHECK(!omen.isHero()); + CHECK_EQ(omen.heroLevel, 0); +} + +VF_TEST(aRaidThatKillsEveryVillagerEndsInDefeat) { + World world = makeRaidWorld(); + PoiRegistry pois; + pois.scan(world, villageCenter, 12, 4); + std::vector villagers = makeVillagers(2); + VillagerSchedule schedule; + TimeOfDay clock(static_cast(0.35 * 24000.0)); + OmenState omen = omenAt(1); + + RaidManager raid(villageCenter, 31u, raidDifficultyEasy); + CHECK(raid.tryStart(omen, {0.5f, static_cast(walkY), 0.5f}, 32.0f)); + CHECK_EQ(golemCapFor(2), 0); + + int guard = 0; + while (raid.active() && guard++ < 20000) + raid.tick(world, villagers, pois, omen, schedule, clock, 1); + + CHECK(raid.state() == RaidState::Defeat); + CHECK_EQ(livingVillagerCount(villagers), 0); + CHECK_EQ(raid.villagersLost(), 2); + CHECK_EQ(raid.golems().spawnedTotal, 0); + CHECK(!omen.isHero()); + for (const Villager& villager : villagers) { + CHECK_EQ(villager.gossip.valueOf(GossipKind::MajorPositive), 0); + CHECK_EQ(villager.workstationIndex, -1); + CHECK_EQ(villager.bedIndex, -1); + } + CHECK_EQ(pois.freeBedCount(), pois.bedCount()); +} + +VF_TEST(golemsSpawnInProportionToThePopulationAndStopAtTheCap) { + CHECK_EQ(golemCapFor(0), 0); + CHECK_EQ(golemCapFor(golemMinimumVillagers - 1), 0); + CHECK_EQ(golemCapFor(golemMinimumVillagers), 1); + CHECK_EQ(golemCapFor(8), 2); + CHECK_EQ(golemCapFor(12), 3); + + GolemSpawner spawner; + int spawns = 0; + for (int tick = 0; tick < 2000; ++tick) + if (tickGolemSpawner(spawner, 12, 1)) ++spawns; + CHECK_EQ(spawns, 3); + CHECK_EQ(spawner.golemsAlive, 3); + CHECK_EQ(spawner.spawnedTotal, 3); + + spawner.golemsAlive = 0; + CHECK(tickGolemSpawner(spawner, 12, spawner.intervalTicks + 1)); + CHECK_EQ(spawner.spawnedTotal, 4); + + GolemSpawner hamlet; + for (int tick = 0; tick < 2000; ++tick) tickGolemSpawner(hamlet, 2, 1); + CHECK_EQ(hamlet.spawnedTotal, 0); + CHECK(!tickGolemSpawner(hamlet, 12, 0)); +} + +VF_TEST(aDefendedVillageFieldsGolemsWhileTheRaidIsRunning) { + World world = makeRaidWorld(); + PoiRegistry pois; + pois.scan(world, villageCenter, 12, 4); + std::vector villagers = makeVillagers(4); + VillagerSchedule schedule; + TimeOfDay clock(static_cast(0.35 * 24000.0)); + OmenState omen = omenAt(1); + + RaidManager raid(villageCenter, 77u, raidDifficultyEasy); + CHECK(raid.tryStart(omen, {0.5f, static_cast(walkY), 0.5f}, 32.0f)); + raid.tick(world, villagers, pois, omen, schedule, clock, 400); + + CHECK(raid.golems().spawnedTotal >= 1); + CHECK(raid.golems().golemsAlive >= 1); + CHECK(raid.golems().golemsAlive <= golemCapFor(4)); + CHECK(raid.livingRaiders() < raid.wave().total()); +} + +VF_TEST(golemsTurnOnThePlayerOnceTheVillageReputationCollapses) { + GolemSpawner spawner; + GossipLedger calm; + calm.add(GossipKind::Trading, gossipTradingGain * 4); + updateGolemHostility(spawner, buildReputation(calm, {})); + CHECK(!spawner.hostileToPlayer); +