diff --git a/core/src/Enchant.cpp b/core/src/Enchant.cpp index 3b4231d..d76d72e 100644 --- a/core/src/Enchant.cpp +++ b/core/src/Enchant.cpp @@ -298,3 +298,113 @@ EnchantSet rollEnchantments(Enchantment::Target target, int enchantability, int float spread = 1.0f + (unitFloat(rng) + unitFloat(rng) - 1.0f) * 0.15f; modified = std::max(1, static_cast(std::lround(static_cast(modified) * spread))); + std::vector candidates; + for (int slot = 0; slot < 3; ++slot) { + candidates.clear(); + int totalWeight = 0; + for (int k = 0; k < static_cast(EnchantKind::Count); ++k) { + EnchantKind kind = static_cast(k); + if (!enchantApplies(kind, target)) continue; + if (chosen.levelOf(kind) > 0 || chosen.conflictsWith(kind)) continue; + for (int level = maxEnchantLevel(kind); level >= 1; --level) { + if (modified < enchantMinCost(kind, level)) continue; + if (modified > enchantMaxCost(kind, level)) continue; + candidates.push_back({kind, level}); + totalWeight += enchantWeight(kind); + break; + } + } + if (candidates.empty()) break; + int pick = rollBelow(rng, totalWeight); + Enchantment picked = candidates.back(); + for (const Enchantment& candidate : candidates) { + pick -= enchantWeight(candidate.kind); + if (pick < 0) { + picked = candidate; + break; + } + } + chosen.entries.push_back(picked); + if (slot == 2) break; + modified = modified * 4 / 5; + if (rollBelow(rng, 50) > modified) break; + } + return chosen; +} + +void refreshOffers(EnchantTableState& state) { + uint32_t rng = state.seed != 0 ? state.seed : 0x9e3779b9u; + int shelves = std::min(std::max(state.bookshelves, 0), maxBookshelfPower); + int base = 1 + rollBelow(rng, 8) + shelves / 2 + rollBelow(rng, shelves + 1); + + int slotLevels[3] = { + std::max(base / 3, 1), + (base * 2) / 3 + 1, + std::max(base, shelves * 2), + }; + + for (int slot = 0; slot < 3; ++slot) { + EnchantOffer offer; + offer.lapisCost = slot + 1; + offer.requiredLevels = slotLevels[slot]; + uint32_t slotRng = rng ^ (0x85ebca6bu * static_cast(slot + 1)); + offer.enchants = rollEnchantments(state.target, state.enchantability, slotLevels[slot], + slotRng); + if (offer.enchants.empty()) offer.requiredLevels = 0; + state.offers[static_cast(slot)] = offer; + } +} + +EnchantOffer takeOffer(EnchantTableState& state, int slot, int playerLevels, int lapisCount) { + if (slot < 0 || slot > 2) return {}; + const EnchantOffer& offer = state.offers[static_cast(slot)]; + if (!offer.valid()) return {}; + if (playerLevels < offer.requiredLevels || lapisCount < offer.lapisCost) return {}; + + EnchantOffer granted = offer; + state.seed = state.seed * 1664525u + 1013904223u + static_cast(slot); + refreshOffers(state); + return granted; +} + +int anvilRepairCost(int priorWork) { + int clamped = std::min(std::max(priorWork, 0), 30); + return (1 << clamped) - 1; +} + +EnchantOffer anvilCombine(const EnchantSet& target, int targetPriorWork, const EnchantSet& sacrifice, + int sacrificePriorWork) { + EnchantOffer merged; + merged.enchants = target; + merged.priorWork = std::max(targetPriorWork, sacrificePriorWork) + 1; + + int cost = anvilRepairCost(targetPriorWork) + anvilRepairCost(sacrificePriorWork); + for (const Enchantment& incoming : sacrifice.entries) { + int existing = merged.enchants.levelOf(incoming.kind); + if (existing == 0) { + if (merged.enchants.conflictsWith(incoming.kind)) { + cost += 1; + continue; + } + merged.enchants.entries.push_back(incoming); + cost += anvilCostMultiplier(incoming.kind) * incoming.level; + continue; + } + int newLevel = existing; + if (incoming.level > existing) { + newLevel = incoming.level; + } else if (incoming.level == existing) { + newLevel = std::min(existing + 1, maxEnchantLevel(incoming.kind)); + } + for (Enchantment& entry : merged.enchants.entries) { + if (entry.kind == incoming.kind) entry.level = newLevel; + } + cost += anvilCostMultiplier(incoming.kind) * newLevel; + } + + merged.requiredLevels = cost; + merged.tooExpensive = cost >= anvilTooExpensiveCost; + return merged; +} + +} // namespace vf