diff options
author | metamuffin <metamuffin@disroot.org> | 2024-07-15 17:01:29 +0200 |
---|---|---|
committer | metamuffin <metamuffin@disroot.org> | 2024-07-15 17:01:29 +0200 |
commit | a09aa839cb548d9ea1ce2cdd30874054ed9a3a80 (patch) | |
tree | 061cb86d3878f62090dd080eaef1f9db6f9454f7 /light-client/src | |
parent | e04589e8b766882375a30c00fb715687a7cc9821 (diff) | |
download | hurrycurry-a09aa839cb548d9ea1ce2cdd30874054ed9a3a80.tar hurrycurry-a09aa839cb548d9ea1ce2cdd30874054ed9a3a80.tar.bz2 hurrycurry-a09aa839cb548d9ea1ce2cdd30874054ed9a3a80.tar.zst |
first visible tiles in light client
Diffstat (limited to 'light-client/src')
-rw-r--r-- | light-client/src/atlas.rs | 154 | ||||
-rw-r--r-- | light-client/src/game.rs | 36 | ||||
-rw-r--r-- | light-client/src/main.rs | 44 | ||||
-rw-r--r-- | light-client/src/network.rs | 48 |
4 files changed, 252 insertions, 30 deletions
diff --git a/light-client/src/atlas.rs b/light-client/src/atlas.rs new file mode 100644 index 00000000..e75430c5 --- /dev/null +++ b/light-client/src/atlas.rs @@ -0,0 +1,154 @@ +use hurrycurry_protocol::{ + glam::{IVec2, Vec2}, + ClientGamedata, ItemIndex, TileIndex, +}; +use sdl2::{ + pixels::PixelFormatEnum, + rect::Rect, + render::{Canvas, Texture, TextureAccess, TextureCreator}, + video::{Window, WindowContext}, +}; +use std::collections::HashMap; + +pub struct SpriteRenderer<'a> { + texture: Texture<'a>, + + tiles: Vec<Rect>, + items: Vec<Rect>, + + view_scale: u32, + view_offset: Vec2, + + sprites: Vec<DrawItem>, +} + +pub struct DrawItem { + z_order: i32, + src: Rect, + dst: Rect, +} + +impl<'a> SpriteRenderer<'a> { + pub fn init(texture_creator: &'a TextureCreator<WindowContext>) -> Self { + let palette = include_str!("../textures/palette.csv") + .split('\n') + .filter(|l| !l.is_empty()) + .map(|s| { + let mut toks = s.split(","); + ( + toks.next().unwrap().chars().next().unwrap(), + [ + toks.next().unwrap().parse::<u8>().unwrap(), + toks.next().unwrap().parse::<u8>().unwrap(), + toks.next().unwrap().parse::<u8>().unwrap(), + toks.next().unwrap().parse::<u8>().unwrap(), + ], + ) + }) + .collect::<HashMap<_, _>>(); + + let mut texels = vec![255; 1024 * 1024 * 4]; + + for (y, line) in include_str!("../textures/atlas.ta").lines().enumerate() { + if line.is_empty() { + continue; + } + for (x, char) in line.chars().enumerate() { + let color = palette.get(&char).unwrap(); + let base = (y * 1024 + x) * 4; + texels[base..base + 4].copy_from_slice(color); + } + } + + let mut texture = texture_creator + .create_texture( + Some(PixelFormatEnum::RGBA8888), + TextureAccess::Streaming, + 1024, + 1024, + ) + .unwrap(); + + texture.update(None, &texels, 1024 * 4).unwrap(); + + Self { + texture, + items: vec![], + tiles: vec![], + sprites: vec![], + view_offset: Vec2::ZERO, + view_scale: 32, + } + } + + pub fn set_sprite_map(&mut self, data: ClientGamedata) { + let meta = include_str!("../textures/atlas.meta.csv") + .lines() + .filter(|l| !l.is_empty()) + .map(|l| { + let mut toks = l.split(","); + let x: i32 = toks.next().unwrap().parse().unwrap(); + let y: i32 = toks.next().unwrap().parse().unwrap(); + let w: u32 = toks.next().unwrap().parse().unwrap(); + let h: u32 = toks.next().unwrap().parse().unwrap(); + let name = toks.next().unwrap().to_string(); + (name, Rect::new(x, y, w, h)) + }) + .collect::<HashMap<_, _>>(); + + self.items = data + .item_names + .iter() + .map(|i| meta.get(i).copied().unwrap_or(Rect::new(0, 0, 100, 100))) + .collect(); + self.tiles = data + .tile_names + .iter() + .map(|i| meta.get(i).copied().unwrap_or(Rect::new(0, 0, 100, 100))) + .collect(); + } + + pub fn draw_tile(&mut self, TileIndex(i): TileIndex, position: IVec2) { + let p = (self.view_offset.as_ivec2() + position) * self.view_scale as i32; + self.sprites.push(DrawItem { + z_order: position.y, + src: self.tiles[i], + dst: Rect::from_center((p.x as i32, p.y as i32), self.view_scale, self.view_scale), + }); + } + pub fn draw_item(&mut self, ItemIndex(i): ItemIndex, position: Vec2) { + self.sprites.push(DrawItem { + z_order: position.y as i32, + src: self.tiles[i], + dst: Rect::from_center( + (position.x as i32, position.y as i32), + self.view_scale, + self.view_scale, + ), + }) + } + + pub fn submit(&mut self, canvas: &mut Canvas<Window>) { + self.sprites.sort(); + for DrawItem { src, dst, .. } in self.sprites.drain(..) { + canvas.copy(&self.texture, src, dst).unwrap(); + } + } +} + +impl Ord for DrawItem { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.z_order.cmp(&other.z_order) + } +} +impl PartialOrd for DrawItem { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + Some(self.cmp(&other)) + } +} +impl Eq for DrawItem {} +impl PartialEq for DrawItem { + fn eq(&self, other: &Self) -> bool { + self.z_order == other.z_order && self.src == other.src && self.dst == other.dst + } +} diff --git a/light-client/src/game.rs b/light-client/src/game.rs index fd045f53..e432c517 100644 --- a/light-client/src/game.rs +++ b/light-client/src/game.rs @@ -1,4 +1,38 @@ +use crate::atlas::SpriteRenderer; +use hurrycurry_protocol::{glam::IVec2, PacketC, TileIndex}; +use std::collections::HashMap; pub struct Game { - + tiles: HashMap<IVec2, TileIndex>, +} + +impl Game { + pub fn new() -> Self { + Self { + tiles: HashMap::new(), + } + } + + pub fn packet_in(&mut self, packet: PacketC) { + match packet { + PacketC::UpdateMap { + tile, + kind, + neighbors: _, + } => { + if let Some(kind) = kind { + self.tiles.insert(tile, kind); + } else { + self.tiles.remove(&tile); + } + } + _ => (), + } + } + + pub fn render(&self, ctx: &mut SpriteRenderer) { + for (p, tile) in &self.tiles { + ctx.draw_tile(*tile, *p) + } + } } diff --git a/light-client/src/main.rs b/light-client/src/main.rs index cb7e6caf..2145e9b6 100644 --- a/light-client/src/main.rs +++ b/light-client/src/main.rs @@ -1,3 +1,6 @@ +use atlas::SpriteRenderer; +use game::Game; +use hurrycurry_protocol::PacketC; /* Hurry Curry! - a game about cooking Copyright 2024 metamuffin @@ -16,23 +19,15 @@ */ use network::Network; -use sdl2::{ - event::Event, - image::InitFlag, - keyboard::Keycode, - pixels::{Color, PixelFormatEnum}, - render::TextureAccess, -}; +use sdl2::{event::Event, keyboard::Keycode, pixels::Color}; +pub mod atlas; pub mod game; pub mod network; fn main() { - let net = Network::connect("ws://127.0.0.1:27032/"); - let sdl_context = sdl2::init().unwrap(); let video_subsystem = sdl_context.video().unwrap(); - let _image_context = sdl2::image::init(InitFlag::WEBP).unwrap(); let window = video_subsystem .window("Hurry Curry! Light Client", 1280, 720) .position_centered() @@ -48,23 +43,28 @@ fn main() { .map_err(|e| e.to_string()) .unwrap(); let texture_creator = canvas.texture_creator(); - let mut texture = texture_creator - .create_texture( - Some(PixelFormatEnum::RGBA8888), - TextureAccess::Streaming, - 1024, - 1024, - ) - .unwrap(); - texture.update(None, &vec![128; 1024 * 1024], 1024).unwrap(); + let mut net = Network::connect("ws://127.0.0.1/").unwrap(); + let mut game = Game::new(); + let mut renderer = SpriteRenderer::init(&texture_creator); 'mainloop: loop { - canvas.set_draw_color(Color::BLACK); - canvas.clear(); + net.poll(); - canvas.copy(&texture, None, None).unwrap(); + for packet in net.queue_in.drain(..) { + match packet { + PacketC::Data { data } => { + renderer.set_sprite_map(data); + } + _ => game.packet_in(packet), + } + } + + game.render(&mut renderer); + canvas.set_draw_color(Color::BLACK); + canvas.clear(); + renderer.submit(&mut canvas); canvas.present(); for event in sdl_context.event_pump().unwrap().poll_iter() { diff --git a/light-client/src/network.rs b/light-client/src/network.rs index dc6e894f..e3cd1eb5 100644 --- a/light-client/src/network.rs +++ b/light-client/src/network.rs @@ -1,22 +1,56 @@ +use anyhow::Result; use hurrycurry_protocol::{PacketC, PacketS, BINCODE_CONFIG}; use log::warn; use std::{collections::VecDeque, net::TcpStream}; -use tungstenite::{stream::MaybeTlsStream, Message, WebSocket}; +use tungstenite::{ + client::{uri_mode, IntoClientRequest}, + client_tls_with_config, + handshake::client::Request, + stream::{MaybeTlsStream, Mode}, + Message, WebSocket, +}; pub struct Network { sock: WebSocket<MaybeTlsStream<TcpStream>>, - queue_in: VecDeque<PacketC>, - queue_out: VecDeque<PacketS>, + pub queue_in: VecDeque<PacketC>, + pub queue_out: VecDeque<PacketS>, } impl Network { - pub fn connect(addr: &str) -> Self { - let (sock, _resp) = tungstenite::connect(addr).unwrap(); - Self { + pub fn connect(addr: &str) -> Result<Self> { + let (parts, _) = addr.into_client_request().unwrap().into_parts(); + let uri = parts.uri.clone(); + + let mut builder = Request::builder() + .uri(uri.clone()) + .method(parts.method.clone()) + .version(parts.version); + *builder.headers_mut().unwrap() = parts.headers.clone(); + let request = builder.body(()).unwrap(); + + let uri = request.uri(); + let mode = uri_mode(uri)?; + let host = request.uri().host().unwrap(); + let host = if host.starts_with('[') { + &host[1..host.len() - 1] + } else { + host + }; + let port = uri.port_u16().unwrap_or(match mode { + Mode::Plain => 27032, + Mode::Tls => 443, + }); + let stream = TcpStream::connect((host, port))?; + stream.set_nodelay(true).unwrap(); + // stream.set_nonblocking(true).unwrap(); + + let (sock, _) = client_tls_with_config(request, stream, None, None).unwrap(); + + Ok(Self { sock, queue_in: VecDeque::new(), queue_out: VecDeque::new(), - } + }) } pub fn poll(&mut self) { self.queue_in.extend(match self.sock.read() { |