use bytemuck::{AnyBitPattern, NoUninit, Zeroable};
use curseofrust::{
grid::{HabitLand, Tile},
Pos, MAX_HEIGHT, MAX_PLAYERS, MAX_WIDTH,
};
use std::mem::offset_of;
mod client;
mod server;
pub use client::*;
pub use server::*;
pub use bytemuck;
#[derive(Debug, Clone, Copy)]
#[repr(C, packed)]
pub struct C2SData {
pub x: u8,
pub y: u8,
#[doc(alias = "info")]
pub msg: u8,
}
pub const C2S_SIZE: usize = std::mem::size_of::<C2SData>() + 1;
#[repr(C)]
#[allow(dead_code)]
struct UnsafeC2SData {
x: u8,
y: u8,
#[doc(alias = "info")]
msg: u8,
}
pub mod client_msg {
pub const CONNECT: u8 = 1;
pub const BUILD: u8 = 20;
pub const FLAG_ON: u8 = 21;
pub const FLAG_OFF: u8 = 22;
pub const FLAG_OFF_ALL: u8 = 23;
pub const FLAG_OFF_HALF: u8 = 24;
pub const IS_ALIVE: u8 = 30;
pub const PAUSE: u8 = 40;
pub const UNPAUSE: u8 = 41;
}
pub mod server_msg {
pub const CONN_ACCEPTED: u8 = 5;
pub const CONN_REJECTED: u8 = 6;
pub const STATE: u8 = 10;
}
#[repr(u8)]
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash)]
#[non_exhaustive]
pub enum TileClass {
#[doc(alias = "Abyss")]
Void = 0,
Mountain = 1,
Mine = 2,
Grassland = 3,
Village = 4,
Town = 5,
#[doc(alias = "Castle")]
Fortress = 6,
#[doc(hidden)]
Other = u8::MAX,
}
impl From<&Tile> for TileClass {
#[inline]
fn from(value: &Tile) -> Self {
match value {
Tile::Void => TileClass::Void,
Tile::Mountain => TileClass::Mountain,
Tile::Mine(_) => TileClass::Mine,
Tile::Habitable { land, .. } => match land {
HabitLand::Fortress => TileClass::Fortress,
HabitLand::Town => TileClass::Town,
HabitLand::Village => TileClass::Village,
HabitLand::Grassland => TileClass::Grassland,
_ => TileClass::Other,
},
_ => TileClass::Other,
}
}
}
impl From<u8> for TileClass {
#[inline]
fn from(value: u8) -> Self {
match value {
0 => TileClass::Void,
1 => TileClass::Mountain,
2 => TileClass::Mine,
3 => TileClass::Grassland,
4 => TileClass::Village,
5 => TileClass::Town,
6 => TileClass::Fortress,
_ => TileClass::Other,
}
}
}
impl From<TileClass> for Tile {
#[inline]
fn from(value: TileClass) -> Self {
match value {
TileClass::Void => Tile::Void,
TileClass::Mountain => Tile::Mountain,
TileClass::Mine => Tile::Mine(Default::default()),
TileClass::Grassland | TileClass::Village | TileClass::Town | TileClass::Fortress => {
Tile::Habitable {
land: match value {
TileClass::Grassland => HabitLand::Grassland,
TileClass::Village => HabitLand::Village,
TileClass::Town => HabitLand::Town,
TileClass::Fortress => HabitLand::Fortress,
_ => unreachable!(),
},
units: [0u16; MAX_PLAYERS],
owner: Default::default(),
}
}
TileClass::Other => Tile::Void,
}
}
}
#[derive(Debug, Clone, Copy)]
#[repr(C, packed)]
pub struct S2CData {
#[doc(alias = "control")]
pub player: u8,
pub pause_request: u8,
__pad0: [u8; __S2C_PAD_0_LEN],
pub gold: [u32; MAX_PLAYERS],
pub time: u32,
pub width: u8,
pub height: u8,
pub flag: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
pub owner: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
__pad1: [u8; __S2C_PAD_1_LEN],
pub pop: [[u16; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
pub tile: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
__pad2: [u8; __S2C_PAD_2_LEN],
}
pub const S2C_SIZE: usize = std::mem::size_of::<S2CData>() + 1;
#[repr(C)]
struct UnsafeS2CData {
player: u8,
pause_request: u8,
gold: [u32; MAX_PLAYERS],
time: u32,
width: u8,
height: u8,
flag: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
owner: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
pop: [[u16; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
tile: [[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize],
}
const __S2C_PAD_0_LEN: usize = offset_of!(UnsafeS2CData, gold)
- offset_of!(UnsafeS2CData, pause_request)
- std::mem::size_of::<u8>();
const __S2C_PAD_1_LEN: usize = offset_of!(UnsafeS2CData, pop)
- offset_of!(UnsafeS2CData, owner)
- std::mem::size_of::<[[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize]>();
const __S2C_PAD_2_LEN: usize = std::mem::size_of::<UnsafeS2CData>()
- offset_of!(UnsafeS2CData, tile)
- std::mem::size_of::<[[u8; MAX_HEIGHT as usize]; MAX_WIDTH as usize]>();
unsafe impl Zeroable for C2SData {}
unsafe impl AnyBitPattern for C2SData {}
unsafe impl NoUninit for C2SData {}
unsafe impl Zeroable for S2CData {}
unsafe impl AnyBitPattern for S2CData {}
unsafe impl NoUninit for S2CData {}
impl From<(Pos, u8)> for C2SData {
#[inline]
fn from(value: (Pos, u8)) -> Self {
Self {
x: value.0 .0 as u8,
y: value.0 .1 as u8,
msg: value.1,
}
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn s2c_data_layout() {
assert_eq!(
std::mem::size_of::<S2CData>(),
std::mem::size_of::<UnsafeS2CData>()
);
macro_rules! assert_offset_eq {
($($f:ident),*$(,)?) => {
$(
assert_eq!(
std::mem::offset_of!(S2CData, $f),
std::mem::offset_of!(UnsafeS2CData, $f),
);
)*
};
}
assert_offset_eq! {
player,
pause_request,
gold,
time,
width,
height,
flag,
owner,
pop,
tile,
}
}
#[test]
fn c2s_data_layout() {
assert_eq!(
std::mem::size_of::<C2SData>(),
std::mem::size_of::<UnsafeC2SData>()
);
macro_rules! assert_offset_eq {
($($f:ident),*$(,)?) => {
$(
assert_eq!(
std::mem::offset_of!(C2SData, $f),
std::mem::offset_of!(UnsafeC2SData, $f),
);
)*
};
}
assert_offset_eq! {
x,
y,
msg,
}
}
}