1use yog_data::{Atom, Schema};
5
6use crate::{BlockPos, Point};
7
8pub mod entity_flags {
10 pub const LIVING: u32 = 1;
12 pub const PLAYER: u32 = 1 << 1;
14 pub const ANIMAL: u32 = 1 << 2;
16 pub const MONSTER: u32 = 1 << 3;
18 pub const ITEM: u32 = 1 << 4;
20 pub const MOB: u32 = 1 << 5;
22 pub const ARMOR_STAND: u32 = 1 << 6;
24 pub const ALIVE: u32 = 1 << 7;
26 pub const SPECTATOR: u32 = 1 << 8;
28 pub const PART: u32 = 1 << 9;
30 pub const VILLAGER: u32 = 1 << 10;
32 pub const SNEAKING: u32 = 1 << 11;
34 pub const ON_GROUND: u32 = 1 << 12;
36 pub const HORIZONTAL_COLLISION: u32 = 1 << 13;
38 pub const GLIDING: u32 = 1 << 14;
40}
41
42#[derive(Debug, Clone, PartialEq, Schema)]
44#[data(view)]
45pub struct EntityInfo {
46 pub type_id: Atom,
48 pub dimension: Atom,
50 pub pos: Point,
52 pub eye_height: f64,
54 pub width: f64,
56 pub height: f64,
57 pub flags: u32,
59 pub name: String,
61}
62
63impl EntityInfo {
64 pub fn has(&self, flag: u32) -> bool {
65 self.flags & flag != 0
66 }
67
68 pub fn eye_pos(&self) -> Point {
70 Point {
71 y: self.pos.y + self.eye_height,
72 ..self.pos
73 }
74 }
75}
76
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Schema)]
80pub enum Face {
81 Down,
82 Up,
83 North,
84 South,
85 West,
86 East,
87}
88
89impl Face {
90 pub const ALL: [Face; 6] = [
91 Face::Down,
92 Face::Up,
93 Face::North,
94 Face::South,
95 Face::West,
96 Face::East,
97 ];
98
99 pub fn from_index(i: u8) -> Option<Self> {
100 Self::ALL.get(i as usize).copied()
101 }
102
103 pub fn index(self) -> u8 {
104 self as u8
105 }
106
107 pub fn normal(self) -> (i32, i32, i32) {
109 match self {
110 Face::Down => (0, -1, 0),
111 Face::Up => (0, 1, 0),
112 Face::North => (0, 0, -1),
113 Face::South => (0, 0, 1),
114 Face::West => (-1, 0, 0),
115 Face::East => (1, 0, 0),
116 }
117 }
118}
119
120#[derive(Debug, Clone, Copy, PartialEq, Schema)]
122#[data(view)]
123pub struct BlockHit {
124 pub pos: BlockPos,
125 pub face: Face,
127 pub hit: Point,
129}
130
131#[derive(Debug, Clone, PartialEq, Schema)]
133#[data(view)]
134pub struct BlockInfo {
135 pub state: Atom,
137 pub hardness: f32,
139 pub replaceable: bool,
141 pub air: bool,
143 pub fluid: Option<String>,
145 pub fluid_source: bool,
147 pub redstone: u8,
149 pub in_world: bool,
151}
152
153impl BlockInfo {
154 pub fn block_id(&self) -> &str {
156 crate::split_state(&self.state).0
157 }
158}
159
160#[derive(Debug, Clone, PartialEq, Schema)]
162#[data(view)]
163pub struct PlayerInfo {
164 pub uuid: String,
165 pub gamemode: Atom,
167 pub selected_slot: u32,
169 pub xp_level: i32,
170 pub xp_total: i32,
172 pub air: i32,
174 pub may_fly: bool,
176 pub flying: bool,
177}
178
179impl PlayerInfo {
180 pub fn is_creative(&self) -> bool {
181 self.gamemode == "creative"
182 }
183}
184
185#[derive(Debug, Clone, PartialEq, Schema)]
187#[data(view)]
188pub struct Stack {
189 pub item: Atom,
190 pub count: u32,
191 pub snbt: String,
193}
194
195#[derive(Debug, Clone, Copy, PartialEq, Schema)]
199#[data(view)]
200pub struct DigSpeed {
201 pub slot: u32,
202 pub per_tick: f32,
203}
204
205#[derive(Debug, Clone, PartialEq, Schema)]
207#[data(view)]
208pub struct SlotStack {
209 pub slot: u32,
210 pub stack: Stack,
211}
212
213#[derive(Debug, Clone, PartialEq, Schema)]
216#[data(view)]
217pub struct ScreenSlot {
218 pub stack: Option<Stack>,
219 pub x: i32,
220 pub y: i32,
221}
222
223#[derive(Debug, Clone, Copy, PartialEq, Default, Schema)]
225#[data(view)]
226pub struct Rotation {
227 pub yaw: f32,
228 pub pitch: f32,
229}
230
231#[derive(Debug, Clone, PartialEq, Schema)]
233#[data(view)]
234pub struct PlacedBlock {
235 pub pos: BlockPos,
236 pub state: Atom,
238}
239
240impl PlacedBlock {
241 pub fn block_id(&self) -> &str {
243 crate::split_state(&self.state).0
244 }
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250
251 fn round_trip<T: Schema>(v: &T) -> T {
252 yog_data::read_bytes(&yog_data::binary::encode(&v.write())).unwrap()
253 }
254
255 #[test]
256 fn the_faces_go_by_the_games_direction_names() {
257 let names: Vec<String> = Face::ALL
258 .iter()
259 .map(|f| f.write().as_str().unwrap_or_default().to_string())
260 .collect();
261 assert_eq!(names, yog_ids::direction::ALL_VALUES);
262 }
263
264 #[test]
265 fn records_round_trip_as_data() {
266 let e = EntityInfo {
267 type_id: "minecraft:zombie".into(),
268 dimension: "minecraft:overworld".into(),
269 pos: Point {
270 x: 1.5,
271 y: 64.0,
272 z: -2.0,
273 },
274 eye_height: 1.74,
275 width: 0.6,
276 height: 1.95,
277 flags: entity_flags::LIVING | entity_flags::MONSTER,
278 name: "Zom\tbie".into(),
279 };
280 assert!(e.has(entity_flags::LIVING) && !e.has(entity_flags::PLAYER));
281 assert_eq!(e.eye_pos().y, 65.74);
282 assert_eq!(round_trip(&e), e);
283 let h = BlockHit {
284 pos: BlockPos::new(1, 2, 3),
285 face: Face::East,
286 hit: Point {
287 x: 1.0,
288 y: 2.5,
289 z: 3.5,
290 },
291 };
292 assert_eq!(round_trip(&h), h);
293 assert_eq!(h.face.normal(), (1, 0, 0));
294 }
295
296 #[test]
297 fn faces_are_named_as_the_game_names_them() {
298 let names: Vec<String> = Face::ALL
299 .iter()
300 .map(|f| yog_data::text::to_string(&f.write()))
301 .collect();
302 assert_eq!(
303 names,
304 [
305 "\"down\"",
306 "\"up\"",
307 "\"north\"",
308 "\"south\"",
309 "\"west\"",
310 "\"east\""
311 ]
312 );
313 }
314}