From 562ef29d54420d1d90216c0c5fa905148e4457a7 Mon Sep 17 00:00:00 2001 From: DualFroz Date: Wed, 2 Sep 2026 18:20:58 +0200 Subject: [PATCH] feat(vf): add Enchantment and EnchantSet --- core/include/vf/Enchant.h | 110 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 110 insertions(+) create mode 100644 core/include/vf/Enchant.h diff --git a/core/include/vf/Enchant.h b/core/include/vf/Enchant.h new file mode 100644 index 0000000..c461f13 --- /dev/null +++ b/core/include/vf/Enchant.h @@ -0,0 +1,110 @@ +#pragma once + +#include +#include +#include +#include "Item.h" +#include "Math.h" +#include "World.h" + +namespace vf { + +enum class EnchantKind : uint8_t { + Protection, FireProtection, BlastProtection, ProjectileProtection, FeatherFalling, + Respiration, AquaAffinity, Thorns, + Sharpness, Smite, BaneOfArthropods, Knockback, FireAspect, Looting, + Efficiency, SilkTouch, Unbreaking, Fortune, + Power, Punch, Flame, Infinity, Mending, + Count +}; + +struct Enchantment { + // What a piece of gear counts as when deciding which enchantments may land + // on it. Armour slots are separate targets because Respiration, Aqua + // Affinity and Feather Falling are slot bound. + enum class Target : uint8_t { + Sword, Axe, Pickaxe, Shovel, Bow, Helmet, Chestplate, Leggings, Boots, Book, + Count + }; + + EnchantKind kind = EnchantKind::Protection; + int level = 1; +}; + +int maxEnchantLevel(EnchantKind kind); +int enchantWeight(EnchantKind kind); +int anvilCostMultiplier(EnchantKind kind); +int enchantMinCost(EnchantKind kind, int level); +int enchantMaxCost(EnchantKind kind, int level); +bool enchantsConflict(EnchantKind a, EnchantKind b); +bool enchantApplies(EnchantKind kind, Enchantment::Target target); + +// Vanilla enchantability: how many extra cost points the material buys during +// a table roll. Non-tool items return 0. +int enchantabilityOf(ItemId item); +Enchantment::Target enchantTargetOf(ItemId item); + +struct EnchantSet { + std::vector entries; + + int levelOf(EnchantKind kind) const; + bool has(EnchantKind kind) const { return levelOf(kind) > 0; } + bool conflictsWith(EnchantKind kind) const; + bool empty() const { return entries.empty(); } +}; + +// Adds or upgrades an enchantment. Rejects levels above the cap, downgrades and +// anything conflicting with what is already there. +bool addEnchant(EnchantSet& set, EnchantKind kind, int level); +bool removeEnchant(EnchantSet& set, EnchantKind kind); + +// One row of the enchanting table, and also the outcome of an anvil merge: +// both are "these enchantments for this many levels". +struct EnchantOffer { + EnchantSet enchants; + int requiredLevels = 0; + int lapisCost = 0; + int priorWork = 0; + bool tooExpensive = false; + + bool valid() const { return !tooExpensive && requiredLevels > 0 && !enchants.empty(); } +}; + +struct EnchantTableState { + uint32_t seed = 1; + int bookshelves = 0; + Enchantment::Target target = Enchantment::Target::Sword; + int enchantability = 14; + std::array offers{}; +}; + +constexpr int maxBookshelfPower = 15; +constexpr int anvilTooExpensiveCost = 40; + +// Counts shelves on the ring two blocks out from the table, at table height and +// one above, ignoring any shelf whose line of sight to the table is blocked. +// Clamped to maxBookshelfPower. +int bookshelfCount(const World& world, const Vec3i& tablePos); + +// Rolls a weighted set of enchantments for a target at a given effective level. +// Deterministic in `rng`. +EnchantSet rollEnchantments(Enchantment::Target target, int enchantability, int enchantLevel, + uint32_t& rng); + +// Fills the three rows from the table seed and shelf count. Calling it twice on +// an unchanged state yields identical offers. +void refreshOffers(EnchantTableState& state); + +// Consumes a row when the player can pay for it, reseeding the table so the +// next refresh differs. Returns an invalid offer when the row is unaffordable. +EnchantOffer takeOffer(EnchantTableState& state, int slot, int playerLevels, int lapisCount); + +int anvilRepairCost(int priorWork); + +// Merges `sacrifice` into `target` the way an anvil does: matching levels step +// up, conflicts cost a penalty level and are dropped, and the prior work +// penalty doubles the base price every time. +EnchantOffer anvilCombine(const EnchantSet& target, int targetPriorWork, const EnchantSet& sacrifice, + int sacrificePriorWork); + +} // namespace vf