#include "testHarness.h" #include "vf/Boss.h" #include #include using namespace vf; namespace { DragonPlayerInput clingingTo(const DragonFight& fight, float melee) { DragonPlayerInput input; input.present = true; input.position = fight.state().position; input.meleeDamage = melee; return input; } // Shoots down every crystal in order, breaking a cage first where one stands. int shatterEveryCrystal(DragonFight& fight) { int destroyed = 0; for (int index = 0; index < static_cast(fight.crystals().size()); ++index) { if (fight.crystals()[static_cast(index)].cageIntact) { DragonPlayerInput breaking = clingingTo(fight, 0.0f); breaking.shootCrystal = index; breaking.breakCage = true; fight.tick(breaking); } DragonPlayerInput shot = clingingTo(fight, 0.0f); shot.shootCrystal = index; destroyed += fight.tick(shot).crystalsDestroyed; } return destroyed; } WitherFightConfig quickSummon(int summonTicks) { WitherFightConfig config; config.summonTicks = summonTicks; return config; } // Drives the wither out of the summon charge and into its ranged phase. void runUpToRangedPhase(WitherFight& fight, std::vector& targets) { std::vector nothing; for (int step = 0; step < 200 && fight.state().phase != BossPhase::Ranged; ++step) { fight.tick(nothing, targets); } } World witherAltar(const Vec3i& base, bool alongX) { World world; world.setBlock(base.x, base.y, base.z, witherBaseBlock()); for (int along = -1; along <= 1; ++along) { int ax = alongX ? base.x + along : base.x; int az = alongX ? base.z : base.z + along; world.setBlock(ax, base.y + 1, az, witherBaseBlock()); world.setBlock(ax, base.y + 2, az, witherHeadBlock()); } return world; } } // namespace VF_TEST(everyBossKindAndPhaseHasAName) { for (int index = 0; index < static_cast(BossKind::Count); ++index) { CHECK(std::string(bossKindName(static_cast(index))) != std::string("unknown")); } for (int index = 0; index < static_cast(BossPhase::Count); ++index) { CHECK(std::string(bossPhaseName(static_cast(index))) != std::string("unknown")); } CHECK(std::string(bossPhaseName(BossPhase::Armored)) == std::string("armored")); } VF_TEST(theBossBarTranslatesHealthIntoNotchesAndPercent) { BossBar bar; bar.style = BossBarStyle::Notched10; bar.setFromHealth(200.0f, 200.0f); CHECK_NEAR(bar.progress, 1.0f, 1e-6f); CHECK_EQ(bar.notchCount(), 10); CHECK_EQ(bar.filledNotches(), 10); CHECK_EQ(bar.percent(), 100); bar.setFromHealth(95.0f, 200.0f); CHECK_EQ(bar.filledNotches(), 4); CHECK_EQ(bar.percent(), 48); bar.setFromHealth(0.0f, 200.0f); CHECK_EQ(bar.filledNotches(), 0); CHECK_EQ(bar.percent(), 0); // Overheal and negative health both clamp instead of drawing past the ends. bar.setFromHealth(500.0f, 200.0f); CHECK_NEAR(bar.progress, 1.0f, 1e-6f); bar.setFromHealth(-10.0f, 200.0f); CHECK_NEAR(bar.progress, 0.0f, 1e-6f); bar.style = BossBarStyle::Notched6; bar.setFromHealth(150.0f, 300.0f); CHECK_EQ(bar.notchCount(), 6); CHECK_EQ(bar.filledNotches(), 3); bar.style = BossBarStyle::Solid; CHECK_EQ(bar.notchCount(), 1); bar.style = BossBarStyle::Notched12; CHECK_EQ(bar.notchCount(), 12); bar.style = BossBarStyle::Notched20; CHECK_EQ(bar.notchCount(), 20); } VF_TEST(crystalsAreBuiltOnTopOfTheGeneratedObsidianPillars) { EndGenerator end; std::vector pillars = end.pillars(); std::vector crystals = crystalsFromPillars(pillars); CHECK_EQ(crystals.size(), pillars.size()); CHECK(crystals.size() >= 8u); int caged = 0; for (std::size_t i = 0; i < crystals.size(); ++i) { CHECK(crystals[i].alive); CHECK_EQ(crystals[i].position.x, pillars[i].base.x); CHECK_EQ(crystals[i].position.z, pillars[i].base.z); CHECK_EQ(crystals[i].position.y, pillars[i].base.y + pillars[i].height + 1); CHECK(crystals[i].caged == pillars[i].crowned); CHECK(crystals[i].cageIntact == pillars[i].crowned); if (crystals[i].caged) ++caged; } // The tall pillars in the generated ring carry cages. CHECK(caged > 0); } VF_TEST(theDragonOrbitsTheGeneratedExitPortalColumn) { EndGenerator end; DragonFight fight(end, 1234u); CHECK(fight.state().kind == BossKind::EnderDragon); CHECK(fight.state().bar.visible); CHECK(fight.state().bar.color == BossBarColor::Pink); CHECK_NEAR(fight.portalCenter().y, static_cast(end.surfaceHeight(0, 0)), 1e-4f); CHECK_EQ(fight.eggPosition().y, end.surfaceHeight(0, 0) + 1); CHECK(fight.brain().structureValid()); DragonPlayerInput idle; idle.present = false; float startAngle = fight.state().orbitAngle; for (int step = 0; step < 20; ++step) fight.tick(idle); CHECK(fight.state().phase == BossPhase::Circling); CHECK(fight.state().orbitAngle > startAngle); float radius = behaviorHorizontalDistance(fight.state().position, fight.portalCenter()); CHECK_NEAR(radius, fight.settings().orbitRadius, 0.05f); CHECK_EQ(fight.aliveCrystalCount(), static_cast(fight.crystals().size())); } VF_TEST(livingCrystalsHealTheDragonFasterThanTheSwordDrainsIt) { EndGenerator end; DragonFight fight(end, 99u); float fullHealth = fight.state().health;