/*
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, fmt::Display};
pub use glam;
pub mod movement;
pub const VERSION: (u32, u32) = (7, 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 {
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,
}
#[derive(Debug, Clone, Serialize, Deserialize, Encode, Decode)]
#[serde(rename_all = "snake_case", tag = "type")]
pub enum PacketS {
Join {
name: String,
character: i32,
#[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,
#[bincode(with_serde)]
pos: Option,
},
Communicate {
player: PlayerID,
message: Option,
timeout: Option,
},
/// For use in replay sessions only
ReplayTick {
dt: f64,
},
#[serde(skip)]
#[bincode(skip)]
/// For internal use only (customers)
ReplaceHand {
player: PlayerID,
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,
},
}
#[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,
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],
},
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,
},
/// 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, Copy, Serialize, Deserialize, Encode, Decode, Default)]
pub struct MessageTimeout {
pub remaining: f32,
pub initial: f32,
}
#[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,
},
}
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 speed(&self) -> Option {
match self {
Recipe::Passive { speed, .. } => Some(*speed),
Recipe::Active { speed, .. } => Some(*speed),
_ => None,
}
}
pub fn revert_speed(&self) -> Option {
match self {
Recipe::Passive { revert_speed, .. } => *revert_speed,
_ => 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})"),
}
}
}