/*
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, 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(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,
#[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,
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),
}
#[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,
},
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,
},
}