aboutsummaryrefslogtreecommitdiff
path: root/pixel-client/src/game.rs
diff options
context:
space:
mode:
authormetamuffin <metamuffin@disroot.org>2024-07-16 23:38:46 +0200
committermetamuffin <metamuffin@disroot.org>2024-07-16 23:38:46 +0200
commit775b0148cec4329a6abb19d03220dc1d8a8b68c3 (patch)
tree9e715df1db4f23a9c3f1e9c07cf7e93e376b512f /pixel-client/src/game.rs
parent3a358c6dd39aa78319549658adf1028cea61f643 (diff)
downloadhurrycurry-775b0148cec4329a6abb19d03220dc1d8a8b68c3.tar
hurrycurry-775b0148cec4329a6abb19d03220dc1d8a8b68c3.tar.bz2
hurrycurry-775b0148cec4329a6abb19d03220dc1d8a8b68c3.tar.zst
rename pixel client
Diffstat (limited to 'pixel-client/src/game.rs')
-rw-r--r--pixel-client/src/game.rs344
1 files changed, 344 insertions, 0 deletions
diff --git a/pixel-client/src/game.rs b/pixel-client/src/game.rs
new file mode 100644
index 00000000..7d8e466a
--- /dev/null
+++ b/pixel-client/src/game.rs
@@ -0,0 +1,344 @@
+/*
+ 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 <https://www.gnu.org/licenses/>.
+
+*/
+use crate::{
+ helper::Vec2InterpolateExt,
+ render::{
+ misc::MiscTextures,
+ sprite::{Sprite, SpriteDraw},
+ AtlasLayout, 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::Rect,
+};
+use std::collections::{HashMap, HashSet, VecDeque};
+
+pub struct Game {
+ data: ClientGamedata,
+ tiles: HashMap<IVec2, Tile>,
+ tilemap: Tilemap,
+ collision_map: HashSet<IVec2>,
+ players: HashMap<PlayerID, Player>,
+ my_id: PlayerID,
+
+ camera_center: Vec2,
+ misc_textures: MiscTextures,
+ item_sprites: Vec<Sprite>,
+ movement_send_cooldown: f32,
+ interacting: bool,
+ score: Score,
+}
+
+#[derive(Debug, Default)]
+pub struct Score {
+ points: i64,
+ demands_failed: usize,
+ demands_completed: usize,
+ time_remaining: f32,
+}
+
+pub struct Tile {
+ _kind: TileIndex,
+ item: Option<Item>,
+}
+
+pub struct Player {
+ movement: MovementBase,
+ item: Option<Item>,
+ _name: String,
+ _character: i32,
+}
+
+pub struct Item {
+ position: Vec2,
+ kind: ItemIndex,
+ progress: Option<(f32, bool)>,
+}
+
+impl Game {
+ pub fn new(layout: &AtlasLayout) -> 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.,
+ misc_textures: MiscTextures::init(layout),
+ item_sprites: Vec::new(),
+ interacting: false,
+ score: Score::default(),
+ camera_center: Vec2::ZERO,
+ }
+ }
+
+ 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.atlas_layout());
+ self.item_sprites = data
+ .item_names
+ .iter()
+ .map(|name| {
+ Sprite::new(
+ renderer
+ .atlas_layout()
+ .get(&format!("{name}+a"))
+ .copied()
+ .unwrap_or_else(|| {
+ warn!("no sprite for item {name:?}");
+ Rect::new(0, 0, 32, 24)
+ }),
+ Vec2::new(0., 0.0),
+ 0.1,
+ )
+ })
+ .collect();
+ self.data = data;
+ }
+ PacketC::UpdateMap {
+ tile,
+ kind,
+ neighbors,
+ } => {
+ if let Some(kind) = kind {
+ self.tiles.insert(
+ tile,
+ Tile {
+ _kind: 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: character,
+ _name: 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,
+ progress: None,
+ })
+ }
+ PacketC::SetProgress {
+ item,
+ progress,
+ warn,
+ } => {
+ self.get_item(item).as_mut().unwrap().progress = progress.map(|s| (s, warn));
+ }
+ PacketC::Collide {
+ player: _,
+ force: _,
+ } => (),
+ PacketC::Communicate { .. } => {
+ // TODO
+ }
+ PacketC::ServerMessage { text: _ } => {
+ // TODO
+ }
+ PacketC::Score {
+ points,
+ demands_failed,
+ demands_completed,
+ time_remaining,
+ } => {
+ self.score.points = points;
+ self.score.demands_completed = demands_completed;
+ self.score.demands_failed = demands_failed;
+ self.score.time_remaining = time_remaining.unwrap_or(-1.);
+ }
+ PacketC::SetIngame { state: _, lobby: _ } => {
+ // TODO
+ }
+ PacketC::Error { message } => {
+ warn!("server error: {message:?}")
+ }
+ _ => (),
+ }
+ }
+
+ pub fn get_item(&mut self, location: ItemLocation) -> &mut Option<Item> {
+ 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<PacketS>) {
+ let mut 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);
+
+ if interact {
+ direction *= 0.;
+ }
+
+ self.movement_send_cooldown -= dt;
+ let send_movement = self.movement_send_cooldown < 0.;
+ if send_movement {
+ self.movement_send_cooldown += 0.04
+ }
+
+ self.score.time_remaining -= dt;
+ self.score.time_remaining -= self.score.time_remaining.max(0.);
+
+ if interact != self.interacting {
+ if interact {
+ packet_out.push_back(PacketS::Interact {
+ pos: Some(self.players[&self.my_id].movement.get_interact_target()),
+ });
+ } else {
+ packet_out.push_back(PacketS::Interact { pos: None });
+ }
+ self.interacting = interact;
+ }
+
+ if let Some(player) = self.players.get_mut(&self.my_id) {
+ let movement_packet = player
+ .movement
+ .update(&self.collision_map, direction, boost, dt);
+ if send_movement {
+ packet_out.push_back(movement_packet);
+ }
+
+ self.camera_center.exp_to(player.movement.position, dt * 5.);
+ }
+
+ for (_pid, player) in &mut self.players {
+ 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) {
+ ctx.set_view(-self.camera_center + (ctx.size / ctx.get_scale() / 2.), 1.);
+
+ self.tilemap.draw(ctx);
+
+ for p in self.players.values() {
+ ctx.draw_world(self.misc_textures.player.at(p.movement.position));
+ if let Some(item) = &p.item {
+ item.draw(ctx, &self.item_sprites, &self.misc_textures)
+ }
+ }
+ for tile in self.tiles.values() {
+ if let Some(item) = &tile.item {
+ item.draw(ctx, &self.item_sprites, &self.misc_textures)
+ }
+ }
+ }
+}
+
+impl Item {
+ pub fn draw(&self, ctx: &mut SpriteRenderer, item_sprites: &[Sprite], misc: &MiscTextures) {
+ ctx.draw_world(item_sprites[self.kind.0].at(self.position));
+ if let Some((progress, warn)) = self.progress {
+ let (bg, fg) = if warn {
+ ([100, 0, 0, 200], [255, 0, 0, 200])
+ } else {
+ ([0, 100, 0, 200], [0, 255, 0, 200])
+ };
+ ctx.draw_world(SpriteDraw::overlay(
+ misc.solid,
+ self.position + Vec2::new(-0.5, -1.3),
+ Vec2::new(1., 0.2),
+ Some(bg),
+ ));
+ ctx.draw_world(SpriteDraw::overlay(
+ misc.solid,
+ self.position + Vec2::new(-0.5, -1.3),
+ Vec2::new(progress, 0.2),
+ Some(fg),
+ ))
+ }
+ }
+}