Skip to main content

azalea_core/
game_type.rs

1use std::io::{self, Cursor, Write};
2
3use azalea_buf::{AzBuf, AzBufVar, BufReadError};
4use azalea_chat::translatable_component::TranslatableComponent;
5use tracing::debug;
6
7/// A Minecraft gamemode, like survival or creative.
8///
9/// When this is used as a component, it is only present for local players. For
10/// non-local players, their gamemode must be looked up in the tab list.
11#[cfg_attr(feature = "bevy_ecs", derive(bevy_ecs::component::Component))]
12#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq)]
13pub enum GameMode {
14    #[default]
15    Survival,
16    Creative,
17    Adventure,
18    Spectator,
19}
20
21impl GameMode {
22    pub fn to_id(&self) -> u8 {
23        match self {
24            GameMode::Survival => 0,
25            GameMode::Creative => 1,
26            GameMode::Adventure => 2,
27            GameMode::Spectator => 3,
28        }
29    }
30
31    /// Get the ID of the game mode, but return -1 if the game type is invalid.
32    pub fn to_optional_id<T: Into<Option<GameMode>>>(game_type: T) -> i8 {
33        match game_type.into() {
34            Some(game_type) => game_type.to_id() as i8,
35            None => -1,
36        }
37    }
38
39    pub fn from_id(id: u8) -> Option<GameMode> {
40        Some(match id {
41            0 => GameMode::Survival,
42            1 => GameMode::Creative,
43            2 => GameMode::Adventure,
44            3 => GameMode::Spectator,
45            _ => return None,
46        })
47    }
48
49    pub fn from_optional_id(id: i8) -> Option<OptionalGameType> {
50        Some(
51            match id {
52                -1 => None,
53                id => Some(GameMode::from_id(id as u8)?),
54            }
55            .into(),
56        )
57    }
58
59    /// The short translatable display name for the gamemode, like "Survival".
60    pub fn short_name(&self) -> TranslatableComponent {
61        TranslatableComponent::new(format!("selectWorld.gameMode.{}", self.name()), vec![])
62    }
63
64    /// The long translatable display name for the gamemode, like "Survival
65    /// Mode".
66    pub fn long_name(&self) -> TranslatableComponent {
67        TranslatableComponent::new(format!("gameMode.{}", self.name()), vec![])
68    }
69
70    pub fn from_name(name: &str) -> GameMode {
71        match name {
72            "survival" => GameMode::Survival,
73            "creative" => GameMode::Creative,
74            "adventure" => GameMode::Adventure,
75            "spectator" => GameMode::Spectator,
76            _ => panic!("Unknown game type name: {name}"),
77        }
78    }
79
80    /// The internal name for the game mode, like "survival".
81    pub fn name(&self) -> &'static str {
82        match self {
83            GameMode::Survival => "survival",
84            GameMode::Creative => "creative",
85            GameMode::Adventure => "adventure",
86            GameMode::Spectator => "spectator",
87        }
88    }
89}
90
91impl GameMode {
92    /// Whether the player can't interact with blocks while in this game mode.
93    ///
94    /// Returns true if you're in adventure or spectator.
95    pub fn is_block_placing_restricted(&self) -> bool {
96        matches!(self, GameMode::Adventure | GameMode::Spectator)
97    }
98}
99
100impl AzBuf for GameMode {
101    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
102        let id = u32::azalea_read_var(buf)?;
103        let id = id.try_into().unwrap_or_else(|_| {
104            debug!("Unknown game mode id {id}, defaulting to survival");
105            0
106        });
107        Ok(GameMode::from_id(id).unwrap_or_else(|| {
108            debug!("Unknown game mode id {id}, defaulting to survival");
109            GameMode::Survival
110        }))
111    }
112    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
113        u8::azalea_write(&self.to_id(), buf)
114    }
115}
116
117/// Rust doesn't let us `impl AzBuf for Option<GameType>` so we have to
118/// make a new type :(
119#[derive(Clone, Copy, Debug, Hash, PartialEq)]
120pub struct OptionalGameType(pub Option<GameMode>);
121
122impl From<Option<GameMode>> for OptionalGameType {
123    fn from(game_type: Option<GameMode>) -> Self {
124        OptionalGameType(game_type)
125    }
126}
127
128impl From<OptionalGameType> for Option<GameMode> {
129    fn from(optional_game_type: OptionalGameType) -> Self {
130        optional_game_type.0
131    }
132}
133
134impl AzBuf for OptionalGameType {
135    fn azalea_read(buf: &mut Cursor<&[u8]>) -> Result<Self, BufReadError> {
136        let id = i8::azalea_read(buf)?;
137        GameMode::from_optional_id(id).ok_or(BufReadError::UnexpectedEnumVariant { id: id as i32 })
138    }
139    fn azalea_write(&self, buf: &mut impl Write) -> io::Result<()> {
140        GameMode::to_optional_id(*self).azalea_write(buf)
141    }
142}