azalea_core/
game_type.rs

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