use basalt_mc_protocol::packets::play::types::{RecipeDisplay, SlotDisplay};
use basalt_types::{Decode, Encode, EncodedSize, Slot};
fn roundtrip<T>(value: &T) -> Vec<u8>
where
T: Encode + Decode + EncodedSize + std::fmt::Debug + PartialEq,
{
let mut buf = Vec::new();
value.encode(&mut buf).expect("encode");
assert_eq!(
buf.len(),
value.encoded_size(),
"encoded_size disagrees with encode output length"
);
let mut cursor: &[u8] = &buf;
let decoded = T::decode(&mut cursor).expect("decode");
assert!(
cursor.is_empty(),
"decode left {} unread bytes",
cursor.len()
);
assert_eq!(&decoded, value, "encode→decode round trip mismatch");
buf
}
#[test]
fn slot_display_empty_writes_only_tag() {
assert_eq!(roundtrip(&SlotDisplay::Empty), vec![0x00]);
}
#[test]
fn slot_display_any_fuel() {
assert_eq!(roundtrip(&SlotDisplay::AnyFuel), vec![0x01]);
}
#[test]
fn slot_display_item_writes_tag_plus_varint() {
let bytes = roundtrip(&SlotDisplay::Item { data: 879 });
assert_eq!(bytes, vec![0x02, 0xef, 0x06]);
}
#[test]
fn slot_display_tag_writes_string() {
let bytes = roundtrip(&SlotDisplay::Tag {
data: "minecraft:logs".into(),
});
let mut expected = vec![0x04, 14];
expected.extend_from_slice(b"minecraft:logs");
assert_eq!(bytes, expected);
}
#[test]
fn slot_display_smithing_trim_three_nested() {
let st = SlotDisplay::SmithingTrim {
base: Box::new(SlotDisplay::Item { data: 10 }),
material: Box::new(SlotDisplay::Item { data: 20 }),
pattern: Box::new(SlotDisplay::Item { data: 30 }),
};
let bytes = roundtrip(&st);
assert_eq!(bytes, vec![0x05, 0x02, 0x0a, 0x02, 0x14, 0x02, 0x1e]);
}
#[test]
fn slot_display_with_remainder_recursive() {
let inner = SlotDisplay::WithRemainder {
input: Box::new(SlotDisplay::Item { data: 1 }),
remainder: Box::new(SlotDisplay::Empty),
};
let outer = SlotDisplay::WithRemainder {
input: Box::new(SlotDisplay::Item { data: 2 }),
remainder: Box::new(inner),
};
let bytes = roundtrip(&outer);
assert_eq!(bytes, vec![0x06, 0x02, 0x02, 0x06, 0x02, 0x01, 0x00]);
}
#[test]
fn slot_display_composite_count_prefixed_vec() {
let composite = SlotDisplay::Composite {
data: vec![
SlotDisplay::Item { data: 1 },
SlotDisplay::Item { data: 2 },
SlotDisplay::Empty,
],
};
let bytes = roundtrip(&composite);
assert_eq!(bytes, vec![0x07, 3, 0x02, 0x01, 0x02, 0x02, 0x00]);
}
#[test]
fn recipe_display_crafting_shaped_layout() {
let rd = RecipeDisplay::CraftingShaped {
width: 2,
height: 2,
ingredients: vec![
SlotDisplay::Item { data: 1 },
SlotDisplay::Item { data: 1 },
SlotDisplay::Item { data: 1 },
SlotDisplay::Item { data: 1 },
],
result: SlotDisplay::Empty,
crafting_station: SlotDisplay::Empty,
};
let bytes = roundtrip(&rd);
assert_eq!(
bytes,
vec![
0x01, 0x02, 0x02, 0x04, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x02, 0x01, 0x00, 0x00, ]
);
}
#[test]
fn recipe_display_crafting_shapeless_layout() {
let rd = RecipeDisplay::CraftingShapeless {
ingredients: vec![SlotDisplay::Item { data: 5 }],
result: SlotDisplay::Empty,
crafting_station: SlotDisplay::Empty,
};
let bytes = roundtrip(&rd);
assert_eq!(bytes, vec![0x00, 0x01, 0x02, 0x05, 0x00, 0x00]);
}
#[test]
fn recipe_display_furnace_with_duration_and_xp() {
let rd = RecipeDisplay::Furnace {
ingredient: SlotDisplay::Empty,
fuel: SlotDisplay::AnyFuel,
result: SlotDisplay::Empty,
crafting_station: SlotDisplay::Empty,
duration: 200,
experience: 0.5,
};
let bytes = roundtrip(&rd);
let mut expected = vec![0x02, 0x00, 0x01, 0x00, 0x00];
expected.extend_from_slice(&[0xc8, 0x01]); expected.extend_from_slice(&0.5f32.to_be_bytes());
assert_eq!(bytes, expected);
}
#[test]
fn recipe_display_stonecutter_layout() {
let rd = RecipeDisplay::Stonecutter {
ingredient: SlotDisplay::Item { data: 1 },
result: SlotDisplay::Item { data: 2 },
crafting_station: SlotDisplay::Empty,
};
assert_eq!(roundtrip(&rd), vec![0x03, 0x02, 0x01, 0x02, 0x02, 0x00]);
}
#[test]
fn recipe_display_smithing_layout() {
let rd = RecipeDisplay::Smithing {
template: SlotDisplay::Item { data: 1 },
base: SlotDisplay::Item { data: 2 },
addition: SlotDisplay::Item { data: 3 },
result: SlotDisplay::Item { data: 4 },
crafting_station: SlotDisplay::Empty,
};
assert_eq!(
roundtrip(&rd),
vec![0x04, 0x02, 0x01, 0x02, 0x02, 0x02, 0x03, 0x02, 0x04, 0x00]
);
}
#[test]
fn slot_display_item_stack_carries_full_slot() {
let bytes = roundtrip(&SlotDisplay::ItemStack {
data: Slot::new(280, 4),
});
assert_eq!(bytes[0], 0x03);
}
mod crafting_requirements {
use basalt_mc_protocol::packets::play::misc::ClientboundPlayRecipeBookAddEntriesRecipe;
use basalt_mc_protocol::packets::play::types::{RecipeDisplay, SlotDisplay};
use basalt_types::{Decode, Encode, EncodedSize};
fn entry(req: Option<Vec<u8>>) -> ClientboundPlayRecipeBookAddEntriesRecipe {
ClientboundPlayRecipeBookAddEntriesRecipe {
display_id: 7,
display: RecipeDisplay::CraftingShapeless {
ingredients: vec![SlotDisplay::Item { data: 1 }],
result: SlotDisplay::Empty,
crafting_station: SlotDisplay::Empty,
},
group: 0,
category: 3,
crafting_requirements: req,
}
}
#[test]
fn none_writes_single_false_byte() {
let value = entry(None);
let mut buf = Vec::new();
value.encode(&mut buf).expect("encode");
assert_eq!(
buf,
vec![
0x07, 0x00, 0x01, 0x02, 0x01, 0x00,
0x00, 0x00, 0x03, 0x00, ]
);
assert_eq!(buf.len(), value.encoded_size());
let mut cursor: &[u8] = &buf;
let decoded =
ClientboundPlayRecipeBookAddEntriesRecipe::decode(&mut cursor).expect("decode");
assert_eq!(decoded, value);
}
#[test]
fn some_writes_true_then_varint_length_then_bytes() {
let payload = vec![0xaa, 0xbb, 0xcc];
let value = entry(Some(payload.clone()));
let mut buf = Vec::new();
value.encode(&mut buf).expect("encode");
let mut expected = vec![
0x07, 0x00, 0x01, 0x02, 0x01, 0x00, 0x00, 0x00, 0x03, 0x01, 0x03, ];
expected.extend_from_slice(&payload);
assert_eq!(buf, expected);
assert_eq!(buf.len(), value.encoded_size());
let mut cursor: &[u8] = &buf;
let decoded =
ClientboundPlayRecipeBookAddEntriesRecipe::decode(&mut cursor).expect("decode");
assert_eq!(decoded, value);
}
}