/* 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::{HashMap, HashSet}, fmt::Display, }; pub use glam; pub mod movement; pub const VERSION: (u32, u32) = (4, 0); 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, Serialize, Deserialize, Encode, Decode)] pub struct MapMetadata { name: String, players: usize, difficulty: i32, } #[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: HashMap, pub recipes: Vec, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] #[serde(rename_all = "snake_case", tag = "type")] pub enum PacketS { Join { name: String, character: i32, #[serde(skip)] 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, #[bincode(with_serde)] pos: Option, }, Communicate { player: PlayerID, message: Option, persist: bool, }, #[serde(skip)] #[bincode(skip)] /// For internal use only ReplaceHand { player: PlayerID, item: Option, }, /// For use in replay sessions only ReplayTick { dt: f64, }, } #[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)] #[serde(rename_all = "snake_case")] pub enum Message { Text(String), Item(ItemIndex), Effect(String), } #[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, 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, }, SetProgress { item: ItemLocation, progress: Option, warn: bool, }, UpdateMap { #[bincode(with_serde)] tile: IVec2, kind: Option, neighbors: [Option; 4], }, Communicate { player: PlayerID, message: Option, persist: bool, }, ServerMessage { text: String, }, Score(Score), SetIngame { state: bool, lobby: bool, }, Error { message: String, }, Menu(Menu), MovementSync { player: PlayerID, }, Environment { effects: HashSet, }, /// 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 { Book, Score(Score), } #[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 { duration: f32, revert_duration: Option, tile: Option, input: ItemIndex, output: Option, warn: bool, }, Active { duration: f32, tile: Option, input: ItemIndex, outputs: [Option; 2], }, Instant { tile: Option, inputs: [Option; 2], outputs: [Option; 2], points: i64, }, } impl Gamedata { pub fn tile_name(&self, index: TileIndex) -> &String { &self.tile_names[index.0] } pub fn is_tile_colliding(&self, index: TileIndex) -> bool { self.tile_collide[index.0] } pub fn is_tile_interactable(&self, index: TileIndex) -> bool { self.tile_interact[index.0] } pub fn item_name(&self, index: ItemIndex) -> &String { &self.item_names[index.0] } pub fn recipe(&self, index: RecipeIndex) -> &Recipe { &self.recipes[index.0] } pub fn get_tile_by_name(&self, name: &str) -> Option { self.tile_names .iter() .position(|t| t == name) .map(TileIndex) } pub fn get_item_by_name(&self, name: &str) -> Option { self.item_names .iter() .position(|t| t == name) .map(ItemIndex) } pub fn recipes(&self) -> impl Iterator { self.recipes .iter() .enumerate() .map(|(i, e)| (RecipeIndex(i), e)) } } impl Recipe { pub fn tile(&self) -> Option { match self { Recipe::Passive { tile, .. } => *tile, Recipe::Active { tile, .. } => *tile, Recipe::Instant { tile, .. } => *tile, } } pub fn duration(&self) -> Option { match self { Recipe::Passive { duration, .. } => Some(*duration), Recipe::Active { duration, .. } => Some(*duration), _ => None, } } pub fn revert_duration(&self) -> Option { match self { Recipe::Passive { revert_duration, .. } => *revert_duration, _ => None, } } pub fn warn(&self) -> bool { match self { Recipe::Passive { warn, .. } => *warn, _ => false, } } pub fn inputs(&self) -> Vec { match self { Recipe::Passive { input, .. } => vec![*input], Recipe::Active { input, .. } => vec![*input], Recipe::Instant { inputs, .. } => inputs.iter().flat_map(|e| e.to_owned()).collect(), } } pub fn outputs(&self) -> Vec { match self { Recipe::Passive { output, .. } => output.iter().copied().collect(), Recipe::Active { outputs, .. } => outputs.iter().flat_map(|e| e.to_owned()).collect(), Recipe::Instant { outputs, .. } => outputs.iter().flat_map(|e| e.to_owned()).collect(), } } pub fn supports_tile(&self, tile: Option) -> bool { if let Some(tile_constraint) = self.tile() { if let Some(tile) = tile { tile == tile_constraint } else { false } } else { true } } } #[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), } impl Display for ItemLocation { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { ItemLocation::Tile(pos) => write!(f, "tile({pos})"), ItemLocation::Player(PlayerID(id)) => write!(f, "player({id})"), } } }