Skip to main content

yog_core/
local.rs

1//! What this client's player is and sees (`yog_event::client::local`), and
2//! how it clicks a container (`client::control`): records the host builds.
3
4use yog_data::{Atom, Schema};
5
6use crate::{BlockPos, Point};
7
8/// This client's player: where it is and looks, how it moves and fares.
9#[derive(Debug, Clone, PartialEq, Schema)]
10#[data(view)]
11pub struct LocalState {
12    pub pos: Point,
13    /// Degrees: yaw 0 looks south (+z), 90 west; pitch 90 looks down.
14    pub yaw: f32,
15    pub pitch: f32,
16    /// Blocks a tick.
17    pub velocity: Point,
18    pub on_ground: bool,
19    pub in_water: bool,
20    pub eye_height: f64,
21    pub health: f32,
22    pub food: i32,
23    pub dimension: Atom,
24    /// The selected hotbar slot, 0–8.
25    pub slot: u32,
26    /// How charged the next attack is: 1 does full damage.
27    pub attack_strength: f32,
28}
29
30impl LocalState {
31    /// Where its eyes are.
32    pub fn eye_pos(&self) -> Point {
33        Point {
34            y: self.pos.y + self.eye_height,
35            ..self.pos
36        }
37    }
38}
39
40/// One kind of block in a [`BlockArea`]: how it is to walk through.
41#[derive(Debug, Clone, PartialEq, Schema)]
42#[data(view)]
43pub struct BlockKind {
44    pub id: Atom,
45    /// Can be stood on and can't be walked through.
46    pub solid: bool,
47    pub liquid: bool,
48    /// Ladders, vines: climbed.
49    pub climbable: bool,
50    /// Hurts to touch (fire, lava, cactus, berry bush…).
51    pub harmful: bool,
52}
53
54/// The blocks between `min` and `max` (what isn't loaded reads as
55/// bedrock): a palette and an index into it per block, x fastest, then z,
56/// then y.
57#[derive(Debug, Clone, PartialEq, Schema)]
58#[data(view)]
59pub struct BlockArea {
60    pub min: BlockPos,
61    pub max: BlockPos,
62    pub palette: Vec<BlockKind>,
63    pub blocks: Vec<u16>,
64}
65
66impl BlockArea {
67    /// The kind of block at `pos`, if it's inside.
68    pub fn at(&self, pos: impl Into<BlockPos>) -> Option<&BlockKind> {
69        let index = self.blocks.get(self.index(pos.into())?)?;
70        self.palette.get(usize::from(*index))
71    }
72
73    /// The block at `pos` made the palette's `kind` (a block dug or put,
74    /// known without looking again); whether it's inside.
75    pub fn set(&mut self, pos: impl Into<BlockPos>, kind: u16) -> bool {
76        match self.index(pos.into()).and_then(|i| self.blocks.get_mut(i)) {
77            Some(block) if usize::from(kind) < self.palette.len() => {
78                *block = kind;
79                true
80            }
81            _ => false,
82        }
83    }
84
85    /// Where `pos`'s block is in [`BlockArea::blocks`], if it's inside.
86    fn index(&self, pos: BlockPos) -> Option<usize> {
87        let (w, d) = (self.max.x - self.min.x + 1, self.max.z - self.min.z + 1);
88        let (x, y, z) = (pos.x - self.min.x, pos.y - self.min.y, pos.z - self.min.z);
89        let inside = (0..w).contains(&x)
90            && (0..d).contains(&z)
91            && (0..=self.max.y - self.min.y).contains(&y);
92        inside
93            .then(|| usize::try_from((y * d + z) * w + x).ok())
94            .flatten()
95    }
96}
97
98/// An entity near this client's player.
99#[derive(Debug, Clone, PartialEq, Schema)]
100#[data(view)]
101pub struct NearbyEntity {
102    pub uuid: String,
103    pub entity_type: Atom,
104    pub pos: Point,
105    pub eye_height: f64,
106    /// Blocks a tick.
107    pub velocity: Point,
108    pub health: Option<f32>,
109    /// A player's name.
110    pub name: Option<String>,
111    /// A monster, hostile to players.
112    pub hostile: bool,
113}
114
115/// How a container slot is clicked: the game's own modes (`ClickType`).
116#[derive(Debug, Clone, Copy, PartialEq, Eq, Schema)]
117pub enum ClickMode {
118    Pickup,
119    QuickMove,
120    Swap,
121    Clone,
122    Throw,
123    QuickCraft,
124    PickupAll,
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn an_area_finds_its_blocks() {
133        let kind = |id: &str| BlockKind {
134            id: id.into(),
135            solid: true,
136            liquid: false,
137            climbable: false,
138            harmful: false,
139        };
140        let mut area = BlockArea {
141            min: BlockPos::new(0, 0, 0),
142            max: BlockPos::new(1, 1, 0),
143            palette: vec![kind("a"), kind("b")],
144            blocks: vec![0, 1, 1, 0],
145        };
146        assert_eq!(
147            area.at(BlockPos::new(1, 0, 0)).map(|k| k.id.as_str()),
148            Some("b")
149        );
150        assert_eq!(
151            area.at(BlockPos::new(0, 1, 0)).map(|k| k.id.as_str()),
152            Some("b")
153        );
154        assert_eq!(area.at(BlockPos::new(2, 0, 0)), None);
155        assert!(area.set(BlockPos::new(1, 0, 0), 0));
156        assert_eq!(
157            area.at(BlockPos::new(1, 0, 0)).map(|k| k.id.as_str()),
158            Some("a")
159        );
160        assert!(!area.set(BlockPos::new(2, 0, 0), 0));
161        assert!(!area.set(BlockPos::new(0, 0, 0), 2));
162    }
163}