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, PartialEq, Schema)]
197#[data(view)]
198pub struct SlotStack {
199 pub slot: u32,
200 pub stack: Stack,
201}
202
203#[derive(Debug, Clone, PartialEq, Schema)]
206#[data(view)]
207pub struct ScreenSlot {
208 pub stack: Option<Stack>,
209 pub x: i32,
210 pub y: i32,
211}
212
213#[derive(Debug, Clone, Copy, PartialEq, Default, Schema)]
215#[data(view)]
216pub struct Rotation {
217 pub yaw: f32,
218 pub pitch: f32,
219}
220
221#[derive(Debug, Clone, PartialEq, Schema)]
223#[data(view)]
224pub struct PlacedBlock {
225 pub pos: BlockPos,
226 pub state: Atom,
228}
229
230impl PlacedBlock {
231 pub fn block_id(&self) -> &str {
233 crate::split_state(&self.state).0
234 }
235}
236
237#[cfg(test)]
238mod tests {
239 use super::*;
240
241 fn round_trip<T: Schema>(v: &T) -> T {
242 yog_data::read_bytes(&yog_data::binary::encode(&v.write())).unwrap()
243 }
244
245 #[test]
246 fn the_faces_go_by_the_games_direction_names() {
247 let names: Vec<String> = Face::ALL
248 .iter()
249 .map(|f| f.write().as_str().unwrap_or_default().to_string())
250 .collect();
251 assert_eq!(names, yog_ids::direction::ALL_VALUES);
252 }
253
254 #[test]
255 fn records_round_trip_as_data() {
256 let e = EntityInfo {
257 type_id: "minecraft:zombie".into(),
258 dimension: "minecraft:overworld".into(),
259 pos: Point {
260 x: 1.5,
261 y: 64.0,
262 z: -2.0,
263 },
264 eye_height: 1.74,
265 width: 0.6,
266 height: 1.95,
267 flags: entity_flags::LIVING | entity_flags::MONSTER,
268 name: "Zom\tbie".into(),
269 };
270 assert!(e.has(entity_flags::LIVING) && !e.has(entity_flags::PLAYER));
271 assert_eq!(e.eye_pos().y, 65.74);
272 assert_eq!(round_trip(&e), e);
273 let h = BlockHit {
274 pos: BlockPos::new(1, 2, 3),
275 face: Face::East,
276 hit: Point {
277 x: 1.0,
278 y: 2.5,
279 z: 3.5,
280 },
281 };
282 assert_eq!(round_trip(&h), h);
283 assert_eq!(h.face.normal(), (1, 0, 0));
284 }
285
286 #[test]
287 fn faces_are_named_as_the_game_names_them() {
288 let names: Vec<String> = Face::ALL
289 .iter()
290 .map(|f| yog_data::text::to_string(&f.write()))
291 .collect();
292 assert_eq!(
293 names,
294 [
295 "\"down\"",
296 "\"up\"",
297 "\"north\"",
298 "\"south\"",
299 "\"west\"",
300 "\"east\""
301 ]
302 );
303 }
304}