From c1b5937b64649904689fea8da2c06e0b66ae8332 Mon Sep 17 00:00:00 2001 From: metamuffin Date: Thu, 9 Jun 2022 16:01:55 +0200 Subject: asdfss --- client/src/world/helper.rs | 18 +++++ client/src/world/map.rs | 19 ++++-- client/src/world/mod.rs | 136 +++---------------------------------- client/src/world/tee.rs | 166 +++++++++++++++++++++++++++++++++++++++++++++ renderer/src/main.rs | 9 ++- renderer/src/map.rs | 91 +++++++++++-------------- renderer/src/tee.rs | 94 +++++++++++++++++++------ 7 files changed, 324 insertions(+), 209 deletions(-) create mode 100644 client/src/world/helper.rs create mode 100644 client/src/world/tee.rs diff --git a/client/src/world/helper.rs b/client/src/world/helper.rs new file mode 100644 index 0000000..fab452a --- /dev/null +++ b/client/src/world/helper.rs @@ -0,0 +1,18 @@ +use std::fmt::Display; + +#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] +pub struct Color { + pub a: u8, + pub r: u8, + pub g: u8, + pub b: u8, +} + +impl Display for Color { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_fmt(format_args!( + "#{:02x}{:02x}{:02x}{:02x}", + self.r, self.g, self.b, self.a + )) + } +} diff --git a/client/src/world/map.rs b/client/src/world/map.rs index fcc24c0..c72bfa9 100644 --- a/client/src/world/map.rs +++ b/client/src/world/map.rs @@ -12,8 +12,10 @@ use std::{ pub use mapfile::format; pub use mapfile::{ format::Tile, - reader::{self, Color, LayerTilemapType}, + reader::{self, Color as BadColor, LayerTilemapType}, }; + +use super::helper::Color; pub const TILE_NUM: u32 = 16; pub struct Layer { @@ -77,7 +79,12 @@ impl Map { }; let tiles = map.layer_tiles(tilemap.tiles(normal.data)).unwrap(); layers.push(Layer { - color: normal.color, + color: Color { + r: normal.color.red, + g: normal.color.green, + b: normal.color.blue, + a: normal.color.alpha, + }, image: normal.image, kind: tilemap.type_, tiles, @@ -94,10 +101,10 @@ impl Map { None => Array2::from_elem( (1, 1), Color { - alpha: 255, - red: 255, - blue: 255, - green: 0, + a: 255, + r: 255, + g: 0, + b: 255, }, ), Some(image_idx) => { diff --git a/client/src/world/mod.rs b/client/src/world/mod.rs index bbc505c..8a16016 100644 --- a/client/src/world/mod.rs +++ b/client/src/world/mod.rs @@ -1,154 +1,34 @@ -use self::map::Map; +use self::{map::Map, tee::Tees}; use crate::client::{helper::get_map_path, ClientMesgOut}; -use gamenet::{ - enums::{Emote, Team, Weapon}, - SnapObj, -}; -use std::{collections::BTreeMap, fs::File}; +use std::fs::File; pub mod map; +pub mod tee; +pub mod helper; pub use gamenet::enums; -#[derive(Debug)] -pub struct Tee { - pub name: String, - pub skin: String, - pub clan: String, - - pub local: bool, - pub latency: i32, - pub score: i32, - - pub team: Team, - pub weapon: Weapon, - pub armor: i32, - pub ammo: i32, - pub emote: Emote, - pub attack_tick: i32, - - pub tick: i32, - pub angle: i32, - pub x: i32, - pub y: i32, - pub vel_x: i32, - pub vel_y: i32, - pub hook_x: i32, - pub hook_y: i32, - pub hook_dx: i32, - pub hook_dy: i32, - pub hook_player: i32, - pub hook_state: i32, -} - -impl Default for Tee { - fn default() -> Self { - Self { - x: Default::default(), - y: Default::default(), - local: false, - team: Team::Spectators, - latency: Default::default(), - score: Default::default(), - weapon: Weapon::Shotgun, - armor: Default::default(), - ammo: Default::default(), - attack_tick: Default::default(), - emote: Emote::Normal, - tick: Default::default(), - angle: Default::default(), - vel_x: Default::default(), - vel_y: Default::default(), - hook_x: Default::default(), - hook_y: Default::default(), - hook_dx: Default::default(), - hook_dy: Default::default(), - hook_player: Default::default(), - hook_state: Default::default(), - name: Default::default(), - skin: Default::default(), - clan: Default::default(), - } - } -} - pub struct World { pub map: Map, - pub tees: BTreeMap, + pub tees: Tees, } impl World { pub fn new() -> Self { Self { map: Map::empty(), - tees: BTreeMap::new(), + tees: Tees::new(), } } pub fn update(&mut self, m: &ClientMesgOut) { + self.tees.update(m); match m { ClientMesgOut::MapChange { name, crc } => { let file = File::open(get_map_path(name.as_str(), *crc)).unwrap(); self.map = Map::load(file).unwrap(); } - ClientMesgOut::Snaps(s) => { - self.tees.clear(); - for (id, o) in s { - let e = self.tees.entry(*id).or_default(); - match o { - SnapObj::ClientInfo(o) => { - e.name = i32_to_string(o.name); - e.skin = i32_to_string(o.skin); - e.clan = i32_to_string(o.clan); - } - SnapObj::PlayerInfo(o) => { - e.local = o.local == 1; - e.team = o.team; - e.latency = o.latency; - e.score = o.score; - } - SnapObj::Character(c) => { - e.ammo = c.ammo_count; - e.weapon = c.weapon; - e.emote = c.emote; - e.attack_tick = c.attack_tick; - - e.x = c.character_core.x; - e.y = c.character_core.y; - e.vel_x = c.character_core.vel_x; - e.vel_y = c.character_core.vel_y; - - e.tick = c.character_core.tick; - e.hook_x = c.character_core.hook_x; - e.hook_y = c.character_core.hook_y; - e.hook_player = c.character_core.hooked_player; - e.hook_dx = c.character_core.hook_dx; - e.hook_dy = c.character_core.hook_dy; - e.hook_state = c.character_core.hook_state; - } - _ => (), - } - } - } + _ => (), } } - - pub fn local_tee(&self) -> Option<&Tee> { - self.tees.values().find(|e| e.local) - } -} - -fn i32_to_string(k: [i32; S]) -> String { - let mut bytes = vec![]; - for i in 0..S { - bytes.push(((((k[i]) >> 24) & 0xff) - 128) as u8); - bytes.push(((((k[i]) >> 16) & 0xff) - 128) as u8); - bytes.push(((((k[i]) >> 8) & 0xff) - 128) as u8); - bytes.push((((k[i]) & 0xff) - 128) as u8); - } - let len = bytes.iter().position(|e| *e == 0).unwrap_or(S); - while bytes.len() > len { - bytes.pop(); - } - String::from_utf8(bytes).unwrap() } diff --git a/client/src/world/tee.rs b/client/src/world/tee.rs new file mode 100644 index 0000000..5458fe6 --- /dev/null +++ b/client/src/world/tee.rs @@ -0,0 +1,166 @@ +use std::collections::BTreeMap; + +use super::helper::Color; +use crate::client::ClientMesgOut; +use gamenet::{ + enums::{Emote, Team, Weapon}, + SnapObj, +}; + +pub struct Tees { + pub inner: BTreeMap, +} + +pub struct Tee { + pub name: String, + pub skin: String, + pub clan: String, + + pub local: bool, + pub latency: i32, + pub score: i32, + + pub team: Team, + pub weapon: Weapon, + pub armor: i32, + pub ammo: i32, + pub emote: Emote, + pub attack_tick: i32, + + pub tick: i32, + pub angle: i32, + pub x: i32, + pub y: i32, + pub vel_x: i32, + pub vel_y: i32, + pub hook_x: i32, + pub hook_y: i32, + pub hook_dx: i32, + pub hook_dy: i32, + pub hook_player: i32, + pub hook_state: i32, + + pub color_feet: Color, + pub color_body: Color, + pub use_custom_colors: bool, + pub country: i32, +} + +impl Default for Tee { + fn default() -> Self { + Self { + x: Default::default(), + y: Default::default(), + local: false, + team: Team::Spectators, + latency: Default::default(), + score: Default::default(), + weapon: Weapon::Shotgun, + armor: Default::default(), + ammo: Default::default(), + attack_tick: Default::default(), + emote: Emote::Normal, + tick: Default::default(), + angle: Default::default(), + vel_x: Default::default(), + vel_y: Default::default(), + hook_x: Default::default(), + hook_y: Default::default(), + hook_dx: Default::default(), + hook_dy: Default::default(), + hook_player: Default::default(), + hook_state: Default::default(), + name: Default::default(), + skin: Default::default(), + clan: Default::default(), + color_feet: Color { + a: 0, + b: 0, + g: 0, + r: 0, + }, + color_body: Color { + a: 0, + b: 0, + g: 0, + r: 0, + }, + use_custom_colors: Default::default(), + country: Default::default(), + } + } +} + +impl Tees { + pub fn new() -> Self { + Self { + inner: BTreeMap::new(), + } + } + pub fn update(&mut self, m: &ClientMesgOut) { + match m { + ClientMesgOut::Snaps(s) => { + self.inner.clear(); + for (id, o) in s { + let e = self.inner.entry(*id).or_default(); + match o { + SnapObj::ClientInfo(o) => { + e.name = i32_to_string(o.name); + e.skin = i32_to_string(o.skin); + e.clan = i32_to_string(o.clan); + e.color_feet = unsafe { std::mem::transmute(o.color_feet) }; + e.color_body = unsafe { std::mem::transmute(o.color_body) }; + e.use_custom_colors = o.use_custom_color != 0 + } + SnapObj::PlayerInfo(o) => { + e.local = o.local == 1; + e.team = o.team; + e.latency = o.latency; + e.score = o.score; + } + SnapObj::Character(c) => { + e.ammo = c.ammo_count; + e.weapon = c.weapon; + e.emote = c.emote; + e.attack_tick = c.attack_tick; + + e.x = c.character_core.x; + e.y = c.character_core.y; + e.vel_x = c.character_core.vel_x; + e.vel_y = c.character_core.vel_y; + + e.tick = c.character_core.tick; + e.hook_x = c.character_core.hook_x; + e.hook_y = c.character_core.hook_y; + e.hook_player = c.character_core.hooked_player; + e.hook_dx = c.character_core.hook_dx; + e.hook_dy = c.character_core.hook_dy; + e.hook_state = c.character_core.hook_state; + } + _ => (), + } + } + } + _ => {} + } + } + + pub fn local(&self) -> Option<&Tee> { + self.inner.values().find(|e| e.local) + } +} + +fn i32_to_string(k: [i32; S]) -> String { + let mut bytes = vec![]; + for i in 0..S { + bytes.push(((((k[i]) >> 24) & 0xff) - 128) as u8); + bytes.push(((((k[i]) >> 16) & 0xff) - 128) as u8); + bytes.push(((((k[i]) >> 8) & 0xff) - 128) as u8); + bytes.push((((k[i]) & 0xff) - 128) as u8); + } + let len = bytes.iter().position(|e| *e == 0).unwrap_or(S); + while bytes.len() > len { + bytes.pop(); + } + String::from_utf8(bytes).unwrap() +} diff --git a/renderer/src/main.rs b/renderer/src/main.rs index 63396cc..985a717 100644 --- a/renderer/src/main.rs +++ b/renderer/src/main.rs @@ -15,7 +15,7 @@ use signal_hook::{ }; use skia_safe::{ gpu::{gl::FramebufferInfo, BackendRenderTarget, SurfaceOrigin}, - Canvas, Color, Color4f, ColorSpace, ColorType, Paint, Point, Surface, + Canvas, Color, ColorType, Surface, }; use std::{ collections::HashSet, convert::TryInto, net::IpAddr, process::exit, str::FromStr, @@ -228,7 +228,12 @@ impl Renderer { } pub fn draw(&mut self, canvas: &mut Canvas, dims: (f32, f32)) { canvas.clear(Color::TRANSPARENT); - let center = self.world.local_tee().map(|t| (t.x, t.y)).unwrap_or((0, 0)); + let center = self + .world + .tees + .local() + .map(|t| (t.x, t.y)) + .unwrap_or((0, 0)); canvas.save(); canvas.translate((dims.0 / 2.0, dims.1 / 2.0)); diff --git a/renderer/src/map.rs b/renderer/src/map.rs index f7672c1..a1e8d94 100644 --- a/renderer/src/map.rs +++ b/renderer/src/map.rs @@ -1,17 +1,17 @@ use std::collections::HashMap; -use log::{info, warn}; +use log::info; use skia_safe::{ - canvas::SrcRectConstraint, BlendMode, Canvas, Color4f, ColorSpace, ISize, Image, Matrix, Paint, - Rect, + canvas::SrcRectConstraint, Canvas, Color4f, ColorSpace, ISize, Image, Matrix, Paint, Rect, }; use twclient::world::{ + helper::Color, map::{format, TILE_NUM}, World, }; pub struct MapRenderer { - tileset: HashMap, Image>, + tileset: HashMap<(Option, Color), Image>, } const TILE_SIZE: f32 = 32.0; @@ -27,27 +27,35 @@ impl MapRenderer { pub fn map_changed(&mut self, world: &World) { self.tileset.clear(); for (key, t) in &world.map.tilesets { - let mut bytes: Vec = Vec::with_capacity(t.dim().0 * t.dim().1 * 4); - info!("loading tileset: {:?} => {:?}", key, t.dim()); - for ((_x, _y), c) in t.indexed_iter() { - bytes.push(c.red); - bytes.push(c.green); - bytes.push(c.blue); - bytes.push(c.alpha); + for layer in world.map.layers.iter().filter(|l| l.image == *key) { + let tint = layer.color; + let mut bytes: Vec = Vec::with_capacity(t.dim().0 * t.dim().1 * 4); + info!( + "loading tileset: (texture: {:?}, tint: {}) => {:?}", + key, + tint, + t.dim() + ); + for ((_x, _y), c) in t.indexed_iter() { + bytes.push((c.r as u32 * tint.r as u32) as u8); + bytes.push((c.g as u32 * tint.g as u32) as u8); + bytes.push((c.b as u32 * tint.b as u32) as u8); + bytes.push((c.a as u32 * tint.a as u32) as u8); + } + let d = skia_safe::Data::new_copy(&bytes); + let v = skia_safe::Image::from_raster_data( + &skia_safe::ImageInfo::new( + ISize::new(t.dim().0 as i32, t.dim().1 as i32), + skia_safe::ColorType::RGBA8888, + skia_safe::AlphaType::Premul, + ColorSpace::new_srgb_linear(), + ), + d, + t.dim().0 * 4, + ) + .unwrap(); + self.tileset.insert((*key, tint), v); } - let d = skia_safe::Data::new_copy(&bytes); - let v = skia_safe::Image::from_raster_data( - &skia_safe::ImageInfo::new( - ISize::new(t.dim().0 as i32, t.dim().1 as i32), - skia_safe::ColorType::RGBA8888, - skia_safe::AlphaType::Premul, - ColorSpace::new_srgb_linear(), - ), - d, - t.dim().0 * 4, - ) - .unwrap(); - self.tileset.insert(*key, v); } } @@ -66,25 +74,24 @@ impl MapRenderer { grid_paint.set_anti_alias(true); let center = world - .local_tee() + .tees + .local() .map(|t| (t.x / 32, t.y / 32)) .unwrap_or((0, 0)); for l in &world.map.layers { - let tileset = self.tileset.get(&l.image).unwrap(); + let tileset = self.tileset.get(&(l.image, l.color)).unwrap(); let mut layer_tint = Paint::new( Color4f { - a: 0.0, - b: 0.0, - g: 0.0, - r: 0.0, + a: 1.0, + b: 1.0, + g: 1.0, + r: 1.0, }, &ColorSpace::new_srgb(), ); layer_tint.set_style(skia_safe::PaintStyle::Fill); - layer_tint.set_blend_mode(BlendMode::Multiply); - layer_tint.set_argb(l.color.alpha, l.color.red, l.color.green, l.color.blue); for layer_y in (center.1 - 15)..(center.1 + 15) { for layer_x in (center.0 - 15)..(center.0 + 15) { @@ -142,26 +149,6 @@ impl MapRenderer { &layer_tint, ); - // if hflip { - // canvas.draw_rect( - // Rect { - // top: -TILE_SIZE / 2.0, - // bottom: TILE_SIZE / 2.0, - // left: -TILE_SIZE / 2.0, - // right: TILE_SIZE / 2.0, - // }, - // &Paint::new( - // Color4f { - // a: 0.5, - // b: 1.0, - // g: 0.0, - // r: 1.0, - // }, - // &ColorSpace::new_srgb(), - // ), - // ); - // } - canvas.restore(); } } diff --git a/renderer/src/tee.rs b/renderer/src/tee.rs index 98ac218..d796f5e 100644 --- a/renderer/src/tee.rs +++ b/renderer/src/tee.rs @@ -1,22 +1,41 @@ -use skia_safe::{Canvas, Color4f, ColorSpace, Font, Paint, Point}; -use twclient::world::World; +use std::{collections::BTreeMap, fs::File, io::Read}; -const TEE_RADIUS: f32 = 16.0; +use skia_safe::{ + canvas::SrcRectConstraint, utils::text_utils::Align, Canvas, Color4f, ColorSpace, Data, Font, + Image, Paint, Point, Rect, +}; +use twclient::world::{tee::Tee, World}; -pub struct TeeRenderer {} +const TEE_COLL_RADIUS: f32 = 16.0; +const TEE_REND_RADIUS: f32 = 32.0; + +pub struct TeeRenderer { + skins: BTreeMap, +} impl TeeRenderer { pub fn new() -> Self { - Self {} + Self { + skins: BTreeMap::new(), + } } pub fn draw(&mut self, world: &World, canvas: &mut Canvas) { + for t in world.tees.inner.values() { + canvas.save(); + canvas.translate((t.x as f32, t.y as f32)); + self.draw_tee(canvas, t); + canvas.restore(); + } + } + + pub fn draw_tee(&mut self, canvas: &mut Canvas, tee: &Tee) { let tee_paint = Paint::new( Color4f { a: 1.0, - r: 0.2, - g: 0.0, - b: 0.2, + r: 1.0, + g: 1.0, + b: 1.0, }, &ColorSpace::new_srgb(), ); @@ -29,18 +48,51 @@ impl TeeRenderer { }, &ColorSpace::new_srgb(), ); - for t in world.tees.values() { - let origin = Point { - x: t.x as f32, - y: t.y as f32, - }; - canvas.draw_circle(origin, TEE_RADIUS, &tee_paint); - canvas.draw_str( - t.name.as_str(), - (origin.x, origin.y - 20.0), - &Font::default(), - &name_paint, - ); - } + let skin_texture = self + .skins + .entry(tee.skin.clone()) + .or_insert_with(|| TeeRenderer::load_skin(tee.name.as_str()).unwrap()); + + let origin = Point { + x: tee.x as f32, + y: tee.y as f32, + }; + + canvas.draw_image_rect( + skin_texture, + Some(( + &Rect { + top: 16.0, + left: 16.0, + right: 96.0, + bottom: 96.0, + }, + SrcRectConstraint::Strict, + )), + Rect { + top: -TEE_REND_RADIUS, + left: -TEE_REND_RADIUS, + right: TEE_REND_RADIUS, + bottom: TEE_REND_RADIUS, + }, + &tee_paint, + ); + + canvas.draw_str_align( + tee.name.as_str(), + (origin.x, origin.y - 20.0), + &Font::default(), + &name_paint, + Align::Center, + ); + } + + pub fn load_skin(_name: &str) -> Option { + let path = "/usr/share/ddnet/data/skins/default.png"; + let mut file = File::open(path).unwrap(); + let mut data = Vec::new(); + file.read_to_end(&mut data).unwrap(); + let data = Data::new_copy(&data); + Image::from_encoded(data) } } -- cgit v1.2.3-70-g09d2