Skip to main content

yog_core/
query.rs

1//! What the world queries answer (`yog_event`'s entity, block, player and
2//! world actions): records the host builds and the bus carries as Yog Data.
3
4use yog_data::{Atom, Schema};
5
6use crate::{BlockPos, Point};
7
8/// What kind of entity something is, as a bit set ([`EntityInfo::flags`]).
9pub mod entity_flags {
10    /// A `LivingEntity` (mobs, players, armor stands).
11    pub const LIVING: u32 = 1;
12    /// A player.
13    pub const PLAYER: u32 = 1 << 1;
14    /// An `Animal` or `WaterAnimal`.
15    pub const ANIMAL: u32 = 1 << 2;
16    /// An `Enemy` (hostile mobs).
17    pub const MONSTER: u32 = 1 << 3;
18    /// A dropped item (`ItemEntity`).
19    pub const ITEM: u32 = 1 << 4;
20    /// A `Mob` (has AI).
21    pub const MOB: u32 = 1 << 5;
22    /// An armor stand.
23    pub const ARMOR_STAND: u32 = 1 << 6;
24    /// Alive (not removed, not dead).
25    pub const ALIVE: u32 = 1 << 7;
26    /// A spectating player.
27    pub const SPECTATOR: u32 = 1 << 8;
28    /// A part of a multi-part entity (ender dragon parts).
29    pub const PART: u32 = 1 << 9;
30    /// A villager.
31    pub const VILLAGER: u32 = 1 << 10;
32    /// Sneaking (shift held).
33    pub const SNEAKING: u32 = 1 << 11;
34    /// Standing on the ground.
35    pub const ON_GROUND: u32 = 1 << 12;
36    /// Bumped into something sideways in its last move.
37    pub const HORIZONTAL_COLLISION: u32 = 1 << 13;
38    /// Fall-flying with an elytra.
39    pub const GLIDING: u32 = 1 << 14;
40}
41
42/// A loaded entity (`entity::Info`).
43#[derive(Debug, Clone, PartialEq, Schema)]
44#[data(view)]
45pub struct EntityInfo {
46    /// Registry id of its type, e.g. `"minecraft:zombie"`.
47    pub type_id: Atom,
48    /// Dimension id, e.g. `"minecraft:overworld"`.
49    pub dimension: Atom,
50    /// Feet position.
51    pub pos: Point,
52    /// Eye height above `pos`.
53    pub eye_height: f64,
54    /// Bounding box width and height.
55    pub width: f64,
56    pub height: f64,
57    /// [`entity_flags`] bits.
58    pub flags: u32,
59    /// Display name (custom name, player name, or the type's name).
60    pub name: String,
61}
62
63impl EntityInfo {
64    pub fn has(&self, flag: u32) -> bool {
65        self.flags & flag != 0
66    }
67
68    /// Eye position.
69    pub fn eye_pos(&self) -> Point {
70        Point {
71            y: self.pos.y + self.eye_height,
72            ..self.pos
73        }
74    }
75}
76
77/// The six block faces, in Minecraft's `Direction` order, named as
78/// `yog_ids::direction` names them (a test holds them to it).
79#[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    /// Unit step out of the face.
108    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/// A block a ray stopped at.
121#[derive(Debug, Clone, Copy, PartialEq, Schema)]
122#[data(view)]
123pub struct BlockHit {
124    pub pos: BlockPos,
125    /// The face the ray entered through.
126    pub face: Face,
127    /// The exact hit point (on the block's surface).
128    pub hit: Point,
129}
130
131/// A block's properties (`block::Info`).
132#[derive(Debug, Clone, PartialEq, Schema)]
133#[data(view)]
134pub struct BlockInfo {
135    /// Full block state, e.g. `"minecraft:oak_log[axis=y]"`.
136    pub state: Atom,
137    /// Destroy time; negative for unbreakable blocks (bedrock).
138    pub hardness: f32,
139    /// Placing a block here replaces this one (air, grass, water…).
140    pub replaceable: bool,
141    /// Air of any kind.
142    pub air: bool,
143    /// Id of the fluid in this block (`"minecraft:water"`), if any.
144    pub fluid: Option<String>,
145    /// Whether that fluid is a source block.
146    pub fluid_source: bool,
147    /// Redstone power the block receives (0–15).
148    pub redstone: u8,
149    /// Inside the world's build height and world border.
150    pub in_world: bool,
151}
152
153impl BlockInfo {
154    /// Registry id of the block, without its state properties.
155    pub fn block_id(&self) -> &str {
156        crate::split_state(&self.state).0
157    }
158}
159
160/// An online player's state (`player::Info`).
161#[derive(Debug, Clone, PartialEq, Schema)]
162#[data(view)]
163pub struct PlayerInfo {
164    pub uuid: String,
165    /// `"survival"`, `"creative"`, `"adventure"` or `"spectator"`.
166    pub gamemode: Atom,
167    /// Selected hotbar slot (0–8).
168    pub selected_slot: u32,
169    pub xp_level: i32,
170    /// Total experience points.
171    pub xp_total: i32,
172    /// Air supply in ticks (300 when full).
173    pub air: i32,
174    /// Whether the player may fly (`abilities.mayfly`).
175    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/// A stack of items: what it is, how many, its data (SNBT).
186#[derive(Debug, Clone, PartialEq, Schema)]
187#[data(view)]
188pub struct Stack {
189    pub item: Atom,
190    pub count: u32,
191    /// `{}` when it has none.
192    pub snbt: String,
193}
194
195/// How fast a block is dug with the best tool in the hotbar (Baritone's
196/// `ToolSet`): the slot, and the share of the block broken a tick (0: it
197/// can't be).
198#[derive(Debug, Clone, Copy, PartialEq, Schema)]
199#[data(view)]
200pub struct DigSpeed {
201    pub slot: u32,
202    pub per_tick: f32,
203}
204
205/// A stack in an inventory slot.
206#[derive(Debug, Clone, PartialEq, Schema)]
207#[data(view)]
208pub struct SlotStack {
209    pub slot: u32,
210    pub stack: Stack,
211}
212
213/// A slot of an open inventory screen: what it holds, where it's drawn
214/// (GUI pixels from the screen's corner).
215#[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/// Which way an entity faces, in degrees.
224#[derive(Debug, Clone, Copy, PartialEq, Default, Schema)]
225#[data(view)]
226pub struct Rotation {
227    pub yaw: f32,
228    pub pitch: f32,
229}
230
231/// A block found in the world: where, and its state.
232#[derive(Debug, Clone, PartialEq, Schema)]
233#[data(view)]
234pub struct PlacedBlock {
235    pub pos: BlockPos,
236    /// Full block state, e.g. `"minecraft:oak_log[axis=y]"`.
237    pub state: Atom,
238}
239
240impl PlacedBlock {
241    /// Registry id of the block, without its state properties.
242    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}