use crate::components::BlockPosition;
use crate::container::{ContainerBacking, InventoryType};
use crate::world::block_entity::BlockEntity;
use basalt_types::Slot;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CloseReason {
Manual,
Disconnect,
ForceClose,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WindowSlotKind {
CraftOutput,
CraftGrid,
Armor,
MainInventory,
Hotbar,
Offhand,
Container,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DragType {
LeftDrag,
RightDrag,
MiddleDrag,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContainerClickType {
LeftClick,
RightClick,
ShiftClick,
DoubleClick,
DropSlot {
drop_all: bool,
},
HotbarSwap {
hotbar: u8,
},
OffhandSwap,
}
pub use crate::world::block_entity::BlockEntityKind;
#[derive(Debug, Clone)]
pub struct ContainerOpenRequestEvent {
pub inventory_type: InventoryType,
pub backing: ContainerBacking,
pub title: String,
pub cancelled: bool,
}
crate::game_cancellable_event!(ContainerOpenRequestEvent);
#[derive(Debug, Clone)]
pub struct ContainerOpenedEvent {
pub window_id: u8,
pub inventory_type: InventoryType,
pub backing: ContainerBacking,
pub viewer_count: u32,
}
crate::game_event!(ContainerOpenedEvent);
#[derive(Debug, Clone)]
pub struct ContainerClosedEvent {
pub window_id: u8,
pub inventory_type: InventoryType,
pub backing: ContainerBacking,
pub reason: CloseReason,
pub viewer_count: u32,
pub crafting_grid_state: Option<[Slot; 9]>,
}
crate::game_event!(ContainerClosedEvent);
#[derive(Debug, Clone)]
pub struct ContainerClickEvent {
pub window_id: u8,
pub backing: ContainerBacking,
pub slot_index: i16,
pub window_slot_kind: WindowSlotKind,
pub click_type: ContainerClickType,
pub cursor_before: Slot,
pub cancelled: bool,
}
crate::game_cancellable_event!(ContainerClickEvent);
#[derive(Debug, Clone)]
pub struct ContainerDragEvent {
pub window_id: u8,
pub backing: ContainerBacking,
pub affected_slots: Vec<(i16, Slot)>,
pub drag_type: DragType,
pub cursor: Slot,
pub cancelled: bool,
}
crate::game_cancellable_event!(ContainerDragEvent);
#[derive(Debug, Clone)]
pub struct ContainerSlotChangedEvent {
pub window_id: u8,
pub backing: ContainerBacking,
pub slot_index: i16,
pub old: Slot,
pub new: Slot,
}
crate::game_event!(ContainerSlotChangedEvent);
#[derive(Debug, Clone)]
pub struct BlockEntityCreatedEvent {
pub position: BlockPosition,
pub kind: BlockEntityKind,
}
crate::game_event!(BlockEntityCreatedEvent);
#[derive(Debug, Clone)]
pub struct BlockEntityModifiedEvent {
pub position: BlockPosition,
pub kind: BlockEntityKind,
}
crate::game_event!(BlockEntityModifiedEvent);
#[derive(Debug, Clone)]
pub struct BlockEntityDestroyedEvent {
pub position: BlockPosition,
pub kind: BlockEntityKind,
pub last_state: BlockEntity,
}
crate::game_event!(BlockEntityDestroyedEvent);
#[cfg(test)]
mod tests {
use crate::events::{BusKind, Event, EventRouting};
use super::*;
#[test]
fn close_reason_variants() {
assert_eq!(CloseReason::Manual, CloseReason::Manual);
assert_eq!(CloseReason::Disconnect, CloseReason::Disconnect);
assert_eq!(CloseReason::ForceClose, CloseReason::ForceClose);
assert_ne!(CloseReason::Manual, CloseReason::Disconnect);
}
#[test]
fn window_slot_kind_variants() {
let variants = [
WindowSlotKind::CraftOutput,
WindowSlotKind::CraftGrid,
WindowSlotKind::Armor,
WindowSlotKind::MainInventory,
WindowSlotKind::Hotbar,
WindowSlotKind::Offhand,
WindowSlotKind::Container,
];
for (i, a) in variants.iter().enumerate() {
assert_eq!(a, a, "variant should equal itself");
for b in variants.iter().skip(i + 1) {
assert_ne!(a, b, "distinct variants should differ");
}
}
}
#[test]
fn drag_type_variants() {
assert_eq!(DragType::LeftDrag, DragType::LeftDrag);
assert_eq!(DragType::RightDrag, DragType::RightDrag);
assert_eq!(DragType::MiddleDrag, DragType::MiddleDrag);
assert_ne!(DragType::LeftDrag, DragType::RightDrag);
}
#[test]
fn container_click_type_variants() {
assert_eq!(ContainerClickType::LeftClick, ContainerClickType::LeftClick);
assert_eq!(
ContainerClickType::RightClick,
ContainerClickType::RightClick
);
assert_eq!(
ContainerClickType::ShiftClick,
ContainerClickType::ShiftClick
);
assert_eq!(
ContainerClickType::DoubleClick,
ContainerClickType::DoubleClick
);
assert_eq!(
ContainerClickType::DropSlot { drop_all: true },
ContainerClickType::DropSlot { drop_all: true }
);
assert_ne!(
ContainerClickType::DropSlot { drop_all: false },
ContainerClickType::DropSlot { drop_all: true }
);
assert_eq!(
ContainerClickType::HotbarSwap { hotbar: 3 },
ContainerClickType::HotbarSwap { hotbar: 3 }
);
assert_ne!(
ContainerClickType::HotbarSwap { hotbar: 0 },
ContainerClickType::HotbarSwap { hotbar: 1 }
);
assert_eq!(
ContainerClickType::OffhandSwap,
ContainerClickType::OffhandSwap
);
}
#[test]
fn block_entity_kind_variants() {
assert_eq!(BlockEntityKind::Chest, BlockEntityKind::Chest);
}
#[test]
fn container_open_request_cancellation() {
let mut event = ContainerOpenRequestEvent {
inventory_type: InventoryType::Generic9x3,
backing: ContainerBacking::Virtual,
title: "Test".to_string(),
cancelled: false,
};
assert!(!event.is_cancelled());
event.cancel();
assert!(event.is_cancelled());
}
#[test]
fn container_opened_construction() {
let event = ContainerOpenedEvent {
window_id: 1,
inventory_type: InventoryType::Generic9x3,
backing: ContainerBacking::Block {
position: BlockPosition { x: 5, y: 64, z: 3 },
},
viewer_count: 1,
};
assert_eq!(event.window_id, 1);
assert_eq!(event.inventory_type, InventoryType::Generic9x3);
assert_eq!(event.viewer_count, 1);
}
#[test]
fn container_opened_not_cancellable() {
let mut event = ContainerOpenedEvent {
window_id: 1,
inventory_type: InventoryType::Generic9x3,
backing: ContainerBacking::Virtual,
viewer_count: 0,
};
event.cancel(); assert!(!event.is_cancelled());
}
#[test]
fn container_closed_construction() {
let event = ContainerClosedEvent {
window_id: 2,
inventory_type: InventoryType::Hopper,
backing: ContainerBacking::Block {
position: BlockPosition {
x: 10,
y: 32,
z: -5,
},
},
reason: CloseReason::Manual,
viewer_count: 0,
crafting_grid_state: None,
};
assert_eq!(event.window_id, 2);
assert_eq!(event.reason, CloseReason::Manual);
}
#[test]
fn container_closed_not_cancellable() {
let mut event = ContainerClosedEvent {
window_id: 1,
inventory_type: InventoryType::Generic9x3,
backing: ContainerBacking::Virtual,
reason: CloseReason::Disconnect,
viewer_count: 0,
crafting_grid_state: None,
};
event.cancel();
assert!(!event.is_cancelled());
}
#[test]
fn container_click_cancellation() {
let mut event = ContainerClickEvent {
window_id: 1,
backing: ContainerBacking::Virtual,
slot_index: 5,
window_slot_kind: WindowSlotKind::Container,
click_type: ContainerClickType::LeftClick,
cursor_before: Slot::empty(),
cancelled: false,
};
assert!(!event.is_cancelled());
event.cancel();
assert!(event.is_cancelled());
}
#[test]
fn container_click_field_access() {
let event = ContainerClickEvent {
window_id: 3,
backing: ContainerBacking::Block {
position: BlockPosition { x: 0, y: 64, z: 0 },
},
slot_index: 12,
window_slot_kind: WindowSlotKind::Hotbar,
click_type: ContainerClickType::HotbarSwap { hotbar: 2 },
cursor_before: Slot::new(1, 32),
cancelled: false,
};
assert_eq!(event.slot_index, 12);
assert_eq!(event.window_slot_kind, WindowSlotKind::Hotbar);
assert_eq!(
event.click_type,
ContainerClickType::HotbarSwap { hotbar: 2 }
);
}
#[test]
fn container_drag_cancellation() {
let mut event = ContainerDragEvent {
window_id: 1,
backing: ContainerBacking::Virtual,
affected_slots: vec![(0, Slot::new(1, 16)), (1, Slot::new(1, 16))],
drag_type: DragType::LeftDrag,
cursor: Slot::new(1, 32),
cancelled: false,
};
assert!(!event.is_cancelled());
event.cancel();
assert!(event.is_cancelled());
}
#[test]
fn container_drag_field_access() {
let event = ContainerDragEvent {
window_id: 2,
backing: ContainerBacking::Virtual,
affected_slots: vec![(5, Slot::new(3, 1))],
drag_type: DragType::RightDrag,
cursor: Slot::new(3, 5),
cancelled: false,
};
assert_eq!(event.affected_slots.len(), 1);
assert_eq!(event.drag_type, DragType::RightDrag);
}
#[test]
fn container_slot_changed_construction() {
let event = ContainerSlotChangedEvent {
window_id: 1,
backing: ContainerBacking::Block {
position: BlockPosition { x: 5, y: 64, z: 3 },
},
slot_index: 7,
old: Slot::empty(),
new: Slot::new(1, 1),
};
assert_eq!(event.slot_index, 7);
}
#[test]
fn container_slot_changed_not_cancellable() {
let mut event = ContainerSlotChangedEvent {
window_id: 1,
backing: ContainerBacking::Virtual,
slot_index: 0,
old: Slot::empty(),
new: Slot::empty(),
};
event.cancel();
assert!(!event.is_cancelled());
}
#[test]
fn block_entity_created_construction() {
let event = BlockEntityCreatedEvent {
position: BlockPosition { x: 5, y: 64, z: 3 },
kind: BlockEntityKind::Chest,
};
assert_eq!(event.position, BlockPosition { x: 5, y: 64, z: 3 });
assert_eq!(event.kind, BlockEntityKind::Chest);
}
#[test]
fn block_entity_created_not_cancellable() {
let mut event = BlockEntityCreatedEvent {
position: BlockPosition { x: 0, y: 0, z: 0 },
kind: BlockEntityKind::Chest,
};
event.cancel();
assert!(!event.is_cancelled());
}
#[test]
fn block_entity_modified_construction() {
let event = BlockEntityModifiedEvent {
position: BlockPosition {
x: -10,
y: 32,
z: 100,
},
kind: BlockEntityKind::Chest,
};
assert_eq!(event.kind, BlockEntityKind::Chest);
}
#[test]
fn block_entity_modified_not_cancellable() {
let mut event = BlockEntityModifiedEvent {
position: BlockPosition { x: 0, y: 0, z: 0 },
kind: BlockEntityKind::Chest,
};
event.cancel();
assert!(!event.is_cancelled());
}
#[test]
fn block_entity_destroyed_carries_last_state() {
let be = BlockEntity::empty_chest();
let event = BlockEntityDestroyedEvent {
position: BlockPosition {
x: 100,
y: 64,
z: -50,
},
kind: BlockEntityKind::Chest,
last_state: be,
};
match &event.last_state {
BlockEntity::Chest { slots } => {
assert_eq!(slots.len(), 27);
}
}
}
#[test]
fn block_entity_destroyed_not_cancellable() {
let mut event = BlockEntityDestroyedEvent {
position: BlockPosition { x: 0, y: 0, z: 0 },
kind: BlockEntityKind::Chest,
last_state: BlockEntity::empty_chest(),
};
event.cancel();
assert!(!event.is_cancelled());
}
#[test]
fn all_events_route_to_game_bus() {
assert_eq!(ContainerOpenRequestEvent::BUS, BusKind::Game);
assert_eq!(ContainerOpenedEvent::BUS, BusKind::Game);
assert_eq!(ContainerClosedEvent::BUS, BusKind::Game);
assert_eq!(ContainerClickEvent::BUS, BusKind::Game);
assert_eq!(ContainerDragEvent::BUS, BusKind::Game);
assert_eq!(ContainerSlotChangedEvent::BUS, BusKind::Game);
assert_eq!(BlockEntityCreatedEvent::BUS, BusKind::Game);
assert_eq!(BlockEntityModifiedEvent::BUS, BusKind::Game);
assert_eq!(BlockEntityDestroyedEvent::BUS, BusKind::Game);
}
#[test]
fn bus_kind_method_matches_const() {
let events_game: Vec<Box<dyn Event>> = vec![
Box::new(ContainerOpenRequestEvent {
inventory_type: InventoryType::Generic9x3,
backing: ContainerBacking::Virtual,
title: String::new(),
cancelled: false,
}),
Box::new(ContainerOpenedEvent {
window_id: 1,
inventory_type: InventoryType::Generic9x3,
backing: ContainerBacking::Virtual,
viewer_count: 0,
}),
Box::new(ContainerClosedEvent {
window_id: 1,
inventory_type: InventoryType::Generic9x3,
backing: ContainerBacking::Virtual,
reason: CloseReason::Manual,
viewer_count: 0,
crafting_grid_state: None,
}),
Box::new(ContainerClickEvent {
window_id: 1,
backing: ContainerBacking::Virtual,
slot_index: 0,
window_slot_kind: WindowSlotKind::Container,
click_type: ContainerClickType::LeftClick,
cursor_before: Slot::empty(),
cancelled: false,
}),
Box::new(ContainerDragEvent {
window_id: 1,
backing: ContainerBacking::Virtual,
affected_slots: vec![],
drag_type: DragType::LeftDrag,
cursor: Slot::empty(),
cancelled: false,
}),
Box::new(ContainerSlotChangedEvent {
window_id: 1,
backing: ContainerBacking::Virtual,
slot_index: 0,
old: Slot::empty(),
new: Slot::empty(),
}),
Box::new(BlockEntityCreatedEvent {
position: BlockPosition { x: 0, y: 0, z: 0 },
kind: BlockEntityKind::Chest,
}),
Box::new(BlockEntityModifiedEvent {
position: BlockPosition { x: 0, y: 0, z: 0 },
kind: BlockEntityKind::Chest,
}),
Box::new(BlockEntityDestroyedEvent {
position: BlockPosition { x: 0, y: 0, z: 0 },
kind: BlockEntityKind::Chest,
last_state: BlockEntity::empty_chest(),
}),
];
for event in &events_game {
assert_eq!(event.bus_kind(), BusKind::Game);
}
}
}