test: cover testBoss
This commit is contained in:
1 parent
3fdfd38b99
commit
41e4b6f89f
1 file changed
+157
@@ -157,3 +157,160 @@ VF_TEST(livingCrystalsHealTheDragonFasterThanTheSwordDrainsIt) {
|
|||||||
DragonFight fight(end, 99u);
|
DragonFight fight(end, 99u);
|
||||||
float fullHealth = fight.state().health;
|
float fullHealth = fight.state().health;
|
||||||
|
|
||||||
|
float totalHealed = 0.0f;
|
||||||
|
for (int step = 0; step < 200; ++step) {
|
||||||
|
BossTickReport report = fight.tick(clingingTo(fight, 5.0f));
|
||||||
|
totalHealed += report.healedThisTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(totalHealed > 0.0f);
|
||||||
|
CHECK(fight.state().damageTaken > 0.0f);
|
||||||
|
CHECK_NEAR(fight.state().health, fullHealth, 1e-3f);
|
||||||
|
CHECK(!fight.exitPortalOpen());
|
||||||
|
CHECK(fight.aliveCrystalCount() == static_cast<int>(fight.crystals().size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(aCagedCrystalIgnoresShotsUntilTheCageComesDown) {
|
||||||
|
EndGenerator end;
|
||||||
|
DragonFight fight(end, 7u);
|
||||||
|
|
||||||
|
int cagedIndex = -1;
|
||||||
|
for (int index = 0; index < static_cast<int>(fight.crystals().size()); ++index) {
|
||||||
|
if (fight.crystals()[static_cast<std::size_t>(index)].cageIntact) {
|
||||||
|
cagedIndex = index;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CHECK(cagedIndex >= 0);
|
||||||
|
if (cagedIndex < 0) return;
|
||||||
|
|
||||||
|
DragonPlayerInput blocked = clingingTo(fight, 0.0f);
|
||||||
|
blocked.shootCrystal = cagedIndex;
|
||||||
|
CHECK_EQ(fight.tick(blocked).crystalsDestroyed, 0);
|
||||||
|
CHECK(fight.crystals()[static_cast<std::size_t>(cagedIndex)].alive);
|
||||||
|
|
||||||
|
DragonPlayerInput breaking = blocked;
|
||||||
|
breaking.breakCage = true;
|
||||||
|
CHECK_EQ(fight.tick(breaking).crystalsDestroyed, 0);
|
||||||
|
CHECK(!fight.crystals()[static_cast<std::size_t>(cagedIndex)].cageIntact);
|
||||||
|
|
||||||
|
CHECK_EQ(fight.tick(blocked).crystalsDestroyed, 1);
|
||||||
|
CHECK(!fight.crystals()[static_cast<std::size_t>(cagedIndex)].alive);
|
||||||
|
|
||||||
|
// A second shot at the same wreck changes nothing.
|
||||||
|
CHECK_EQ(fight.tick(blocked).crystalsDestroyed, 0);
|
||||||
|
// Nonsense indices are ignored rather than crashing.
|
||||||
|
DragonPlayerInput bogus = clingingTo(fight, 0.0f);
|
||||||
|
bogus.shootCrystal = 9999;
|
||||||
|
CHECK_EQ(fight.tick(bogus).crystalsDestroyed, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(theWholeDragonFightRunsFromTheCrystalsToTheEgg) {
|
||||||
|
EndGenerator end;
|
||||||
|
DragonFight fight(end, 4242u);
|
||||||
|
int crystalCount = static_cast<int>(fight.crystals().size());
|
||||||
|
|
||||||
|
CHECK_EQ(shatterEveryCrystal(fight), crystalCount);
|
||||||
|
CHECK_EQ(fight.aliveCrystalCount(), 0);
|
||||||
|
|
||||||
|
int descentTicks = 0;
|
||||||
|
while (fight.state().phase != BossPhase::Perching && descentTicks < 200) {
|
||||||
|
fight.tick(clingingTo(fight, 0.0f));
|
||||||
|
++descentTicks;
|
||||||
|
}
|
||||||
|
CHECK(fight.state().phase == BossPhase::Perching);
|
||||||
|
|
||||||
|
int settleTicks = 0;
|
||||||
|
while (fight.state().position.y > fight.portalCenter().y + fight.settings().perchHeight + 0.01f &&
|
||||||
|
settleTicks < 300) {
|
||||||
|
fight.tick(clingingTo(fight, 0.0f));
|
||||||
|
++settleTicks;
|
||||||
|
}
|
||||||
|
CHECK(settleTicks < 300);
|
||||||
|
CHECK_NEAR(fight.state().position.y,
|
||||||
|
fight.portalCenter().y + fight.settings().perchHeight, 1e-3f);
|
||||||
|
|
||||||
|
BossTickReport report;
|
||||||
|
int killTicks = 0;
|
||||||
|
while (!report.defeated && killTicks < 600) {
|
||||||
|
report = fight.tick(clingingTo(fight, 8.0f));
|
||||||
|
++killTicks;
|
||||||
|
}
|
||||||
|
|
||||||
|
CHECK(report.defeated);
|
||||||
|
CHECK(fight.state().phase == BossPhase::Defeated);
|
||||||
|
CHECK(!fight.state().alive);
|
||||||
|
CHECK(!fight.state().bar.visible);
|
||||||
|
CHECK_NEAR(fight.state().health, 0.0f, 1e-4f);
|
||||||
|
CHECK(fight.state().attacksLaunched > 0);
|
||||||
|
CHECK(fight.exitPortalOpen());
|
||||||
|
CHECK(fight.dragonEggDropped());
|
||||||
|
CHECK(fight.eggPosition() == Vec3i(0, end.surfaceHeight(0, 0) + 1, 0));
|
||||||
|
|
||||||
|
// The tick after the kill is a no-op, not a second death.
|
||||||
|
BossTickReport after = fight.tick(clingingTo(fight, 8.0f));
|
||||||
|
CHECK(after.defeated);
|
||||||
|
CHECK(!after.phaseChanged);
|
||||||
|
|
||||||
|
World world;
|
||||||
|
CHECK(fight.placeVictoryStructures(world, end));
|
||||||
|
CHECK(world.blockAt(0, end.surfaceHeight(0, 0), 0) == Block::EndPortal);
|
||||||
|
CHECK(world.blockAt(2, end.surfaceHeight(0, 0), 2) == Block::Bedrock);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(aLivingDragonRefusesToHandOverItsRewards) {
|
||||||
|
EndGenerator end;
|
||||||
|
DragonFight fight(end, 5u);
|
||||||
|
World world;
|
||||||
|
CHECK(!fight.placeVictoryStructures(world, end));
|
||||||
|
CHECK_EQ(world.chunkCount(), 0u);
|
||||||
|
CHECK(!fight.dragonEggDropped());
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(twoDragonFightsOnTheSameSeedAndScriptRunIdentically) {
|
||||||
|
EndGenerator end;
|
||||||
|
|
||||||
|
auto playOut = [&end](std::vector<float>& healthTrack, Vec3& endPosition, int& attacks) {
|
||||||
|
DragonFight fight(end, 31337u);
|
||||||
|
shatterEveryCrystal(fight);
|
||||||
|
for (int step = 0; step < 300; ++step) {
|
||||||
|
BossTickReport report = fight.tick(clingingTo(fight, 3.0f));
|
||||||
|
healthTrack.push_back(report.bossHealth);
|
||||||
|
}
|
||||||
|
endPosition = fight.state().position;
|
||||||
|
attacks = fight.state().attacksLaunched;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::vector<float> firstTrack;
|
||||||
|
std::vector<float> secondTrack;
|
||||||
|
Vec3 firstEnd;
|
||||||
|
Vec3 secondEnd;
|
||||||
|
int firstAttacks = 0;
|
||||||
|
int secondAttacks = 0;
|
||||||
|
playOut(firstTrack, firstEnd, firstAttacks);
|
||||||
|
playOut(secondTrack, secondEnd, secondAttacks);
|
||||||
|
|
||||||
|
CHECK_EQ(firstTrack.size(), secondTrack.size());
|
||||||
|
for (std::size_t i = 0; i < firstTrack.size() && i < secondTrack.size(); ++i) {
|
||||||
|
CHECK_NEAR(firstTrack[i], secondTrack[i], 1e-6f);
|
||||||
|
}
|
||||||
|
CHECK_NEAR(firstEnd.x, secondEnd.x, 1e-6f);
|
||||||
|
CHECK_NEAR(firstEnd.y, secondEnd.y, 1e-6f);
|
||||||
|
CHECK_EQ(firstAttacks, secondAttacks);
|
||||||
|
CHECK(firstAttacks > 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
VF_TEST(theWitherIsCalledOutOfASoulSandPatternInEitherOrientation) {
|
||||||
|
Vec3i base{4, 10, -3};
|
||||||
|
|
||||||
|
World alongX = witherAltar(base, true);
|
||||||
|
bool orientation = false;
|
||||||
|
CHECK(witherPatternAt(alongX, base, true));
|
||||||
|
CHECK(!witherPatternAt(alongX, base, false));
|
||||||
|
CHECK(findWitherPattern(alongX, base, orientation));
|
||||||
|
CHECK(orientation);
|
||||||
|
|
||||||
|
World alongZ = witherAltar(base, false);
|
||||||
|
CHECK(findWitherPattern(alongZ, base, orientation));
|
||||||
|
CHECK(!orientation);
|
||||||
|
|
||||||
Reference in new issue
Block a user