aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormat <git@matdoes.dev>2025-08-18 16:42:48 -0545
committermat <git@matdoes.dev>2025-08-18 16:42:48 -0545
commit9c39e05d37926de13588777ecd84f34d2f6e0a1d (patch)
treeae6427df80f5e96a1e9f7370de30d398d917c5fa
parentc2ada754f9888fe791a5f604534c55cc6da71590 (diff)
downloadazalea-drasl-9c39e05d37926de13588777ecd84f34d2f6e0a1d.tar.xz
fix ClientboundMerchantOffers
-rw-r--r--azalea-client/src/plugins/interact/mod.rs37
-rw-r--r--azalea-inventory/src/components.rs10
-rw-r--r--azalea-inventory/src/slot.rs11
-rw-r--r--azalea-protocol/src/packets/game/c_merchant_offers.rs117
-rw-r--r--azalea/src/pathfinder/goto_event.rs5
5 files changed, 155 insertions, 25 deletions
diff --git a/azalea-client/src/plugins/interact/mod.rs b/azalea-client/src/plugins/interact/mod.rs
index 0275ca97..547d58ff 100644
--- a/azalea-client/src/plugins/interact/mod.rs
+++ b/azalea-client/src/plugins/interact/mod.rs
@@ -11,14 +11,14 @@ use azalea_core::{
tick::GameTick,
};
use azalea_entity::{
- Attributes, Crouching, LocalEntity, LookDirection, PlayerAbilities,
+ Attributes, LocalEntity, LookDirection, PlayerAbilities,
attributes::{
creative_block_interaction_range_modifier, creative_entity_interaction_range_modifier,
},
clamp_look_direction,
};
use azalea_inventory::{ItemStack, ItemStackData, components};
-use azalea_physics::PhysicsSet;
+use azalea_physics::{PhysicsSet, local_player::PhysicsState};
use azalea_protocol::packets::game::{
ServerboundInteract, ServerboundUseItem,
s_interact::{self, InteractionHand},
@@ -250,7 +250,7 @@ pub fn handle_start_use_item_queued(
&mut BlockStatePredictionHandler,
&HitResultComponent,
&LookDirection,
- &Crouching,
+ &PhysicsState,
Option<&Mining>,
)>,
entity_id_query: Query<&MinecraftEntityId>,
@@ -261,7 +261,7 @@ pub fn handle_start_use_item_queued(
mut prediction_handler,
hit_result,
look_direction,
- crouching,
+ physics_state,
mining,
) in query
{
@@ -332,17 +332,26 @@ pub fn handle_start_use_item_queued(
continue;
};
- commands.trigger(SendPacketEvent::new(
- entity,
- ServerboundInteract {
- entity_id,
- action: s_interact::ActionType::InteractAt {
- location: r.location,
- hand: InteractionHand::MainHand,
- },
- using_secondary_action: **crouching,
+ let mut interact = ServerboundInteract {
+ entity_id,
+ action: s_interact::ActionType::InteractAt {
+ location: r.location,
+ hand: InteractionHand::MainHand,
},
- ));
+ using_secondary_action: physics_state.trying_to_crouch,
+ };
+ commands.trigger(SendPacketEvent::new(entity, interact.clone()));
+ // TODO: this is true if the interaction failed, which i think can only happen
+ // in certain cases when interacting with armor stands
+ let consumes_action = false;
+ if !consumes_action {
+ // but yes, most of the time vanilla really does send two interact packets like
+ // this
+ interact.action = s_interact::ActionType::Interact {
+ hand: InteractionHand::MainHand,
+ };
+ commands.trigger(SendPacketEvent::new(entity, interact));
+ }
}
}
}
diff --git a/azalea-inventory/src/components.rs b/azalea-inventory/src/components.rs
index 3da393e5..f63ca1ad 100644
--- a/azalea-inventory/src/components.rs
+++ b/azalea-inventory/src/components.rs
@@ -130,11 +130,15 @@ macro_rules! define_data_components {
pub unsafe fn azalea_write_as(
&self,
kind: registry::DataComponentKind,
- buf: &mut Vec<u8>,
+ buf: &mut impl std::io::Write,
) -> io::Result<()> {
+ let mut value = Vec::new();
match kind {
- $( DataComponentKind::$x => unsafe { self.$x.encode(buf) }, )*
- }
+ $( DataComponentKind::$x => unsafe { self.$x.encode(&mut value)? }, )*
+ };
+ buf.write_all(&value)?;
+
+ Ok(())
}
/// # Safety
///
diff --git a/azalea-inventory/src/slot.rs b/azalea-inventory/src/slot.rs
index e9da4856..2d4202e8 100644
--- a/azalea-inventory/src/slot.rs
+++ b/azalea-inventory/src/slot.rs
@@ -349,6 +349,17 @@ impl DataComponentPatch {
)
})
}
+ /// Insert a new component into this patch, or mark a component as removed.
+ ///
+ /// # Safety
+ /// The [`DataComponentUnion`] must be of the correct kind.
+ pub unsafe fn unchecked_insert_component(
+ &mut self,
+ kind: DataComponentKind,
+ value: Option<DataComponentUnion>,
+ ) {
+ self.components.insert(kind, value);
+ }
}
impl Drop for DataComponentPatch {
diff --git a/azalea-protocol/src/packets/game/c_merchant_offers.rs b/azalea-protocol/src/packets/game/c_merchant_offers.rs
index f50f9932..8d4ba9b1 100644
--- a/azalea-protocol/src/packets/game/c_merchant_offers.rs
+++ b/azalea-protocol/src/packets/game/c_merchant_offers.rs
@@ -1,6 +1,16 @@
-use azalea_buf::AzBuf;
-use azalea_inventory::ItemStack;
+use std::{
+ any::Any,
+ fmt::{self, Debug},
+ io::{self, Cursor, Write},
+};
+
+use azalea_buf::{AzBuf, AzaleaRead, AzaleaWrite, BufReadError};
+use azalea_inventory::{
+ DataComponentPatch, ItemStack, ItemStackData,
+ components::{self, DataComponentUnion},
+};
use azalea_protocol_macros::ClientboundGamePacket;
+use azalea_registry::{DataComponentKind, Item};
#[derive(Clone, Debug, AzBuf, ClientboundGamePacket)]
pub struct ClientboundMerchantOffers {
@@ -17,14 +27,105 @@ pub struct ClientboundMerchantOffers {
#[derive(Clone, Debug, AzBuf)]
pub struct MerchantOffer {
- pub base_cost_a: ItemStack,
+ pub base_cost_a: ItemCost,
pub result: ItemStack,
- pub cost_b: ItemStack,
+ pub cost_b: Option<ItemCost>,
pub out_of_stock: bool,
- pub uses: u32,
- pub max_uses: u32,
- pub xp: u32,
+ pub uses: i32,
+ pub max_uses: i32,
+ pub xp: i32,
pub special_price_diff: i32,
pub price_multiplier: f32,
- pub demand: u32,
+ pub demand: i32,
+}
+
+/// An item that a merchant can buy.
+///
+/// This can be converted into an [`ItemStackData`] with
+/// [`Self::into_item_stack`].
+#[derive(Clone, Debug, AzBuf)]
+pub struct ItemCost {
+ pub item: Item,
+ #[var]
+ pub count: i32,
+ pub components: DataComponentExactPredicate,
+}
+impl ItemCost {
+ pub fn into_item_stack(self) -> ItemStackData {
+ let mut component_patch = DataComponentPatch::default();
+ for component in self.components.expected {
+ unsafe {
+ component_patch.unchecked_insert_component(component.kind, Some(component.value));
+ }
+ }
+ // TODO: add a fast way to iterate over default components, and insert the ones
+ // that aren't present as None
+
+ ItemStackData {
+ kind: self.item,
+ count: self.count,
+ component_patch,
+ }
+ }
+}
+
+/// Similar to [`DataComponentPatch`], but it's only additive, meaning that
+/// there are no `None` values.
+///
+/// If you got this from [`ItemCost`], consider using
+/// [`ItemCost::into_item_stack`] for a better API instead.
+#[derive(Clone, Debug, AzBuf)]
+pub struct DataComponentExactPredicate {
+ pub expected: Vec<TypedDataComponent>,
+}
+
+pub struct TypedDataComponent {
+ kind: DataComponentKind,
+ value: DataComponentUnion,
+}
+impl TypedDataComponent {
+ pub fn kind(&self) -> &DataComponentKind {
+ &self.kind
+ }
+ pub fn value(&self) -> &DataComponentUnion {
+ &self.value
+ }
+ pub fn as_dyn(&self) -> &dyn components::EncodableDataComponent {
+ // SAFETY: the kind is correct because we got it from azalea_read_as, and the
+ // kind isn't mutable
+ unsafe { self.value.as_kind(self.kind) }
+ }
+ pub fn get<T: components::DataComponentTrait>(&self) -> Option<&T> {
+ let component = self.as_dyn();
+ let component_any = component as &dyn Any;
+ component_any.downcast_ref::<T>()
+ }
+}
+impl AzaleaRead for TypedDataComponent {
+ fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
+ let kind = DataComponentKind::azalea_read(buf)?;
+ let value = DataComponentUnion::azalea_read_as(kind, buf)?;
+ Ok(Self { kind, value })
+ }
+}
+impl AzaleaWrite for TypedDataComponent {
+ fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
+ self.kind.azalea_write(buf)?;
+ unsafe { self.value.azalea_write_as(self.kind, buf) }
+ }
+}
+impl Debug for TypedDataComponent {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("TypedDataComponent")
+ .field("kind", &self.kind)
+ .finish()
+ }
+}
+impl Clone for TypedDataComponent {
+ fn clone(&self) -> Self {
+ Self {
+ kind: self.kind,
+ value: unsafe { self.value.clone_as(self.kind) },
+ }
+ }
}
diff --git a/azalea/src/pathfinder/goto_event.rs b/azalea/src/pathfinder/goto_event.rs
index 8a4f78d0..bd0e1540 100644
--- a/azalea/src/pathfinder/goto_event.rs
+++ b/azalea/src/pathfinder/goto_event.rs
@@ -115,3 +115,8 @@ impl PathfinderOpts {
self
}
}
+impl Default for PathfinderOpts {
+ fn default() -> Self {
+ Self::new()
+ }
+}