diff options
Diffstat (limited to 'client')
-rw-r--r-- | client/Cargo.toml | 46 | ||||
-rw-r--r-- | client/src/client/helper.rs | 87 | ||||
-rw-r--r-- | client/src/client/mod.rs | 431 | ||||
-rw-r--r-- | client/src/lib.rs | 21 | ||||
-rw-r--r-- | client/src/main.rs | 40 | ||||
-rw-r--r-- | client/src/world/helper.rs | 19 | ||||
-rw-r--r-- | client/src/world/map.rs | 158 | ||||
-rw-r--r-- | client/src/world/mod.rs | 34 | ||||
-rw-r--r-- | client/src/world/tee.rs | 167 |
9 files changed, 3 insertions, 1000 deletions
diff --git a/client/Cargo.toml b/client/Cargo.toml index 1e58906..09c50c3 100644 --- a/client/Cargo.toml +++ b/client/Cargo.toml @@ -1,48 +1,6 @@ [package] -name = "twclient" +name = "client" version = "0.1.0" -edition = "2021" -authors = [ - "metamuffin <metamuffin@disroot.org>", - "heinrich5991 <heinrich5991@gmail.com>", -] -license = "AGPL-3.0-only" +edition = "2024" [dependencies] -socket = { path = "../../libtw2/socket" } -datafile = { path = "../../libtw2/datafile" } -map = { path = "../../libtw2/map" } -net = { path = "../../libtw2/net" } -common = { path = "../../libtw2/common" } -event_loop = { path = "../../libtw2/event_loop" } -gamenet_teeworlds_0_5 = { path = "../../libtw2/gamenet/teeworlds-0.5", optional = true } -gamenet_teeworlds_0_6 = { path = "../../libtw2/gamenet/teeworlds-0.6", optional = true } -gamenet_teeworlds_0_7 = { path = "../../libtw2/gamenet/teeworlds-0.7", optional = true } -gamenet_ddnet = { path = "../../libtw2/gamenet/ddnet", optional = true } -logger = { path = "../../libtw2/logger" } -packer = { path = "../../libtw2/packer" } -# snapshot = { path = "../../libtw2/snapshot" } -snapshot = { path = "../snapshot" } - -ndarray = "0.15.4" -arrayvec = "0.5.2" -clap = "2.31.2" -hexdump = "0.1.1" -itertools = ">=0.3.0,<0.5.0" -log = "0.3.1" -rand = "0.8.3" -tempfile = "2.0.0" -warn = ">=0.1.1,<0.3.0" -env_logger = "0.9.0" -signal-hook = "0.3.14" -lazy_static = "1.4.0" -anyhow = "1.0.57" -crossbeam-channel = "0.5.4" - - -[features] -default = ["gamenet_ddnet_0_6"] -gamenet_0_5 = ["dep:gamenet_teeworlds_0_5", "snapshot/gamenet_teeworlds_0_5"] -gamenet_0_6 = ["dep:gamenet_teeworlds_0_6", "snapshot/gamenet_teeworlds_0_6"] -gamenet_0_7 = ["dep:gamenet_teeworlds_0_7", "snapshot/gamenet_teeworlds_0_7"] -gamenet_ddnet_0_6 = ["dep:gamenet_ddnet", "snapshot/gamenet_ddnet_0_6"] diff --git a/client/src/client/helper.rs b/client/src/client/helper.rs deleted file mode 100644 index af19301..0000000 --- a/client/src/client/helper.rs +++ /dev/null @@ -1,87 +0,0 @@ -use arrayvec::ArrayVec; -use event_loop::{Chunk, Loop, PeerId}; -use gamenet::msg::{Game, System}; -use hexdump::hexdump_iter; -use itertools::Itertools; -use log::LogLevel; -use log::{log, log_enabled, warn}; -use packer::with_packer; -use snapshot::format::Item as SnapItem; -use std::{fmt, path::PathBuf}; - -pub trait LoopExt: Loop { - fn send_system<'a, S: Into<System<'a>>>(&mut self, pid: PeerId, msg: S) { - fn inner<L: Loop + ?Sized>(msg: System, pid: PeerId, evloop: &mut L) { - let mut buf: ArrayVec<[u8; 2048]> = ArrayVec::new(); - with_packer(&mut buf, |p| msg.encode(p).unwrap()); - evloop.send(Chunk { - pid, - vital: true, - data: &buf, - }) - } - inner(msg.into(), pid, self) - } - fn send_game<'a, G: Into<Game<'a>>>(&mut self, pid: PeerId, msg: G) { - fn inner<L: Loop + ?Sized>(msg: Game, pid: PeerId, evloop: &mut L) { - let mut buf: ArrayVec<[u8; 2048]> = ArrayVec::new(); - with_packer(&mut buf, |p| msg.encode(p).unwrap()); - evloop.send(Chunk { - pid, - vital: true, - data: &buf, - }) - } - inner(msg.into(), pid, self) - } -} -impl<L: Loop> LoopExt for L {} - -pub fn need_file(crc: i32, name: &str) -> bool { - let mut path = PathBuf::new(); - path.push("/tmp/maps"); - path.push(format!("{}_{:08x}.map", name, crc)); - !path.exists() -} - -pub fn hexdump(level: LogLevel, data: &[u8]) { - if log_enabled!(level) { - hexdump_iter(data).foreach(|s| log!(level, "{}", s)); - } -} -pub struct Warn<'a>(pub &'a [u8]); - -impl<'a, W: fmt::Debug> warn::Warn<W> for Warn<'a> { - fn warn(&mut self, w: W) { - warn!("{:?}", w); - hexdump(LogLevel::Warn, self.0); - } -} - -#[derive(Debug)] -pub struct WarnSnap<'a>(pub SnapItem<'a>); - -impl<'a, W: fmt::Debug> warn::Warn<W> for WarnSnap<'a> { - fn warn(&mut self, w: W) { - warn!("{:?} for {:?}", w, self.0); - } -} - -pub fn check_dummy_map(name: &[u8], crc: u32, size: i32) -> bool { - if name != b"dummy" { - return false; - } - match (crc, size) { - (0xbeae0b9f, 549) => {} - (0x6c760ac4, 306) => {} - _ => warn!("unknown dummy map, crc={}, size={}", crc, size), - } - true -} - -pub fn get_map_path(name: &str, crc: i32) -> PathBuf { - let mut path = PathBuf::new(); - path.push("/tmp/maps"); - path.push(format!("{}_{:08x}.map", name, crc)); - path -} diff --git a/client/src/client/mod.rs b/client/src/client/mod.rs deleted file mode 100644 index c5e7b9e..0000000 --- a/client/src/client/mod.rs +++ /dev/null @@ -1,431 +0,0 @@ -pub mod helper; - -use self::helper::get_map_path; -use super::gamenet::{ - enums::{Team, VERSION}, - msg::{ - self, - game::{ClSetTeam, ClStartInfo}, - system::{EnterGame, Info, Input, MapChange, MapData}, - system::{Ready, RequestMapData}, - Game, System, SystemOrGame, - }, - snap_obj::obj_size, - SnapObj, -}; -use crate::client::helper::{check_dummy_map, hexdump, WarnSnap}; -use crate::{ - client::helper::{need_file, LoopExt, Warn}, - SHOULD_EXIT, -}; -use common::{num::Cast, pretty}; -use crossbeam_channel::{Receiver, Sender}; -use event_loop::{Addr, Application, Chunk, ConnlessChunk, Loop, PeerId, SocketLoop, Timeout}; -use log::LogLevel; -use log::{debug, error, info, log, warn}; -use packer::{IntUnpacker, Unpacker}; -use std::{ - borrow::{Borrow, Cow}, - collections::HashSet, - fs, - io::{self, Write}, - mem, - net::IpAddr, - sync::atomic::Ordering, - u32, -}; -use tempfile::{NamedTempFile, NamedTempFileOptions}; -use warn::Log; - -pub use super::gamenet::snap_obj::PlayerInput; - -pub struct ClientConfig { - pub nick: String, - pub clan: String, - pub timeout: String, -} - -pub struct Client { - pid: PeerId, - config: ClientConfig, - - current_votes: HashSet<Vec<u8>>, - snaps: snapshot::Manager, - num_snaps_since_reset: u64, - dummy_map: bool, - state: ClientState, - download: Option<Download>, - - input: PlayerInput, - - interface_receive: Receiver<ClientMesgIn>, - interface_send: Sender<ClientMesgOut>, -} - -pub struct ClientInterface { - pub send: Sender<ClientMesgIn>, - pub receive: Receiver<ClientMesgOut>, -} - -pub enum ClientMesgIn { - Input(PlayerInput), -} -pub enum ClientMesgOut { - MapChange { name: String, crc: i32 }, - Snaps(Vec<(u16, SnapObj)>), -} - -#[derive(Clone, Copy, Debug)] -enum ClientState { - Connection, - MapChange, - MapData(i32, i32), - ConReady, - ReadyToEnter, -} - -impl Default for ClientState { - fn default() -> ClientState { - ClientState::Connection - } -} - -impl Client { - pub fn new_evloop() -> SocketLoop { - SocketLoop::client() - } - - pub fn new( - evloop: &mut SocketLoop, - ip: IpAddr, - port: u16, - config: ClientConfig, - ) -> (Self, ClientInterface) { - fs::create_dir_all("/tmp/maps").unwrap(); - fs::create_dir_all("/tmp/downloading").unwrap(); - let (a, b) = crossbeam_channel::unbounded(); - let (c, d) = crossbeam_channel::unbounded(); - ( - Client { - pid: evloop.connect(Addr { ip, port }), - config, - current_votes: HashSet::new(), - snaps: snapshot::Manager::new(), - num_snaps_since_reset: 0, - dummy_map: false, - state: ClientState::Connection, - download: None, - interface_receive: b, - interface_send: c, - input: PlayerInput::default(), - }, - ClientInterface { - receive: d, - send: a, - }, - ) - } - pub fn run(self, evloop: SocketLoop) { - evloop.run(self) - } -} - -impl<'a, L: Loop> Application<L> for Client { - fn needs_tick(&mut self) -> Timeout { - Timeout::inactive() - } - fn on_tick(&mut self, evloop: &mut L) { - if SHOULD_EXIT.load(Ordering::Relaxed) { - warn!("exiting peer {}", self.pid); - evloop.disconnect(self.pid, b"error"); - } - for m in self.interface_receive.try_iter() { - match m { - ClientMesgIn::Input(i) => self.input = i, - } - } - } - fn on_packet(&mut self, evloop: &mut L, chunk: Chunk) { - let pid = chunk.pid; - - let msg = match msg::decode(&mut Warn(chunk.data), &mut Unpacker::new(chunk.data)) { - Ok(m) => m, - Err(err) => { - warn!("decode error {:?}:", err); - hexdump(LogLevel::Warn, chunk.data); - return; - } - }; - debug!("{:?}", msg); - match msg { - SystemOrGame::Game(Game::SvMotd(..)) - | SystemOrGame::Game(Game::SvKillMsg(..)) - | SystemOrGame::Game(Game::SvTuneParams(..)) - | SystemOrGame::Game(Game::SvWeaponPickup(..)) - | SystemOrGame::System(System::InputTiming(..)) - | SystemOrGame::Game(Game::SvExtraProjectile(..)) => {} - SystemOrGame::Game(Game::SvChat(chat)) => { - if chat.client_id == -1 { - info!("[server]: {}", pretty::AlmostString::new(chat.message)); - } else { - info!( - "[team {}]: {}", - chat.team, - pretty::AlmostString::new(chat.message) - ) - } - } - SystemOrGame::Game(Game::SvBroadcast(broadcast)) => { - info!( - "broadcast: {}", - pretty::AlmostString::new(broadcast.message) - ); - } - _ => {} - } - - match msg { - SystemOrGame::System(ref msg) => match *msg { - System::MapChange(MapChange { crc, size, name }) => { - if let Some(_) = size.try_usize() { - if name.iter().any(|&b| b == b'/' || b == b'\\') { - error!("invalid map name"); - evloop.disconnect(pid, b"error"); - return; - } - match self.state { - ClientState::MapChange => {} - ClientState::ReadyToEnter if self.dummy_map => {} - _ => warn!("map change from state {:?}", self.state), - } - self.dummy_map = check_dummy_map(name, crc as u32, size); - self.current_votes.clear(); - self.num_snaps_since_reset = 0; - self.snaps.reset(); - info!("map change: {}", pretty::AlmostString::new(name)); - let name = String::from_utf8_lossy(name); - if let Cow::Owned(..) = name { - warn!("weird characters in map name"); - } - let mut start_download = false; - if need_file(crc, &name) { - if let Err(e) = self.open_download_file(crc, name.borrow()) { - error!("error opening file {:?}", e); - } else { - start_download = true; - } - } - if start_download { - info!("download starting"); - evloop.send_system(pid, RequestMapData { chunk: 0 }); - self.state = ClientState::MapData(crc, 0); - } else { - self.state = ClientState::ConReady; - evloop.send_system(pid, Ready); - - self.interface_send - .send(ClientMesgOut::MapChange { - name: name.to_string(), - crc, - }) - .unwrap(); - } - } else { - error!("invalid map size"); - evloop.disconnect(pid, b"error"); - return; - } - } - System::Snap(_) | System::SnapEmpty(_) | System::SnapSingle(_) => { - self.num_snaps_since_reset += 1; - { - let a = gamenet::msg::system::Snap; - let b = gamenet_ddnet::msg::system::Snap; - let res = match *msg { - System::Snap(s) => self.snaps.snap(&mut Log, obj_size, s), - System::SnapEmpty(s) => self.snaps.snap_empty(&mut Log, obj_size, s), - System::SnapSingle(s) => self.snaps.snap_single(&mut Log, obj_size, s), - _ => unreachable!(), - }; - match res { - Ok(Some(snap)) => { - let snaps = snap - .items() - .filter_map(|item| { - SnapObj::decode_obj( - &mut WarnSnap(item), - item.type_id.into(), - &mut IntUnpacker::new(item.data), - ) - .map(|o| (item.id, o)) - .ok() - }) - .collect::<Vec<(u16, SnapObj)>>(); - - self.interface_send - .send(ClientMesgOut::Snaps(snaps.clone())) - .unwrap(); - } - Ok(None) => { - self.num_snaps_since_reset -= 1; - } - Err(err) => warn!("snapshot error {:?}", err), - } - } - // DDNet needs the INPUT message as the first - // chunk of the packet. - evloop.force_flush(pid); - let tick = self.snaps.ack_tick().unwrap_or(-1); - evloop.send_system( - pid, - Input { - ack_snapshot: tick, - intended_tick: tick, - input_size: mem::size_of::<PlayerInput>().assert_i32(), - input: self.input, - }, - ); - } - _ => {} - }, - SystemOrGame::Game(ref msg) => match *msg { - _ => {} - }, - } - match self.state { - ClientState::Connection => unreachable!(), - ClientState::MapChange => {} // Handled above. - ClientState::MapData(cur_crc, cur_chunk) => match msg { - SystemOrGame::System(System::MapData(MapData { - last, - crc, - chunk, - data, - })) => { - if cur_crc == crc && cur_chunk == chunk { - let res = self.write_download_file(data); - if let Err(ref err) = res { - error!("error writing file {:?}", err); - } - if last != 0 || res.is_err() { - if !res.is_err() { - if let Err(err) = self.finish_download_file() { - error!("error finishing file {:?}", err); - } - if last != 1 { - warn!("weird map data packet"); - } - } - self.state = ClientState::ConReady; - evloop.send_system(pid, Ready); - - info!("download finished"); - } else { - let cur_chunk = cur_chunk.checked_add(1).unwrap(); - self.state = ClientState::MapData(cur_crc, cur_chunk); - evloop.send_system(pid, RequestMapData { chunk: cur_chunk }); - } - } else { - if cur_crc != crc || cur_chunk < chunk { - warn!("unsolicited map data crc={:08x} chunk={}", crc, chunk); - warn!("want crc={:08x} chunk={}", cur_crc, cur_chunk); - } - } - } - _ => {} - }, - ClientState::ConReady => match msg { - SystemOrGame::System(System::ConReady(..)) => { - evloop.send_game( - pid, - ClStartInfo { - name: self.config.nick.as_bytes(), - clan: self.config.clan.as_bytes(), - country: -1, - skin: b"limekittygirl", - use_custom_color: true, - color_body: 0xFF00FF, - color_feet: 0x550055, - }, - ); - self.state = ClientState::ReadyToEnter; - } - _ => {} - }, - ClientState::ReadyToEnter => match msg { - SystemOrGame::Game(Game::SvReadyToEnter(..)) => { - evloop.send_system(pid, EnterGame); - evloop.send_game(pid, ClSetTeam { team: Team::Red }); - } - _ => {} - }, - } - evloop.flush(pid); - } - fn on_connless_packet(&mut self, _: &mut L, chunk: ConnlessChunk) { - warn!( - "connless packet {} {:?}", - chunk.addr, - pretty::Bytes::new(chunk.data) - ); - } - fn on_connect(&mut self, _: &mut L, _: PeerId) { - unreachable!(); - } - fn on_ready(&mut self, evloop: &mut L, pid: PeerId) { - if pid != self.pid { - error!("not our pid: {} vs {}", pid, self.pid); - } - self.state = ClientState::MapChange; - evloop.send_system( - pid, - Info { - version: VERSION.as_bytes(), - password: Some(b""), - }, - ); - evloop.flush(pid); - } - fn on_disconnect(&mut self, _: &mut L, pid: PeerId, remote: bool, reason: &[u8]) { - if remote { - error!( - "disconnected pid={:?} error={}", - pid, - pretty::AlmostString::new(reason) - ); - } - } -} - -struct Download { - file: NamedTempFile, - crc: i32, - name: String, -} - -impl Client { - fn open_download_file(&mut self, crc: i32, name: &str) -> Result<(), io::Error> { - self.download = Some(Download { - file: NamedTempFileOptions::new() - .prefix(&format!("{}_{:08x}_", name, crc)) - .suffix(".map") - .create_in("/tmp/downloading")?, - crc, - name: name.to_string(), - }); - Ok(()) - } - fn write_download_file(&mut self, data: &[u8]) -> Result<(), io::Error> { - self.download.as_mut().unwrap().file.write_all(data) - } - fn finish_download_file(&mut self) -> Result<(), io::Error> { - let download = self.download.take().unwrap(); - let path = get_map_path(download.name.as_str(), download.crc); - download - .file - .persist(&path) - .map(|_| ()) - .map_err(|e| e.error)?; - Ok(()) - } -} diff --git a/client/src/lib.rs b/client/src/lib.rs deleted file mode 100644 index 3c19df7..0000000 --- a/client/src/lib.rs +++ /dev/null @@ -1,21 +0,0 @@ -#![feature(exclusive_range_pattern)] - -pub mod client; -pub mod world; - -#[cfg(feature = "gamenet_ddnet_0_6")] -pub extern crate gamenet_ddnet as gamenet; -#[cfg(feature = "gamenet_0_5")] -pub extern crate gamenet_teeworlds_0_5 as gamenet; -#[cfg(feature = "gamenet_0_6")] -pub extern crate gamenet_teeworlds_0_6 as gamenet; -#[cfg(feature = "gamenet_0_7")] -pub extern crate gamenet_teeworlds_0_7 as gamenet; - - -use std::sync::atomic::AtomicBool; - -use lazy_static::lazy_static; -lazy_static! { - pub static ref SHOULD_EXIT: AtomicBool = AtomicBool::new(false); -} diff --git a/client/src/main.rs b/client/src/main.rs index b79f338..e7a11a9 100644 --- a/client/src/main.rs +++ b/client/src/main.rs @@ -1,41 +1,3 @@ -use log::{error, log, warn}; -use signal_hook::{ - consts::{SIGINT, SIGTERM}, - iterator::Signals, - low_level::exit, -}; -use std::{net::IpAddr, str::FromStr, sync::atomic::Ordering, thread, time::Duration}; -use twclient::{ - client::{Client, ClientConfig}, - SHOULD_EXIT, -}; - fn main() { - env_logger::init(); - - let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap(); - thread::spawn(move || { - for sig in signals.forever() { - warn!("received signal {:?}", sig); - SHOULD_EXIT.store(true, Ordering::Relaxed); - thread::sleep(Duration::from_secs(3)); - error!("exit timeout!"); - exit(1); - } - }); - - let config = ClientConfig { - nick: String::from("metamuffin"), - clan: String::from("rustacean"), - timeout: String::from("asgefdhjikhjfhjf"), - }; - let mut args = std::env::args().skip(1); - let ip = IpAddr::from_str(args.next().unwrap().as_str()).unwrap(); - let port = u16::from_str(args.next().unwrap().as_str()).unwrap(); - drop(ip); - drop(port); - drop(config); - drop(Client::new_evloop()); - todo!() - // Client::run_thing(ip, port, config) + println!("Hello, world!"); } diff --git a/client/src/world/helper.rs b/client/src/world/helper.rs deleted file mode 100644 index cb34a9f..0000000 --- a/client/src/world/helper.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::fmt::Display; - -#[derive(Debug, PartialEq, Eq, Hash, Clone, Copy)] -#[repr(C)] -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 deleted file mode 100644 index c8522b9..0000000 --- a/client/src/world/map.rs +++ /dev/null @@ -1,158 +0,0 @@ -use ::map as mapfile; -use anyhow::Error; -use common::{num::Cast, pretty, vec}; -use log::{info, log, warn}; -use ndarray::Array2; -use std::{ - collections::{hash_map, HashMap}, - fs::File, - mem, -}; - -pub use mapfile::format; -pub use mapfile::{ - format::Tile, - reader::{self, Color as BadColor, LayerTilemapType}, -}; - -use super::helper::Color; -pub const TILE_NUM: u32 = 16; - -pub struct Layer { - pub color: Color, - pub image: Option<usize>, - pub tiles: Array2<Tile>, - pub kind: LayerTilemapType, - pub offset: (i32, i32), -} - -pub struct Map { - pub layers: Vec<Layer>, - pub tilesets: HashMap<Option<usize>, Array2<Color>>, - pub name: String, - pub crc: i32, -} - -impl Map { - pub fn empty() -> Self { - Self { - layers: Vec::new(), - tilesets: HashMap::new(), - crc: 0, - name: String::from("twclient dummy map"), - } - } - - pub fn load(file: File, name: &str, crc: i32) -> Result<Self, Error> { - info!("loading map"); - let datafile = datafile::Reader::new(file).unwrap(); - let mut map = mapfile::Reader::from_datafile(datafile); - - let mut layers = vec![]; - for group_idx in map.group_indices() { - let group = map.group(group_idx).unwrap(); - - if group.parallax_x != 100 || group.parallax_y != 100 || group.clipping.is_some() { - warn!( - "skipping layer: {}: parallax_x:{} parallax_y:{} clipping:{:?}", - pretty::AlmostString::new(&group.name), - group.parallax_x, - group.parallax_y, - group.clipping, - ); - continue; - } - - for layer_idx in group.layer_indices { - info!("loading {}", pretty::AlmostString::new(&group.name)); - let layer = map.layer(layer_idx).unwrap(); - let tilemap = if let reader::LayerType::Tilemap(t) = layer.t { - t - } else { - warn!("non-tilemap layer skipped"); - continue; - }; - let normal = if let Some(n) = tilemap.type_.to_normal() { - n - } else { - warn!("non-normal layer skipped"); - continue; - }; - let tiles = map.layer_tiles(tilemap.tiles(normal.data)).unwrap(); - layers.push(Layer { - 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, - offset: (group.offset_x, group.offset_y), - }); - } - } - - let mut tilesets = HashMap::new(); - for layer in &layers { - match tilesets.entry(layer.image) { - hash_map::Entry::Occupied(_) => {} - hash_map::Entry::Vacant(v) => { - let data = match layer.image { - None => Array2::from_elem( - (1, 1), - Color { - a: 255, - r: 255, - g: 0, - b: 255, - }, - ), - Some(image_idx) => { - let image = map.image(image_idx).unwrap(); - let height = image.height.usize(); - let width = image.width.usize(); - match image.data { - Some(d) => { - let data = map.image_data(d).unwrap(); - if data.len() % mem::size_of::<Color>() != 0 { - panic!("image shape invalid"); - } - let data: Vec<Color> = unsafe { vec::transmute(data) }; - Array2::from_shape_vec((height, width), data).unwrap() - } - None => { - warn!("layer with external tileset skipped"); - continue; - // let image_name = map.image_name(image.name)?; - // // WARN? Unknown external image - // // WARN! Wrong dimensions - // str::from_utf8(&image_name).ok() - // .and_then(sanitize) - // .map(&mut external_tileset_loader) - // .transpose()? - // .unwrap_or(None) - // .unwrap_or_else(|| Array2::from_elem((1, 1), Color::white())) - } - } - } - }; - v.insert(data); - } - } - } - - info!( - "{} layers + {} tilesets loaded", - layers.len(), - tilesets.len() - ); - Ok(Self { - tilesets, - layers, - crc, - name: String::from(name), - }) - } -} diff --git a/client/src/world/mod.rs b/client/src/world/mod.rs deleted file mode 100644 index 7b25a53..0000000 --- a/client/src/world/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -use self::{map::Map, tee::Tees}; -use crate::client::{helper::get_map_path, ClientMesgOut}; -use std::fs::File; - -pub mod helper; -pub mod map; -pub mod tee; - -pub use gamenet::enums; - -pub struct World { - pub map: Map, - pub tees: Tees, -} - -impl World { - pub fn new() -> Self { - Self { - map: Map::empty(), - 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, name.as_str(), *crc).unwrap(); - } - _ => (), - } - } -} diff --git a/client/src/world/tee.rs b/client/src/world/tee.rs deleted file mode 100644 index 6b33f48..0000000 --- a/client/src/world/tee.rs +++ /dev/null @@ -1,167 +0,0 @@ -use std::{collections::BTreeMap, f32::consts::PI}; - -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: f32, - 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.angle = c.character_core.angle as f32 / 1600.0 * 2.0 * PI; - 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() -} |