Skip to main content

curseofrust_msg/
lib.rs

1//! Curseofwar messaging protocol implementation.
2
3use bytemuck::{AnyBitPattern, NoUninit, Zeroable};
4use curseofrust::{
5    grid::{HabitLand, Tile},
6    Pos, MAX_HEIGHT, MAX_PLAYERS, MAX_WIDTH,
7};
8
9use std::mem::offset_of;
10
11mod client;
12mod server;
13
14pub use client::*;
15pub use server::*;
16
17pub use bytemuck;
18
19/// Data structure a client transferred to a server.
20#[derive(Debug, Clone, Copy)]
21#[repr(C, packed)]
22pub struct C2SData {
23    /// The targeting X position.
24    pub x: u8,
25    /// The targeting Y position.
26    pub y: u8,
27    /// The message.
28    #[doc(alias = "info")]
29    pub msg: u8,
30}
31
32pub const C2S_SIZE: usize = std::mem::size_of::<C2SData>() + 1;
33
34#[repr(C)]
35#[allow(dead_code)]
36struct UnsafeC2SData {
37    x: u8,
38    y: u8,
39    #[doc(alias = "info")]
40    msg: u8,
41}
42
43/// Message a client transferred to a server.
44pub mod client_msg {
45    pub const CONNECT: u8 = 1;
46    pub const BUILD: u8 = 20;
47
48    pub const FLAG_ON: u8 = 21;
49    pub const FLAG_OFF: u8 = 22;
50    pub const FLAG_OFF_ALL: u8 = 23;
51    pub const FLAG_OFF_HALF: u8 = 24;
52
53    pub const IS_ALIVE: u8 = 30;
54    pub const PAUSE: u8 = 40;
55    pub const UNPAUSE: u8 = 41;
56}
57
58/// Message a server transferred to a client.
59pub mod server_msg {
60    pub const CONN_ACCEPTED: u8 = 5;
61    pub const CONN_REJECTED: u8 = 6;
62
63    pub const STATE: u8 = 10;
64}
65
66/// Class of tiles.
67#[repr(u8)]
68#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
69#[non_exhaustive]
70pub enum TileClass {
71    #[doc(alias = "Abyss")]
72    Void = 0,
73    Mountain = 1,
74    Mine = 2,
75    Grassland = 3,
76    Village = 4,
77    Town = 5,
78    #[doc(alias = "Castle")]
79    Fortress = 6,
80    #[doc(hidden)]
81    Other = u8::MAX,
82}
83
84impl From<&Tile> for TileClass {
85    #[inline]
86    fn from(value: &Tile) -> Self {
87        match value {
88            Tile::Void => TileClass::Void,
89            Tile::Mountain => TileClass::Mountain,
90            Tile::Mine(_) => TileClass::Mine,
91            Tile::Habitable { land, .. } => match land {
92                HabitLand::Fortress => TileClass::Fortress,
93                HabitLand::Town => TileClass::Town,
94                HabitLand::Village => TileClass::Village,
95                HabitLand::Grassland => TileClass::Grassland,
96                _ => TileClass::Other,
97            },
98            _ => TileClass::Other,
99        }
100    }
101}
102
103impl From<u8> for TileClass {
104    #[inline]
105    fn from(value: u8) -> Self {
106        match value {
107            0 => TileClass::Void,
108            1 => TileClass::Mountain,
109            2 => TileClass::Mine,
110            3 => TileClass::Grassland,
111            4 => TileClass::Village,
112            5 => TileClass::Town,
113            6 => TileClass::Fortress,
114            _ => TileClass::Other,
115        }
116    }
117}
118
119impl From<TileClass> for Tile {
120    #[inline]
121    fn from(value: TileClass) -> Self {
122        match value {
123            TileClass::Void => Tile::Void,
124            TileClass::Mountain => Tile::Mountain,
125            TileClass::Mine => Tile::Mine(Default::default()),
126            TileClass::Grassland | TileClass::Village | TileClass::Town | TileClass::Fortress => {
127                Tile::Habitable {
128                    land: match value {
129                        TileClass::Grassland => HabitLand::Grassland,
130                        TileClass::Village => HabitLand::Village,
131                        TileClass::Town => HabitLand::Town,
132                        TileClass::Fortress => HabitLand::Fortress,
133                        _ => unreachable!(),
134                    },
135                    units: [0u16; MAX_PLAYERS],
136                    owner: Default::default(),
137                }
138            }
139            TileClass::Other => Tile::Void,
140        }
141    }
142}
143
144/// Data structure a server transferred to a client.
145#[derive(Debug, Clone, Copy)]
146#[repr(C, packed)]
147pub struct S2CData {
148    /// Player you control.
149    #[doc(alias = "control")]
150    pub player: u8,
151    /// Pause request.
152    pub pause_request: u8,
153    __pad0: [u8; __S2C_PAD_0_LEN],
154
155    /// Gold counts.
156    pub gold: [u32; MAX_PLAYERS],
157    /// Current time.
158    pub time: u32,
159
160    /// Width of the grid.
161    pub width: u8,
162    /// Height of the grid.
163    pub height: u8,
164    /// Flag powers of the grid.
165    pub flag: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
166    /// Owner of each grid.
167    pub owner: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
168    __pad1: [u8; __S2C_PAD_1_LEN],
169    /// Population of each grid.
170    pub pop: [[u16; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
171    /// Population of each grid.
172    pub tile: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
173    __pad2: [u8; __S2C_PAD_2_LEN],
174}
175
176pub const S2C_SIZE: usize = std::mem::size_of::<S2CData>() + 1;
177
178#[repr(C)]
179struct UnsafeS2CData {
180    player: u8,
181    pause_request: u8,
182    gold: [u32; MAX_PLAYERS],
183    time: u32,
184    width: u8,
185    height: u8,
186    flag: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
187    owner: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
188    pop: [[u16; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
189    tile: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
190}
191
192const __S2C_PAD_0_LEN: usize = offset_of!(UnsafeS2CData, gold)
193    - offset_of!(UnsafeS2CData, pause_request)
194    - std::mem::size_of::<u8>();
195const __S2C_PAD_1_LEN: usize = offset_of!(UnsafeS2CData, pop)
196    - offset_of!(UnsafeS2CData, owner)
197    - std::mem::size_of::<[[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize]>();
198const __S2C_PAD_2_LEN: usize = std::mem::size_of::<UnsafeS2CData>()
199    - offset_of!(UnsafeS2CData, tile)
200    - std::mem::size_of::<[[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize]>();
201
202//SAFETY: `C2SData` and `S2CData` are manually padded.
203unsafe impl Zeroable for C2SData {}
204unsafe impl AnyBitPattern for C2SData {}
205unsafe impl NoUninit for C2SData {}
206unsafe impl Zeroable for S2CData {}
207unsafe impl AnyBitPattern for S2CData {}
208unsafe impl NoUninit for S2CData {}
209
210impl From<(Pos, u8)> for C2SData {
211    #[inline]
212    fn from(value: (Pos, u8)) -> Self {
213        Self {
214            x: value.0 .0 as u8,
215            y: value.0 .1 as u8,
216            msg: value.1,
217        }
218    }
219}
220
221#[cfg(test)]
222mod tests {
223    use crate::*;
224
225    #[test]
226    fn s2c_data_layout() {
227        assert_eq!(
228            std::mem::size_of::<S2CData>(),
229            std::mem::size_of::<UnsafeS2CData>()
230        );
231
232        macro_rules! assert_offset_eq {
233            ($($f:ident),*$(,)?) => {
234                $(
235                assert_eq!(
236                    std::mem::offset_of!(S2CData, $f),
237                    std::mem::offset_of!(UnsafeS2CData, $f),
238                );
239                )*
240            };
241        }
242
243        assert_offset_eq! {
244            player,
245            pause_request,
246            gold,
247            time,
248            width,
249            height,
250            flag,
251            owner,
252            pop,
253            tile,
254        }
255    }
256
257    #[test]
258    fn c2s_data_layout() {
259        assert_eq!(
260            std::mem::size_of::<C2SData>(),
261            std::mem::size_of::<UnsafeC2SData>()
262        );
263
264        macro_rules! assert_offset_eq {
265            ($($f:ident),*$(,)?) => {
266                $(
267                assert_eq!(
268                    std::mem::offset_of!(C2SData, $f),
269                    std::mem::offset_of!(UnsafeC2SData, $f),
270                );
271                )*
272            };
273        }
274
275        assert_offset_eq! {
276            x,
277            y,
278            msg,
279        }
280    }
281}