/* 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 crate::{ sprite_renderer::{SpriteRect, SpriteRenderer}, tilemap::Tilemap, }; use hurrycurry_protocol::{ glam::{IVec2, Vec2}, movement::MovementBase, ClientGamedata, ItemIndex, ItemLocation, PacketC, PacketS, PlayerID, TileIndex, }; use log::{info, warn}; use sdl2::{ keyboard::{KeyboardState, Scancode}, rect::{FRect, Rect}, }; use std::collections::{HashMap, HashSet, VecDeque}; pub struct Game { data: ClientGamedata, tiles: HashMap, tilemap: Tilemap, collision_map: HashSet, players: HashMap, my_id: PlayerID, item_sprites: Vec, movement_send_cooldown: f32, } pub struct Tile { kind: TileIndex, item: Option, } pub struct Player { movement: MovementBase, item: Option, name: String, character: i32, } pub struct Item { position: Vec2, kind: ItemIndex, } impl Game { pub fn new() -> Self { Self { tiles: HashMap::new(), players: HashMap::new(), tilemap: Tilemap::default(), my_id: PlayerID(0), data: ClientGamedata::default(), collision_map: HashSet::new(), movement_send_cooldown: 0., item_sprites: Vec::new(), } } pub fn packet_in(&mut self, packet: PacketC, renderer: &mut SpriteRenderer) { match packet { PacketC::Init { id } => self.my_id = id, PacketC::Data { data } => { self.tilemap.init(&data.tile_names, renderer.metadata()); self.item_sprites = data .item_names .iter() .map(|name| { SpriteRect::new( renderer .metadata() .get(&format!("{name}:a")) .copied() .unwrap_or(Rect::new(0, 0, 32, 24)), ) }) .collect(); self.data = data; } 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.collision_map.remove(&tile); } else { self.collision_map.insert(tile); } } else { self.tiles.remove(&tile); self.collision_map.remove(&tile); } self.tilemap.set(tile, kind, neighbors); } PacketC::AddPlayer { id, position, character, name, } => { info!("add player {} {name:?}", id.0); self.players.insert( id, Player { character, name, item: None, movement: MovementBase { position, facing: Vec2::X, rotation: 0., velocity: Vec2::ZERO, boosting: false, stamina: 0., }, }, ); } PacketC::RemovePlayer { id } => { info!("remove player {}", id.0); self.players.remove(&id); } PacketC::Position { player, pos, rot, boosting, } => { if player != self.my_id { if let Some(p) = self.players.get_mut(&player) { p.movement.position = pos; p.movement.rotation = rot; p.movement.boosting = boosting; } } } 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, position: Vec2::ZERO, }) } PacketC::SetProgress { item, progress, warn, } => (), PacketC::Collide { player, force } => (), PacketC::Communicate { player, message, persist, } => (), PacketC::ServerMessage { text } => (), PacketC::Score { points, demands_failed, demands_completed, time_remaining, } => (), PacketC::SetIngame { state, lobby } => (), PacketC::Error { message } => { warn!("server error: {message:?}") } _ => (), } } 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) => &mut self.players.get_mut(&pid).unwrap().item, } } pub fn tick(&mut self, dt: f32, keyboard: &KeyboardState, packet_out: &mut VecDeque) { let direction = IVec2::new( keyboard.is_scancode_pressed(Scancode::D) as i32 - keyboard.is_scancode_pressed(Scancode::A) as i32, keyboard.is_scancode_pressed(Scancode::S) as i32 - keyboard.is_scancode_pressed(Scancode::W) as i32, ) .as_vec2(); let boost = keyboard.is_scancode_pressed(Scancode::K); let interact = keyboard.is_scancode_pressed(Scancode::Space) | keyboard.is_scancode_pressed(Scancode::J); self.movement_send_cooldown -= dt; let send_movement = self.movement_send_cooldown < 0.; if send_movement { self.movement_send_cooldown += 0.04 } for (pid, player) in &mut self.players { if *pid == self.my_id { let movement_packet = player .movement .update(&self.collision_map, direction, boost, dt); if send_movement { packet_out.push_back(movement_packet); } } if let Some(item) = &mut player.item { item.position = player.movement.position } } for (pos, tile) in &mut self.tiles { if let Some(item) = &mut tile.item { item.position = pos.as_vec2() + 0.5 } } } pub fn draw(&self, ctx: &mut SpriteRenderer) { self.tilemap.draw(ctx); for p in self.players.values() { let src = ctx.misc_textures().player; let dst = FRect::new( p.movement.position.x - src.width() as f32 / 32. / 2., p.movement.position.y + 0.3 - src.height() as f32 / 24., src.width() as f32 / 32., src.height() as f32 / 24., ); ctx.draw(dst.y + dst.h + 1., src, dst); if let Some(item) = &p.item { item.draw(ctx, &self.item_sprites) } } for tile in self.tiles.values() { if let Some(item) = &tile.item { item.draw(ctx, &self.item_sprites) } } } } impl Item { pub fn draw(&self, ctx: &mut SpriteRenderer, item_sprites: &[SpriteRect]) { eprintln!("item {} at {}", self.kind.0, self.position); item_sprites[self.kind.0].draw_at(ctx, self.position) } }