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