From 73e65e5dd03714583aa1b116271e37e55dc298be Mon Sep 17 00:00:00 2001 From: DualFroz Date: Thu, 10 Sep 2026 23:30:44 +0200 Subject: [PATCH] test: extend coverage of testBrewing --- tests/testBrewing.cpp | 223 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 223 insertions(+) diff --git a/tests/testBrewing.cpp b/tests/testBrewing.cpp index b0828d1..4509b74 100644 --- a/tests/testBrewing.cpp +++ b/tests/testBrewing.cpp @@ -313,3 +313,226 @@ VF_TEST(bottlesTheIngredientCannotChangeAreLeftAsTheyWere) { CHECK(canBrew(stand)); for (int tick = 0; tick < brewDurationTicks; ++tick) tickBrewing(stand); + CHECK_EQ(stand.bottles[0].potion.kind, PotionKind::Swiftness); + CHECK_EQ(stand.bottles[1].potion.kind, PotionKind::Water); + CHECK(!stand.bottles[2].occupied); +} + +VF_TEST(theStandRefusesAMixedIngredientStackButTakesMoreOfTheSame) { + BrewingStandState stand = fueledStand(PotionItem{}, 1); + + CHECK(setIngredient(stand, Ingredient::Sugar, 2)); + CHECK_EQ(stand.ingredientCount, 2); + CHECK(!setIngredient(stand, Ingredient::Redstone, 1)); + CHECK_EQ(stand.ingredient, Ingredient::Sugar); + CHECK_EQ(stand.ingredientCount, 2); + + CHECK(setIngredient(stand, Ingredient::Sugar, 3)); + CHECK_EQ(stand.ingredientCount, 5); + + CHECK(!setIngredient(stand, Ingredient::None, 1)); + CHECK(!setIngredient(stand, Ingredient::Sugar, 0)); + CHECK(!setIngredient(stand, Ingredient::Sugar, -4)); +} + +VF_TEST(everyCompletedBrewSpendsExactlyOneIngredientAndOneFuelCharge) { + BrewingStandState stand; + setBottle(stand, 0, PotionItem{}); + addFuel(stand, 1); + setIngredient(stand, Ingredient::NetherWart, 3); + + for (int tick = 0; tick < brewDurationTicks; ++tick) tickBrewing(stand); + CHECK_EQ(stand.ingredientCount, 2); + CHECK_EQ(stand.ingredient, Ingredient::NetherWart); + CHECK_EQ(stand.fuel, brewsPerBlazePowder - 1); + + // The bottle is awkward now and nether wart does nothing to it, so the + // remaining stack sits unused instead of burning fuel. + CHECK(!canBrew(stand)); + for (int tick = 0; tick < brewDurationTicks; ++tick) CHECK(!tickBrewing(stand)); + CHECK_EQ(stand.ingredientCount, 2); + CHECK_EQ(stand.fuel, brewsPerBlazePowder - 1); +} + +VF_TEST(fuelIsCountedInBrewsAndOneBlazePowderBuysTwenty) { + BrewingStandState stand; + CHECK(!addFuel(stand, 0)); + CHECK(!addFuel(stand, -3)); + CHECK_EQ(stand.fuel, 0); + + CHECK(addFuel(stand, 1)); + CHECK_EQ(stand.fuel, brewsPerBlazePowder); + CHECK(addFuel(stand, 2)); + CHECK_EQ(stand.fuel, 3 * brewsPerBlazePowder); +} + +VF_TEST(anEmptyFuelBarStopsTheStandEvenWithAValidRecipeLoaded) { + BrewingStandState stand; + setBottle(stand, 0, PotionItem{}); + setIngredient(stand, Ingredient::NetherWart, 1); + CHECK(!canBrew(stand)); + CHECK(!tickBrewing(stand)); + CHECK_EQ(stand.progressTicks, 0); + + stand.fuel = 1; + CHECK(canBrew(stand)); + for (int tick = 0; tick < brewDurationTicks; ++tick) tickBrewing(stand); + CHECK_EQ(stand.fuel, 0); + CHECK_EQ(stand.bottles[0].potion.kind, PotionKind::Awkward); + + setIngredient(stand, Ingredient::Sugar, 1); + CHECK(!canBrew(stand)); + CHECK(!tickBrewing(stand)); +} + +VF_TEST(pullingABottleMidBrewThrowsAwayTheProgressBar) { + BrewingStandState stand = fueledStand(PotionItem{}, 1); + setIngredient(stand, Ingredient::NetherWart, 1); + for (int tick = 0; tick < 200; ++tick) tickBrewing(stand); + CHECK_EQ(stand.progressTicks, 200); + + bool taken = false; + PotionItem pulled = takeBottle(stand, 0, taken); + CHECK(taken); + CHECK_EQ(pulled.kind, PotionKind::Water); + CHECK_EQ(stand.progressTicks, 0); + CHECK(!stand.bottles[0].occupied); + CHECK(!tickBrewing(stand)); + + CHECK(setBottle(stand, 0, PotionItem{})); + for (int tick = 1; tick < brewDurationTicks; ++tick) { + CHECK_EQ(tickBrewing(stand), false); + } + CHECK(tickBrewing(stand)); +} + +VF_TEST(bottleSlotsRejectOutOfRangeIndicesAndDoubleFilling) { + BrewingStandState stand; + CHECK(!setBottle(stand, -1, PotionItem{})); + CHECK(!setBottle(stand, brewingBottleSlots, PotionItem{})); + CHECK(setBottle(stand, 0, PotionItem{})); + CHECK(!setBottle(stand, 0, potionOf(PotionKind::Awkward))); + CHECK_EQ(stand.bottles[0].potion.kind, PotionKind::Water); + + bool taken = true; + takeBottle(stand, 1, taken); + CHECK(!taken); + taken = true; + takeBottle(stand, brewingBottleSlots, taken); + CHECK(!taken); +} + +VF_TEST(theFourBasePotionsCarryNoEffectAndCannotBeDrunkForOne) { + const PotionKind effectless[] = {PotionKind::Water, PotionKind::Mundane, PotionKind::Thick, + PotionKind::Awkward}; + for (PotionKind kind : effectless) { + PotionItem potion = potionOf(kind); + CHECK(!potionHasEffect(potion)); + CHECK_EQ(potionEffect(potion).kind, EffectKind::Count); + + EffectSet target; + CHECK(!drinkPotion(target, potion)); + CHECK(target.active.empty()); + } +} + +VF_TEST(drinkingWorksOnlyInDrinkableFormAndLandsTheCatalogDuration) { + EffectSet target; + PotionItem regeneration = potionOf(PotionKind::Regeneration); + CHECK(drinkPotion(target, regeneration)); + CHECK_EQ(effectLevel(target, EffectKind::Regeneration), 1); + CHECK_EQ(effectDuration(target, EffectKind::Regeneration), 900); + + PotionItem splash = regeneration; + splash.form = PotionItem::Form::Splash; + EffectSet other; + CHECK(!drinkPotion(other, splash)); + CHECK(other.active.empty()); + + PotionItem strong = regeneration; + strong.upgraded = true; + CHECK(drinkPotion(target, strong)); + CHECK_EQ(effectLevel(target, EffectKind::Regeneration), 2); + CHECK_EQ(effectDuration(target, EffectKind::Regeneration), 440); +} + +VF_TEST(splashPotencyFallsOffLinearlyAndClampsAtBothEnds) { + CHECK_NEAR(splashPotency(0.0f, 4.0f), 1.0f, 0.0001f); + CHECK_NEAR(splashPotency(1.0f, 4.0f), 0.75f, 0.0001f); + CHECK_NEAR(splashPotency(4.0f, 4.0f), 0.0f, 0.0001f); + CHECK_NEAR(splashPotency(9.0f, 4.0f), 0.0f, 0.0001f); + CHECK_NEAR(splashPotency(-2.0f, 4.0f), 1.0f, 0.0001f); + CHECK_NEAR(splashPotency(1.0f, 0.0f), 0.0f, 0.0001f); +} + +VF_TEST(aSplashTimedPotionKeepsItsLevelAndLosesDurationWithDistance) { + PotionItem splash = potionOf(PotionKind::Swiftness); + splash.form = PotionItem::Form::Splash; + + EffectSet atImpact; + CHECK(splashPotion(atImpact, splash, 0.0f, 4.0f)); + CHECK_EQ(effectLevel(atImpact, EffectKind::Speed), 1); + CHECK_EQ(effectDuration(atImpact, EffectKind::Speed), 2700); + + EffectSet halfway; + CHECK(splashPotion(halfway, splash, 2.0f, 4.0f)); + CHECK_EQ(effectDuration(halfway, EffectKind::Speed), 1350); + + EffectSet outOfRange; + CHECK(!splashPotion(outOfRange, splash, 4.0f, 4.0f)); + CHECK(outOfRange.active.empty()); + + EffectSet wrongForm; + CHECK(!splashPotion(wrongForm, potionOf(PotionKind::Swiftness), 0.0f, 4.0f)); +} + +VF_TEST(aSplashInstantPotionScalesItsPotencyInsteadOfItsDuration) { + PotionItem harming = potionOf(PotionKind::Harming); + harming.form = PotionItem::Form::Splash; + + EffectSet target; + CHECK(splashPotion(target, harming, 3.0f, 4.0f)); + CHECK_EQ(target.active.size(), std::size_t{1}); + CHECK_EQ(target.active[0].kind, EffectKind::InstantDamage); + CHECK_EQ(target.active[0].durationTicks, 1); + CHECK_NEAR(target.active[0].potency, 0.25f, 0.0001f); + + PotionItem strong = harming; + strong.upgraded = true; + EffectSet direct; + CHECK(splashPotion(direct, strong, 0.0f, 4.0f)); + CHECK_EQ(direct.active[0].level(), 2); + CHECK_NEAR(direct.active[0].potency, 1.0f, 0.0001f); +} + +VF_TEST(everyPotionKindKeepsTheLevelAndDurationInvariantsOfItsModifiers) { + for (int index = 0; index < static_cast(PotionKind::Count); ++index) { + PotionKind kind = static_cast(index); + PotionItem plain = potionOf(kind); + EffectInstance base = potionEffect(plain); + + CHECK_EQ(potionHasEffect(plain), base.kind != EffectKind::Count); + if (!potionHasEffect(plain)) { + CHECK_EQ(base.durationTicks, 0); + continue; + } + + CHECK(base.durationTicks > 0); + CHECK_EQ(base.level(), 1); + + int upgradedDuration = durationOf(kind, true, false); + int extendedDuration = durationOf(kind, false, true); + CHECK(extendedDuration >= base.durationTicks); + CHECK(levelOf(kind, false, true) == 1); + + int upgradedLevel = levelOf(kind, true, false); + CHECK(upgradedLevel >= 1); + if (upgradedLevel > 1 && !isInstantEffect(base.kind)) { + CHECK(upgradedDuration < base.durationTicks); + } + if (isInstantEffect(base.kind)) { + CHECK_EQ(base.durationTicks, 1); + CHECK_EQ(upgradedDuration, 1); + } + } +}