aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--client/src/world/helper.rs18
-rw-r--r--client/src/world/map.rs19
-rw-r--r--client/src/world/mod.rs136
-rw-r--r--client/src/world/tee.rs166
-rw-r--r--renderer/src/main.rs9
-rw-r--r--renderer/src/map.rs91
-rw-r--r--renderer/src/tee.rs94
7 files changed, 324 insertions, 209 deletions
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<u16, Tee>,
+ 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<const S: usize>(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<u16, Tee>,
+}
+
+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<const S: usize>(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<Option<usize>, Image>,
+ tileset: HashMap<(Option<usize>, 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<u8> = 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<u8> = 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<String, Image>,
+}
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<Image> {
+ 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)
}
}