diff options
Diffstat (limited to 'server/registry/src')
-rw-r--r-- | server/registry/src/lobby.rs | 164 | ||||
-rw-r--r-- | server/registry/src/main.rs | 25 |
2 files changed, 184 insertions, 5 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(()) +} diff --git a/server/registry/src/main.rs b/server/registry/src/main.rs index 670ac3a6..2e0b7656 100644 --- a/server/registry/src/main.rs +++ b/server/registry/src/main.rs @@ -15,12 +15,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>. */ +#![feature(never_type)] pub mod conn_test; pub mod list; +pub mod lobby; pub mod register; use hurrycurry_protocol::registry::Entry; use list::{generate_html_list, generate_json_list, r_list}; +use lobby::lobby_wrapper; use log::{error, info}; use register::r_register; use rocket::{get, routes, Config}; @@ -28,7 +31,7 @@ use std::{ cmp::Reverse, collections::HashMap, env::var, - net::{IpAddr, Ipv4Addr}, + net::{IpAddr, Ipv4Addr, SocketAddr}, str::FromStr, sync::Arc, time::{Duration, Instant}, @@ -40,19 +43,29 @@ const MAX_SERVERS: usize = 128; fn main() { env_logger::init_from_env("LOG"); + + let address = var("BIND_ADDR") + .map(|a| IpAddr::from_str(&a).unwrap()) + .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)); + let http_port = var("PORT").map(|p| p.parse().unwrap()).unwrap_or(27033); + let lobby_port = var("LOBBY_PORT") + .map(|p| p.parse().unwrap()) + .unwrap_or(27034); + let lobby_addr = SocketAddr::new(address, lobby_port); + let registry = Arc::new(RwLock::new(Registry::default())); + tokio::runtime::Builder::new_multi_thread() .enable_all() .build() .unwrap() .block_on(async move { tokio::task::spawn(Registry::update_loop(registry.clone())); + tokio::task::spawn(lobby_wrapper(registry.clone(), lobby_addr)); rocket::build() .configure(Config { - address: var("BIND_ADDR") - .map(|a| IpAddr::from_str(&a).unwrap()) - .unwrap_or(IpAddr::V4(Ipv4Addr::LOCALHOST)), - port: var("PORT").map(|p| p.parse().unwrap()).unwrap_or(27033), + address, + port: http_port, ..Default::default() }) .manage(registry) @@ -70,6 +83,7 @@ fn main() { struct Registry { json_response: Arc<str>, html_response: Arc<str>, + entries: Vec<Entry>, servers: HashMap<u128, InternalEntry>, } @@ -107,6 +121,7 @@ impl Registry { self.json_response = generate_json_list(&list)?; self.html_response = generate_html_list(&list)?; + self.entries = list; info!("done. {} servers registered", self.servers.len()); Ok(()) |