aboutsummaryrefslogtreecommitdiff
path: root/client
diff options
context:
space:
mode:
Diffstat (limited to 'client')
-rw-r--r--client/Cargo.toml46
-rw-r--r--client/src/client/helper.rs87
-rw-r--r--client/src/client/mod.rs431
-rw-r--r--client/src/lib.rs21
-rw-r--r--client/src/main.rs40
-rw-r--r--client/src/world/helper.rs19
-rw-r--r--client/src/world/map.rs158
-rw-r--r--client/src/world/mod.rs34
-rw-r--r--client/src/world/tee.rs167
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()
-}