diff --git a/tests/testTrade.cpp b/tests/testTrade.cpp index 024fe9e..a6a690b 100644 --- a/tests/testTrade.cpp +++ b/tests/testTrade.cpp @@ -157,3 +157,159 @@ VF_TEST(restockRefillsUsesAndLetsDemandAndPriceFallBack) { Container wallet = makeWallet(400); TradeSession session(villager, offers, wallet); + for (int i = 0; i < 10; ++i) CHECK(session.trade(comparatorOffer).success); + int busyPrice = session.priceOf(comparatorOffer); + int busyDemand = offers.at(comparatorOffer).demand; + CHECK_EQ(offers.at(comparatorOffer).uses, 10); + + CHECK(restockOffers(offers)); + CHECK_EQ(offers.at(comparatorOffer).uses, 0); + CHECK_EQ(offers.at(comparatorOffer).demand, busyDemand - tradeDemandDecayPerRestock); + CHECK(session.priceOf(comparatorOffer) <= busyPrice); + CHECK(session.available(comparatorOffer)); + + for (int i = 0; i < 20; ++i) restockOffers(offers); + CHECK_EQ(offers.at(comparatorOffer).demand, 0); + CHECK_EQ(session.priceOf(comparatorOffer), offers.at(comparatorOffer).basePrice()); + CHECK(!restockOffers(offers)); +} + +VF_TEST(theVillagerRestocksAtMostTwiceADayAndOnlyAtItsOwnWorkstation) { + World world = makeShopWorld(); + PoiRegistry pois; + pois.scan(world, villageCenter, 10, 4); + + Villager villager = makeLibrarian(3); + TradeOfferList offers = buildTradeOffers(VillagerProfession::Librarian, 1u); + Container wallet = makeWallet(400); + TradeSession session(villager, offers, wallet); + VillagerSchedule schedule; + TimeOfDay clock(static_cast(0.35 * 24000.0)); + + CHECK(session.trade(comparatorOffer).success); + CHECK(!tickVillagerRestock(villager, offers, clock)); + + for (int tick = 0; tick < 400; ++tick) + tickVillager(world, villager, pois, clock, schedule, villageCenter, false, 0.05f); + CHECK(villager.activity == VillagerActivity::Work); + CHECK(villager.state == VillagerState::AtTarget); + CHECK_EQ(pois.at(villager.workstationIndex).block, Block::Lectern); + + CHECK(tickVillagerRestock(villager, offers, clock)); + CHECK_EQ(villager.restocksToday, 1); + CHECK(!tickVillagerRestock(villager, offers, clock)); + + CHECK(session.trade(comparatorOffer).success); + CHECK(tickVillagerRestock(villager, offers, clock)); + CHECK_EQ(villager.restocksToday, tradeRestocksPerDay); + + CHECK(session.trade(comparatorOffer).success); + CHECK(!tickVillagerRestock(villager, offers, clock)); + + clock.setTick(static_cast(24000 + 0.35 * 24000.0)); + CHECK(tickVillagerRestock(villager, offers, clock)); + CHECK_EQ(villager.restocksToday, 1); + CHECK_EQ(villager.lastRestockDay, 1); +} + +VF_TEST(tradingWithoutEnoughEmeraldsFailsAndLeavesTheInventoryAlone) { + Villager villager = makeLibrarian(3); + TradeOfferList offers = buildTradeOffers(VillagerProfession::Librarian, 1u); + Container wallet = makeWallet(2); + TradeSession session(villager, offers, wallet); + + CHECK(session.available(comparatorOffer)); + CHECK(!session.canAfford(comparatorOffer)); + TradeResult outcome = session.trade(comparatorOffer); + CHECK(!outcome.success); + CHECK(outcome.reason == TradeResult::Reason::MissingInputA); + CHECK_EQ(stockOf(wallet, ItemId::EmeraldGem), 2); + CHECK_EQ(stockOf(wallet, ItemId::Comparator), 0); + CHECK_EQ(offers.at(comparatorOffer).uses, 0); + CHECK_EQ(villager.experience, villagerExperienceForLevel(3)); + + CHECK(session.trade(-1).reason == TradeResult::Reason::NoSuchOffer); + CHECK(session.trade(offers.size()).reason == TradeResult::Reason::NoSuchOffer); + CHECK(!session.available(99)); + CHECK_EQ(session.priceOf(99), 0); +} + +VF_TEST(aDeadOrUnemployedVillagerRefusesToTradeAtAll) { + Villager villager = makeLibrarian(3); + TradeOfferList offers = buildTradeOffers(VillagerProfession::Librarian, 1u); + Container wallet = makeWallet(400); + TradeSession session(villager, offers, wallet); + CHECK(session.trade(comparatorOffer).success); + + villager.alive = false; + TradeResult dead = session.trade(comparatorOffer); + CHECK(!dead.success); + CHECK(dead.reason == TradeResult::Reason::VillagerUnavailable); + + villager.alive = true; + villager.profession = VillagerProfession::Unemployed; + CHECK(session.trade(comparatorOffer).reason == TradeResult::Reason::VillagerUnavailable); + CHECK_EQ(session.tradesCompleted(), 1); +} + +VF_TEST(heroOfTheVillageCutsPricesWhileHittingAVillagerRaisesThem) { + Villager villager = makeLibrarian(3); + TradeOfferList offers = buildTradeOffers(VillagerProfession::Librarian, 1u); + Container wallet = makeWallet(400); + TradeSession session(villager, offers, wallet); + + int listPrice = session.priceOf(comparatorOffer); + CHECK_EQ(listPrice, offers.at(comparatorOffer).basePrice()); + + ReputationView neutral = buildReputation(villager.gossip, {}); + session.setPriceModifier(priceModifierFor(neutral, 1, 0)); + int heroPrice = session.priceOf(comparatorOffer); + CHECK(heroPrice < listPrice); + CHECK_EQ(session.priceModifier().totalPercent(), heroDiscountPercentPerLevel); + + villager.gossip.add(GossipKind::MinorNegative, gossipMinorNegativeGain); + CHECK_EQ(villagerHitCount(villager.gossip), 1); + session.refreshPrices(buildReputation(villager.gossip, {}), 0); + int angryPrice = session.priceOf(comparatorOffer); + CHECK(angryPrice > listPrice); + CHECK(session.priceModifier().totalPercent() < 0); + + session.refreshPrices(buildReputation(villager.gossip, {}), 1); + CHECK(session.priceOf(comparatorOffer) < angryPrice); +} + +VF_TEST(priceNeverDropsBelowOneNoMatterHowLargeTheDiscount) { + TradeOffer offer; + offer.inputA = ItemId::EmeraldGem; + offer.inputACount = 2; + offer.output = ItemId::Bread; + offer.outputCount = 4; + + PriceModifier generous; + generous.heroLevel = 5; + CHECK_EQ(generous.totalPercent(), 90); + CHECK_EQ(offerPrice(offer, generous), 1); + + PriceModifier absurd; + absurd.reputationPercent = 60; + absurd.heroLevel = 5; + CHECK_EQ(absurd.totalPercent(), 90); + CHECK_EQ(offerPrice(offer, absurd), 1); + + PriceModifier hostile; + hostile.hitCount = 8; + CHECK_EQ(hostile.totalPercent(), -160); + CHECK_EQ(offerPrice(offer, hostile), 5); + + offer.demand = 40; + CHECK(offerPrice(offer, PriceModifier{}) > 2); + CHECK_EQ(offerPrice(offer, generous), 1); +} + +VF_TEST(aCompletedTradeMovesBothSidesAndFeedsTheVillagerExperience) { + Villager villager = makeLibrarian(1); + villager.experience = 0; + TradeOfferList offers = buildTradeOffers(VillagerProfession::Librarian, 1u); + Container wallet = makeWallet(40); + TradeSession session(villager, offers, wallet); +