summaryrefslogtreecommitdiff
path: root/server/registry/src/lobby.rs
diff options
context:
space:
mode:
Diffstat (limited to 'server/registry/src/lobby.rs')
-rw-r--r--server/registry/src/lobby.rs164
1 files changed, 164 insertions, 0 deletions
diff --git a/server/registry/src/lobby.rs b/server/registry/src/lobby.rs
new file mode 100644
index 00000000..f74dd48e
--- /dev/null
+++ b/server/registry/src/lobby.rs
@@ -0,0 +1,164 @@
+use crate::Registry;
+use anyhow::Result;
+use hurrycurry_protocol::{
+ glam::{ivec2, IVec2, Vec2},
+ movement::MovementBase,
+ registry::Entry,
+ Gamedata, PacketC, PacketS, PlayerID, TileIndex, VERSION,
+};
+use log::{error, info, warn};
+use rocket::futures::{SinkExt, StreamExt};
+use std::{
+ collections::{HashMap, HashSet},
+ 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<RwLock<Registry>>, 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<RwLock<Registry>>, 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 mut tiles = HashMap::<IVec2, TileIndex>::new();
+ for x in -5..5 + 5 * entries.len() as i32 {
+ for y in -5..5 {
+ tiles.insert(ivec2(x, y), TileIndex(0));
+ }
+ }
+ let portal_location = |i: usize| ivec2(i as i32 * 5 + 5, 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_collide: TILES.iter().map(|(_, c)| *c).collect(),
+ tile_interact: TILES.iter().map(|_| false).collect(),
+ 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::SetIngame {
+ state: true,
+ lobby: false, // very ironic
+ });
+
+ let (mut write, mut read) = sock.split();
+
+ loop {
+ for p in out.drain(..) {
+ write
+ .send(tokio_tungstenite::tungstenite::Message::Text(
+ serde_json::to_string(&p).unwrap(),
+ ))
+ .await?;
+ }
+
+ let Some(message) = read.next().await.transpose()? else {
+ break;
+ };
+ let packet = match message {
+ Message::Text(line) => match serde_json::from_str::<PacketS>(&line) {
+ Ok(p) => p,
+ Err(e) => {
+ warn!("Invalid json packet: {e}");
+ break;
+ }
+ },
+ Message::Close(_) => break,
+ _ => continue,
+ };
+
+ let mut joined = false;
+ let mut redirected = false;
+ let mut movement = MovementBase::new(Vec2::ZERO);
+ let mut last_movement = Instant::now();
+ 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,
+ character,
+ name,
+ });
+ 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 {
+ redirected = true;
+ out.push(PacketC::Redirect {
+ uri: e.address.clone(),
+ });
+ }
+ }
+ }
+ }
+ _ => (),
+ }
+ }
+ Ok(())
+}