#include "vf/Brewing.h" #include #include namespace vf { namespace { using Ingredient = BrewRecipe::Ingredient; struct PotionProfile { EffectKind kind = EffectKind::Count; int baseTicks = 0; int extendedTicks = 0; // zero: redstone does nothing int upgradedTicks = 0; // zero: glowstone does nothing int upgradedLevel = 1; }; const PotionProfile& profileFor(PotionKind kind) { static const PotionProfile profiles[] = { {EffectKind::Count, 0, 0, 0, 1}, // Water {EffectKind::Count, 0, 0, 0, 1}, // Mundane {EffectKind::Count, 0, 0, 0, 1}, // Thick {EffectKind::Count, 0, 0, 0, 1}, // Awkward {EffectKind::InstantHealth, 1, 0, 1, 2}, // Healing {EffectKind::InstantDamage, 1, 0, 1, 2}, // Harming {EffectKind::Regeneration, 900, 1800, 440, 2}, // Regeneration {EffectKind::Poison, 900, 1800, 432, 2}, // Poison {EffectKind::Speed, 3600, 9600, 1800, 2}, // Swiftness {EffectKind::Slowness, 1800, 4800, 400, 4}, // Slowness {EffectKind::JumpBoost, 3600, 9600, 1800, 2}, // Leaping {EffectKind::Strength, 3600, 9600, 1800, 2}, // Strength {EffectKind::Weakness, 1800, 4800, 0, 1}, // Weakness {EffectKind::FireResistance, 3600, 9600, 0, 1}, // FireResistance {EffectKind::WaterBreathing, 3600, 9600, 0, 1}, // WaterBreathing {EffectKind::NightVision, 3600, 9600, 0, 1}, // NightVision {EffectKind::Invisibility, 3600, 9600, 0, 1}, // Invisibility }; static_assert(sizeof(profiles) / sizeof(profiles[0]) == static_cast(PotionKind::Count), "potion profile table must cover every PotionKind"); return profiles[static_cast(kind)]; } bool applyModifier(PotionItem& potion, Ingredient ingredient) { const PotionProfile& profile = profileFor(potion.kind); switch (ingredient) { case Ingredient::Glowstone: if (profile.upgradedTicks == 0 || potion.upgraded) return false; potion.upgraded = true; potion.extended = false; return true; case Ingredient::Redstone: if (profile.extendedTicks == 0 || potion.extended) return false; potion.extended = true; potion.upgraded = false; return true; case Ingredient::Gunpowder: if (potion.form == PotionItem::Form::Splash) return false; potion.form = PotionItem::Form::Splash; return true; default: return false; } } } // namespace const std::vector& brewRecipes() { static const std::vector recipes = { {PotionKind::Water, Ingredient::NetherWart, PotionKind::Awkward}, {PotionKind::Water, Ingredient::Glowstone, PotionKind::Thick}, {PotionKind::Water, Ingredient::Redstone, PotionKind::Mundane}, {PotionKind::Water, Ingredient::FermentedSpiderEye, PotionKind::Weakness}, {PotionKind::Awkward, Ingredient::Sugar, PotionKind::Swiftness}, {PotionKind::Awkward, Ingredient::GlisteringMelon, PotionKind::Healing}, {PotionKind::Awkward, Ingredient::SpiderEye, PotionKind::Poison}, {PotionKind::Awkward, Ingredient::BlazePowder, PotionKind::Strength}, {PotionKind::Awkward, Ingredient::MagmaCream, PotionKind::FireResistance}, {PotionKind::Awkward, Ingredient::PufferFish, PotionKind::WaterBreathing}, {PotionKind::Awkward, Ingredient::GoldenCarrot, PotionKind::NightVision}, {PotionKind::Awkward, Ingredient::GhastTear, PotionKind::Regeneration}, {PotionKind::Awkward, Ingredient::RabbitFoot, PotionKind::Leaping}, {PotionKind::NightVision, Ingredient::FermentedSpiderEye, PotionKind::Invisibility}, {PotionKind::Healing, Ingredient::FermentedSpiderEye, PotionKind::Harming}, {PotionKind::Poison, Ingredient::FermentedSpiderEye, PotionKind::Harming}, {PotionKind::Swiftness, Ingredient::FermentedSpiderEye, PotionKind::Slowness}, {PotionKind::Leaping, Ingredient::FermentedSpiderEye, PotionKind::Slowness}, {PotionKind::Strength, Ingredient::FermentedSpiderEye, PotionKind::Weakness}, }; return recipes; } PotionItem brewResult(const PotionItem& base, Ingredient ingredient, bool& changed) { PotionItem brewed = base; changed = false; if (ingredient == Ingredient::None) return brewed; for (const BrewRecipe& recipe : brewRecipes()) { if (recipe.input != base.kind || recipe.ingredient != ingredient) continue; brewed.kind = recipe.output; brewed.upgraded = false; brewed.extended = false; changed = true; return brewed; } changed = applyModifier(brewed, ingredient); return brewed; } bool setBottle(BrewingStandState& stand, int slot, const PotionItem& potion) { if (slot < 0 || slot >= brewingBottleSlots) return false; BrewingSlot& bottle = stand.bottles[static_cast(slot)]; if (bottle.occupied) return false; bottle.potion = potion; bottle.occupied = true; stand.progressTicks = 0; return true; } PotionItem takeBottle(BrewingStandState& stand, int slot, bool& taken) { taken = false; if (slot < 0 || slot >= brewingBottleSlots) return {}; BrewingSlot& bottle = stand.bottles[static_cast(slot)]; if (!bottle.occupied) return {}; PotionItem removed = bottle.potion; bottle = BrewingSlot{}; taken = true; stand.progressTicks = 0; return removed; } bool setIngredient(BrewingStandState& stand, Ingredient ingredient, int count) { if (ingredient == Ingredient::None || count <= 0) return false; if (stand.ingredientCount > 0 && stand.ingredient != ingredient) return false; if (stand.ingredient == ingredient) { stand.ingredientCount += count; return true; } stand.ingredient = ingredient; stand.ingredientCount = count; stand.progressTicks = 0; return true; } bool addFuel(BrewingStandState& stand, int blazePowderCount) { if (blazePowderCount <= 0) return false; stand.fuel += blazePowderCount * brewsPerBlazePowder; return true; } bool canBrew(const BrewingStandState& stand) { if (stand.fuel <= 0) return false; if (stand.ingredient == Ingredient::None || stand.ingredientCount <= 0) return false; for (const BrewingSlot& bottle : stand.bottles) { if (!bottle.occupied) continue; bool changed = false; brewResult(bottle.potion, stand.ingredient, changed); if (changed) return true; } return false; } bool tickBrewing(BrewingStandState& stand) { if (!canBrew(stand)) { stand.brewing = false; stand.progressTicks = 0; return false; } stand.brewing = true; ++stand.progressTicks; if (stand.progressTicks < brewDurationTicks) return false; for (BrewingSlot& bottle : stand.bottles) { if (!bottle.occupied) continue; bool changed = false; PotionItem brewed = brewResult(bottle.potion, stand.ingredient, changed); if (changed) bottle.potion = brewed; } if (--stand.ingredientCount <= 0) { stand.ingredientCount = 0; stand.ingredient = Ingredient::None; } --stand.fuel; stand.progressTicks = 0; stand.brewing = false; return true; } bool potionHasEffect(const PotionItem& potion) { return profileFor(potion.kind).kind != EffectKind::Count; } EffectInstance potionEffect(const PotionItem& potion) { const PotionProfile& profile = profileFor(potion.kind); if (profile.kind == EffectKind::Count) { return EffectInstance{EffectKind::Count, 0, 0, 1.0f, false}; } int level = 1; int duration = profile.baseTicks; if (potion.upgraded && profile.upgradedTicks > 0) { level = profile.upgradedLevel; duration = profile.upgradedTicks; } else if (potion.extended && profile.extendedTicks > 0) { duration = profile.extendedTicks; } return makeEffect(profile.kind, level, duration); } bool drinkPotion(EffectSet& target, const PotionItem& potion) { if (potion.form != PotionItem::Form::Drinkable) return false; if (!potionHasEffect(potion)) return false; return applyEffect(target, potionEffect(potion)); } float splashPotency(float distance, float radius) { if (radius <= 0.0f) return 0.0f; float potency = 1.0f - distance / radius; return std::max(0.0f, std::min(1.0f, potency)); } bool splashPotion(EffectSet& target, const PotionItem& potion, float distance, float radius) { if (potion.form != PotionItem::Form::Splash) return false; if (!potionHasEffect(potion)) return false; float potency = splashPotency(distance, radius); if (potency <= 0.0f) return false; EffectInstance effect = potionEffect(potion); if (isInstantEffect(effect.kind)) { effect.potency = potency; return applyEffect(target, effect); } int ticks = static_cast(std::floor(static_cast(effect.durationTicks) * 0.75f * potency)); if (ticks < 1) return false; effect.durationTicks = ticks; return applyEffect(target, effect); } } // namespace vf