use crate::{ data::Gamedata, interaction::{interact, tick_tile, InteractEffect, TickEffect}, protocol::{ItemIndex, PacketC, PacketS, PlayerID, RecipeIndex, TileIndex}, }; use anyhow::{anyhow, bail, Result}; use glam::{IVec2, Vec2}; use log::info; use std::{ collections::{HashMap, VecDeque}, ops::Deref, sync::Arc, }; #[derive(Debug, PartialEq)] pub struct Involvement { pub recipe: RecipeIndex, pub progress: f32, pub working: usize, } #[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 character: usize, pub position: Vec2, pub interacting: Option, pub item: Option, } pub struct Game { data: Arc, tiles: HashMap, players: HashMap, packet_out: VecDeque, } impl Game { pub fn new(gamedata: Arc) -> Self { let mut g = Self { data: gamedata.clone(), packet_out: Default::default(), players: Default::default(), tiles: Default::default(), }; for (&p, &t) in &gamedata.initial_map { g.tiles.insert(p, t.into()); } g } pub fn tiles(&self) -> &HashMap { &self.tiles } pub fn packet_out(&mut self) -> Option { self.packet_out.pop_front() } pub fn prime_client(&self, id: PlayerID) -> Vec { let mut out = Vec::new(); out.push(PacketC::Init { id, data: self.data.deref().to_owned(), }); for (&id, player) in &self.players { out.push(PacketC::AddPlayer { id, position: player.position, character: player.character, name: player.name.clone(), item: player.item.as_ref().map(|i| i.kind), }) } for (&tile, tdata) in &self.tiles { out.push(PacketC::UpdateMap { pos: tile, neighbours: [ self.tiles.get(&(tile + IVec2::NEG_Y)).map(|e| e.kind), self.tiles.get(&(tile + IVec2::NEG_X)).map(|e| e.kind), self.tiles.get(&(tile + IVec2::Y)).map(|e| e.kind), self.tiles.get(&(tile + IVec2::X)).map(|e| e.kind), ], tile: tdata.kind.clone(), }); if let Some(item) = &tdata.item { out.push(PacketC::ProduceItem { tile, item: item.kind, }) } } out } pub fn packet_in(&mut self, player: PlayerID, packet: PacketS) -> Result<()> { match packet { PacketS::Join { name, character } => { self.players.insert( player, Player { item: None, character, position: if player < 0 { self.data.customer_spawn } else { self.data.chef_spawn }, interacting: None, name: name.clone(), }, ); self.packet_out.push_back(PacketC::AddPlayer { id: player, name, position: self.data.chef_spawn, character, item: None, }); } PacketS::Leave => { let p = self .players .remove(&player) .ok_or(anyhow!("player does not exist"))?; if let Some(item) = p.item { let pos = p.position.floor().as_ivec2(); if let Some(tile) = self.tiles.get_mut(&pos) { if tile.item.is_none() { self.packet_out.push_back(PacketC::ProduceItem { tile: pos, item: item.kind, }); tile.item = Some(item); } } } self.packet_out .push_back(PacketC::RemovePlayer { id: player }) } PacketS::Position { pos, rot } => { self.packet_out .push_back(PacketC::Position { player, pos, rot }); } PacketS::Collide { player, force } => { self.packet_out .push_back(PacketC::Collide { player, force }); } PacketS::Interact { pos, edge } => { info!("interact {pos:?} edge={edge}"); let pid = player; let player = self .players .get_mut(&player) .ok_or(anyhow!("player does not exist"))?; let tile = self .tiles .get_mut(&pos) .ok_or(anyhow!("tile does not exist"))?; if edge && player.interacting.is_some() { bail!("already interacting") } if !edge && player.interacting != Some(pos) { bail!("already not interacting here") } let tile_had_item = tile.item.is_some(); let player_had_item = player.item.is_some(); if let Some(effect) = interact(&self.data, edge, tile, player) { match effect { InteractEffect::Put => self.packet_out.push_back(PacketC::PutItem { player: pid, tile: pos, }), InteractEffect::Take => self.packet_out.push_back(PacketC::TakeItem { player: pid, tile: pos, }), InteractEffect::Produce => { if tile_had_item { self.packet_out .push_back(PacketC::ConsumeItem { tile: pos }); } if player_had_item { self.packet_out.push_back(PacketC::PutItem { player: pid, tile: pos, }); self.packet_out .push_back(PacketC::ConsumeItem { tile: pos }); } if let Some(i) = &player.item { self.packet_out.push_back(PacketC::ProduceItem { tile: pos, item: i.kind, }); self.packet_out.push_back(PacketC::TakeItem { player: pid, tile: pos, }) } if let Some(i) = &tile.item { self.packet_out.push_back(PacketC::ProduceItem { tile: pos, item: i.kind, }); } } } } player.interacting = if edge { Some(pos) } else { None }; } } Ok(()) } pub fn tick(&mut self, dt: f32) { for (&pos, tile) in &mut self.tiles { if let Some(effect) = tick_tile(dt, &self.data, tile) { match effect { TickEffect::Progress => self.packet_out.push_back(PacketC::SetActive { tile: pos, progress: tile .item .as_ref() .unwrap() .active .as_ref() .map(|i| i.progress), }), TickEffect::Produce => { self.packet_out .push_back(PacketC::ConsumeItem { tile: pos }); if let Some(item) = &tile.item { self.packet_out.push_back(PacketC::ProduceItem { tile: pos, item: item.kind, }); } } } } // let items = tile.items.iter().map(|e| self.items[e]).collect::>(); // tick_tile( // dt, // &self.data, // tile.kind, // &mut tile.active, // items, // |out| match out { // Out::Take(_) | Out::Put => { // unreachable!() // } // Out::Produce(kind) => { // info!("produce"); // let id = self.item_id_counter; // self.item_id_counter += 1; // self.items.insert(id, kind); // tile.items.push(id); // self.packet_out // .push_back(PacketC::ProduceItem { id, pos, kind }); // } // Out::Consume(index) => { // info!("consume"); // let id = tile.items.remove(index); // info!("left {:?}", tile.items); // self.packet_out.push_back(PacketC::ConsumeItem { id, pos }); // } // Out::SetActive(progress) => { // self.packet_out.push_back(PacketC::SetActive { // tile: pos, // progress, // }); // } // }, // ); } } } impl From for Tile { fn from(kind: TileIndex) -> Self { Self { kind, item: None } } }