feat: add restockOffers and tickVillagerRestock to Trade
This commit is contained in:
1 parent
e8d9fe3a04
commit
1c229a18f2
1 file changed
+167
@@ -305,3 +305,170 @@ TradeOfferList buildTradeOffers(VillagerProfession profession, uint32_t seed) {
|
|||||||
const OfferSpec* specs = specsForProfession(profession, specCount);
|
const OfferSpec* specs = specsForProfession(profession, specCount);
|
||||||
if (specs == nullptr) return list;
|
if (specs == nullptr) return list;
|
||||||
|
|
||||||
|
uint32_t rng = seed ^ (static_cast<uint32_t>(profession) * 0x9e3779b9u);
|
||||||
|
for (int i = 0; i < specCount; ++i) {
|
||||||
|
const OfferSpec& spec = specs[i];
|
||||||
|
TradeOffer offer;
|
||||||
|
offer.tier = static_cast<TradeTier>(spec.tier);
|
||||||
|
offer.inputA = spec.inputA;
|
||||||
|
offer.inputACount = spec.inputACount;
|
||||||
|
offer.inputB = spec.inputB;
|
||||||
|
offer.inputBCount = spec.inputBCount;
|
||||||
|
offer.output = spec.output;
|
||||||
|
offer.outputCount = spec.outputCount;
|
||||||
|
offer.maxUses = spec.maxUses;
|
||||||
|
offer.experienceReward = spec.experience;
|
||||||
|
offer.priceMultiplier = spec.inputA == ItemId::EmeraldGem ? 0.05f : 0.02f;
|
||||||
|
|
||||||
|
uint32_t draw = nextRandom(rng);
|
||||||
|
if (offer.inputACount >= 6) {
|
||||||
|
offer.inputACount += static_cast<int>(draw % 5u) - 2;
|
||||||
|
} else if (offer.outputCount >= 3) {
|
||||||
|
offer.outputCount += static_cast<int>(draw % 3u) - 1;
|
||||||
|
}
|
||||||
|
list.offers.push_back(offer);
|
||||||
|
}
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool restockOffers(TradeOfferList& offers) {
|
||||||
|
bool changed = false;
|
||||||
|
for (TradeOffer& offer : offers.offers) {
|
||||||
|
if (offer.uses > 0) {
|
||||||
|
offer.uses = 0;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
int faded = offer.demand - tradeDemandDecayPerRestock;
|
||||||
|
if (faded < 0) faded = 0;
|
||||||
|
if (faded != offer.demand) {
|
||||||
|
offer.demand = faded;
|
||||||
|
changed = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return changed;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool tickVillagerRestock(Villager& villager, TradeOfferList& offers, const TimeOfDay& clock) {
|
||||||
|
long long day = clock.dayCount();
|
||||||
|
if (villager.lastRestockDay != day) {
|
||||||
|
villager.lastRestockDay = day;
|
||||||
|
villager.restocksToday = 0;
|
||||||
|
}
|
||||||
|
if (!villager.alive || villager.restocksToday >= tradeRestocksPerDay) return false;
|
||||||
|
if (villager.activity != VillagerActivity::Work ||
|
||||||
|
villager.state != VillagerState::AtTarget) return false;
|
||||||
|
|
||||||
|
bool anyUsed = false;
|
||||||
|
for (const TradeOffer& offer : offers.offers)
|
||||||
|
if (offer.uses > 0) anyUsed = true;
|
||||||
|
if (!anyUsed) return false;
|
||||||
|
|
||||||
|
restockOffers(offers);
|
||||||
|
++villager.restocksToday;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char* tradeResultReasonName(TradeResult::Reason reason) {
|
||||||
|
switch (reason) {
|
||||||
|
case TradeResult::Reason::Ok: return "ok";
|
||||||
|
case TradeResult::Reason::NoSuchOffer: return "noSuchOffer";
|
||||||
|
case TradeResult::Reason::VillagerUnavailable: return "villagerUnavailable";
|
||||||
|
case TradeResult::Reason::LevelLocked: return "levelLocked";
|
||||||
|
case TradeResult::Reason::OfferExhausted: return "offerExhausted";
|
||||||
|
case TradeResult::Reason::MissingInputA: return "missingInputA";
|
||||||
|
case TradeResult::Reason::MissingInputB: return "missingInputB";
|
||||||
|
case TradeResult::Reason::NoRoom: return "noRoom";
|
||||||
|
case TradeResult::Reason::Count: break;
|
||||||
|
}
|
||||||
|
return "unknown";
|
||||||
|
}
|
||||||
|
|
||||||
|
TradeSession::TradeSession(Villager& villager, TradeOfferList& offers, Container& customerInventory)
|
||||||
|
: villager(villager), offers(offers), inventory(customerInventory) {}
|
||||||
|
|
||||||
|
void TradeSession::setPriceModifier(const PriceModifier& newModifier) { modifier = newModifier; }
|
||||||
|
|
||||||
|
void TradeSession::refreshPrices(const ReputationView& reputation, int heroLevel) {
|
||||||
|
modifier = priceModifierFor(reputation, heroLevel, villagerHitCount(villager.gossip));
|
||||||
|
}
|
||||||
|
|
||||||
|
int TradeSession::priceOf(int index) const {
|
||||||
|
if (index < 0 || index >= offers.size()) return 0;
|
||||||
|
return offerPrice(offers.at(index), modifier);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TradeSession::available(int index) const {
|
||||||
|
if (index < 0 || index >= offers.size()) return false;
|
||||||
|
if (!villager.alive || !villager.employed()) return false;
|
||||||
|
if (!offers.unlocked(index, villager.level)) return false;
|
||||||
|
return !offers.at(index).exhausted();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool TradeSession::canAfford(int index) const {
|
||||||
|
if (!available(index)) return false;
|
||||||
|
const TradeOffer& offer = offers.at(index);
|
||||||
|
if (containerCount(inventory, offer.inputA) < priceOf(index)) return false;
|
||||||
|
if (offer.hasSecondaryInput() && containerCount(inventory, offer.inputB) < offer.inputBCount)
|
||||||
|
return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
TradeResult TradeSession::trade(int index) {
|
||||||
|
TradeResult outcome;
|
||||||
|
if (index < 0 || index >= offers.size()) {
|
||||||
|
outcome.reason = TradeResult::Reason::NoSuchOffer;
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
|
if (!villager.alive || !villager.employed()) {
|
||||||
|
outcome.reason = TradeResult::Reason::VillagerUnavailable;
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
|
if (!offers.unlocked(index, villager.level)) {
|
||||||
|
outcome.reason = TradeResult::Reason::LevelLocked;
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
TradeOffer& offer = offers.at(index);
|
||||||
|
if (offer.exhausted()) {
|
||||||
|
outcome.reason = TradeResult::Reason::OfferExhausted;
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
int price = offerPrice(offer, modifier);
|
||||||
|
if (containerCount(inventory, offer.inputA) < price) {
|
||||||
|
outcome.reason = TradeResult::Reason::MissingInputA;
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
|
if (offer.hasSecondaryInput() && containerCount(inventory, offer.inputB) < offer.inputBCount) {
|
||||||
|
outcome.reason = TradeResult::Reason::MissingInputB;
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
|
if (!containerHasRoom(inventory, offer.output, offer.outputCount)) {
|
||||||
|
outcome.reason = TradeResult::Reason::NoRoom;
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
inventory.extract(offer.inputA, price);
|
||||||
|
if (offer.hasSecondaryInput()) inventory.extract(offer.inputB, offer.inputBCount);
|
||||||
|
inventory.insert(offer.output, offer.outputCount);
|
||||||
|
|
||||||
|
++offer.uses;
|
||||||
|
offer.demand = clampInt(offer.demand + tradeDemandPerUse, 0, tradeDemandCap);
|
||||||
|
|
||||||
|
outcome.success = true;
|
||||||
|
outcome.reason = TradeResult::Reason::Ok;
|
||||||
|
outcome.pricePaid = price;
|
||||||
|
outcome.secondaryPaid = offer.hasSecondaryInput() ? offer.inputBCount : 0;
|
||||||
|
outcome.received = offer.output;
|
||||||
|
outcome.receivedCount = offer.outputCount;
|
||||||
|
outcome.experienceGained = offer.experienceReward;
|
||||||
|
outcome.leveledUp = grantVillagerExperience(villager, offer.experienceReward);
|
||||||
|
|
||||||
|
villager.gossip.add(GossipKind::Trading, gossipTradingGain);
|
||||||
|
|
||||||
|
++completed;
|
||||||
|
if (offer.inputA == ItemId::EmeraldGem) spent += price;
|
||||||
|
return outcome;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace vf
|
||||||
Reference in new issue
Block a user