Skip to main content

steel_utils/types/
gameplay.rs

1use std::io::{self, Cursor, Write};
2
3use bitflags::bitflags;
4use serde::{Deserialize, Serialize};
5
6use crate::{
7    codec::VarInt,
8    serial::{ReadFrom, WriteTo},
9};
10
11/// The game type.
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[expect(missing_docs, reason = "variant names are self-explanatory")]
14pub enum GameType {
15    Survival = 0,
16    Creative = 1,
17    Adventure = 2,
18    Spectator = 3,
19}
20
21impl GameType {
22    /// Returns the name of the game type.
23    #[must_use]
24    pub const fn name(self) -> &'static str {
25        match self {
26            GameType::Survival => "survival",
27            GameType::Creative => "creative",
28            GameType::Adventure => "adventure",
29            GameType::Spectator => "spectator",
30        }
31    }
32}
33
34impl ReadFrom for GameType {
35    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
36        let value = VarInt::read(data)?.0;
37        match value {
38            0 => Ok(GameType::Survival),
39            1 => Ok(GameType::Creative),
40            2 => Ok(GameType::Adventure),
41            3 => Ok(GameType::Spectator),
42            _ => Err(io::Error::new(
43                io::ErrorKind::InvalidData,
44                "Invalid GameType",
45            )),
46        }
47    }
48}
49
50impl From<GameType> for i8 {
51    fn from(value: GameType) -> Self {
52        value as i8
53    }
54}
55
56impl From<GameType> for i32 {
57    fn from(value: GameType) -> Self {
58        value as i32
59    }
60}
61
62impl From<GameType> for f32 {
63    fn from(value: GameType) -> Self {
64        f32::from(value as i8)
65    }
66}
67
68impl From<i8> for GameType {
69    fn from(value: i8) -> Self {
70        match value {
71            1 => GameType::Creative,
72            2 => GameType::Adventure,
73            3 => GameType::Spectator,
74            _ => GameType::Survival,
75        }
76    }
77}
78
79impl From<i32> for GameType {
80    fn from(value: i32) -> Self {
81        match value {
82            1 => GameType::Creative,
83            2 => GameType::Adventure,
84            3 => GameType::Spectator,
85            _ => GameType::Survival,
86        }
87    }
88}
89
90impl From<f32> for GameType {
91    fn from(value: f32) -> Self {
92        match value {
93            1. => GameType::Creative,
94            2. => GameType::Adventure,
95            3. => GameType::Spectator,
96            _ => GameType::Survival,
97        }
98    }
99}
100
101/// World difficulty level.
102///
103/// Controls starvation damage thresholds, mob spawning behavior,
104/// and various other gameplay tweaks.
105#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
106#[repr(u8)]
107pub enum Difficulty {
108    /// No hostile mobs, no starvation, health regenerates quickly.
109    Peaceful = 0,
110    /// Hostile mobs deal less damage, starvation stops at 10 HP.
111    Easy = 1,
112    /// Default difficulty, starvation stops at 1 HP.
113    #[default]
114    Normal = 2,
115    /// Hostile mobs deal more damage, starvation can kill.
116    Hard = 3,
117}
118
119#[expect(clippy::match_same_arms, reason = "cause it looks better")]
120impl From<u8> for Difficulty {
121    fn from(value: u8) -> Self {
122        match value {
123            0 => Difficulty::Peaceful,
124            1 => Difficulty::Easy,
125            2 => Difficulty::Normal,
126            3 => Difficulty::Hard,
127            _ => Difficulty::Normal,
128        }
129    }
130}
131
132impl From<Difficulty> for u8 {
133    fn from(value: Difficulty) -> Self {
134        value as u8
135    }
136}
137
138impl ReadFrom for Difficulty {
139    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
140        let value = <u8 as ReadFrom>::read(data)?;
141        match value {
142            0 => Ok(Difficulty::Peaceful),
143            1 => Ok(Difficulty::Easy),
144            2 => Ok(Difficulty::Normal),
145            3 => Ok(Difficulty::Hard),
146            _ => Err(io::Error::new(
147                io::ErrorKind::InvalidData,
148                format!("Invalid Difficulty: {value}"),
149            )),
150        }
151    }
152}
153
154impl WriteTo for Difficulty {
155    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
156        (*self as u8).write(writer)
157    }
158}
159
160impl Serialize for Difficulty {
161    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
162    where
163        S: serde::Serializer,
164    {
165        serializer.serialize_u8(*self as u8)
166    }
167}
168
169impl<'de> Deserialize<'de> for Difficulty {
170    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
171    where
172        D: serde::Deserializer<'de>,
173    {
174        let id = u8::deserialize(deserializer)?;
175        Ok(Self::from(id))
176    }
177}
178
179/// Represents the hand used for an interaction.
180#[derive(Debug, Clone, Copy, PartialEq, Eq)]
181#[repr(u8)]
182pub enum InteractionHand {
183    /// The main hand.
184    MainHand = 0,
185    /// The off hand.
186    OffHand = 1,
187}
188
189impl WriteTo for InteractionHand {
190    fn write(&self, writer: &mut impl Write) -> io::Result<()> {
191        VarInt(*self as i32).write(writer)
192    }
193}
194
195impl ReadFrom for InteractionHand {
196    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
197        let id = VarInt::read(data)?.0;
198        match id {
199            0 => Ok(InteractionHand::MainHand),
200            1 => Ok(InteractionHand::OffHand),
201            _ => Err(io::Error::other("Invalid InteractionHand id")),
202        }
203    }
204}
205
206/// Which side of a sign a text edit applies to.
207#[derive(Debug, Clone, Copy, PartialEq, Eq)]
208pub enum SignTextSlot {
209    /// The back text of the sign.
210    Back = 0,
211    /// The front text of the sign.
212    Front = 1,
213}
214
215impl SignTextSlot {
216    /// Returns whether this slot is the front text.
217    #[must_use]
218    pub const fn is_front(self) -> bool {
219        matches!(self, SignTextSlot::Front)
220    }
221}
222
223impl ReadFrom for SignTextSlot {
224    fn read(data: &mut Cursor<&[u8]>) -> io::Result<Self> {
225        // Vanilla uses ByIdMap::continuous with OutOfBoundsStrategy::ZERO, so any
226        // unknown id falls back to the first variant instead of failing the read.
227        Ok(match VarInt::read(data)?.0 {
228            1 => SignTextSlot::Front,
229            _ => SignTextSlot::Back,
230        })
231    }
232}
233
234/// Flags that control how a block update is processed.
235#[derive(Clone, Copy, Debug, PartialEq, Eq)]
236pub struct UpdateFlags(u16);
237
238bitflags! {
239    impl UpdateFlags: u16 {
240        const UPDATE_NEIGHBORS = 1;
241        const UPDATE_CLIENTS = 1 << 1;
242        const UPDATE_INVISIBLE = 1 << 2;
243        const UPDATE_IMMEDIATE = 1 << 3;
244        const UPDATE_KNOWN_SHAPE = 1 << 4;
245        const UPDATE_SUPPRESS_DROPS = 1 << 5;
246        const UPDATE_MOVE_BY_PISTON = 1 << 6;
247        const UPDATE_SKIP_SHAPE_UPDATE_ON_WIRE = 1 << 7;
248        const UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS = 1 << 8;
249        const UPDATE_SKIP_ON_PLACE = 1 << 9;
250
251        const UPDATE_NONE = Self::UPDATE_INVISIBLE.bits() | Self::UPDATE_SKIP_BLOCK_ENTITY_SIDEEFFECTS.bits();
252        const UPDATE_ALL = Self::UPDATE_NEIGHBORS.bits() | Self::UPDATE_CLIENTS.bits();
253        const UPDATE_ALL_IMMEDIATE = Self::UPDATE_ALL.bits() | Self::UPDATE_IMMEDIATE.bits();
254    }
255}