diff --git a/core/src/Trade.cpp b/core/src/Trade.cpp index f6ee58b..ca0c054 100644 --- a/core/src/Trade.cpp +++ b/core/src/Trade.cpp @@ -153,3 +153,155 @@ const OfferSpec butcherSpecs[] = { {4, ItemId::EmeraldGem, 4, ItemId::None, 0, ItemId::CookedBeef, 8, 12, 30}, }; +const OfferSpec leatherworkerSpecs[] = { + {0, ItemId::Leaves, 26, ItemId::None, 0, ItemId::EmeraldGem, 1, 16, 2}, + {0, ItemId::EmeraldGem, 2, ItemId::None, 0, ItemId::Planks, 8, 16, 1}, + {1, ItemId::RottenFlesh, 26, ItemId::None, 0, ItemId::EmeraldGem, 1, 16, 5}, + {1, ItemId::EmeraldGem, 3, ItemId::None, 0, ItemId::Ladder, 4, 16, 5}, + {2, ItemId::Snow, 24, ItemId::None, 0, ItemId::EmeraldGem, 1, 12, 10}, + {2, ItemId::EmeraldGem, 4, ItemId::None, 0, ItemId::Ice, 6, 12, 10}, + {3, ItemId::EmeraldGem, 5, ItemId::None, 0, ItemId::Chest, 2, 12, 15}, + {4, ItemId::EmeraldGem, 6, ItemId::IronIngot, 2, ItemId::Furnace, 2, 9, 30}, +}; + +const OfferSpec masonSpecs[] = { + {0, ItemId::Stone, 20, ItemId::None, 0, ItemId::EmeraldGem, 1, 16, 2}, + {0, ItemId::EmeraldGem, 1, ItemId::None, 0, ItemId::Cobblestone, 10, 16, 1}, + {1, ItemId::Sand, 20, ItemId::None, 0, ItemId::EmeraldGem, 1, 16, 5}, + {1, ItemId::EmeraldGem, 1, ItemId::None, 0, ItemId::Sandstone, 6, 16, 5}, + {2, ItemId::Gravel, 20, ItemId::None, 0, ItemId::EmeraldGem, 1, 12, 10}, + {2, ItemId::EmeraldGem, 2, ItemId::None, 0, ItemId::Obsidian, 1, 12, 10}, + {3, ItemId::EmeraldGem, 3, ItemId::None, 0, ItemId::Glowstone, 2, 12, 15}, + {4, ItemId::EmeraldGem, 5, ItemId::None, 0, ItemId::Sandstone, 12, 9, 30}, +}; + +const OfferSpec* specsForProfession(VillagerProfession profession, int& outCount) { + switch (profession) { + case VillagerProfession::Farmer: outCount = 8; return farmerSpecs; + case VillagerProfession::Fisherman: outCount = 8; return fishermanSpecs; + case VillagerProfession::Shepherd: outCount = 8; return shepherdSpecs; + case VillagerProfession::Fletcher: outCount = 8; return fletcherSpecs; + case VillagerProfession::Librarian: outCount = 8; return librarianSpecs; + case VillagerProfession::Cartographer: outCount = 8; return cartographerSpecs; + case VillagerProfession::Cleric: outCount = 8; return clericSpecs; + case VillagerProfession::Armorer: outCount = 8; return armorerSpecs; + case VillagerProfession::Weaponsmith: outCount = 8; return weaponsmithSpecs; + case VillagerProfession::Toolsmith: outCount = 8; return toolsmithSpecs; + case VillagerProfession::Butcher: outCount = 8; return butcherSpecs; + case VillagerProfession::Leatherworker: outCount = 8; return leatherworkerSpecs; + case VillagerProfession::Mason: outCount = 8; return masonSpecs; + default: break; + } + outCount = 0; + return nullptr; +} + +int containerCount(const Container& container, ItemId item) { + int total = 0; + for (int i = 0; i < container.size(); ++i) { + const ItemSlot& slot = container.slot(i); + if (!slot.empty() && slot.item == item) total += slot.count; + } + return total; +} + +bool containerHasRoom(const Container& container, ItemId item, int count) { + int capacity = 0; + int limit = maxStackSize(item); + for (int i = 0; i < container.size(); ++i) { + const ItemSlot& slot = container.slot(i); + if (slot.empty()) capacity += limit; + else if (slot.item == item && slot.count < limit) capacity += limit - slot.count; + if (capacity >= count) return true; + } + return capacity >= count; +} + +} // namespace + +const char* tradeTierName(TradeTier tier) { + switch (tier) { + case TradeTier::Novice: return "novice"; + case TradeTier::Apprentice: return "apprentice"; + case TradeTier::Journeyman: return "journeyman"; + case TradeTier::Expert: return "expert"; + case TradeTier::Master: return "master"; + case TradeTier::Count: break; + } + return "unknown"; +} + +TradeTier tierForLevel(int level) { + int clamped = clampInt(level, 1, villagerMaxLevel); + return static_cast(clamped - 1); +} + +int levelForTier(TradeTier tier) { return static_cast(tier) + 1; } + +int PriceModifier::totalPercent() const { + int percent = reputationPercent + heroLevel * heroDiscountPercentPerLevel - + hitCount * hitSurchargePercent; + return clampInt(percent, -300, 90); +} + +PriceModifier priceModifierFor(const ReputationView& reputation, int heroLevel, int hitCount) { + PriceModifier modifier; + modifier.reputationPercent = reputation.discountPercent(); + modifier.heroLevel = heroLevel < 0 ? 0 : heroLevel; + modifier.hitCount = hitCount < 0 ? 0 : hitCount; + return modifier; +} + +int villagerHitCount(const GossipLedger& gossip) { + return gossip.valueOf(GossipKind::MinorNegative) / gossipMinorNegativeGain; +} + +int offerPrice(const TradeOffer& offer, const PriceModifier& modifier) { + int base = offer.basePrice(); + int demandDelta = static_cast( + std::lround(static_cast(offer.demand) * offer.priceMultiplier * base)); + if (demandDelta < 0) demandDelta = 0; + int raw = base + demandDelta; + int shift = static_cast( + std::lround(static_cast(raw) * modifier.totalPercent() / 100.0)); + int adjusted = raw - shift; + return adjusted < 1 ? 1 : adjusted; +} + +bool TradeOfferList::unlocked(int index, int level) const { + if (index < 0 || index >= size()) return false; + return static_cast(offers[index].tier) < clampInt(level, 1, villagerMaxLevel); +} + +int TradeOfferList::unlockedCount(int level) const { + int count = 0; + for (int i = 0; i < size(); ++i) + if (unlocked(i, level)) ++count; + return count; +} + +int TradeOfferList::countForTier(TradeTier tier) const { + int count = 0; + for (const TradeOffer& offer : offers) + if (offer.tier == tier) ++count; + return count; +} + +bool TradeOfferList::allExhausted(int level) const { + int reachable = 0; + for (int i = 0; i < size(); ++i) { + if (!unlocked(i, level)) continue; + ++reachable; + if (!offers[i].exhausted()) return false; + } + return reachable > 0; +} + +TradeOfferList buildTradeOffers(VillagerProfession profession, uint32_t seed) { + TradeOfferList list; + list.profession = profession; + + int specCount = 0; + const OfferSpec* specs = specsForProfession(profession, specCount); + if (specs == nullptr) return list; +