use std::{collections::HashMap, fmt::Display, mem, time::SystemTime};
use bitflags::bitflags;
use serde::{Deserialize, Deserializer, Serialize, de::DeserializeOwned};
use serde_json::Value;
use serde_repr::{Deserialize_repr, Serialize_repr};
use serde_with::{DisplayFromStr, TimestampSeconds, serde_as};
use ustr::{Ustr, UstrMap, UstrSet};
mod bounce;
mod print;
pub(crate) use bounce::*;
pub use print::TextColor;
pub(crate) use print::*;
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "cmd")]
pub(crate) enum ClientMessage {
Connect(Connect),
ConnectUpdate(ConnectUpdate),
Sync,
LocationChecks(LocationChecks),
LocationScouts(LocationScouts),
CreateHints(CreateHints),
UpdateHint(UpdateHint),
StatusUpdate(StatusUpdate),
Say(Say),
GetDataPackage(GetDataPackage),
Bounce(Bounce),
Get(Get),
Set(Set),
SetNotify(SetNotify),
}
#[derive(Debug, Clone, Deserialize)]
#[serde(tag = "cmd")]
pub(crate) enum ServerMessage<S: DeserializeOwned + 'static> {
RoomInfo(RoomInfo),
ConnectionRefused(ConnectionRefused),
#[serde(deserialize_with = "deserialize_connected_with_optional_slot_data")]
Connected(Connected<S>),
ReceivedItems(ReceivedItems),
LocationInfo(LocationInfo),
RoomUpdate(RoomUpdate),
#[serde(rename = "Print")]
PlainPrint(PlainPrint),
#[serde(rename = "PrintJSON")]
RawPrint(NetworkPrint),
DataPackage(DataPackage),
Bounced(Bounced),
InvalidPacket(InvalidPacket),
Retrieved(Retrieved),
SetReply(SetReply),
}
impl<S: DeserializeOwned + 'static> ServerMessage<S> {
pub(crate) fn type_name(&self) -> &'static str {
use ServerMessage::*;
match self {
RoomInfo(_) => "RoomInfo",
ConnectionRefused(_) => "ConnectionRefused",
Connected(_) => "Connected",
ReceivedItems(_) => "ReceivedItems",
LocationInfo(_) => "LocationInfo",
RoomUpdate(_) => "RoomUpdate",
PlainPrint(_) => "Print",
RawPrint(_) => "PrintJSON",
DataPackage(_) => "DataPackage",
Bounced(_) => "Bounced",
InvalidPacket(_) => "InvalidPacket",
Retrieved(_) => "Retrieved",
SetReply(_) => "SetReply",
}
}
}
#[derive(Debug, Clone, Copy, Deserialize_repr)]
#[repr(u8)]
pub enum Permission {
Disabled = 0,
Enabled = 1,
Goal = 2,
Auto = 6,
AutoEnabled = 7,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct NetworkVersion {
pub(crate) major: u16,
pub(crate) minor: u16,
pub(crate) build: u16,
pub(crate) class: String,
}
impl Display for NetworkVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}.{}", self.major, self.minor, self.build)
}
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct NetworkPlayer {
pub(crate) team: u32,
pub(crate) slot: u32,
pub(crate) alias: String,
pub(crate) name: Ustr,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct NetworkItem {
pub(crate) item: i64,
pub(crate) location: i64,
pub(crate) player: u32,
pub(crate) flags: NetworkItemFlags,
}
bitflags! {
#[repr(transparent)]
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(from = "u8")]
#[serde(into = "u8")]
pub(crate) struct NetworkItemFlags: u8 {
const PROGRESSION = 0b001;
const USEFUL = 0b010;
const TRAP = 0b100;
}
}
impl From<u8> for NetworkItemFlags {
fn from(value: u8) -> NetworkItemFlags {
NetworkItemFlags::from_bits_retain(value)
}
}
impl From<NetworkItemFlags> for u8 {
fn from(value: NetworkItemFlags) -> Self {
value.bits()
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub(crate) enum SlotType {
Spectator = 0,
Player = 1,
Group = 2,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct NetworkSlot {
pub(crate) name: Ustr,
pub(crate) game: Ustr,
pub(crate) r#type: SlotType,
pub(crate) group_members: Vec<u32>,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct Connect {
pub(crate) password: Option<String>,
pub(crate) game: Option<Ustr>,
pub(crate) name: Ustr,
pub(crate) uuid: String,
pub(crate) version: NetworkVersion,
pub(crate) items_handling: ItemsHandlingFlags,
pub(crate) tags: UstrSet,
pub(crate) slot_data: bool,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct ConnectUpdate {
pub(crate) items_handling: Option<ItemsHandlingFlags>,
pub(crate) tags: Option<Vec<Ustr>>,
}
bitflags! {
#[derive(Debug, Clone, Copy, Serialize)]
#[serde(into = "u8")]
pub(crate) struct ItemsHandlingFlags: u8 {
const OTHER_WORLDS = 0b001;
const OWN_WORLD = 0b011;
const STARTING_INVENTORY = 0b101;
}
}
impl From<ItemsHandlingFlags> for u8 {
fn from(value: ItemsHandlingFlags) -> Self {
value.bits()
}
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct LocationChecks {
pub(crate) locations: Vec<i64>,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct LocationScouts {
pub(crate) locations: Vec<i64>,
pub(crate) create_as_hint: CreateAsHint,
}
#[derive(Debug, Clone, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum CreateAsHint {
No = 0,
All = 1,
New = 2,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct CreateHints {
pub(crate) locations: Vec<i64>,
pub(crate) player: u32,
pub(crate) status: HintStatus,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct UpdateHint {
pub(crate) player: u32,
pub(crate) location: i64,
pub(crate) status: HintStatus,
}
#[derive(Debug, Clone, Default, Serialize_repr)]
#[repr(u8)]
pub enum HintStatus {
#[default]
Unspecified = 0,
NoPriority = 10,
Avoid = 20,
Priority = 30,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct StatusUpdate {
pub(crate) status: ClientStatus,
}
#[derive(Debug, Clone, Serialize_repr, Deserialize_repr)]
#[repr(u8)]
pub enum ClientStatus {
Unknown = 0,
Connected = 5,
Ready = 10,
Playing = 20,
Goal = 30,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct Say {
pub(crate) text: String,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct GetDataPackage {
#[serde(default)]
#[serde(skip_serializing_if = "Option::is_none")]
pub(crate) games: Option<Vec<String>>,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct Get {
pub(crate) keys: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct Set {
pub(crate) key: String,
pub(crate) default: Value,
pub(crate) want_reply: bool,
pub(crate) operations: Vec<DataStorageOperation>,
}
#[derive(Debug, Clone, Serialize)]
#[serde(tag = "operation", content = "value", rename_all = "snake_case")]
pub enum DataStorageOperation {
Replace(Value),
Default,
Add(f64),
#[serde(rename = "add")]
Appends(Vec<Value>),
#[serde(rename = "mul")]
Multiply(f64),
#[serde(rename = "pow")]
Exponentiate(f64),
Mod(f64),
Floor,
Ceil,
Max(i64),
Min(i64),
And(i64),
Or(i64),
Xor(i64),
LeftShift(u8),
RightShift(u8),
Remove(Value),
#[serde(rename = "pop")]
RemoveIndex(i64),
#[serde(rename = "pop")]
RemoveKey(String),
#[serde(rename = "update")]
Union(Vec<Value>),
Update(HashMap<String, Value>),
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct SetNotify {
pub(crate) keys: Vec<String>,
}
#[serde_as]
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct RoomInfo {
pub(crate) version: NetworkVersion,
pub(crate) generator_version: NetworkVersion,
pub(crate) tags: UstrSet,
#[serde(rename = "password")]
pub(crate) password_required: bool,
pub(crate) permissions: PermissionMap,
pub(crate) hint_cost: u8,
pub(crate) location_check_points: u64,
pub(crate) games: UstrSet,
pub(crate) datapackage_checksums: UstrMap<String>,
pub(crate) seed_name: String,
#[serde_as(as = "TimestampSeconds<f64>")]
pub(crate) time: SystemTime,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct PermissionMap {
pub(crate) release: Permission,
pub(crate) collect: Permission,
pub(crate) remaining: Permission,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct ConnectionRefused {
#[serde(default)]
pub(crate) errors: Vec<String>,
}
#[serde_as]
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct Connected<S> {
pub(crate) team: u32,
pub(crate) slot: u32,
pub(crate) players: Vec<NetworkPlayer>,
pub(crate) missing_locations: Vec<i64>,
pub(crate) checked_locations: Vec<i64>,
pub(crate) slot_data: S,
#[serde_as(as = "HashMap<DisplayFromStr, _>")]
pub(crate) slot_info: HashMap<u32, NetworkSlot>,
pub(crate) hint_points: u64,
}
fn deserialize_connected_with_optional_slot_data<'de, S, D>(
deserializer: D,
) -> Result<Connected<S>, D::Error>
where
S: DeserializeOwned + 'static,
D: Deserializer<'de>,
{
Ok(if try_specialize::static_type_eq::<S, ()>() {
let inner = Connected::<Option<()>>::deserialize(deserializer)?;
let connected = Connected {
team: inner.team,
slot: inner.slot,
players: inner.players,
missing_locations: inner.missing_locations,
checked_locations: inner.checked_locations,
slot_data: (),
slot_info: inner.slot_info,
hint_points: inner.hint_points,
};
let typed_connected =
unsafe { mem::transmute_copy::<Connected<()>, Connected<S>>(&connected) };
mem::forget(connected);
typed_connected
} else {
Connected::<S>::deserialize(deserializer)?
})
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct ReceivedItems {
pub(crate) index: usize,
pub(crate) items: Vec<NetworkItem>,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct LocationInfo {
pub(crate) locations: Vec<NetworkItem>,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct RoomUpdate {
pub(crate) tags: Option<UstrSet>,
pub(crate) permissions: Option<PermissionMap>,
pub(crate) hint_cost: Option<u8>,
pub(crate) location_check_points: Option<u64>,
pub(crate) hint_points: Option<u64>,
pub(crate) players: Option<Vec<NetworkPlayer>>,
pub(crate) checked_locations: Option<Vec<i64>>,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct DataPackage {
pub(crate) data: DataPackageObject,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct DataPackageObject {
pub(crate) games: UstrMap<GameData>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub(crate) struct GameData {
pub(crate) item_name_to_id: HashMap<Ustr, i64>,
pub(crate) location_name_to_id: HashMap<Ustr, i64>,
pub(crate) checksum: String,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct InvalidPacket {
pub(crate) text: String,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct Retrieved {
pub(crate) keys: HashMap<String, Value>,
}
#[derive(Debug, Clone, Deserialize)]
pub(crate) struct SetReply {
pub(crate) key: String,
pub(crate) value: Value,
pub(crate) original_value: Option<Value>, pub(crate) slot: Option<u32>,
}