/*
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 .
*/
#![feature(map_many_mut, let_chains)]
pub mod network;
pub mod spatial_index;
use hurrycurry_protocol::{
glam::IVec2, movement::MovementBase, Gamedata, Hand, ItemIndex, ItemLocation, Message, MessageTimeout, PacketC, PlayerClass, PlayerID, RecipeIndex, Score, TileIndex
};
use spatial_index::SpatialIndex;
use std::{
collections::{HashMap, HashSet},
sync::Arc,
time::Instant,
};
#[derive(Debug, PartialEq)]
pub struct Involvement {
pub position: f32,
pub speed: f32,
pub recipe: RecipeIndex,
pub player: Option,
pub warn: bool,
}
#[derive(Debug, PartialEq)]
pub struct Item {
pub kind: ItemIndex,
pub active: Option,
}
pub struct Tile {
pub kind: TileIndex,
pub item: Option- ,
}
pub struct Player {
pub name: String,
pub class: PlayerClass,
pub character: i32,
pub interacting: Option<(IVec2, Hand)>,
pub items: [Option
- ; 2],
pub communicate_persist: Option<(Message, MessageTimeout)>,
pub movement: MovementBase,
}
#[derive(Default)]
pub struct Game {
pub data: Arc,
pub tiles: HashMap,
pub walkable: HashSet,
pub players: HashMap,
pub players_spatial_index: SpatialIndex,
pub end: Option,
pub lobby: bool,
pub environment_effects: HashSet,
pub score: Score,
}
impl Game {
pub fn apply_packet(&mut self, packet: PacketC) {
match packet {
PacketC::Data { data } => {
self.data = Arc::new(data);
}
PacketC::AddPlayer {
id,
position,
character,
class,
name,
} => {
self.players.insert(
id,
Player {
name,
character,
class,
interacting: None,
items: [const { None }; 2],
communicate_persist: None,
movement: MovementBase::new(position),
},
);
}
PacketC::RemovePlayer { id } => {
self.players.remove(&id);
}
PacketC::Movement {
player,
pos,
rot,
boost,
dir,
} => {
if let Some(p) = self.players.get_mut(&player) {
p.movement.input(dir, boost);
p.movement.position = pos;
p.movement.rotation = rot;
}
}
PacketC::MoveItem { from, to } => {
*self.get_item(to) = self.get_item(from).take();
}
PacketC::SetItem { location, item } => {
*self.get_item(location) = item.map(|kind| Item { kind, active: None });
}
PacketC::ClearProgress { item } => {
self.get_item(item).as_mut().unwrap().active = None;
}
PacketC::SetProgress {
item,
position,
player,
speed,
warn,
} => {
self.get_item(item).as_mut().unwrap().active = Some(Involvement {
player,
speed,
warn,
position,
recipe: RecipeIndex(0),
});
}
PacketC::UpdateMap {
tile,
kind,
neighbors: _,
} => {
if let Some(kind) = kind {
self.tiles.insert(tile, Tile { kind, item: None });
if self.data.tile_collide[kind.0] {
self.walkable.remove(&tile);
} else {
self.walkable.insert(tile);
}
} else {
self.tiles.remove(&tile);
self.walkable.remove(&tile);
}
}
PacketC::Communicate {
player,
message,
timeout,
} => {
if let Some(timeout) = &timeout
&& let Some(player) = self.players.get_mut(&player)
{
player.communicate_persist = message.to_owned().map(|m| (m, *timeout));
}
}
PacketC::Score(score) => {
self.score = score;
}
PacketC::SetIngame { state: _, lobby } => {
self.lobby = lobby;
}
PacketC::Environment { effects } => {
self.environment_effects = effects;
}
_ => (),
}
}
pub fn tick(&mut self, dt: f32) {
self.score.time_remaining -= dt as f64;
self.score.time_remaining -= self.score.time_remaining.max(0.);
for (&pid, player) in &mut self.players {
player.movement.update(&self.walkable, dt);
if let Some((_, timeout)) = &mut player.communicate_persist {
timeout.remaining -= dt;
if timeout.remaining < 0. {
player.communicate_persist = None;
}
}
self.players_spatial_index
.update_entry(pid, player.movement.position);
}
for player in self.players.values_mut() {
for item in &mut player.items {
if let Some(item) = item {
if let Some(active) = &mut item.active {
active.position += active.speed;
}
}
}
}
for tile in self.tiles.values_mut() {
if let Some(item) = &mut tile.item {
if let Some(active) = &mut item.active {
active.position += active.speed;
}
}
}
self.players_spatial_index.all(|p1, pos1| {
self.players_spatial_index.query(pos1, 2., |p2, _pos2| {
if let [Some(a), Some(b)] = self.players.get_many_mut([&p1, &p2]) {
a.movement.collide(&mut b.movement, dt)
}
})
});
}
pub fn get_item(&mut self, location: ItemLocation) -> &mut Option
- {
match location {
ItemLocation::Tile(pos) => &mut self.tiles.get_mut(&pos).unwrap().item,
ItemLocation::Player(pid, hand) => {
&mut self.players.get_mut(&pid).unwrap().items[hand.index()]
}
}
}
}
impl From for Tile {
fn from(kind: TileIndex) -> Self {
Self { kind, item: None }
}
}