diff --git a/crates/horizon-client/src/game/advancements.rs b/crates/horizon-client/src/game/advancements.rs index 0f76d0ca9..f7bafd767 100644 --- a/crates/horizon-client/src/game/advancements.rs +++ b/crates/horizon-client/src/game/advancements.rs @@ -708,6 +708,27 @@ impl Game<'_> { let it = item_view(stack); self.trigger(name, |c, w| adv::item_entity(c, &me, target, &it, w)); } + /// `TradeTrigger`: the villager and the item the trade gave. + pub(super) fn trigger_trade(&mut self, villager: &EntityView, stack: &ItemStack) { + let me = self.player_entity(); + let villager = villager.clone(); + let it = item_view(stack); + self.trigger("minecraft:villager_trade", move |c, w| { + let pctx = adv::Entities { + this: Some(&me), + origin: me.position, + tool: None, + block_pos: None, + }; + let vctx = adv::Entities { + this: Some(&villager), + ..pctx + }; + adv::conditions(&c["player"], &pctx, w) + && adv::conditions(&c["villager"], &vctx, w) + && adv::item(&c["item"], &it, w) + }); + } /// `EnterBlockTrigger`: the block, its state properties and the player. pub(super) fn trigger_enter_block(&mut self, state: horizon_world::registry::BlockStateId) { let Some(definition) = self.pack.assets.registry.state(state).cloned() else { diff --git a/crates/horizon-client/src/game/lan_trading.rs b/crates/horizon-client/src/game/lan_trading.rs index 7beeeba2f..0177a24b1 100644 --- a/crates/horizon-client/src/game/lan_trading.rs +++ b/crates/horizon-client/src/game/lan_trading.rs @@ -277,7 +277,12 @@ impl Game<'_> { g.carried = menu.carried.take(); } self.register_exploration_maps(made); - gm.merchant.session = self.merchant_apply(session, gm.merchant.mob); + let (session, traded) = self.merchant_apply(session, gm.merchant.mob); + gm.merchant.session = session; + if !traded.is_empty() { + // The guest's own statistic and advancements. + self.as_guest(host, conn, |g| g.merchant_traded(gm.merchant.mob, &traded)); + } self.drop_guest_items(host, gi, dropped); self.lan_send_offers(host, conn, &mut gm, false); self.lan_send_merchant_content(host, conn, &mut gm); diff --git a/crates/horizon-client/src/game/trading.rs b/crates/horizon-client/src/game/trading.rs index 6986d8baa..cf03000de 100644 --- a/crates/horizon-client/src/game/trading.rs +++ b/crates/horizon-client/src/game/trading.rs @@ -398,18 +398,21 @@ impl Game<'_> { .as_ref() .and_then(|s| s.merchant.as_ref()) .and_then(|m| m.mob); - let session = self.merchant_apply(session, mob); + let (session, traded) = self.merchant_apply(session, mob); + self.merchant_traded(mob, &traded); if let Some(m) = self.station.as_mut().and_then(|s| s.merchant.as_mut()) { m.session = session; } } - /// What a menu click's session did, applied to the world and its mob. + /// What a menu click's session did, applied to the world and its mob; + /// with the result of each trade made, for `merchant_traded`. pub(super) fn merchant_apply( &mut self, session: Box, mob: Option, - ) -> Box { + ) -> (Box, Vec) { let mut session = session; + let mut traded = Vec::new(); let events = std::mem::take(&mut session.events); let (villager, origin) = (session.villager, session.origin); if let Some(m) = mob.and_then(|id| self.mobs.iter_mut().find(|m| m.id == id)) { @@ -438,11 +441,26 @@ impl Game<'_> { }; self.audio.play(sound, Some(origin), 1.0, pitch); } - merchant::Event::Traded(_) => self.stats.custom("traded_with_villager", 1), + merchant::Event::Traded(index) => { + traded.extend(session.offers.get(index).map(|o| o.result.clone())); + } merchant::Event::TradeSound => {} } } - session + (session, traded) + } + /// `MerchantResultSlot.checkTakeAchievements` and `AbstractVillager.notifyTrade` + /// for each trade made: the statistic and the `villager_trade` trigger. + pub(super) fn merchant_traded(&mut self, mob: Option, results: &[ItemStack]) { + for result in results { + self.stats.custom("traded_with_villager", 1); + let villager = mob + .and_then(|id| self.mobs.iter().find(|m| m.id == id)) + .map(|m| self.mob_entity(m)); + if let Some(villager) = villager { + self.trigger_trade(&villager, result); + } + } } /// `Villager.customServerAiStep`: the TRADE reputation event of the last /// trade, a tick later; the prices follow the new reputation.