/*
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]) {
item_sprites[self.kind.0].draw_at(ctx, self.position)
}
}