diff --git a/tests/testRaid.cpp b/tests/testRaid.cpp index 391edea..2c82c15 100644 --- a/tests/testRaid.cpp +++ b/tests/testRaid.cpp @@ -319,3 +319,140 @@ VF_TEST(golemsTurnOnThePlayerOnceTheVillageReputationCollapses) { updateGolemHostility(spawner, buildReputation(calm, {})); CHECK(!spawner.hostileToPlayer); + GossipLedger angry; + angry.add(GossipKind::MajorNegative, gossipMajorNegativeGain); + std::vector village{&angry, &angry}; + updateGolemHostility(spawner, buildReputation(calm, village)); + CHECK(spawner.hostileToPlayer); + + std::vector mixed{&angry, &calm, &calm, &calm, &calm, &calm}; + updateGolemHostility(spawner, buildReputation(calm, mixed)); + CHECK(!spawner.hostileToPlayer); +} + +VF_TEST(twoRaidManagersOnTheSameSeedProduceTheSameRaid) { + World world = makeRaidWorld(); + VillagerSchedule schedule; + TimeOfDay clock(static_cast(0.35 * 24000.0)); + + PoiRegistry firstPois; + firstPois.scan(world, villageCenter, 12, 4); + std::vector firstVillagers = makeVillagers(4); + OmenState firstOmen = omenAt(2); + RaidManager first(villageCenter, 4242u, raidDifficultyHard); + CHECK(first.tryStart(firstOmen, {0.5f, static_cast(walkY), 0.5f}, 32.0f)); + first.tick(world, firstVillagers, firstPois, firstOmen, schedule, clock, 300); + + PoiRegistry secondPois; + secondPois.scan(world, villageCenter, 12, 4); + std::vector secondVillagers = makeVillagers(4); + OmenState secondOmen = omenAt(2); + RaidManager second(villageCenter, 4242u, raidDifficultyHard); + CHECK(second.tryStart(secondOmen, {0.5f, static_cast(walkY), 0.5f}, 32.0f)); + second.tick(world, secondVillagers, secondPois, secondOmen, schedule, clock, 300); + + CHECK_EQ(first.totalSpawned(), second.totalSpawned()); + CHECK_EQ(first.livingRaiders(), second.livingRaiders()); + CHECK_EQ(first.golems().spawnedTotal, second.golems().spawnedTotal); + CHECK(first.state() == second.state()); + + bool identical = first.raiders().size() == second.raiders().size(); + for (std::size_t i = 0; i < first.raiders().size() && identical; ++i) { + const Raider& a = first.raiders()[i]; + const Raider& b = second.raiders()[i]; + if (a.kind != b.kind || a.alive != b.alive) identical = false; + if (std::fabs(a.position.x - b.position.x) > 1e-5f) identical = false; + if (std::fabs(a.position.z - b.position.z) > 1e-5f) identical = false; + if (std::fabs(a.health - b.health) > 1e-5f) identical = false; + } + CHECK(identical); + + RaidManager other(villageCenter, 99u, raidDifficultyHard); + OmenState otherOmen = omenAt(2); + std::vector otherVillagers = makeVillagers(4); + PoiRegistry otherPois; + otherPois.scan(world, villageCenter, 12, 4); + CHECK(other.tryStart(otherOmen, {0.5f, static_cast(walkY), 0.5f}, 32.0f)); + other.tick(world, otherVillagers, otherPois, otherOmen, schedule, clock, 1); + CHECK_EQ(other.wave().total(), first.wave().total()); +} + +VF_TEST(villagersPanicHomeInsteadOfWorkingWhileTheRaidIsRunning) { + 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)); + + for (int tick = 0; tick < 400; ++tick) + for (Villager& villager : villagers) + tickVillager(world, villager, pois, clock, schedule, villageCenter, false, 0.05f); + CHECK(villagers[0].activity == VillagerActivity::Work); + CHECK(villagers[0].employed()); + int workBefore = villagers[0].workTicks; + + OmenState omen = omenAt(1); + RaidManager raid(villageCenter, 8u, raidDifficultyEasy); + CHECK(raid.tryStart(omen, {0.5f, static_cast(walkY), 0.5f}, 32.0f)); + raid.tick(world, villagers, pois, omen, schedule, clock, 120); + + CHECK(villagers[0].activity == VillagerActivity::Panic); + CHECK_EQ(villagers[0].workTicks, workBefore); + CHECK(villagers[0].bedIndex >= 0); + CHECK(villagers[0].alive); + CHECK(pois.at(villagers[0].bedIndex).ownerId == villagers[0].id); +} + +VF_TEST(aWaveAdvancesOnTimeoutWhenTheRaidersRefuseToDie) { + 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, 606u, raidDifficultyNormal); + CHECK(raid.tryStart(omen, {0.5f, static_cast(walkY), 0.5f}, 32.0f)); + + int firstWaveSize = 0; + for (int tick = 0; tick < raidWaveTimeoutTicks - 5; ++tick) { + raid.tick(world, villagers, pois, omen, schedule, clock, 1); + for (Villager& villager : villagers) + if (villager.alive) villager.health = 20.0f; + if (firstWaveSize == 0) firstWaveSize = raid.totalSpawned(); + } + CHECK_EQ(raid.waveIndex(), 0); + CHECK_EQ(raid.totalSpawned(), firstWaveSize); + CHECK(raid.livingRaiders() > 0); + + for (int tick = 0; tick < 20; ++tick) { + raid.tick(world, villagers, pois, omen, schedule, clock, 1); + for (Villager& villager : villagers) + if (villager.alive) villager.health = 20.0f; + } + CHECK_EQ(raid.waveIndex(), 1); + CHECK(raid.totalSpawned() > firstWaveSize); + CHECK(raid.state() == RaidState::Fighting); + CHECK_EQ(livingVillagerCount(villagers), 2); +} + +VF_TEST(raiderKindsCarryDistinctStatsAndReadableNames) { + const Raider::Stats& footSoldier = raiderStats(RaiderKind::Raider); + const Raider::Stats& brute = raiderStats(RaiderKind::Ravager); + CHECK(brute.maxHealth > footSoldier.maxHealth); + CHECK(brute.damage > footSoldier.damage); + CHECK(brute.speed > footSoldier.speed); + CHECK(raiderStats(RaiderKind::Witch).attackRange > footSoldier.attackRange); + + CHECK_EQ(std::string(raiderKindName(RaiderKind::Vindicator)), std::string("vindicator")); + CHECK_EQ(std::string(raidStateName(RaidState::Victory)), std::string("victory")); + CHECK_EQ(std::string(raidStateName(RaidState::Defeat)), std::string("defeat")); + + RaidManager raid(villageCenter, 1u); + CHECK(raid.center() == villageCenter); + CHECK_EQ(raid.livingRaiders(), 0); + CHECK(!raid.damageRaider(0, 5.0f)); + CHECK(raid.state() == RaidState::Inactive); +}