aboutsummaryrefslogtreecommitdiff
path: root/client/src/skin_manager.rs
diff options
context:
space:
mode:
Diffstat (limited to 'client/src/skin_manager.rs')
-rw-r--r--client/src/skin_manager.rs146
1 files changed, 146 insertions, 0 deletions
diff --git a/client/src/skin_manager.rs b/client/src/skin_manager.rs
new file mode 100644
index 0000000..1f6624d
--- /dev/null
+++ b/client/src/skin_manager.rs
@@ -0,0 +1,146 @@
+// Adapted from twgpu-tools
+// AGPL-3.0-only, Copyright 2025 Patiga
+
+use anyhow::Result;
+use image::{ImageFormat, RgbaImage};
+use std::{collections::HashSet, io::BufReader, sync::Arc};
+use twgpu::{
+ blit::Blit,
+ sprites::{AtlasToken, SpriteTextures, TeeSprite},
+};
+use wgpu::{Device, Queue, Texture};
+
+pub fn load_png(path: &str, version: twmap::Version) -> Result<RgbaImage> {
+ let file = twstorage::read_file(path, version.into())?;
+ let reader = image::io::Reader::with_format(BufReader::new(file), ImageFormat::Png);
+ Ok(reader.decode()?.into_rgba8())
+}
+
+pub fn init_sprite_textures(
+ textures: &mut SpriteTextures,
+ version: twmap::Version,
+ device: &Device,
+ queue: &Queue,
+) -> Result<()> {
+ println!("Loading sprite textures from filesystem");
+ let game = load_png("game.png", version)?;
+ let particles = load_png("particles.png", version)?;
+ let emoticons = load_png("emoticons.png", version)?;
+ let extras = load_png("extras.png", version)?;
+ textures.game_skin = textures.register_atlas_image(&game, device, queue);
+ textures.particles = textures.register_atlas_image(&particles, device, queue);
+ textures.emoticons = textures.register_atlas_image(&emoticons, device, queue);
+ textures.extras = textures.register_atlas_image(&extras, device, queue);
+ Ok(())
+}
+
+type SkinResult = Result<(String, Vec<Texture>), (String, String)>;
+
+pub struct SkinManager {
+ known: HashSet<String>,
+ handles: Vec<std::thread::JoinHandle<SkinResult>>,
+ blit: Arc<Blit>,
+ device: Arc<Device>,
+ queue: Arc<Queue>,
+}
+
+fn load_skin(name: &str, blit: &Blit, device: &Device, queue: &Queue) -> Result<Vec<Texture>> {
+ // TODO check filename
+ let path = format!("skins/{name}.png");
+ let path2 = format!("downloadedskins/{name}.png");
+ let image = match load_png(&path, twmap::Version::DDNet06) {
+ Err(_) => load_png(&path2, twmap::Version::DDNet06)?,
+ Ok(img) => img,
+ };
+
+ let texture = blit.upload_mipmapped_atlas::<TeeSprite>(&image, device, queue);
+ Ok(texture)
+}
+
+impl SkinManager {
+ pub fn new(
+ blit: Arc<Blit>,
+ textures: &mut SpriteTextures,
+ device: Arc<Device>,
+ queue: Arc<Queue>,
+ ) -> Self {
+ let mut manager = Self {
+ known: HashSet::new(),
+ handles: Vec::new(),
+ blit,
+ device,
+ queue,
+ };
+ if let Some(default) = manager.load_skin("default", textures) {
+ textures.default_skin = default;
+ }
+ if let Some(ninja) = manager.load_skin("x_ninja", textures) {
+ textures.ninja_skin = ninja;
+ }
+ manager
+ }
+
+ pub fn load_skin(
+ &mut self,
+ name: &str,
+ textures: &mut SpriteTextures,
+ ) -> Option<AtlasToken<TeeSprite>> {
+ let texture = match load_skin(name, &self.blit, &self.device, &self.queue) {
+ Ok(texture) => texture,
+ Err(err) => {
+ println!("Error loading skin '{name}': {err}");
+ return None;
+ }
+ };
+ self.known.insert(name.to_string());
+ let token = textures.register_skin_texture(texture, name.to_string(), &self.device);
+ Some(token)
+ }
+
+ pub fn queue_load_skin(&mut self, name: String) {
+ let device = self.device.clone();
+ let queue = self.queue.clone();
+ let blit = self.blit.clone();
+ let handle = std::thread::spawn(move || match load_skin(&name, &blit, &device, &queue) {
+ Err(err) => Err((name, err.to_string())),
+ Ok(texture) => Ok((name, texture)),
+ });
+ self.handles.push(handle);
+ }
+
+ pub fn wait_for_queued(&mut self, textures: &mut SpriteTextures) {
+ while let Some(handle) = self.handles.pop() {
+ match handle.join().unwrap() {
+ Ok((name, texture)) => {
+ textures.register_skin_texture(texture, name, &self.device);
+ }
+ Err((name, err)) => println!("Error with skin '{name}': {err}"),
+ }
+ }
+ }
+
+ pub fn poll_queued(&mut self, textures: &mut SpriteTextures) {
+ while !self.handles.is_empty() {
+ if self.handles[0].is_finished() {
+ match self.handles.remove(0).join().unwrap() {
+ Ok((name, texture)) => {
+ textures.register_skin_texture(texture, name, &self.device);
+ }
+ Err((name, err)) => println!("Error with skin '{name}': {err}"),
+ }
+ } else {
+ return;
+ }
+ }
+ }
+
+ pub fn queue_snap_skins(&mut self, snap: &twsnap::Snap) {
+ for player in snap.players.values() {
+ if !self.known.contains(player.skin.as_str()) {
+ println!("New skin: '{}'", player.skin);
+ self.known.insert(player.skin.to_string());
+ self.queue_load_skin(player.skin.to_string());
+ }
+ }
+ }
+}