diff options
author | metamuffin <metamuffin@disroot.org> | 2024-12-25 23:13:12 +0100 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-12-25 23:13:12 +0100 |
commit | 6ca4b3377a08fa30a13835d59f9558419f7f5cd1 (patch) | |
tree | 711c5fd11a86a2f228f2f14912357f46ad7d7b73 | |
parent | 31507963a05dffb3327b62dd54629420061d6c4d (diff) | |
download | hurrycurry-6ca4b3377a08fa30a13835d59f9558419f7f5cd1.tar hurrycurry-6ca4b3377a08fa30a13835d59f9558419f7f5cd1.tar.bz2 hurrycurry-6ca4b3377a08fa30a13835d59f9558419f7f5cd1.tar.zst |
editor save and play features
-rw-r--r-- | Cargo.lock | 18 | ||||
-rw-r--r-- | server/editor/Cargo.toml | 4 | ||||
-rw-r--r-- | server/editor/src/main.rs | 108 | ||||
-rw-r--r-- | server/editor/src/save.rs | 69 |
4 files changed, 176 insertions, 23 deletions
@@ -1204,7 +1204,9 @@ dependencies = [ "hurrycurry-protocol", "log", "rustls", + "serde", "serde_json", + "serde_yml", "shlex", "tokio", "tokio-tungstenite", @@ -2074,9 +2076,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.86" +version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" dependencies = [ "unicode-ident", ] @@ -2722,18 +2724,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.210" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" +checksum = "0b9781016e935a97e8beecf0c933758c97a5520d32930e460142b4cd80c6338e" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.210" +version = "1.0.216" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" +checksum = "46f859dbbf73865c6627ed570e78961cd3ac92407a2d117204c49232485da55e" dependencies = [ "proc-macro2", "quote", @@ -2904,9 +2906,9 @@ checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" -version = "2.0.77" +version = "2.0.91" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "d53cbcb5a243bd33b7858b1d7f4aca2153490815872d86d955d6ea29f743c035" dependencies = [ "proc-macro2", "quote", diff --git a/server/editor/Cargo.toml b/server/editor/Cargo.toml index c8f15b0a..4c0fa307 100644 --- a/server/editor/Cargo.toml +++ b/server/editor/Cargo.toml @@ -12,10 +12,12 @@ anyhow = "1.0.89" tokio-tungstenite = { version = "0.24.0", features = [ "rustls-tls-native-roots", ] } +serde = { version = "1.0.216", features = ["derive"] } rustls = { version = "0.23.13", features = ["ring"] } clap = { version = "4.5.23", features = ["derive"] } futures-util = "0.3.31" shlex = "1.3.0" +serde_yml = "0.0.12" hurrycurry-protocol = { path = "../protocol" } -hurrycurry-client-lib = { path = "../client-lib" } +hurrycurry-client-lib = { path = "../client-lib", features = ["sync-network"] } diff --git a/server/editor/src/main.rs b/server/editor/src/main.rs index 1ae4c81e..c5b6f61d 100644 --- a/server/editor/src/main.rs +++ b/server/editor/src/main.rs @@ -1,16 +1,23 @@ +pub mod save; + use anyhow::{Result, anyhow}; use clap::Parser; use futures_util::{SinkExt, StreamExt}; +use hurrycurry_client_lib::network::sync::Network; use hurrycurry_protocol::{ Gamedata, Hand, Message, PacketC, PacketS, PlayerClass, PlayerID, TileIndex, VERSION, glam::{IVec2, Vec2, ivec2}, movement::MovementBase, }; use log::{debug, info, warn}; +use save::export_state; use std::{ collections::{HashMap, HashSet}, + fs::File, + io::Write, net::SocketAddr, - time::Instant, + thread::{sleep, spawn}, + time::{Duration, Instant}, }; use tokio::net::{TcpListener, TcpStream}; @@ -24,8 +31,9 @@ struct Args { #[derive(Parser)] #[clap(multicall = true)] -enum Command { +pub enum Command { Play, + Save, } #[tokio::main] @@ -41,15 +49,17 @@ async fn main() -> Result<()> { } } -const TILES: &[&str] = &[ - "grass", - "floor", - "counter", - "oven", - "stove", - "cuttingboard", - "chair", - "table", +const TILES: &[(&str, char, u8)] = &[ + ("grass", 'a', 1), + ("floor", 'b', 1), + ("counter", 'c', 0), + ("oven", 'd', 0), + ("stove", 'e', 0), + ("cuttingboard", 'f', 0), + ("chair", 'g', 1), + ("table", 'h', 0), + ("wall", 'h', 2), + ("counter-window", 'h', 0), ]; #[allow(unused_assignments)] @@ -65,6 +75,8 @@ async fn handle_conn(sock: TcpStream, addr: SocketAddr) -> Result<()> { movement: MovementBase::new(Vec2::ZERO), last_movement: Instant::now(), tile: TileIndex(0), + chef_spawn: ivec2(0, 0), + customer_spawn: ivec2(0, 0), }; state.out.push(PacketC::Version { @@ -76,7 +88,7 @@ async fn handle_conn(sock: TcpStream, addr: SocketAddr) -> Result<()> { data: Gamedata { tile_collide: TILES.iter().map(|_| false).collect(), tile_interact: TILES.iter().map(|_| true).collect(), - tile_names: TILES.iter().map(|s| s.to_string()).collect(), + tile_names: TILES.iter().map(|(name, _, _)| name.to_string()).collect(), current_map: "editor".to_owned(), ..Default::default() }, @@ -128,7 +140,7 @@ async fn handle_conn(sock: TcpStream, addr: SocketAddr) -> Result<()> { Ok(()) } -struct State { +pub struct State { tiles: HashMap<IVec2, TileIndex>, walkable: HashSet<IVec2>, out: Vec<PacketC>, @@ -136,6 +148,9 @@ struct State { movement: MovementBase, last_movement: Instant, tile: TileIndex, + + customer_spawn: IVec2, + chef_spawn: IVec2, } impl State { @@ -222,6 +237,7 @@ impl State { message: Some(Message::Text(t)), .. } => { + let t = t.strip_prefix("/").unwrap_or(&t); self.handle_command( Command::try_parse_from( shlex::split(&t) @@ -239,7 +255,7 @@ impl State { self.tile.0 += 1; self.tile.0 %= TILES.len(); self.out.push(PacketC::ServerMessage { - message: Message::Text(format!("tile brush: {}", TILES[self.tile.0])), + message: Message::Text(format!("tile brush: {}", TILES[self.tile.0].0)), error: false, }); } @@ -262,14 +278,78 @@ impl State { Ok(()) } + pub fn save(&mut self) -> Result<()> { + let e = export_state(&self); + File::create("data/maps/editor.yaml")?.write_all(e.as_bytes())?; + self.out.push(PacketC::ServerMessage { + message: Message::Text(format!("Map saved.")), + error: false, + }); + Ok(()) + } + pub fn handle_command(&mut self, command: Command) -> Result<()> { match command { Command::Play => { + self.save()?; + spawn(move || { + if let Err(e) = start_map_bot("ws://127.0.0.1:27032", "editor") { + warn!("editor bot: {e}") + } + }); self.out.push(PacketC::Redirect { uri: vec!["ws://127.0.0.1:27032".to_string()], }); } + Command::Save => { + self.save()?; + } } Ok(()) } } + +fn start_map_bot(address: &str, mapname: &str) -> Result<()> { + let mut network = Network::connect(address)?; + + network.queue_out.push_back(PacketS::Join { + name: "editor-bot".to_owned(), + character: 0, + class: PlayerClass::Bot, + id: None, + }); + + let mut timer = 10.; + loop { + let dt = 1. / 50.; + + network.poll()?; + + while let Some(packet) = network.queue_in.pop_front() { + match &packet { + PacketC::Joined { id } => { + network.queue_out.push_back(PacketS::Communicate { + player: *id, + message: Some(Message::Text(format!("/start {mapname}"))), + timeout: None, + pin: None, + }); + } + PacketC::ServerMessage { + message, + error: true, + } => { + warn!("server error message: {message:?}"); + } + _ => (), + } + } + + timer -= dt; + if timer < 0. { + break; + } + sleep(Duration::from_secs_f32(dt)); + } + Ok(()) +} diff --git a/server/editor/src/save.rs b/server/editor/src/save.rs new file mode 100644 index 00000000..a77cd609 --- /dev/null +++ b/server/editor/src/save.rs @@ -0,0 +1,69 @@ +use crate::{State, TILES}; +use hurrycurry_protocol::glam::{IVec2, ivec2}; +use serde::Serialize; +use std::collections::{HashMap, HashSet}; + +#[rustfmt::skip] +#[derive(Debug, Clone, Serialize)] +pub struct MapDecl { + map: Vec<String>, + tiles: HashMap<char, String>, + items: HashMap<char, String>, + collider: Vec<String>, + walkable: Vec<String>, + chef_spawn: char, + customer_spawn: char, + score_baseline: i64, +} + +pub fn export_state(state: &State) -> String { + let mut cmin = IVec2::MAX; + let mut cmax = IVec2::MIN; + for pos in state.tiles.keys().copied() { + cmin = cmin.min(pos); + cmax = cmax.max(pos); + } + + let mut map = Vec::new(); + let mut tiles = HashMap::new(); + let mut collider = HashSet::new(); + let mut walkable = HashSet::new(); + for y in cmin.y..=cmax.y { + let mut line = String::new(); + for x in cmin.x..=cmax.x { + let p = ivec2(x, y); + line.push(if let Some(t) = state.tiles.get(&p) { + let c = if p == state.chef_spawn { + '~' + } else if p == state.customer_spawn { + '!' + } else { + TILES[t.0].1 + }; + if TILES[t.0].2 == 2 { + collider.insert(TILES[t.0].0.to_string()); + } + if TILES[t.0].2 == 1 { + walkable.insert(TILES[t.0].0.to_string()); + } + tiles.insert(c, TILES[t.0].0.to_string()); + c + } else { + ' ' + }) + } + map.push(line); + } + + let decl: MapDecl = MapDecl { + map, + tiles, + items: HashMap::new(), + collider: collider.into_iter().collect(), + walkable: walkable.into_iter().collect(), + chef_spawn: '~', + customer_spawn: '!', + score_baseline: 200, + }; + serde_yml::to_string(&decl).unwrap() +} |