use crate::Registry; use anyhow::Result; use hurrycurry_protocol::{ glam::{ivec2, vec2, IVec2, Vec2}, movement::MovementBase, registry::Entry, Gamedata, PacketC, PacketS, PlayerClass, PlayerID, TileIndex, VERSION, }; use log::{error, info, warn}; use rocket::futures::{SinkExt, StreamExt}; use std::{ collections::{BTreeMap, HashMap, HashSet}, f32::consts::PI, net::SocketAddr, sync::Arc, time::Instant, }; use tokio::{ net::{TcpListener, TcpStream}, spawn, sync::RwLock, }; use tokio_tungstenite::tungstenite::Message; pub(crate) async fn lobby_wrapper(registry: Arc>, saddr: SocketAddr) { let Err(e) = lobby(registry, saddr).await; error!("lobby crashed: {e}"); } const TILES: &[(&str, bool)] = &[("grass", false), ("black-hole", false)]; async fn lobby(registry: Arc>, saddr: SocketAddr) -> Result { let ws_listener = TcpListener::bind(saddr).await?; loop { let (sock, addr) = ws_listener.accept().await?; let r = registry.clone(); spawn(async move { let entries = r.read().await.entries.clone(); if let Err(e) = handle_conn(sock, addr, &entries).await { warn!("client error: {e}"); } }); } } #[allow(unused_assignments)] async fn handle_conn(sock: TcpStream, addr: SocketAddr, entries: &[Entry]) -> Result<()> { let sock = tokio_tungstenite::accept_async(sock).await?; info!("{addr} connected via websocket"); let ecount = entries.len().max(2) as f32; let radius = ecount * 5. / PI; let portal_location = |i: usize| { let t = i as f32 / ecount * PI * 2.; vec2(t.sin() * radius, t.cos() * radius).round().as_ivec2() }; let mut tiles = HashMap::::new(); let bounds = (radius + 5.).ceil() as i32; for x in -bounds..bounds { for y in -bounds..bounds { tiles.insert(ivec2(x, y), TileIndex(0)); } } for (i, _) in entries.iter().enumerate() { tiles.insert(portal_location(i), TileIndex(1)); } let mut out = Vec::new(); out.push(PacketC::Version { major: VERSION.0, minor: VERSION.1, supports_bincode: false, }); out.push(PacketC::Data { data: Gamedata { tile_walkable: (0..TILES.len()).map(TileIndex).collect(), tile_placeable_items: BTreeMap::new(), tile_names: TILES.iter().map(|(s, _)| s.to_string()).collect(), current_map: "registry".to_owned(), ..Default::default() }, }); let walkable = HashSet::from_iter(tiles.iter().filter(|(_, v)| !TILES[v.0].1).map(|(k, _)| *k)); for (&tile, &kind) in &tiles { out.push(PacketC::UpdateMap { tile, kind: Some(kind), neighbors: [None, None, None, None], }); } out.push(PacketC::FlushMap); out.push(PacketC::SetIngame { state: true, lobby: false, // very ironic }); let (mut write, mut read) = sock.split(); let mut joined = false; let mut redirected = false; let mut movement = MovementBase::new(Vec2::ZERO); let mut last_movement = Instant::now(); loop { for p in out.drain(..) { write .send(tokio_tungstenite::tungstenite::Message::Text( serde_json::to_string(&p).unwrap().into(), )) .await?; } let Some(message) = read.next().await.transpose()? else { break; }; let packet = match message { Message::Text(line) => match serde_json::from_str::(&line) { Ok(p) => p, Err(e) => { warn!("Invalid json packet: {e}"); break; } }, Message::Close(_) => break, _ => continue, }; match packet { PacketS::Join { character, name, .. } if !joined => { out.push(PacketC::Joined { id: PlayerID(0) }); out.push(PacketC::AddPlayer { id: PlayerID(0), position: movement.position, class: PlayerClass::Chef, character, name, }); for (i, e) in entries.iter().enumerate() { out.push(PacketC::ServerHint { position: Some(portal_location(i)), message: Some(hurrycurry_protocol::Message::Text(e.name.clone())), player: PlayerID(0), }); } joined = true; } PacketS::Leave { .. } if joined => { out.push(PacketC::RemovePlayer { id: PlayerID(0) }); joined = false; } PacketS::Movement { player, dir, boost, pos, } => { let dt = last_movement.elapsed(); last_movement += dt; movement.position = pos.unwrap_or(movement.position); movement.input(dir, boost); movement.update(&walkable, dt.as_secs_f32()); out.push(movement.movement_packet_c(player)); if !redirected { for (i, e) in entries.iter().enumerate() { if movement .position .distance(portal_location(i).as_vec2() + 0.5) < 0.5 { redirected = true; out.push(PacketC::Redirect { uri: e.address.clone(), }); break; } } } } _ => (), } } Ok(()) }