/* Hurry Curry! - a game about cooking Copyright 2024 metamuffin This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, version 3 of the License only. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details. You should have received a copy of the GNU Affero General Public License along with this program. If not, see . */ use bincode::{ config::{standard, Configuration, Limit, LittleEndian, Varint}, Decode, Encode, }; use glam::{IVec2, Vec2}; use serde::{Deserialize, Serialize}; use std::{collections::HashSet, sync::LazyLock}; pub use glam; pub mod helpers; pub mod movement; pub mod registry; pub static VERSION: LazyLock<(u32, u32)> = LazyLock::new(|| { ( env!("CARGO_PKG_VERSION_MAJOR").parse().unwrap(), env!("CARGO_PKG_VERSION_MINOR").parse().unwrap(), ) }); #[test] fn test_version_parse() { let _ = *VERSION; } pub const BINCODE_CONFIG: Configuration> = standard().with_limit(); #[derive( Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[serde(transparent)] pub struct PlayerID(pub i64); #[derive( Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[serde(transparent)] pub struct ItemIndex(pub usize); #[derive( Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[serde(transparent)] pub struct TileIndex(pub usize); #[derive( Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[serde(transparent)] pub struct RecipeIndex(pub usize); #[derive( Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, PartialOrd, Ord, Hash, )] #[serde(transparent)] pub struct DemandIndex(pub usize); #[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq, Hash)] #[serde(transparent)] pub struct Hand(pub usize); #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] pub struct MapMetadata { pub name: String, pub players: usize, pub difficulty: i32, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] pub struct Demand { pub input: ItemIndex, pub output: Option, pub duration: f32, pub points: i64, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default)] #[rustfmt::skip] pub struct Gamedata { pub current_map: String, pub item_names: Vec, pub tile_names: Vec, pub tile_collide: Vec, pub tile_interact: Vec, pub maps: Vec<(String, MapMetadata)>, pub bot_algos: Vec, pub recipes: Vec, pub demands: Vec, pub hand_count: usize, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] #[serde(rename_all = "snake_case", tag = "type")] pub enum PacketS { Join { name: String, character: i32, #[serde(default = "chef_class")] class: PlayerClass, #[serde(skip)] // TODO fix bincode can still set id id: Option, // used entity bots that cant receive a response }, Leave { player: PlayerID, }, Movement { player: PlayerID, #[bincode(with_serde)] dir: Vec2, boost: bool, #[bincode(with_serde)] pos: Option, }, Interact { player: PlayerID, hand: Hand, #[bincode(with_serde)] pos: Option, }, Communicate { player: PlayerID, message: Option, timeout: Option, pin: Option, }, /// For use in replay sessions only ReplayTick { dt: f64, }, #[serde(skip)] #[bincode(skip)] /// For internal use only (customers) ReplaceHand { player: PlayerID, hand: Hand, item: Option, }, #[serde(skip)] #[bincode(skip)] /// For internal use only (customers) ApplyScore(Score), #[serde(skip)] #[bincode(skip)] /// For internal use only (customers) Effect { player: PlayerID, name: String, }, } fn chef_class() -> PlayerClass { PlayerClass::Chef } #[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum PlayerClass { Chef, Bot, Customer, } impl PlayerClass { pub fn is_cheflike(&self) -> bool { matches!(self, Self::Bot | Self::Chef) } } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, PartialEq, Eq)] #[serde(rename_all = "snake_case")] pub enum Message { Translation { id: String, params: Vec }, Text(String), Item(ItemIndex), Tile(TileIndex), } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] #[serde(rename_all = "snake_case", tag = "type")] pub enum PacketC { Version { minor: u32, major: u32, supports_bincode: bool, }, Joined { id: PlayerID, }, Data { data: Gamedata, }, AddPlayer { id: PlayerID, #[bincode(with_serde)] position: Vec2, class: PlayerClass, character: i32, name: String, }, RemovePlayer { id: PlayerID, }, Movement { player: PlayerID, #[bincode(with_serde)] pos: Vec2, rot: f32, #[bincode(with_serde)] dir: Vec2, boost: bool, }, MoveItem { from: ItemLocation, to: ItemLocation, }, SetItem { location: ItemLocation, item: Option, }, ClearProgress { item: ItemLocation, }, SetProgress { player: Option, item: ItemLocation, position: f32, speed: f32, warn: bool, }, UpdateMap { #[bincode(with_serde)] tile: IVec2, kind: Option, neighbors: [Option; 4], }, FlushMap, Communicate { player: PlayerID, message: Option, timeout: Option, }, Effect { name: String, player: PlayerID, }, ServerMessage { message: Message, error: bool, }, ServerHint { #[bincode(with_serde)] position: Option, message: Option, player: PlayerID, }, Score(Score), SetIngame { state: bool, lobby: bool, }, Menu(Menu), MovementSync { player: PlayerID, }, Environment { effects: HashSet, }, TutorialEnded { player: PlayerID, item: ItemIndex, success: bool, }, Redirect { uri: Vec, }, /// For use in replay sessions only ReplayStart, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] #[serde(rename_all = "snake_case", tag = "menu", content = "data")] pub enum Menu { Document(DocumentElement), Score(Score), } #[derive(Debug, Clone, Copy, Serialize, Deserialize, Encode, Decode, Default)] pub struct MessageTimeout { pub remaining: f32, pub initial: f32, pub pinned: bool, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Default)] pub struct Score { pub time_remaining: f64, pub stars: u8, pub points: i64, pub demands_failed: usize, pub demands_completed: usize, pub players: usize, pub active_recipes: usize, pub passive_recipes: usize, pub instant_recipes: usize, } #[derive(Debug, Clone, Serialize, Encode, Decode, Deserialize)] #[serde(rename_all = "snake_case")] pub enum Recipe { Passive { speed: f32, revert_speed: Option, tile: Option, input: ItemIndex, output: Option, warn: bool, }, Active { speed: f32, tile: Option, input: ItemIndex, outputs: [Option; 2], }, Instant { tile: Option, inputs: [Option; 2], outputs: [Option; 2], points: i64, }, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode, Copy, PartialEq, Eq, Hash)] #[serde(rename_all = "snake_case")] pub enum ItemLocation { Tile(#[bincode(with_serde)] IVec2), Player(PlayerID, Hand), } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] #[serde(rename_all = "snake_case", tag = "t")] pub enum DocumentElement { Document { es: Vec, }, /// One page of the document, √2:1 aspect ratio Page { /// Name of background image background: Option, es: Vec, }, /// Implicit element layouting Container { es: Vec, }, List { /// Should only contain par or text elements es: Vec, }, /// Table with elements arranged as row arrays Table { es: Vec>, }, /// A paragraph. Par { /// Should only contain text elements es: Vec, }, /// A text span Text { s: Message, size: f32, color: Option, font: Option, #[serde(default)] bold: bool, }, /// Document part that is only shown conditionally. Used for image attribution Conditional { cond: String, value: bool, e: Box, }, /// Makes the child element clickable that jumps to the label with the same id Ref { id: String, e: Box, }, /// Declares a label Label { id: String, e: Box, }, Align { dir: DocumentAlign, e: Box, }, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] #[serde(rename_all = "snake_case")] pub enum DocumentAlign { FlowEnd, Bottom, }