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