1use 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#[derive(Debug, Clone, Copy)]
21#[repr(C, packed)]
22pub struct C2SData {
23 pub x: u8,
25 pub y: u8,
27 #[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
43pub 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
58pub 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#[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#[derive(Debug, Clone, Copy)]
146#[repr(C, packed)]
147pub struct S2CData {
148 #[doc(alias = "control")]
150 pub player: u8,
151 pub pause_request: u8,
153 __pad0: [u8; __S2C_PAD_0_LEN],
154
155 pub gold: [u32; MAX_PLAYERS],
157 pub time: u32,
159
160 pub width: u8,
162 pub height: u8,
164 pub flag: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
166 pub owner: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
168 __pad1: [u8; __S2C_PAD_1_LEN],
169 pub pop: [[u16; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
171 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
202unsafe 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}