From 4b232371b4a7c66496362e4a9c4c1543f24e12db Mon Sep 17 00:00:00 2001
From: Isaac Thoman <49598528+IsaacThoman@users.noreply.github.com>
Date: Fri, 3 Jan 2025 01:14:25 -0500
Subject: [PATCH] Revert "style: add deno fmt config, CI job"

---
 .github/workflows/ci.yml                |   29 +-
 deno.json                               |   50 +-
 src/app/app.component.ts                |    8 +-
 src/app/app.config.server.ts            |    6 +-
 src/app/app.config.ts                   |   24 +-
 src/app/game/game.component.css         |    1 -
 src/app/game/game.component.html        |    2 +-
 src/app/game/game.component.spec.ts     |   23 +
 src/app/game/game.component.ts          |   29 +-
 src/app/pages/index.page.ts             |   16 +-
 src/client/core/AssetManager.ts         |  142 +-
 src/client/core/CommandManager.ts       |  337 ++---
 src/client/core/Game.ts                 |  132 +-
 src/client/core/Inventory.ts            |  337 +++--
 src/client/core/MapLoader.ts            |   41 +-
 src/client/core/Networking.ts           |  475 +++---
 src/client/core/Player.ts               |  100 +-
 src/client/core/RemoteItemRenderer.ts   |  157 +-
 src/client/core/RemotePlayerRenderer.ts | 1016 +++++++------
 src/client/core/Renderer.ts             |  802 +++++-----
 src/client/core/SettingsManager.ts      |   62 +-
 src/client/input/CollisionManager.ts    |  276 ++--
 src/client/input/HeldItemInput.ts       |   18 +-
 src/client/input/InputHandler.ts        |  683 +++++----
 src/client/input/PointerLockControl.ts  |  126 +-
 src/client/input/TouchInputHandler.ts   |  358 ++---
 src/client/items/BananaGun.ts           |  374 ++---
 src/client/items/FishGun.ts             |  454 +++---
 src/client/items/ItemBase.ts            |  290 ++--
 src/client/main.ts                      |    2 +-
 src/client/ui/ChatOverlay.ts            | 1823 +++++++++++------------
 src/client/ui/HealthIndicator.ts        |  224 ++-
 src/main.server.ts                      |   26 +-
 src/main.ts                             |    4 +-
 src/server/DataValidator.ts             |  135 +-
 src/server/GameEngine.ts                |  311 ++--
 src/server/GameServer.ts                |  301 ++--
 src/server/config.ts                    |  143 +-
 src/server/gamemodes/FFAGamemode.ts     |  238 +--
 src/server/gamemodes/Gamemode.ts        |   26 +-
 src/server/managers/ChatManager.ts      |  151 +-
 src/server/managers/DamageSystem.ts     |  112 +-
 src/server/managers/GameMsgManager.ts   |    6 +-
 src/server/managers/ItemManager.ts      |  236 +--
 src/server/managers/PlayerManager.ts    |  315 ++--
 src/server/models/ChatMessage.ts        |    8 +-
 src/server/models/DamageRequest.ts      |   10 +-
 src/server/models/ItemRespawnPoint.ts   |   12 +-
 src/server/models/MapData.ts            |   54 +-
 src/server/models/Player.ts             |   53 +-
 src/server/models/PlayerExtras.ts       |   13 +-
 src/server/models/Quaternion.ts         |    4 +-
 src/server/models/RespawnPoint.ts       |    4 +-
 src/server/models/ServerInfo.ts         |   40 +-
 src/server/models/Vector3.ts            |   32 +-
 src/server/models/WorldItem.ts          |   12 +-
 src/styles.css                          |   92 +-
 57 files changed, 5337 insertions(+), 5418 deletions(-)
 create mode 100644 src/app/game/game.component.spec.ts

diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c7526ece..1fc7e1e0 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -3,7 +3,7 @@ name: CI
 on:
   push:
     branches:
-      - "**"
+      - '**'
   pull_request:
     branches:
       - main
@@ -24,7 +24,7 @@ jobs:
       - name: Set up Deno
         uses: denoland/setup-deno@v1
         with:
-          deno-version: "latest"
+          deno-version: 'latest'
       - name: Build project
         run: deno task build
       - name: List build directory contents
@@ -39,7 +39,7 @@ jobs:
       - name: Set up Deno
         uses: denoland/setup-deno@v1
         with:
-          deno-version: "latest"
+          deno-version: 'latest'
       - name: Build project
         run: deno task build
       - name: Run lint
@@ -53,25 +53,11 @@ jobs:
       - name: Set up Deno
         uses: denoland/setup-deno@v1
         with:
-          deno-version: "latest"
+          deno-version: 'latest'
       - name: install
         run: deno install
       - name: check src
         run: deno check ./src/client ./src/server
-  # Format Job
-  format:
-    runs-on: ubuntu-latest
-    steps:
-      - name: Checkout repository
-        uses: actions/checkout@v3
-      - name: Set up Deno
-        uses: denoland/setup-deno@v1
-        with:
-          deno-version: "latest"
-      - name: install
-        run: deno install
-      - name: fmt check src
-        run: deno fmt --check src
 
   # Healthcheck Job
   health:
@@ -82,7 +68,7 @@ jobs:
       - name: Set up Deno
         uses: denoland/setup-deno@v1
         with:
-          deno-version: "latest"
+          deno-version: 'latest'
       - name: Start server
         run: deno task start &
       - name: Health check
@@ -136,7 +122,7 @@ jobs:
       - name: Set up Deno
         uses: denoland/setup-deno@v1
         with:
-          deno-version: "latest"
+          deno-version: 'latest'
       - name: Run security audit
         run: npm audit --audit-level=high
 
@@ -151,7 +137,7 @@ jobs:
       - name: Checkout repository
         uses: actions/checkout@v3
         with:
-          persist-credentials: "false"
+          persist-credentials: 'false'
           fetch-depth: 0
       - name: Set up Node.js
         uses: actions/setup-node@v3
@@ -167,3 +153,4 @@ jobs:
           GIT_COMMITTER_NAME: "github-actions[bot]"
           GIT_COMMITTER_EMAIL: "github-actions[bot]@users.noreply.github.com"
         run: npx semantic-release --extends ./release.config.js
+      
diff --git a/deno.json b/deno.json
index b99aba1b..e0bd5091 100644
--- a/deno.json
+++ b/deno.json
@@ -1,29 +1,23 @@
 {
-	"tasks": {
-		"dev": "deno run -A --node-modules-dir npm:vite",
-		"build": "deno run -A --node-modules-dir npm:vite build",
-		"preview": "deno run -A --node-modules-dir npm:vite preview",
-		"serve": "deno run --allow-net --allow-read jsr:@std/http@1/file-server dist/",
-		"start": "deno task build && deno run --allow-read --allow-env --allow-net --allow-write main.ts"
-	},
-	"compilerOptions": {
-		"lib": ["ES2020", "DOM", "DOM.Iterable", "deno.ns"]
-	},
-	"imports": {
-		"@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.0",
-		"@oak/oak": "jsr:@oak/oak@^17.1.3",
-		"@std/http": "jsr:@std/http@^1.0.10",
-		"vite": "npm:vite@^5.4.8"
-	},
-	"lint": {
-		"include": ["src/"],
-		"exclude": ["dist/", "node_modules/", "public/"]
-	},
-	"fmt": {
-		"options": {
-			"lineWidth": 120,
-			"useTabs": true,
-			"singleQuote": true
-		}
-	}
-}
+  "tasks": {
+    "dev": "deno run -A --node-modules-dir npm:vite",
+    "build": "deno run -A --node-modules-dir npm:vite build",
+    "preview": "deno run -A --node-modules-dir npm:vite preview",
+    "serve": "deno run --allow-net --allow-read jsr:@std/http@1/file-server dist/",
+    "start": "deno task build && deno run --allow-read --allow-env --allow-net --allow-write main.ts"
+  },
+  "compilerOptions": {
+    "lib": ["ES2020", "DOM", "DOM.Iterable", "deno.ns"]
+  },
+  "imports": {
+    "@deno/vite-plugin": "npm:@deno/vite-plugin@^1.0.0",
+    "@oak/oak": "jsr:@oak/oak@^17.1.3",
+    "@std/http": "jsr:@std/http@^1.0.10",
+    "vite": "npm:vite@^5.4.8"
+  },
+  "lint": {
+    "include": ["src/"],
+    "exclude": ["dist/", "node_modules/", "public/"]
+
+    }
+}
\ No newline at end of file
diff --git a/src/app/app.component.ts b/src/app/app.component.ts
index 812d0c0b..83d32386 100644
--- a/src/app/app.component.ts
+++ b/src/app/app.component.ts
@@ -2,9 +2,9 @@ import { Component } from '@angular/core';
 import { RouterOutlet } from '@angular/router';
 
 @Component({
-	selector: 'app-root',
-	standalone: true,
-	imports: [RouterOutlet],
-	template: `<router-outlet />`,
+  selector: 'app-root',
+  standalone: true,
+  imports: [RouterOutlet],
+  template: `<router-outlet />`,
 })
 export class AppComponent {}
diff --git a/src/app/app.config.server.ts b/src/app/app.config.server.ts
index b872e63d..de174d78 100644
--- a/src/app/app.config.server.ts
+++ b/src/app/app.config.server.ts
@@ -1,10 +1,10 @@
-import { ApplicationConfig, mergeApplicationConfig } from '@angular/core';
+import { mergeApplicationConfig, ApplicationConfig } from '@angular/core';
 import { provideServerRendering } from '@angular/platform-server';
 
-import { appConfig } from './app.config.ts';
+import { appConfig } from "./app.config.ts";
 
 const serverConfig: ApplicationConfig = {
-	providers: [provideServerRendering()],
+  providers: [provideServerRendering()],
 };
 
 export const config = mergeApplicationConfig(appConfig, serverConfig);
diff --git a/src/app/app.config.ts b/src/app/app.config.ts
index 0a47c409..a1a58ee8 100644
--- a/src/app/app.config.ts
+++ b/src/app/app.config.ts
@@ -1,16 +1,20 @@
-import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http';
+import {
+  provideHttpClient,
+  withFetch,
+  withInterceptors,
+} from '@angular/common/http';
 import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core';
 import { provideClientHydration } from '@angular/platform-browser';
 import { provideFileRouter, requestContextInterceptor } from '@analogjs/router';
 
 export const appConfig: ApplicationConfig = {
-	providers: [
-		provideZoneChangeDetection({ eventCoalescing: true }),
-		provideFileRouter(),
-		provideHttpClient(
-			withFetch(),
-			withInterceptors([requestContextInterceptor]),
-		),
-		provideClientHydration(),
-	],
+  providers: [
+    provideZoneChangeDetection({ eventCoalescing: true }),
+    provideFileRouter(),
+    provideHttpClient(
+      withFetch(),
+      withInterceptors([requestContextInterceptor])
+    ),
+    provideClientHydration(),
+  ],
 };
diff --git a/src/app/game/game.component.css b/src/app/game/game.component.css
index 8b137891..e69de29b 100644
--- a/src/app/game/game.component.css
+++ b/src/app/game/game.component.css
@@ -1 +0,0 @@
-
diff --git a/src/app/game/game.component.html b/src/app/game/game.component.html
index 8485102f..ba88aad9 100644
--- a/src/app/game/game.component.html
+++ b/src/app/game/game.component.html
@@ -1,2 +1,2 @@
 <p>game works!</p>
-<div #rendererContainer class="game-container w-full h-screen absolute top-0 left-0"></div>
+<div #rendererContainer class="game-container w-full h-screen absolute top-0 left-0"></div>
\ No newline at end of file
diff --git a/src/app/game/game.component.spec.ts b/src/app/game/game.component.spec.ts
new file mode 100644
index 00000000..47c16c9b
--- /dev/null
+++ b/src/app/game/game.component.spec.ts
@@ -0,0 +1,23 @@
+import { ComponentFixture, TestBed } from '@angular/core/testing';
+
+import { GameComponent } from './game.component';
+
+describe('GameComponent', () => {
+  let component: GameComponent;
+  let fixture: ComponentFixture<GameComponent>;
+
+  beforeEach(async () => {
+    await TestBed.configureTestingModule({
+      imports: [GameComponent]
+    })
+    .compileComponents();
+
+    fixture = TestBed.createComponent(GameComponent);
+    component = fixture.componentInstance;
+    fixture.detectChanges();
+  });
+
+  it('should create', () => {
+    expect(component).toBeTruthy();
+  });
+});
diff --git a/src/app/game/game.component.ts b/src/app/game/game.component.ts
index 2680403a..0b062685 100644
--- a/src/app/game/game.component.ts
+++ b/src/app/game/game.component.ts
@@ -1,23 +1,22 @@
 // game.component.ts
-import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
-import { Game } from '../../client/core/Game.ts';
+import { Component, ElementRef, ViewChild, AfterViewInit } from '@angular/core';
+import { Game } from "../../client/core/Game.ts";
 
 @Component({
-	selector: 'app-game',
-	templateUrl: './game.component.html',
-	standalone: true,
+  selector: 'app-game',
+  templateUrl: './game.component.html',
+  standalone: true,
 })
 export class GameComponent implements AfterViewInit {
-	@ViewChild('rendererContainer')
-	rendererContainer!: ElementRef;
-	private game?: Game;
+  @ViewChild('rendererContainer') rendererContainer!: ElementRef;
+  private game?: Game;
 
-	ngAfterViewInit() {
-		this.game = new Game(this.rendererContainer.nativeElement);
-		this.game.start();
-	}
+  ngAfterViewInit() {
+    this.game = new Game(this.rendererContainer.nativeElement);
+    this.game.start();
+  }
 
-	ngOnDestroy() {
-		// Add cleanup if needed
-	}
+  ngOnDestroy() {
+    // Add cleanup if needed
+  }
 }
diff --git a/src/app/pages/index.page.ts b/src/app/pages/index.page.ts
index 9441705d..749676b4 100644
--- a/src/app/pages/index.page.ts
+++ b/src/app/pages/index.page.ts
@@ -1,13 +1,13 @@
 import { Component } from '@angular/core';
-import { GameComponent } from '../game/game.component.ts';
+import {GameComponent} from "../game/game.component.ts";
 
 @Component({
-	selector: 'app-home',
-	standalone: true,
-	template: `<app-game></app-game>`,
-	styles: ``,
-	imports: [
-		GameComponent,
-	],
+  selector: 'app-home',
+  standalone: true,
+  template: `<app-game></app-game>`,
+  styles: ``,
+  imports: [
+    GameComponent
+  ]
 })
 export default class HomeComponent {}
diff --git a/src/client/core/AssetManager.ts b/src/client/core/AssetManager.ts
index 6acfa9c3..11b66b1e 100644
--- a/src/client/core/AssetManager.ts
+++ b/src/client/core/AssetManager.ts
@@ -3,87 +3,87 @@ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
 import * as THREE from 'three';
 
 interface AssetEntry {
-	scene: THREE.Group;
-	isLoaded: boolean;
-	callbacks: Array<(scene: THREE.Group) => void>;
+    scene: THREE.Group;
+    isLoaded: boolean;
+    callbacks: Array<(scene: THREE.Group) => void>;
 }
 
 export class AssetManager {
-	private static instance: AssetManager;
-	private assets: Map<string, AssetEntry>;
-	private gltfLoader: GLTFLoader;
+    private static instance: AssetManager;
+    private assets: Map<string, AssetEntry>;
+    private gltfLoader: GLTFLoader;
 
-	private constructor() {
-		this.assets = new Map();
-		this.gltfLoader = new GLTFLoader();
-		const dracoLoader = new DRACOLoader();
-		dracoLoader.setDecoderPath('/draco/');
-		this.gltfLoader.setDRACOLoader(dracoLoader);
-	}
+    private constructor() {
+        this.assets = new Map();
+        this.gltfLoader = new GLTFLoader();
+        const dracoLoader = new DRACOLoader();
+        dracoLoader.setDecoderPath('/draco/');
+        this.gltfLoader.setDRACOLoader(dracoLoader);
+    }
 
-	public static getInstance(): AssetManager {
-		if (!AssetManager.instance) {
-			AssetManager.instance = new AssetManager();
-		}
-		return AssetManager.instance;
-	}
+    public static getInstance(): AssetManager {
+        if (!AssetManager.instance) {
+            AssetManager.instance = new AssetManager();
+        }
+        return AssetManager.instance;
+    }
 
-	private cloneWithNewMaterials(scene: THREE.Group): THREE.Group {
-		const clonedScene = scene.clone();
+    private cloneWithNewMaterials(scene: THREE.Group): THREE.Group {
+        const clonedScene = scene.clone();
 
-		clonedScene.traverse((node) => {
-			if ((node as THREE.Mesh).isMesh) {
-				const mesh = node as THREE.Mesh;
-				if (Array.isArray(mesh.material)) {
-					mesh.material = mesh.material.map((mat) => mat.clone());
-				} else {
-					mesh.material = mesh.material.clone();
-				}
-			}
-		});
+        clonedScene.traverse((node) => {
+            if ((node as THREE.Mesh).isMesh) {
+                const mesh = node as THREE.Mesh;
+                if (Array.isArray(mesh.material)) {
+                    mesh.material = mesh.material.map(mat => mat.clone());
+                } else {
+                    mesh.material = mesh.material.clone();
+                }
+            }
+        });
 
-		return clonedScene;
-	}
+        return clonedScene;
+    }
 
-	public loadAsset(url: string, callback: (scene: THREE.Group) => void): void {
-		if (this.assets.has(url)) {
-			const assetEntry = this.assets.get(url)!;
-			if (assetEntry.isLoaded) {
-				// Asset is already loaded, clone with new materials
-				callback(this.cloneWithNewMaterials(assetEntry.scene));
-			} else {
-				// Asset is loading, add callback to the list
-				assetEntry.callbacks.push(callback);
-			}
-		} else {
-			// Asset not loaded yet, start loading
-			this.assets.set(url, { scene: new THREE.Group(), isLoaded: false, callbacks: [callback] });
-			this.gltfLoader.load(
-				url,
-				(gltf) => {
-					const assetEntry = this.assets.get(url)!;
-					assetEntry.scene = gltf.scene;
-					assetEntry.isLoaded = true;
+    public loadAsset(url: string, callback: (scene: THREE.Group) => void): void {
+        if (this.assets.has(url)) {
+            const assetEntry = this.assets.get(url)!;
+            if (assetEntry.isLoaded) {
+                // Asset is already loaded, clone with new materials
+                callback(this.cloneWithNewMaterials(assetEntry.scene));
+            } else {
+                // Asset is loading, add callback to the list
+                assetEntry.callbacks.push(callback);
+            }
+        } else {
+            // Asset not loaded yet, start loading
+            this.assets.set(url, { scene: new THREE.Group(), isLoaded: false, callbacks: [callback] });
+            this.gltfLoader.load(
+                url,
+                (gltf) => {
+                    const assetEntry = this.assets.get(url)!;
+                    assetEntry.scene = gltf.scene;
+                    assetEntry.isLoaded = true;
 
-					// Call all callbacks waiting for this asset with new material clones
-					assetEntry.callbacks.forEach((cb) => {
-						cb(this.cloneWithNewMaterials(gltf.scene));
-					});
-					assetEntry.callbacks = [];
-				},
-				undefined,
-				(error) => {
-					console.error(`Error loading asset ${url}:`, error);
-				},
-			);
-		}
-	}
+                    // Call all callbacks waiting for this asset with new material clones
+                    assetEntry.callbacks.forEach((cb) => {
+                        cb(this.cloneWithNewMaterials(gltf.scene));
+                    });
+                    assetEntry.callbacks = [];
+                },
+                undefined,
+                (error) => {
+                    console.error(`Error loading asset ${url}:`, error);
+                }
+            );
+        }
+    }
 
-	public preloadAsset(url: string): void {
-		this.loadAsset(url, () => {});
-	}
+    public preloadAsset(url: string): void {
+        this.loadAsset(url, () => {});
+    }
 
-	public clearCache(): void {
-		this.assets.clear();
-	}
+    public clearCache(): void {
+        this.assets.clear();
+    }
 }
diff --git a/src/client/core/CommandManager.ts b/src/client/core/CommandManager.ts
index 44344fed..66612493 100644
--- a/src/client/core/CommandManager.ts
+++ b/src/client/core/CommandManager.ts
@@ -1,190 +1,175 @@
-import { Player } from './Player.ts';
-import { ChatOverlay } from '../ui/ChatOverlay.ts';
-import { SettingsManager } from './SettingsManager.ts';
+import {Player} from "./Player.ts";
+import {ChatOverlay} from "../ui/ChatOverlay.ts";
+import {SettingsManager} from "./SettingsManager.ts";
 
 export class CommandManager {
-	private localPlayer: Player;
-	private chatOverlay: ChatOverlay;
-	private readonly commands: Command[];
-
-	constructor(localPlayer: Player, chatOverlay: ChatOverlay) {
-		this.localPlayer = localPlayer;
-		this.chatOverlay = chatOverlay;
-		this.commands = [];
-		this.init();
-	}
-
-	public init() {
-		this.commands.push(
-			new Command('sense', (args: string[]): string => {
-				if (args[1] == null) {
-					return 'sensitivity is currently ' + (SettingsManager.settings.sense);
-				}
-				const sense: number = Number(args[1]);
-				if (Number.isNaN(sense)) {
-					return args[1] + ' is not a number';
-				}
-				if (sense > 10 || sense <= 0) {
-					return 'sensitivity is not in the valid range of 0 to 10';
-				}
-				SettingsManager.settings.sense = sense;
-				SettingsManager.write();
-				return 'sensitivity is now set to ' + sense;
-			}),
-		);
-
-		this.commands.push(
-			new Command('resetSettings', (): string => {
-				SettingsManager.reset();
-				SettingsManager.write();
-				return 'settings have been reverted to their default states';
-			}),
-		);
-
-		this.commands.push(
-			new Command('controllerSense', (args: string[]): string => {
-				if (args[1] == null) {
-					return 'controller sensitivity is currently ' + (SettingsManager.settings.controllerSense);
-				}
-				const sense = Number(args[1]);
-				if (Number.isNaN(sense)) {
-					return args[1] + ' is not a number';
-				}
-				if (sense > 10 || sense <= 0) {
-					return 'controller sensitivity is not in the valid range of 0 to 10';
-				}
-				SettingsManager.settings.controllerSense = sense;
-				SettingsManager.write();
-				return 'controller sensitivity is now set to ' + sense;
-			}),
-		);
-
-		this.commands.push(
-			new Command('crosshairColor', (args: string[]): string => {
-				if ((args[1] && args[2] && args[3])) {
-					for (let i = 1; i < args.length; i++) {
-						if (Number.isNaN(Number(args[i]))) return args[i] + ' is not a number';
-						if (Number(args[i]) < 0 || Number(args[i]) > 255) {
-							return args[i] + ' is not in range 0-255';
-						}
-					}
-					SettingsManager.settings.crosshairColor = '#' + componentToHex(Number(args[1])) +
-						componentToHex(Number(args[2])) + componentToHex(Number(args[3]));
-				} else if (args[1]) {
-					const color: string | null = cssToHex(args[1]);
-					if (!color) {
-						return args[1] + ' is not a valid color';
-					}
-					SettingsManager.settings.crosshairColor = color;
-				} else {
-					return 'invalid input';
-				}
-				SettingsManager.write();
-				return 'crosshair color set to ' + SettingsManager.settings.crosshairColor;
-			}),
-		);
-
-		this.commands.push(
-			new Command('crosshairType', (args: string[]): string => {
-				if (args[1] == 'cross') SettingsManager.settings.crosshairType = 0;
-				else if (args[1] == 'dot') SettingsManager.settings.crosshairType = 1;
-				else return 'not a valid type (dot or cross)';
-				SettingsManager.write();
-				return 'crosshair type set to ' + args[1];
-			}),
-		);
-
-		this.commands.push(
-			new Command('bobbing', (args: string[]): string => {
-				const bobbing = Number(args[1]);
-				if (Number.isNaN(bobbing)) {
-					return args[1] + ' is not a number';
-				}
-				if (bobbing < 0 || bobbing > 2) {
-					return args[1] + ' is not in range 0 to 2';
-				}
-				SettingsManager.settings.viewBobbingStrength = bobbing;
-				SettingsManager.write();
-				return 'view bobbing strength is now set to ' + args[1];
-			}),
-		);
-
-		this.commands.push(
-			new Command('prettyText', (args: string[]): string => {
-				if (args[1] == null) return 'prettyText is currently ' + SettingsManager.settings.doPrettyText;
-				if (args[1] == 'true') SettingsManager.settings.doPrettyText = true;
-				else if (args[1] == 'false') SettingsManager.settings.doPrettyText = false;
-				else return 'invalid input (true/false)';
-				SettingsManager.write();
-				return 'prettyText set to ' + args[1];
-			}),
-		);
-	}
-
-	public runCmd(cmd: string): boolean {
-		let match: boolean = false;
-		const args: string[] = cmd.substring(1).split(' ');
-		for (let i = 0; i < this.commands.length; i++) {
-			if (args[0].toLowerCase() === this.commands[i].getCmdName()) {
-				match = true;
-				const msg = this.commands[i].run(args);
-
-				const chatMessage = {
-					id: this.localPlayer.id,
-					name: '',
-					message: cmd + ' -> ' + msg,
-				};
-
-				this.chatOverlay.addChatMessage(chatMessage);
-				break;
-			}
-		}
-		return match;
-	}
+    private localPlayer: Player;
+    private chatOverlay: ChatOverlay;
+    private readonly commands: Command[];
+
+    constructor(localPlayer: Player, chatOverlay: ChatOverlay) {
+        this.localPlayer = localPlayer;
+        this.chatOverlay = chatOverlay;
+        this.commands = [];
+        this.init();
+    }
+
+    public init() {
+        this.commands.push(new Command('sense', (args: string[]): string => {
+            if (args[1] == null) {
+                return "sensitivity is currently " + (SettingsManager.settings.sense);
+            }
+            const sense: number = Number(args[1]);
+            if (Number.isNaN(sense)) {
+                return args[1] + " is not a number";
+            }
+            if (sense > 10 || sense <= 0) {
+                return "sensitivity is not in the valid range of 0 to 10";
+            }
+            SettingsManager.settings.sense = sense;
+            SettingsManager.write();
+            return "sensitivity is now set to " + (sense);
+        }));
+
+        this.commands.push(new Command('resetSettings', (): string => {
+            SettingsManager.reset();
+            SettingsManager.write();
+            return "settings have been reverted to their default states";
+        }));
+
+        this.commands.push(new Command('controllerSense', (args: string[]): string => {
+            if (args[1] == null) {
+                return "controller sensitivity is currently " + (SettingsManager.settings.controllerSense);
+            }
+            const sense = Number(args[1]);
+            if (Number.isNaN(sense)) {
+                return args[1] + " is not a number";
+            }
+            if (sense > 10 || sense <= 0) {
+                return "controller sensitivity is not in the valid range of 0 to 10";
+            }
+            SettingsManager.settings.controllerSense = sense;
+            SettingsManager.write();
+            return "controller sensitivity is now set to " + (sense);
+        }));
+
+        this.commands.push(new Command('crosshairColor', (args: string[]): string => {
+            if ((args[1] && args[2] && args[3])) {
+                for (let i = 1; i < args.length; i++) {
+                    if (Number.isNaN(Number(args[i]))) return args[i] + " is not a number";
+                    if (Number(args[i]) < 0 || Number(args[i]) > 255) {
+                        return args[i] + ' is not in range 0-255';
+                    }
+                }
+                SettingsManager.settings.crosshairColor = '#' + componentToHex(Number(args[1])) + componentToHex(Number(args[2])) + componentToHex(Number(args[3]));
+            } else if (args[1]) {
+                const color : string | null = cssToHex(args[1]);
+                if(!color) {
+                    return args[1] + ' is not a valid color';
+                }
+                SettingsManager.settings.crosshairColor = color;
+            } else {
+                return 'invalid input';
+            }
+            SettingsManager.write();
+            return 'crosshair color set to ' + SettingsManager.settings.crosshairColor;
+        }));
+
+        this.commands.push(new Command('crosshairType', (args: string[]): string => {
+            if (args[1] == 'cross') SettingsManager.settings.crosshairType = 0;
+            else if (args[1] == 'dot') SettingsManager.settings.crosshairType = 1;
+            else return 'not a valid type (dot or cross)';
+            SettingsManager.write();
+            return 'crosshair type set to ' + args[1];
+        } ));
+
+        this.commands.push(new Command('bobbing', (args: string[]) : string => {
+            const bobbing = Number(args[1]);
+            if (Number.isNaN(bobbing)) {
+                return args[1] + ' is not a number'
+            }
+            if (bobbing < 0 || bobbing > 2) {
+                return args[1] + ' is not in range 0 to 2';
+            }
+            SettingsManager.settings.viewBobbingStrength = bobbing;
+            SettingsManager.write();
+            return 'view bobbing strength is now set to ' + args[1];
+        }));
+
+        this.commands.push(new Command('prettyText', (args: string[]): string => {
+            if(args[1] == null) return 'prettyText is currently ' + SettingsManager.settings.doPrettyText;
+            if (args[1] == 'true') SettingsManager.settings.doPrettyText = true;
+            else if (args[1] == 'false') SettingsManager.settings.doPrettyText = false;
+            else return 'invalid input (true/false)';
+            SettingsManager.write();
+            return 'prettyText set to ' + args[1];
+        } ));
+    }
+
+    public runCmd(cmd: string): boolean {
+        let match: boolean = false;
+        const args: string[] = cmd.substring(1).split(" ")
+        for (let i = 0; i < this.commands.length; i++) {
+            if (args[0].toLowerCase() === this.commands[i].getCmdName()) {
+                match = true;
+                const msg = this.commands[i].run(args);
+
+                const chatMessage = {
+                    id: this.localPlayer.id,
+                    name: '',
+                    message: cmd + " -> " + msg
+                };
+
+                this.chatOverlay.addChatMessage(chatMessage);
+                break;
+            }
+        }
+        return match;
+    }
 }
 
 class Command {
-	private readonly cmdName: string;
-	private readonly func: (args: string[]) => string;
-	constructor(cmd: string, func: (args: string[]) => string) {
-		this.cmdName = cmd.toLowerCase();
-		this.func = func;
-	}
-	public run(args: string[]): string {
-		return this.func(args);
-	}
-
-	public getCmdName() {
-		return this.cmdName;
-	}
+    private readonly cmdName: string;
+    private readonly func: (args: string[]) => string;
+    constructor(cmd: string, func: (args: string[]) => string) {
+        this.cmdName = cmd.toLowerCase();
+        this.func = func;
+    }
+    public run(args: string[]): string {
+        return this.func(args);
+    }
+
+    public getCmdName() {
+        return this.cmdName;
+    }
 }
 
 function componentToHex(c: number): string {
-	const hex = c.toString(16);
-	return hex.length === 1 ? '0' + hex : hex;
+    const hex = c.toString(16);
+    return hex.length === 1 ? "0" + hex : hex;
 }
 
 function cssToHex(color: string) {
-	// Create a dummy div to get computed color style
-	if (!isColor(color)) return null;
-	const div = document.createElement('div');
-	div.style.color = color;
-	document.body.appendChild(div);
-	const computedColor = getComputedStyle(div).color;
-	document.body.removeChild(div);
-
-	// Extract rgb values
-	const rgbMatch = computedColor.match(/\d+/g);
-	if (rgbMatch) {
-		const [r, g, b] = rgbMatch.map(Number);
-		return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b);
-	}
-	return null;
+    // Create a dummy div to get computed color style
+    if (!isColor(color)) return null;
+    const div = document.createElement('div');
+    div.style.color = color;
+    document.body.appendChild(div);
+    const computedColor = getComputedStyle(div).color;
+    document.body.removeChild(div);
+
+    // Extract rgb values
+    const rgbMatch = computedColor.match(/\d+/g);
+    if (rgbMatch) {
+        const [r, g, b] = rgbMatch.map(Number);
+        return '#' + componentToHex(r) + componentToHex(g) + componentToHex(b);
+    }
+    return null;
 }
 
 function isColor(strColor: string) {
-	const s = new Option().style;
-	s.color = strColor;
-	// If the color is recognized, s.color won't be empty
-	return s.color !== '';
-}
+    const s = new Option().style;
+    s.color = strColor;
+    // If the color is recognized, s.color won't be empty
+    return s.color !== '';
+}
\ No newline at end of file
diff --git a/src/client/core/Game.ts b/src/client/core/Game.ts
index aa449bf8..dbc9143a 100644
--- a/src/client/core/Game.ts
+++ b/src/client/core/Game.ts
@@ -1,73 +1,73 @@
-import { Player } from './Player.ts';
-import { Renderer } from './Renderer.ts';
-import { ChatOverlay } from '../ui/ChatOverlay.ts';
-import { InputHandler } from '../input/InputHandler.ts';
-import { Networking } from './Networking.ts';
-import { CollisionManager } from '../input/CollisionManager.ts';
-import { Inventory } from './Inventory.ts';
-import { HealthIndicator } from '../ui/HealthIndicator.ts';
-import { MapLoader } from './MapLoader.ts';
-import { RemoteItemRenderer } from './RemoteItemRenderer.ts';
-import { TouchInputHandler } from '../input/TouchInputHandler.ts';
+import {Player} from './Player.ts';
+import {Renderer} from './Renderer.ts';
+import {ChatOverlay} from '../ui/ChatOverlay.ts';
+import {InputHandler} from '../input/InputHandler.ts';
+import {Networking} from './Networking.ts';
+import {CollisionManager} from '../input/CollisionManager.ts';
+import {Inventory} from './Inventory.ts';
+import {HealthIndicator} from '../ui/HealthIndicator.ts';
+import {MapLoader} from './MapLoader.ts';
+import {RemoteItemRenderer} from "./RemoteItemRenderer.ts";
+import { TouchInputHandler } from "../input/TouchInputHandler.ts";
 
 export class Game {
-	private localPlayer: Player;
-	private renderer: Renderer;
-	private chatOverlay: ChatOverlay;
-	private inputHandler: InputHandler;
-	private touchInputHandler: TouchInputHandler;
-	private networking: Networking;
-	private collisionManager: CollisionManager;
-	private inventoryManager: Inventory;
-	private mapLoader: MapLoader;
-	private healthIndicator: HealthIndicator;
-	private remoteItemRenderer: RemoteItemRenderer;
-	private gameIndex: number;
-	private static nextGameIndex: number = 0;
+    private localPlayer: Player;
+    private renderer: Renderer;
+    private chatOverlay: ChatOverlay;
+    private inputHandler: InputHandler;
+    private touchInputHandler: TouchInputHandler;
+    private networking: Networking;
+    private collisionManager: CollisionManager;
+    private inventoryManager: Inventory;
+    private mapLoader: MapLoader;
+    private healthIndicator: HealthIndicator;
+    private remoteItemRenderer: RemoteItemRenderer;
+    private gameIndex: number;
+    private static nextGameIndex: number = 0;
 
-	constructor(container: HTMLElement) {
-		this.gameIndex = Game.nextGameIndex++;
-		this.localPlayer = new Player();
-		this.chatOverlay = new ChatOverlay(container, this.localPlayer);
-		this.networking = new Networking(this.localPlayer, this.chatOverlay);
-		this.renderer = new Renderer(container, this.networking, this.localPlayer, this.chatOverlay);
-		this.chatOverlay.setRenderer(this.renderer);
-		this.inputHandler = new InputHandler(this.renderer, this.localPlayer, this.gameIndex);
-		this.touchInputHandler = new TouchInputHandler(this.inputHandler, this.chatOverlay);
-		this.renderer.setInputHandler(this.inputHandler);
-		this.collisionManager = new CollisionManager(this.inputHandler);
-		this.renderer.setCollisionManager(this.collisionManager);
-		this.inventoryManager = new Inventory(this.renderer, this.inputHandler, this.networking, this.localPlayer);
-		this.chatOverlay.setNetworking(this.networking);
-		this.chatOverlay.setInputHandler(this.inputHandler);
-		this.mapLoader = new MapLoader(this.renderer);
-		this.healthIndicator = new HealthIndicator(this.renderer, this.localPlayer, this.networking);
-		this.remoteItemRenderer = new RemoteItemRenderer(this.networking, this.renderer);
-	}
 
-	init() {
-		this.inventoryManager.init();
-		this.healthIndicator.init();
-	}
+    constructor(container: HTMLElement) {
+        this.gameIndex = Game.nextGameIndex++;
+        this.localPlayer = new Player();
+        this.chatOverlay = new ChatOverlay(container,this.localPlayer);
+        this.networking = new Networking(this.localPlayer, this.chatOverlay);
+        this.renderer = new Renderer(container, this.networking, this.localPlayer, this.chatOverlay);
+        this.chatOverlay.setRenderer(this.renderer);
+        this.inputHandler = new InputHandler(this.renderer, this.localPlayer, this.gameIndex);
+        this.touchInputHandler = new TouchInputHandler(this.inputHandler, this.chatOverlay);
+        this.renderer.setInputHandler(this.inputHandler);
+        this.collisionManager = new CollisionManager(this.inputHandler);
+        this.renderer.setCollisionManager(this.collisionManager);
+        this.inventoryManager = new Inventory(this.renderer, this.inputHandler, this.networking, this.localPlayer);
+        this.chatOverlay.setNetworking(this.networking);
+        this.chatOverlay.setInputHandler(this.inputHandler);
+        this.mapLoader = new MapLoader(this.renderer);
+        this.healthIndicator = new HealthIndicator(this.renderer,this.localPlayer, this.networking);
+        this.remoteItemRenderer = new RemoteItemRenderer(this.networking, this.renderer);
+    }
 
-	animate() {
-		this.inputHandler.handleInputs();
-		this.touchInputHandler.onFrame();
-		this.collisionManager.collisionPeriodic(this.localPlayer);
-		this.networking.updatePlayerData();
-		this.chatOverlay.onFrame();
-		this.inventoryManager.onFrame();
-		this.healthIndicator.onFrame();
-		this.renderer.onFrame(this.localPlayer);
-		if (this.networking.getServerInfo().mapName) {
-			this.mapLoader.load('/maps/' + this.networking.getServerInfo().mapName + '/map.glb');
-		}
-		this.remoteItemRenderer.onFrame();
-		requestAnimationFrame(this.animate.bind(this));
-	}
+    init() {
+        this.inventoryManager.init();
+        this.healthIndicator.init();
+    }
 
-	start() {
-		this.init();
-		this.animate();
-	}
+    animate() {
+        this.inputHandler.handleInputs();
+        this.touchInputHandler.onFrame();
+        this.collisionManager.collisionPeriodic(this.localPlayer);
+        this.networking.updatePlayerData();
+        this.chatOverlay.onFrame();
+        this.inventoryManager.onFrame();
+        this.healthIndicator.onFrame();
+        this.renderer.onFrame(this.localPlayer);
+        if(this.networking.getServerInfo().mapName)
+            this.mapLoader.load('/maps/' + this.networking.getServerInfo().mapName + '/map.glb');
+        this.remoteItemRenderer.onFrame();
+        requestAnimationFrame(this.animate.bind(this));
+    }
+
+    start() {
+        this.init();
+        this.animate();
+    }
 }
diff --git a/src/client/core/Inventory.ts b/src/client/core/Inventory.ts
index 48a74713..65c3a15e 100644
--- a/src/client/core/Inventory.ts
+++ b/src/client/core/Inventory.ts
@@ -3,175 +3,172 @@ import { Renderer } from './Renderer.ts';
 import { InputHandler } from '../input/InputHandler.ts';
 import { BananaGun } from '../items/BananaGun.ts';
 import { HeldItemInput } from '../input/HeldItemInput.ts';
-import { Networking } from './Networking.ts';
-import { Player } from './Player.ts';
-import { ItemBase, ItemType } from '../items/ItemBase.ts';
-import { FishGun } from '../items/FishGun.ts';
+import {Networking} from "./Networking.ts";
+import {Player} from "./Player.ts";
+import {ItemBase, ItemType} from "../items/ItemBase.ts";
+import {FishGun} from "../items/FishGun.ts";
 
 export class Inventory {
-	private inventoryItems: ItemBase[] = [];
-	private renderer: Renderer;
-	private inputHandler: InputHandler;
-	private networking: Networking;
-	private inventoryScene: THREE.Scene;
-	private selectedInventoryItem: number = 0;
-	private lastSelectedInventoryItem: number = 0;
-	private cameraY: number = 0;
-	private cameraX: number = 0;
-	private clock: THREE.Clock;
-	private camera: THREE.Camera;
-	private lastInventoryTouchTime: number = 0;
-	private localPlayer: Player;
-	private oldInventory: number[] = [];
-
-	private oldDownPressed: boolean = false;
-	private oldUpPressed: boolean = false;
-	private oldQPressed: boolean = false;
-	private oldNumsPressed: boolean[] = new Array(10).fill(false);
-
-	constructor(renderer: Renderer, inputHandler: InputHandler, networking: Networking, localPlayer: Player) {
-		this.renderer = renderer;
-		this.inputHandler = inputHandler;
-		this.networking = networking;
-		this.inventoryScene = renderer.getInventoryMenuScene();
-		this.clock = new THREE.Clock();
-		this.camera = renderer.getInventoryMenuCamera();
-		this.localPlayer = localPlayer;
-	}
-
-	public init() {
-	}
-
-	private updateInventoryItems() {
-		if (!this.arraysEqual(this.oldInventory, this.localPlayer.inventory)) {
-			for (let i = this.inventoryItems.length - 1; i >= 0; i--) {
-				this.inventoryItems[i].destroy();
-				this.inventoryItems.splice(i, 1);
-			}
-
-			//iterate through every number in localPlayer.inventory
-			for (let i = 0; i < this.localPlayer.inventory.length; i++) {
-				const num = this.localPlayer.inventory[i];
-				switch (num) {
-					case 1: {
-						const banana = new BananaGun(this.renderer, this.networking, i, ItemType.InventoryItem);
-						this.inventoryItems.push(banana);
-						break;
-					}
-					case 2: {
-						const fish = new FishGun(this.renderer, this.networking, i, ItemType.InventoryItem);
-						this.inventoryItems.push(fish);
-						break;
-					}
-					default: {
-						const testItem = new ItemBase(
-							ItemType.InventoryItem,
-							this.renderer.getHeldItemScene(),
-							this.inventoryScene,
-							i,
-						);
-						this.inventoryItems.push(testItem);
-						break;
-					}
-				}
-			}
-		}
-
-		this.oldInventory = this.localPlayer.inventory;
-	}
-
-	public arraysEqual(a: number[], b: number[]) {
-		if (a === b) return true;
-		if (a == null || b == null) return false;
-		if (a.length != b.length) return false;
-		for (let i = 0; i < a.length; ++i) {
-			if (a[i] !== b[i]) return false;
-		}
-		return true;
-	}
-
-	public onFrame() {
-		this.updateInventoryItems();
-		const gamepadInputs = this.inputHandler.getGamepadInputs();
-		const heldItemInput = new HeldItemInput(this.inputHandler.getShoot(), this.inputHandler.getAim(), false);
-		let downPressed = (this.inputHandler.getKey('[') || this.inputHandler.getInventoryIterationTouched()) &&
-			!this.localPlayer.chatActive;
-		let upPressed = this.inputHandler.getKey(']') && !this.localPlayer.chatActive;
-		const qPressed = this.inputHandler.getKey('q') && !this.localPlayer.chatActive;
-		if (gamepadInputs.leftShoulder && !this.localPlayer.chatActive) upPressed = true;
-		if (gamepadInputs.rightShoulder && !this.localPlayer.chatActive) downPressed = true;
-		const lastScroll = this.inputHandler.getScrollClicks();
-		if (lastScroll > 0) upPressed = true;
-		if (lastScroll < 0) downPressed = true;
-
-		if (!this.localPlayer.chatActive) {
-			const nums = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'];
-			for (let i = 0; i < nums.length; i++) {
-				const numPressed = this.inputHandler.getKey(nums[i]);
-				if (numPressed && !this.oldNumsPressed[i]) {
-					this.lastSelectedInventoryItem = this.selectedInventoryItem;
-					this.selectedInventoryItem = i;
-					this.lastInventoryTouchTime = Date.now() / 1000;
-					break;
-				}
-			}
-
-			for (let i = 0; i < nums.length; i++) {
-				this.oldNumsPressed[i] = this.inputHandler.getKey(nums[i]);
-			}
-		}
-
-		if (downPressed || upPressed) this.lastInventoryTouchTime = Date.now() / 1000;
-		const deltaTime = this.clock.getDelta();
-
-		if (downPressed && !this.oldDownPressed) {
-			this.lastSelectedInventoryItem = this.selectedInventoryItem;
-			this.selectedInventoryItem++;
-		}
-		if (upPressed && !this.oldUpPressed) {
-			this.lastSelectedInventoryItem = this.selectedInventoryItem;
-			this.selectedInventoryItem--;
-		}
-		if (this.inputHandler.getKey('enter')) {
-			this.lastInventoryTouchTime = 0; //hide inventory
-		}
-
-		if (qPressed && !this.oldQPressed) {
-			const temp = this.selectedInventoryItem;
-			this.selectedInventoryItem = this.lastSelectedInventoryItem;
-			this.lastSelectedInventoryItem = temp;
-			//this.lastInventoryTouchTime = Date.now() / 1000 - 1.25;
-		}
-
-		if (this.selectedInventoryItem < 0) {
-			this.selectedInventoryItem = this.inventoryItems.length - 1;
-		}
-		if (this.selectedInventoryItem >= this.inventoryItems.length) {
-			this.selectedInventoryItem = 0;
-		}
-
-		if (this.lastSelectedInventoryItem < 0) {
-			this.lastSelectedInventoryItem = this.inventoryItems.length - 1;
-		}
-		if (this.lastSelectedInventoryItem >= this.inventoryItems.length) {
-			this.lastSelectedInventoryItem = 0;
-		}
-
-		this.cameraY = this.selectedInventoryItem; //might be backwards
-		if (Date.now() / 1000 - this.lastInventoryTouchTime > 2) {
-			this.cameraX = -1;
-		} else {
-			this.cameraX = 0;
-		}
-
-		this.camera.position.lerp(new THREE.Vector3(this.cameraX, this.selectedInventoryItem, 5), 0.4 * deltaTime * 60);
-
-		for (const item of this.inventoryItems) {
-			item.onFrame(heldItemInput, this.selectedInventoryItem);
-		}
-
-		this.oldDownPressed = downPressed;
-		this.oldUpPressed = upPressed;
-		this.oldQPressed = qPressed;
-	}
-}
+    private inventoryItems: ItemBase[] = [];
+    private renderer: Renderer;
+    private inputHandler: InputHandler;
+    private networking: Networking;
+    private inventoryScene: THREE.Scene;
+    private selectedInventoryItem: number = 0;
+    private lastSelectedInventoryItem: number = 0;
+    private cameraY: number = 0;
+    private cameraX: number = 0;
+    private clock: THREE.Clock;
+    private camera: THREE.Camera;
+    private lastInventoryTouchTime: number = 0;
+    private localPlayer: Player;
+    private oldInventory: number[] = [];
+
+
+    private oldDownPressed: boolean = false;
+    private oldUpPressed: boolean = false;
+    private oldQPressed: boolean = false;
+    private oldNumsPressed: boolean[] = new Array(10).fill(false);
+
+
+
+    constructor(renderer: Renderer, inputHandler: InputHandler, networking:Networking, localPlayer:Player) {
+        this.renderer = renderer;
+        this.inputHandler = inputHandler;
+        this.networking = networking;
+        this.inventoryScene = renderer.getInventoryMenuScene();
+        this.clock = new THREE.Clock();
+        this.camera = renderer.getInventoryMenuCamera();
+        this.localPlayer = localPlayer;
+    }
+
+    public init() {
+
+    }
+
+    private updateInventoryItems(){
+        if(!this.arraysEqual(this.oldInventory, this.localPlayer.inventory)) {
+            for(let i = this.inventoryItems.length - 1; i >= 0; i--) {
+                this.inventoryItems[i].destroy();
+                this.inventoryItems.splice(i, 1);
+            }
+
+            //iterate through every number in localPlayer.inventory
+            for(let i = 0; i < this.localPlayer.inventory.length; i++) {
+                const num = this.localPlayer.inventory[i];
+                switch(num) {
+                    case 1: {
+                        const banana = new BananaGun(this.renderer, this.networking, i, ItemType.InventoryItem);
+                        this.inventoryItems.push(banana);
+                        break;
+                    }
+                    case 2: {
+                        const fish = new FishGun(this.renderer, this.networking, i, ItemType.InventoryItem);
+                        this.inventoryItems.push(fish);
+                        break;
+                    }
+                    default: {
+                            const testItem = new ItemBase(ItemType.InventoryItem, this.renderer.getHeldItemScene(), this.inventoryScene, i);
+                            this.inventoryItems.push(testItem);
+                            break;
+                    }
+                }
+
+            }
+        }
+
+        this.oldInventory = this.localPlayer.inventory;
+    }
+
+    public arraysEqual(a: number[], b: number[]) {
+        if (a === b) return true;
+        if (a == null || b == null) return false;
+        if (a.length != b.length) return false;
+        for (let i = 0; i < a.length; ++i)
+            if (a[i] !== b[i]) return false;
+        return true;
+    }
+
+    public onFrame() {
+        this.updateInventoryItems();
+        const gamepadInputs = this.inputHandler.getGamepadInputs();
+        const heldItemInput = new HeldItemInput(this.inputHandler.getShoot(), this.inputHandler.getAim(), false);
+        let downPressed =( this.inputHandler.getKey('[') || this.inputHandler.getInventoryIterationTouched()) && !this.localPlayer.chatActive;
+        let upPressed = this.inputHandler.getKey(']') && !this.localPlayer.chatActive;
+        const qPressed = this.inputHandler.getKey('q') && !this.localPlayer.chatActive;
+        if (gamepadInputs.leftShoulder && !this.localPlayer.chatActive) upPressed = true;
+        if (gamepadInputs.rightShoulder && !this.localPlayer.chatActive) downPressed = true;
+        const lastScroll = this.inputHandler.getScrollClicks();
+        if(lastScroll > 0) upPressed = true;
+        if(lastScroll < 0) downPressed = true;
+
+
+        if(!this.localPlayer.chatActive){
+            const nums = ['1','2','3','4','5','6','7','8','9','0'];
+            for(let i = 0; i < nums.length; i++) {
+                const numPressed = this.inputHandler.getKey(nums[i]);
+                if(numPressed && !this.oldNumsPressed[i]) {
+                    this.lastSelectedInventoryItem = this.selectedInventoryItem;
+                    this.selectedInventoryItem = i;
+                    this.lastInventoryTouchTime = Date.now() / 1000;
+                    break;
+                }
+            }
+
+            for(let i = 0; i < nums.length; i++) {
+                this.oldNumsPressed[i] = this.inputHandler.getKey(nums[i]);
+            }
+        }
+
+
+        if(downPressed || upPressed) this.lastInventoryTouchTime = Date.now() / 1000;
+        const deltaTime = this.clock.getDelta();
+
+        if(downPressed && !this.oldDownPressed){
+            this.lastSelectedInventoryItem = this.selectedInventoryItem;
+            this.selectedInventoryItem++;
+        }
+        if(upPressed && !this.oldUpPressed){
+            this.lastSelectedInventoryItem = this.selectedInventoryItem;
+            this.selectedInventoryItem--;
+        }
+        if(this.inputHandler.getKey('enter'))
+            this.lastInventoryTouchTime = 0; //hide inventory
+
+        if(qPressed && !this.oldQPressed) {
+            const temp = this.selectedInventoryItem;
+            this.selectedInventoryItem = this.lastSelectedInventoryItem;
+            this.lastSelectedInventoryItem = temp;
+            //this.lastInventoryTouchTime = Date.now() / 1000 - 1.25;
+        }
+
+
+
+        if(this.selectedInventoryItem < 0)
+            this.selectedInventoryItem = this.inventoryItems.length - 1;
+        if(this.selectedInventoryItem >= this.inventoryItems.length)
+            this.selectedInventoryItem = 0;
+
+        if(this.lastSelectedInventoryItem < 0)
+            this.lastSelectedInventoryItem = this.inventoryItems.length - 1;
+        if(this.lastSelectedInventoryItem >= this.inventoryItems.length)
+            this.lastSelectedInventoryItem = 0;
+
+        this.cameraY = this.selectedInventoryItem; //might be backwards
+        if(Date.now()/1000 - this.lastInventoryTouchTime > 2)
+            this.cameraX = -1;
+        else
+            this.cameraX = 0;
+
+
+        this.camera.position.lerp(new THREE.Vector3(this.cameraX, this.selectedInventoryItem, 5), 0.4 * deltaTime * 60);
+
+        for(const item of this.inventoryItems) {
+           item.onFrame(heldItemInput, this.selectedInventoryItem);
+        }
+
+        this.oldDownPressed = downPressed;
+        this.oldUpPressed = upPressed;
+        this.oldQPressed = qPressed;
+    }
+}
\ No newline at end of file
diff --git a/src/client/core/MapLoader.ts b/src/client/core/MapLoader.ts
index 6587db3a..d17ebb3a 100644
--- a/src/client/core/MapLoader.ts
+++ b/src/client/core/MapLoader.ts
@@ -1,28 +1,29 @@
 import * as THREE from 'three';
 import { Renderer } from './Renderer.ts';
-import { computeBoundsTree } from 'three-mesh-bvh';
-import { CollisionManager } from '../input/CollisionManager.ts';
-import { AssetManager } from './AssetManager.ts';
+import {computeBoundsTree} from "three-mesh-bvh";
+import {CollisionManager} from "../input/CollisionManager.ts";
+import { AssetManager } from "./AssetManager.ts";
 
 THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
 
 export class MapLoader {
-	private scene: THREE.Scene;
-	private mapObject: THREE.Group | undefined;
-	private mapUrl: string = '';
+    private scene: THREE.Scene;
+    private mapObject: THREE.Group | undefined;
+    private mapUrl: string = '';
 
-	constructor(renderer: Renderer) {
-		this.scene = renderer.getScene();
-	}
+    constructor(renderer: Renderer) {
+        this.scene = renderer.getScene();
+    }
 
-	public load(mapUrl: string) {
-		if (mapUrl === this.mapUrl) return;
-		this.mapUrl = mapUrl;
-		AssetManager.getInstance().loadAsset(mapUrl, (scene) => {
-			if (this.mapObject) this.scene.remove(this.mapObject);
-			this.mapObject = scene;
-			CollisionManager.staticGeometry(scene);
-			this.scene.add(this.mapObject);
-		});
-	}
-}
+    public load(mapUrl: string) {
+        if(mapUrl === this.mapUrl) return;
+        this.mapUrl = mapUrl;
+        AssetManager.getInstance().loadAsset(mapUrl, (scene) => {
+            if(this.mapObject) this.scene.remove(this.mapObject);
+            this.mapObject = scene;
+            CollisionManager.staticGeometry(scene);
+            this.scene.add(this.mapObject);
+        });
+    }
+
+}
\ No newline at end of file
diff --git a/src/client/core/Networking.ts b/src/client/core/Networking.ts
index e6e79acb..d1a7be05 100644
--- a/src/client/core/Networking.ts
+++ b/src/client/core/Networking.ts
@@ -4,256 +4,251 @@ import { ChatOverlay } from '../ui/ChatOverlay.ts';
 import * as THREE from 'three';
 
 export interface RemotePlayer {
-	idLastDamagedBy: number;
-	latency: number;
-	id: number;
-	position: { x: number; y: number; z: number };
-	velocity: { x: number; y: number; z: number };
-	lookQuaternion: [number, number, number, number];
-	name: string;
-	gravity: number;
-	forced: boolean;
-	health: number;
-	inventory: number[];
-	chatActive: boolean;
-	chatMsg: string;
-	playerSpectating: number;
-	gameMsgs: string[];
-	gameMsgs2: string[];
+    idLastDamagedBy: number;
+    latency: number;
+    id: number;
+    position: { x: number, y: number, z: number };
+    velocity: { x: number, y: number, z: number };
+    lookQuaternion: [number, number, number, number];
+    name: string;
+    gravity: number;
+    forced: boolean;
+    health: number;
+    inventory: number[];
+    chatActive: boolean;
+    chatMsg: string;
+    playerSpectating: number;
+    gameMsgs: string[];
+    gameMsgs2: string[];
 }
 
 interface WorldItem {
-	vector: { x: number; y: number; z: number };
-	id: number;
-	itemType: number;
+    vector: { x: number, y: number, z: number };
+    id: number;
+    itemType: number;
 }
 
 interface ServerInfo {
-	name: string;
-	maxPlayers: number;
-	currentPlayers: number;
-	mapName: string;
-	tickRate: number;
-	version: string;
-	gameMode: string;
-	playerMaxHealth: number;
+    name: string;
+    maxPlayers: number;
+    currentPlayers: number;
+    mapName: string;
+    tickRate: number;
+    version: string;
+    gameMode: string;
+    playerMaxHealth: number;
 }
 
 interface LastUploadedLocalPlayer {
-	position: THREE.Vector3;
-	quaternion: THREE.Quaternion;
-	chatMsg: string;
-	velocity: THREE.Vector3;
-	name: string;
+    position: THREE.Vector3;
+    quaternion: THREE.Quaternion;
+    chatMsg: string;
+    velocity: THREE.Vector3;
+    name: string;
 }
 
 export class Networking {
-	private socket: Socket;
-	private gameVersion: string = '';
-	private remotePlayers: RemotePlayer[] = [];
-	private worldItems: WorldItem[] = [];
-	private lastUploadedLocalPlayer: LastUploadedLocalPlayer | null = null;
-	private lastUploadTime: number;
-	private uploadWait: number;
-	private lastLatencyTestEmit: number;
-	private lastLatencyTestGotResponse: boolean;
-	private latencyTestWait: number;
-	private messagesBeingTyped: string[] = [];
-	private localPlayer: Player;
-	private chatOverlay: ChatOverlay;
-	private damagedTimestamp: number = 0;
-	private serverInfo: ServerInfo;
-
-	constructor(localPlayer: Player, chatOverlay: ChatOverlay) {
-		this.localPlayer = localPlayer;
-		this.chatOverlay = chatOverlay;
-
-		this.socket = io();
-		this.fetchVersion();
-
-		this.lastUploadTime = Date.now() / 1000;
-		this.uploadWait = 0.05; //gets replaced by server info
-		this.lastLatencyTestEmit = 0;
-		this.lastLatencyTestGotResponse = false;
-		this.latencyTestWait = 5;
-
-		this.serverInfo = {
-			name: '',
-			maxPlayers: 0,
-			currentPlayers: 0,
-			mapName: '',
-			tickRate: 0,
-			version: '',
-			gameMode: '',
-			playerMaxHealth: 0,
-		};
-
-		this.setupSocketListeners();
-	}
-
-	private async fetchVersion() {
-		try {
-			const response = await fetch('gameVersion.json');
-			const data = await response.json();
-			this.gameVersion = data['version'];
-		} catch (e) {
-			console.error(e);
-		}
-	}
-
-	private setupSocketListeners() {
-		this.socket.on('latencyTest', () => {
-			this.localPlayer.latency = (Date.now() / 1000 - this.lastLatencyTestEmit) * 1000;
-			this.lastLatencyTestGotResponse = true;
-		});
-
-		this.socket.on('remotePlayerData', (data: RemotePlayer[]) => {
-			this.remotePlayers = data;
-			this.processRemotePlayerData();
-		});
-
-		this.socket.on('worldItemData', (data: WorldItem[]) => {
-			this.worldItems = data;
-			this.processWorldItemData();
-		});
-
-		this.socket.on('chatMsg', (data: { id: number; name: string; message: string }) => {
-			if (data.id !== this.localPlayer.id) this.chatOverlay.addChatMessage(data);
-		});
-
-		this.socket.on('serverInfo', (data: ServerInfo) => {
-			this.serverInfo = data;
-			this.onServerInfo();
-		});
-	}
-
-	private onServerInfo() {
-		this.uploadWait = 1 / this.serverInfo.tickRate;
-	}
-	public updatePlayerData() {
-		const currentTime = Date.now() / 1000;
-		this.localPlayer.gameVersion = this.gameVersion;
-		if (currentTime - this.lastUploadTime < this.uploadWait) return;
-
-		if (this.localPlayer.gameVersion === '') return;
-
-		if (
-			this.playersAreEqualEnough(this.localPlayer, this.lastUploadedLocalPlayer) &&
-			currentTime - this.lastUploadTime < 4
-		) {
-			return;
-		}
-
-		this.socket.emit('playerData', this.localPlayer);
-		this.lastUploadedLocalPlayer = {
-			position: this.localPlayer.position.clone(),
-			quaternion: this.localPlayer.quaternion.clone(),
-			chatMsg: this.localPlayer.chatMsg,
-			velocity: this.localPlayer.velocity.clone(),
-			name: this.localPlayer.name,
-		};
-
-		this.lastUploadTime = currentTime;
-
-		if (currentTime - this.lastLatencyTestEmit > this.latencyTestWait) {
-			this.socket.emit('latencyTest');
-			this.lastLatencyTestEmit = currentTime;
-			if (!this.lastLatencyTestGotResponse) {
-				this.localPlayer.latency = 999;
-			}
-			this.lastLatencyTestGotResponse = false;
-		}
-	}
-
-	public processWorldItemData() {
-		// Implementation for processing world items
-	}
-
-	public getServerInfo() {
-		return this.serverInfo;
-	}
-
-	private processRemotePlayerData() {
-		this.messagesBeingTyped = [];
-		for (const remotePlayer of this.remotePlayers) {
-			if (remotePlayer.id === this.localPlayer.id) {
-				if (remotePlayer.forced) {
-					this.localPlayer.position.set(remotePlayer.position.x, remotePlayer.position.y, remotePlayer.position.z);
-					this.localPlayer.velocity.set(remotePlayer.velocity.x, remotePlayer.velocity.y, remotePlayer.velocity.z);
-					this.localPlayer.lookQuaternion.set(...remotePlayer.lookQuaternion);
-					//this.localPlayer.name = remotePlayer.name;
-					this.localPlayer.gravity = remotePlayer.gravity;
-					this.localPlayer.forcedAcknowledged = true;
-				} else {
-					this.localPlayer.forcedAcknowledged = false;
-				}
-				if (remotePlayer.health < this.localPlayer.health) this.damagedTimestamp = Date.now() / 1000;
-				this.localPlayer.health = remotePlayer.health;
-				this.localPlayer.idLastDamagedBy = remotePlayer.idLastDamagedBy;
-				this.localPlayer.inventory = remotePlayer.inventory;
-				this.localPlayer.playerSpectating = remotePlayer.playerSpectating;
-				this.localPlayer.gameMsgs = remotePlayer.gameMsgs;
-				this.localPlayer.gameMsgs2 = remotePlayer.gameMsgs2;
-				continue;
-			}
-			if (remotePlayer.chatActive) {
-				this.messagesBeingTyped.push(`${remotePlayer.name}: ${remotePlayer.chatMsg}`);
-			}
-		}
-	}
-
-	private playersAreEqualEnough(player1: Player, player2: LastUploadedLocalPlayer | null) {
-		if (player1 === null || player2 === null) return false;
-		let out = true;
-		out = out && player1.position.equals(player2.position);
-		out = out && player1.quaternion.equals(player2.quaternion);
-		out = out && player1.chatMsg === player2.chatMsg;
-		out = out && player1.velocity.equals(player2.velocity);
-		out = out && player1.name === player2.name;
-
-		return out;
-	}
-
-	public getDamagedTimestamp() {
-		return this.damagedTimestamp;
-	}
-
-	public getMessagesBeingTyped() {
-		return this.messagesBeingTyped;
-	}
-
-	public getRemotePlayerData(): RemotePlayer[] {
-		return this.remotePlayers;
-	}
-
-	public sendMessage(msg: string) {
-		const chatMessage = {
-			message: msg,
-			id: this.localPlayer.id,
-			name: this.localPlayer.name,
-		};
-		if (msg.length < 1) return;
-		if (chatMessage.message.startsWith('>')) chatMessage.message = '&2' + chatMessage.message;
-		if (msg.charAt(0) === '/') {
-			this.socket.emit('chatMsg', chatMessage);
-			return;
-		}
-		chatMessage.message = '&f' + chatMessage.message;
-		this.chatOverlay.addChatMessage(chatMessage);
-		this.socket.emit('chatMsg', chatMessage);
-	}
-
-	public applyDamage(id: number, damage: number) {
-		const player2 = this.remotePlayers.find((player) => player.id === id);
-		const damageRequest = {
-			localPlayer: this.localPlayer,
-			targetPlayer: player2,
-			damage: damage,
-		};
-		this.socket.emit('applyDamage', damageRequest);
-	}
-
-	public getWorldItemsData() {
-		return this.worldItems;
-	}
+    private socket: Socket;
+    private gameVersion: string = '';
+    private remotePlayers: RemotePlayer[] = [];
+    private worldItems: WorldItem[] = [];
+    private lastUploadedLocalPlayer: LastUploadedLocalPlayer | null = null;
+    private lastUploadTime: number;
+    private uploadWait: number;
+    private lastLatencyTestEmit: number;
+    private lastLatencyTestGotResponse: boolean;
+    private latencyTestWait: number;
+    private messagesBeingTyped: string[] = [];
+    private localPlayer: Player;
+    private chatOverlay: ChatOverlay;
+    private damagedTimestamp: number = 0;
+    private serverInfo: ServerInfo;
+
+    constructor(localPlayer: Player, chatOverlay: ChatOverlay) {
+        this.localPlayer = localPlayer;
+        this.chatOverlay = chatOverlay;
+
+        this.socket = io();
+        this.fetchVersion();
+
+        this.lastUploadTime = Date.now() / 1000;
+        this.uploadWait = 0.05; //gets replaced by server info
+        this.lastLatencyTestEmit = 0;
+        this.lastLatencyTestGotResponse = false;
+        this.latencyTestWait = 5;
+
+        this.serverInfo = {
+            name: '',
+            maxPlayers: 0,
+            currentPlayers: 0,
+            mapName: '',
+            tickRate: 0,
+            version: '',
+            gameMode: '',
+            playerMaxHealth: 0
+        }
+
+        this.setupSocketListeners();
+    }
+
+    private async fetchVersion() {
+        try {
+            const response = await fetch('gameVersion.json');
+            const data = await response.json();
+            this.gameVersion = data['version'];
+        } catch (e) {
+            console.error(e);
+        }
+    }
+
+    private setupSocketListeners() {
+        this.socket.on('latencyTest', () => {
+            this.localPlayer.latency = (Date.now() / 1000 - this.lastLatencyTestEmit) * 1000;
+            this.lastLatencyTestGotResponse = true;
+        });
+
+        this.socket.on('remotePlayerData', (data: RemotePlayer[]) => {
+            this.remotePlayers = data;
+            this.processRemotePlayerData();
+        });
+
+        this.socket.on('worldItemData', (data: WorldItem[]) => {
+            this.worldItems = data;
+            this.processWorldItemData();
+        });
+
+        this.socket.on('chatMsg', (data: { id: number, name: string, message: string }) => {
+            if (data.id !== this.localPlayer.id) this.chatOverlay.addChatMessage(data);
+        });
+
+        this.socket.on('serverInfo', (data: ServerInfo) => {
+            this.serverInfo = data;
+            this.onServerInfo();
+        });
+    }
+
+    private onServerInfo() {
+        this.uploadWait = 1 / this.serverInfo.tickRate;
+    }
+    public updatePlayerData() {
+        const currentTime = Date.now() / 1000;
+        this.localPlayer.gameVersion = this.gameVersion;
+        if (currentTime - this.lastUploadTime < this.uploadWait) return;
+
+        if (this.localPlayer.gameVersion === '') return;
+
+        if (this.playersAreEqualEnough(this.localPlayer, this.lastUploadedLocalPlayer) && currentTime - this.lastUploadTime < 4)
+            return;
+
+        this.socket.emit('playerData', this.localPlayer);
+        this.lastUploadedLocalPlayer = {
+            position: this.localPlayer.position.clone(),
+            quaternion: this.localPlayer.quaternion.clone(),
+            chatMsg: this.localPlayer.chatMsg,
+            velocity: this.localPlayer.velocity.clone(),
+            name: this.localPlayer.name,
+        };
+
+        this.lastUploadTime = currentTime;
+
+        if (currentTime - this.lastLatencyTestEmit > this.latencyTestWait) {
+            this.socket.emit('latencyTest');
+            this.lastLatencyTestEmit = currentTime;
+            if (!this.lastLatencyTestGotResponse) {
+                this.localPlayer.latency = 999;
+            }
+            this.lastLatencyTestGotResponse = false;
+        }
+    }
+
+    public processWorldItemData() {
+        // Implementation for processing world items
+    }
+
+    public getServerInfo() {
+        return this.serverInfo;
+    }
+
+    private processRemotePlayerData() {
+        this.messagesBeingTyped = [];
+        for (const remotePlayer of this.remotePlayers) {
+            if (remotePlayer.id === this.localPlayer.id) {
+                if (remotePlayer.forced) {
+                    this.localPlayer.position.set(remotePlayer.position.x, remotePlayer.position.y, remotePlayer.position.z);
+                    this.localPlayer.velocity.set(remotePlayer.velocity.x, remotePlayer.velocity.y, remotePlayer.velocity.z);
+                    this.localPlayer.lookQuaternion.set(...remotePlayer.lookQuaternion);
+                    //this.localPlayer.name = remotePlayer.name;
+                    this.localPlayer.gravity = remotePlayer.gravity;
+                    this.localPlayer.forcedAcknowledged = true;
+                } else {
+                    this.localPlayer.forcedAcknowledged = false;
+                }
+                if (remotePlayer.health < this.localPlayer.health) this.damagedTimestamp = Date.now() / 1000;
+                this.localPlayer.health = remotePlayer.health;
+                this.localPlayer.idLastDamagedBy = remotePlayer.idLastDamagedBy;
+                this.localPlayer.inventory = remotePlayer.inventory;
+                this.localPlayer.playerSpectating = remotePlayer.playerSpectating;
+                this.localPlayer.gameMsgs = remotePlayer.gameMsgs;
+                this.localPlayer.gameMsgs2 = remotePlayer.gameMsgs2;
+                continue;
+            }
+            if (remotePlayer.chatActive)
+                this.messagesBeingTyped.push(`${remotePlayer.name}: ${remotePlayer.chatMsg}`);
+        }
+    }
+
+    private playersAreEqualEnough(player1: Player, player2: LastUploadedLocalPlayer | null) {
+        if (player1 === null || player2 === null) return false;
+        let out = true;
+        out = out && player1.position.equals(player2.position);
+        out = out && player1.quaternion.equals(player2.quaternion);
+        out = out && player1.chatMsg === player2.chatMsg;
+        out = out && player1.velocity.equals(player2.velocity);
+        out = out && player1.name === player2.name;
+
+        return out;
+    }
+
+    public getDamagedTimestamp() {
+        return this.damagedTimestamp;
+    }
+
+    public getMessagesBeingTyped() {
+        return this.messagesBeingTyped;
+    }
+
+    public getRemotePlayerData(): RemotePlayer[] {
+        return this.remotePlayers;
+    }
+
+    public sendMessage(msg: string) {
+        const chatMessage = {
+            message: msg,
+            id: this.localPlayer.id,
+            name: this.localPlayer.name
+        };
+        if (msg.length < 1) return;
+        if(chatMessage.message.startsWith('>')) chatMessage.message = '&2'+chatMessage.message;
+        if (msg.charAt(0) === '/'){
+            this.socket.emit('chatMsg', chatMessage);
+            return;
+        }
+        chatMessage.message = '&f' + chatMessage.message;
+        this.chatOverlay.addChatMessage(chatMessage);
+        this.socket.emit('chatMsg', chatMessage);
+    }
+
+    public applyDamage(id: number, damage: number) {
+        const player2 = this.remotePlayers.find(player => player.id === id);
+        const damageRequest = {
+            localPlayer: this.localPlayer,
+            targetPlayer: player2,
+            damage: damage
+        };
+        this.socket.emit('applyDamage', damageRequest);
+    }
+
+    public getWorldItemsData() {
+        return this.worldItems;
+    }
 }
diff --git a/src/client/core/Player.ts b/src/client/core/Player.ts
index c88b55bf..a875deee 100644
--- a/src/client/core/Player.ts
+++ b/src/client/core/Player.ts
@@ -1,55 +1,55 @@
 import * as THREE from 'three';
-import { SettingsManager } from './SettingsManager.ts';
+import {SettingsManager} from "./SettingsManager.ts";
 
 export class Player {
-	public position: THREE.Vector3;
-	public velocity: THREE.Vector3;
-	public inputVelocity: THREE.Vector3;
-	public gravity: number;
-	public lookQuaternion: THREE.Quaternion;
-	public quaternion: THREE.Quaternion;
-	public id: number;
-	public gameVersion: string;
-	public name: string;
-	public speed: number;
-	public acceleration: number;
-	public chatActive: boolean;
-	public chatMsg: string;
-	public latency: number;
-	public health: number;
-	public forced: boolean;
-	public forcedAcknowledged: boolean;
-	public inventory: number[];
-	public idLastDamagedBy: number;
-	public playerSpectating: number;
-	public gameMsgs: string[];
-	public gameMsgs2: string[];
+    public position: THREE.Vector3;
+    public velocity: THREE.Vector3;
+    public inputVelocity: THREE.Vector3;
+    public gravity: number;
+    public lookQuaternion: THREE.Quaternion;
+    public quaternion: THREE.Quaternion;
+    public id: number;
+    public gameVersion: string;
+    public name: string;
+    public speed: number;
+    public acceleration: number;
+    public chatActive: boolean;
+    public chatMsg: string;
+    public latency: number;
+    public health: number;
+    public forced: boolean;
+    public forcedAcknowledged: boolean;
+    public inventory: number[];
+    public idLastDamagedBy: number;
+    public playerSpectating: number;
+    public gameMsgs: string[];
+    public gameMsgs2: string[];
 
-	constructor() {
-		this.position = new THREE.Vector3(0, 100, 0);
-		this.velocity = new THREE.Vector3(0, 0, 0);
-		this.inputVelocity = new THREE.Vector3();
-		this.gravity = 0;
-		this.lookQuaternion = new THREE.Quaternion();
-		this.quaternion = new THREE.Quaternion();
-		this.id = Math.floor(Math.random() * 1000000000);
-		this.gameVersion = '';
-		this.name = '';
-		this.speed = 5;
-		this.acceleration = 100;
-		this.chatActive = false;
-		this.chatMsg = '';
-		this.latency = 1000;
-		this.health = 100;
-		this.forced = false;
-		this.forcedAcknowledged = false;
-		this.inventory = [];
-		this.idLastDamagedBy = -1;
-		this.playerSpectating = -1;
-		this.gameMsgs = [];
-		this.gameMsgs2 = [];
+    constructor() {
+        this.position = new THREE.Vector3(0,100,0);
+        this.velocity = new THREE.Vector3(0,0,0);
+        this.inputVelocity = new THREE.Vector3();
+        this.gravity = 0;
+        this.lookQuaternion = new THREE.Quaternion();
+        this.quaternion = new THREE.Quaternion();
+        this.id = Math.floor(Math.random() * 1000000000);
+        this.gameVersion = '';
+        this.name = '';
+        this.speed = 5;
+        this.acceleration = 100;
+        this.chatActive = false;
+        this.chatMsg = '';
+        this.latency = 1000;
+        this.health = 100;
+        this.forced = false;
+        this.forcedAcknowledged = false;
+        this.inventory = [];
+        this.idLastDamagedBy = -1;
+        this.playerSpectating = -1;
+        this.gameMsgs = [];
+        this.gameMsgs2 = [];
 
-		const storedName = SettingsManager.settings.name;
-		if (storedName) this.name = storedName;
-	}
-}
+        const storedName = SettingsManager.settings.name;
+        if (storedName) this.name = storedName;
+    }
+}
\ No newline at end of file
diff --git a/src/client/core/RemoteItemRenderer.ts b/src/client/core/RemoteItemRenderer.ts
index ddff5e29..b3e87ad3 100644
--- a/src/client/core/RemoteItemRenderer.ts
+++ b/src/client/core/RemoteItemRenderer.ts
@@ -7,103 +7,94 @@ import { FishGun } from '../items/FishGun.ts';
 
 // Custom types
 type Vector3Data = {
-	x: number;
-	y: number;
-	z: number;
+    x: number;
+    y: number;
+    z: number;
 };
 
 type WorldItemData = {
-	id: number;
-	itemType: number;
-	vector: Vector3Data;
+    id: number;
+    itemType: number;
+    vector: Vector3Data;
 };
 
 type ItemsToRenderEntry = {
-	id: number;
-	item: ItemBase;
+    id: number;
+    item: ItemBase;
 };
 
 export class RemoteItemRenderer {
-	private networking: Networking;
-	private renderer: Renderer;
-	private itemsToRender: ItemsToRenderEntry[] = [];
-	private worldItemsData: WorldItemData[] = [];
+    private networking: Networking;
+    private renderer: Renderer;
+    private itemsToRender: ItemsToRenderEntry[] = [];
+    private worldItemsData: WorldItemData[] = [];
 
-	constructor(networking: Networking, renderer: Renderer) {
-		this.networking = networking;
-		this.renderer = renderer;
-	}
+    constructor(networking: Networking, renderer: Renderer) {
+        this.networking = networking;
+        this.renderer = renderer;
+    }
 
-	public update() {
-		// Get the latest world items data from networking
-		const newWorldItemsData: WorldItemData[] = this.networking.getWorldItemsData();
+    public update() {
+        // Get the latest world items data from networking
+        const newWorldItemsData: WorldItemData[] = this.networking.getWorldItemsData();
 
-		// Process the new data to update itemsToRender
-		this.updateWorldItems(newWorldItemsData);
-	}
+        // Process the new data to update itemsToRender
+        this.updateWorldItems(newWorldItemsData);
+    }
 
-	private updateWorldItems(newWorldItemsData: WorldItemData[]) {
-		// Update existing items and add new items
-		newWorldItemsData.forEach((worldItemData) => {
-			const existingItem = this.itemsToRender.find((item) => item.id === worldItemData.id);
-			if (existingItem) {
-				// Update position
-				existingItem.item.setWorldPosition(
-					new THREE.Vector3(
-						worldItemData.vector.x,
-						worldItemData.vector.y,
-						worldItemData.vector.z,
-					),
-				);
-			} else {
-				// Create new item
-				const item = this.createItemByType(worldItemData.itemType);
-				if (item) {
-					item.setWorldPosition(
-						new THREE.Vector3(
-							worldItemData.vector.x,
-							worldItemData.vector.y,
-							worldItemData.vector.z,
-						),
-					);
-					this.itemsToRender.push({ id: worldItemData.id, item });
-				}
-			}
-		});
+    private updateWorldItems(newWorldItemsData: WorldItemData[]) {
+        // Update existing items and add new items
+        newWorldItemsData.forEach((worldItemData) => {
+            const existingItem = this.itemsToRender.find(item => item.id === worldItemData.id);
+            if (existingItem) {
+                // Update position
+                existingItem.item.setWorldPosition(new THREE.Vector3(
+                    worldItemData.vector.x,
+                    worldItemData.vector.y,
+                    worldItemData.vector.z
+                ));
+            } else {
+                // Create new item
+                const item = this.createItemByType(worldItemData.itemType);
+                if (item) {
+                    item.setWorldPosition(new THREE.Vector3(
+                        worldItemData.vector.x,
+                        worldItemData.vector.y,
+                        worldItemData.vector.z
+                    ));
+                    this.itemsToRender.push({ id: worldItemData.id, item });
+                }
+            }
+        });
 
-		// Remove items that are no longer in the newWorldItemsData
-		this.itemsToRender = this.itemsToRender.filter((item) => {
-			const existsInNewData = newWorldItemsData.some((worldItemData) => worldItemData.id === item.id);
-			if (!existsInNewData) {
-				// Remove item from scene
-				item.item.destroy();
-			}
-			return existsInNewData;
-		});
-	}
+        // Remove items that are no longer in the newWorldItemsData
+        this.itemsToRender = this.itemsToRender.filter(item => {
+            const existsInNewData = newWorldItemsData.some(worldItemData => worldItemData.id === item.id);
+            if (!existsInNewData) {
+                // Remove item from scene
+                item.item.destroy();
+            }
+            return existsInNewData;
+        });
+    }
 
-	private createItemByType(itemType: number): ItemBase | null {
-		// Create item based on itemType
-		switch (itemType) {
-			case 1:
-				return new BananaGun(this.renderer, this.networking, 0, ItemType.WorldItem);
-			case 2:
-				return new FishGun(this.renderer, this.networking, 0, ItemType.WorldItem);
-			default:
-				// Return a generic item
-				return new ItemBase(
-					ItemType.WorldItem,
-					this.renderer.getEntityScene(),
-					this.renderer.getInventoryMenuScene(),
-					0,
-				);
-		}
-	}
+    private createItemByType(itemType: number): ItemBase | null {
+        // Create item based on itemType
+        switch (itemType) {
+            case 1:
+                return new BananaGun(this.renderer, this.networking, 0, ItemType.WorldItem);
+            case 2:
+                return new FishGun(this.renderer, this.networking, 0, ItemType.WorldItem);
+            default:
+                // Return a generic item
+                return new ItemBase(ItemType.WorldItem, this.renderer.getEntityScene(), this.renderer.getInventoryMenuScene(), 0);
+        }
+    }
 
-	public onFrame() {
-		this.update();
-		this.itemsToRender.forEach((itemEntry) => {
-			itemEntry.item.onFrame(undefined, undefined); // Passing null for input and selectedIndex
-		});
-	}
+    public onFrame() {
+        this.update();
+        this.itemsToRender.forEach(itemEntry => {
+            itemEntry.item.onFrame(undefined, undefined); // Passing null for input and selectedIndex
+        });
+    }
 }
diff --git a/src/client/core/RemotePlayerRenderer.ts b/src/client/core/RemotePlayerRenderer.ts
index 437ffd0f..d1805869 100644
--- a/src/client/core/RemotePlayerRenderer.ts
+++ b/src/client/core/RemotePlayerRenderer.ts
@@ -3,531 +3,521 @@ import { Networking } from './Networking.ts';
 import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
 import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
 import { Player } from './Player.ts';
-import { acceleratedRaycast, computeBoundsTree } from 'three-mesh-bvh';
+import {acceleratedRaycast, computeBoundsTree} from "three-mesh-bvh";
 
 THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
 THREE.Mesh.prototype.raycast = acceleratedRaycast;
 
 interface RemotePlayerData {
-	health: number;
-	id: number;
-	velocity: { x: number; y: number; z: number };
-	position: { x: number; y: number; z: number };
-	quaternion: [number, number, number, number]; // Add quaternion as required
-	forced: boolean;
-	name: string;
-	playerSpectating: number;
+    health: number;
+    id: number;
+    velocity: { x: number; y: number; z: number };
+    position: { x: number; y: number; z: number };
+    quaternion: [number, number, number, number]; // Add quaternion as required
+    forced: boolean;
+    name: string;
+    playerSpectating: number;
 }
 
 interface RemotePlayer extends Omit<RemotePlayerData, 'quaternion'> {
-	quaternion?: [number, number, number, number]; // Optional in case it's missing
+    quaternion?: [number, number, number, number]; // Optional in case it's missing
 }
 
 interface PlayerToRender {
-	id: number;
-	object: THREE.Object3D;
-	objectUUID: string;
-	sphere: THREE.Object3D;
-	nameLabel: THREE.Sprite;
-	name: string;
+    id: number;
+    object: THREE.Object3D;
+    objectUUID: string;
+    sphere: THREE.Object3D;
+    nameLabel: THREE.Sprite;
+    name: string;
 }
 
 export class RemotePlayerRenderer {
-	private entityScene: THREE.Scene;
-	private playersToRender: PlayerToRender[];
-	private possumMesh: THREE.Mesh | undefined;
-	private loader: GLTFLoader;
-	private dracoLoader: DRACOLoader;
-
-	private sphere: THREE.Mesh;
-	private sphereScene: THREE.Scene;
-
-	private raycaster: THREE.Raycaster;
-	private camera: THREE.Camera;
-	private scene: THREE.Scene;
-
-	private isAnimating: { [id: number]: boolean };
-	private animationPhase: { [id: number]: number };
-	private previousVelocity: { [id: number]: number };
-	private lastRunningYOffset: { [id: number]: number };
-
-	private groundTruthPositions: { [id: number]: THREE.Vector3 };
-
-	private networking: Networking;
-	private localPlayer: Player;
-	private deltaTime: number = 0; // Initialize deltaTime to avoid Deno error
-	private static minVelocityToAnimate = 0.1;
-	private static map: THREE.Mesh = new THREE.Mesh();
-
-	private crosshairVec = new THREE.Vector2();
-
-	constructor(
-		networking: Networking,
-		localPlayer: Player,
-		raycaster: THREE.Raycaster,
-		camera: THREE.Camera,
-		scene: THREE.Scene,
-	) {
-		this.networking = networking;
-		this.localPlayer = localPlayer;
-		this.raycaster = raycaster;
-		this.camera = camera;
-		this.scene = scene;
-
-		this.entityScene = new THREE.Scene();
-
-		this.sphere = new THREE.Mesh(new THREE.SphereGeometry(.6), new THREE.MeshBasicMaterial({ color: 0xffffff }));
-		this.sphere.geometry.computeBoundsTree();
-		this.sphereScene = new THREE.Scene();
-
-		this.loader = new GLTFLoader();
-		this.dracoLoader = new DRACOLoader();
-		this.dracoLoader.setDecoderPath('/draco/');
-		this.loader.setDRACOLoader(this.dracoLoader);
-
-		this.possumMesh = undefined;
-		this.loader.load(
-			'models/simplified_possum.glb',
-			(gltf) => {
-				console.time('Computing possum BVH');
-				(<THREE.Mesh> gltf.scene.children[0]).geometry.computeBoundsTree();
-				console.timeEnd('Computing possum BVH');
-				this.possumMesh = <THREE.Mesh> gltf.scene.children[0];
-			},
-			undefined,
-			() => {
-				console.log('possum loading error');
-			},
-		);
-
-		this.playersToRender = [];
-
-		this.isAnimating = {};
-		this.animationPhase = {};
-		this.previousVelocity = {};
-		this.lastRunningYOffset = {};
-
-		this.groundTruthPositions = {};
-	}
-
-	public getEntityScene(): THREE.Scene {
-		return this.entityScene;
-	}
-
-	public update(deltaTime: number): void {
-		this.deltaTime = deltaTime;
-		this.updateRemotePlayers();
-	}
-
-	private updateRemotePlayers(): void {
-		if (!this.possumMesh) return;
-
-		const remotePlayerData: RemotePlayer[] = this.networking.getRemotePlayerData();
-		const localPlayerId = this.localPlayer.id;
-
-		// First, remove all players that should be hidden
-		this.playersToRender = this.playersToRender.filter((player) => {
-			const remotePlayer = remotePlayerData.find((rp) => rp.id === player.id);
-			const shouldHide = !remotePlayer || // Player no longer exists
-				remotePlayer.id === localPlayerId || // Is local player
-				remotePlayer.id === this.localPlayer.playerSpectating || // Is being spectated
-				remotePlayer.playerSpectating !== -1; // Is spectating someone
-
-			if (shouldHide) {
-				// Remove the player's objects from scenes
-				this.entityScene.remove(player.object);
-				this.entityScene.remove(player.nameLabel);
-				this.sphereScene.remove(player.sphere);
-
-				// Clean up associated data
-				delete this.groundTruthPositions[player.id];
-				delete this.isAnimating[player.id];
-				delete this.animationPhase[player.id];
-				delete this.previousVelocity[player.id];
-				delete this.lastRunningYOffset[player.id];
-			}
-
-			return !shouldHide;
-		});
-
-		// Then, update or add remaining valid players
-		remotePlayerData.forEach((remotePlayer) => {
-			// Skip if player should be hidden
-			if (
-				remotePlayer.id === localPlayerId ||
-				remotePlayer.id === this.localPlayer.playerSpectating ||
-				remotePlayer.playerSpectating !== -1
-			) {
-				return;
-			}
-
-			const playerDataWithQuaternion: RemotePlayerData = {
-				...remotePlayer,
-				quaternion: remotePlayer.quaternion || [0, 0, 0, 1],
-			};
-
-			const existingPlayer = this.playersToRender.find((player) => player.id === remotePlayer.id);
-			if (existingPlayer) {
-				this.updatePlayerPosition(existingPlayer.object, existingPlayer.sphere, playerDataWithQuaternion);
-			} else {
-				this.addNewPlayer(playerDataWithQuaternion);
-			}
-		});
-	}
-
-	private updatePlayerPosition(
-		playerObject: THREE.Object3D,
-		playerSphere: THREE.Object3D,
-		remotePlayerData: RemotePlayerData,
-	): void {
-		const velocity = Math.sqrt(
-			Math.pow(remotePlayerData.velocity.x, 2) +
-				Math.pow(remotePlayerData.velocity.y, 2) +
-				Math.pow(remotePlayerData.velocity.z, 2),
-		);
-
-		const playerId = remotePlayerData.id;
-		const prevVelocity = this.previousVelocity[playerId] || 0;
-
-		if (
-			prevVelocity <= RemotePlayerRenderer.minVelocityToAnimate && velocity > RemotePlayerRenderer.minVelocityToAnimate
-		) {
-			this.isAnimating[playerId] = true;
-			this.animationPhase[playerId] = 0;
-		}
-
-		this.previousVelocity[playerId] = velocity;
-
-		// Get or initialize groundTruthPosition
-		if (!this.groundTruthPositions[playerId]) {
-			this.groundTruthPositions[playerId] = new THREE.Vector3(
-				remotePlayerData.position.x,
-				remotePlayerData.position.y,
-				remotePlayerData.position.z,
-			);
-		}
-
-		const groundTruthPosition = this.groundTruthPositions[playerId];
-
-		// Apply velocity to groundTruthPosition
-		groundTruthPosition.x += remotePlayerData.velocity.x * this.deltaTime;
-		groundTruthPosition.y += remotePlayerData.velocity.y * this.deltaTime;
-		groundTruthPosition.z += remotePlayerData.velocity.z * this.deltaTime;
-
-		// If forced, set groundTruthPosition to remotePlayerData.position
-		if (remotePlayerData.forced) {
-			groundTruthPosition.set(
-				remotePlayerData.position.x,
-				remotePlayerData.position.y,
-				remotePlayerData.position.z,
-			);
-		}
-
-		// Lerp groundTruthPosition towards remotePlayerData.position
-		groundTruthPosition.lerp(
-			new THREE.Vector3(
-				remotePlayerData.position.x,
-				remotePlayerData.position.y,
-				remotePlayerData.position.z,
-			),
-			0.1 * this.deltaTime * 60,
-		);
-
-		// Set playerObject.position to groundTruthPosition.clone()
-		playerObject.position.copy(groundTruthPosition);
-		playerSphere.position.copy(groundTruthPosition.clone());
-
-		// Apply animation offsets (e.g., yOffset)
-		if (this.isAnimating[playerId]) {
-			const frequency = 25;
-			this.animationPhase[playerId] += this.deltaTime * frequency;
-
-			const amplitude = 0.08;
-			const yOffset = amplitude * (1 + Math.cos(this.animationPhase[playerId]));
-
-			playerObject.position.y += yOffset;
-			playerSphere.position.y += yOffset;
-			this.lastRunningYOffset[playerId] = yOffset;
-
-			if (velocity <= RemotePlayerRenderer.minVelocityToAnimate && Math.cos(this.animationPhase[playerId]) <= 0) {
-				this.isAnimating[playerId] = false;
-				this.lastRunningYOffset[playerId] = 0;
-			}
-		} else {
-			this.lastRunningYOffset[playerId] = 0;
-		}
-
-		// Apply scared effect
-		const scaredLevel = 1 - Math.pow(remotePlayerData.health / this.networking.getServerInfo().playerMaxHealth, 2); // 0-1
-		playerObject.position.x += (Math.random() - 0.5) * 0.05 * scaredLevel;
-		playerObject.position.y += (Math.random() - 0.5) * 0.05 * scaredLevel;
-		playerObject.position.z += (Math.random() - 0.5) * 0.05 * scaredLevel;
-
-		// Apply quaternion slerp as before
-		const targetQuaternion = new THREE.Quaternion(
-			remotePlayerData.quaternion[0],
-			remotePlayerData.quaternion[1],
-			remotePlayerData.quaternion[2],
-			remotePlayerData.quaternion[3],
-		);
-		const rotationQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);
-		targetQuaternion.multiply(rotationQuaternion);
-
-		playerObject.quaternion.slerp(targetQuaternion, 0.5 * this.deltaTime * 60);
-
-		// Update the position of the name label
-		const player = this.playersToRender.find((p) => p.id === remotePlayerData.id);
-		if (player) {
-			player.nameLabel.position.set(
-				playerObject.position.x,
-				playerObject.position.y + 0.40, // Adjust the Y offset as needed
-				playerObject.position.z,
-			);
-			player.nameLabel.lookAt(this.camera.position);
-
-			// Check if the name has changed
-			if (player.name !== remotePlayerData.name) {
-				player.name = remotePlayerData.name; // Update stored name
-				// Remove old label
-				this.entityScene.remove(player.nameLabel);
-				// Create and add new label
-				player.nameLabel = this.createTextSprite(remotePlayerData.name.toString());
-				player.nameLabel.position.set(
-					playerObject.position.x,
-					playerObject.position.y + 0.40,
-					playerObject.position.z,
-				);
-				this.entityScene.add(player.nameLabel);
-			}
-		}
-	}
-
-	private addNewPlayer(remotePlayerData: RemotePlayerData): void {
-		const object = this.possumMesh!.clone();
-		const sphere = this.sphere.clone();
-
-		// Create a text sprite for the player's name
-		const nameLabel = this.createTextSprite(remotePlayerData.name.toString());
-
-		const newPlayer: PlayerToRender = {
-			id: remotePlayerData.id,
-			object: object,
-			objectUUID: object.uuid,
-			sphere: sphere,
-			nameLabel: nameLabel,
-			name: remotePlayerData.name,
-		};
-
-		this.playersToRender.push(newPlayer);
-		this.entityScene.add(newPlayer.object);
-		this.sphereScene.add(newPlayer.sphere);
-		this.entityScene.add(newPlayer.nameLabel);
-
-		// Initialize groundTruthPosition for the new player
-		this.groundTruthPositions[remotePlayerData.id] = new THREE.Vector3(
-			remotePlayerData.position.x,
-			remotePlayerData.position.y,
-			remotePlayerData.position.z,
-		);
-	}
-
-	private removeInactivePlayers(remotePlayerData: RemotePlayer[]): void {
-		this.playersToRender = this.playersToRender.filter((player) => {
-			const isActive = remotePlayerData.some((remotePlayer) => remotePlayer.id === player.id);
-			if (!isActive) {
-				this.entityScene.remove(player.object);
-				this.entityScene.remove(player.nameLabel);
-				this.sphereScene.remove(player.sphere);
-				// Remove associated data for the player
-				delete this.groundTruthPositions[player.id];
-				delete this.isAnimating[player.id];
-				delete this.animationPhase[player.id];
-				delete this.previousVelocity[player.id];
-				delete this.lastRunningYOffset[player.id];
-			}
-			return isActive;
-		});
-	}
-
-	private createTextSprite(text: string): THREE.Sprite {
-		text = text.replace(/&[0123456789abcdef]/g, ''); // Remove color codes
-		const canvas = document.createElement('canvas');
-		const context = canvas.getContext('2d')!;
-		const fontSize = 64;
-		context.font = `${fontSize}px Comic Sans MS`;
-
-		// Measure the text width and set canvas size accordingly
-		const textWidth = context.measureText(text).width;
-		canvas.width = textWidth * 2; // Increase resolution
-		canvas.height = fontSize * 2; // Increase resolution
-
-		// Redraw the text on the canvas
-		context.font = `${fontSize}px Comic Sans MS`;
-		context.fillStyle = 'rgba(255,255,255,1)';
-		context.textAlign = 'center';
-		context.textBaseline = 'middle';
-		context.fillText(text, canvas.width / 2, canvas.height / 2);
-
-		const texture = new THREE.CanvasTexture(canvas);
-		const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
-		const sprite = new THREE.Sprite(spriteMaterial);
-
-		// Adjust the sprite scale to match the canvas aspect ratio
-		sprite.scale.set((textWidth / fontSize) * 0.4, 0.4, 0.4);
-
-		return sprite;
-	}
-
-	public getRemotePlayerIDsInCrosshair(): number[] {
-		const shotVectors = this.getShotVectorsToPlayersInCrosshair();
-		const playerIDs = shotVectors.map((shot) => shot.playerID);
-		return playerIDs;
-	}
-
-	public getShotVectorsToPlayersInCrosshair(): { playerID: number; vector: THREE.Vector3; hitPoint: THREE.Vector3 }[] {
-		const shotVectors: { playerID: number; vector: THREE.Vector3; hitPoint: THREE.Vector3 }[] = [];
-		const objectsInCrosshair = this.getPlayersInCrosshairWithWalls();
-
-		for (const object of objectsInCrosshair) {
-			for (const player of this.playersToRender) {
-				if (player.objectUUID === object.uuid) {
-					// Find the intersection point on the player
-					const intersection = this.findIntersectionOnPlayer(object);
-					if (intersection) {
-						const vector = new THREE.Vector3().subVectors(intersection.point, this.camera.position);
-						const hitPoint = intersection.point.clone(); // World coordinates of the hit
-						shotVectors.push({ playerID: player.id, vector, hitPoint });
-					}
-					break;
-				}
-			}
-		}
-
-		return shotVectors;
-	}
-
-	private findIntersectionOnPlayer(playerObject: THREE.Object3D): THREE.Intersection | null {
-		this.raycaster.setFromCamera(this.crosshairVec, this.camera);
-
-		const intersects = this.raycaster.intersectObject(playerObject, true);
-		if (intersects.length > 0) {
-			return intersects[0]; // Return the first intersection point
-		}
-		return null;
-	}
-
-	private getPlayersInCrosshairWithWalls(): THREE.Object3D[] {
-		this.raycaster.setFromCamera(this.crosshairVec, this.camera);
-
-		const playerIntersects = this.raycaster.intersectObjects(this.entityScene.children);
-		this.raycaster.firstHitOnly = true;
-		const wallIntersects = this.raycaster.intersectObjects([RemotePlayerRenderer.map]);
-		this.raycaster.firstHitOnly = false;
-
-		const filteredIntersects = playerIntersects.filter((playerIntersect) => {
-			for (const wallIntersect of wallIntersects) {
-				if (wallIntersect.distance < playerIntersect.distance) {
-					return false;
-				}
-			}
-			return true;
-		});
-
-		return filteredIntersects.map((intersect) => intersect.object);
-	}
-
-	public getPlayerSpheresInCrosshairWithWalls(): THREE.Object3D[] {
-		this.raycaster.setFromCamera(this.crosshairVec, this.camera);
-
-		this.sphereScene.updateMatrixWorld();
-
-		this.raycaster.firstHitOnly = true;
-		const playerIntersects = this.raycaster.intersectObjects(this.sphereScene.children);
-		const wallIntersects = this.raycaster.intersectObjects([RemotePlayerRenderer.map]);
-		this.raycaster.firstHitOnly = false;
-
-		const filteredIntersects = playerIntersects.filter((playerIntersect) => {
-			for (const wallIntersect of wallIntersects) {
-				if (wallIntersect.distance < playerIntersect.distance) {
-					return false;
-				}
-			}
-			return true;
-		});
-
-		return filteredIntersects.map((intersect) => intersect.object);
-	}
-
-	public getShotVectorsToPlayersWithOffset(
-		yawOffset: number,
-		pitchOffset: number,
-	): { playerID: number; vector: THREE.Vector3; hitPoint: THREE.Vector3 }[] {
-		const shotVectors: { playerID: number; vector: THREE.Vector3; hitPoint: THREE.Vector3 }[] = [];
-		const offsetDirection = this.calculateOffsetDirection(yawOffset, pitchOffset);
-
-		// Set the raycaster with the offset direction
-		this.raycaster.set(this.camera.position, offsetDirection);
-
-		// Intersect with all potential targets (players and walls)
-		const playerIntersects = this.raycaster.intersectObjects(this.playersToRender.map((p) => p.object), true);
-		this.raycaster.firstHitOnly = true;
-		const wallIntersects = this.raycaster.intersectObjects([RemotePlayerRenderer.map]);
-		this.raycaster.firstHitOnly = false;
-
-		// Filter player intersections based on wall intersections
-		const filteredPlayerIntersects = playerIntersects.filter((playerIntersect) => {
-			for (const wallIntersect of wallIntersects) {
-				if (wallIntersect.distance < playerIntersect.distance) {
-					return false; // A wall is blocking the player
-				}
-			}
-			return true; // No wall is blocking the player
-		});
-
-		// Process the filtered player intersections
-		for (const intersect of filteredPlayerIntersects) {
-			const player = this.playersToRender.find((p) => p.object === intersect.object);
-			if (player) {
-				const vector = new THREE.Vector3().subVectors(intersect.point, this.camera.position);
-				const hitPoint = intersect.point.clone(); // World coordinates of the hit
-				shotVectors.push({ playerID: player.id, vector, hitPoint });
-			}
-		}
-
-		return shotVectors;
-	}
-
-	private calculateOffsetDirection(yawOffset: number, pitchOffset: number): THREE.Vector3 {
-		// Get the camera's current direction
-		const direction = new THREE.Vector3();
-		this.camera.getWorldDirection(direction);
-
-		// Create a quaternion for the yaw and pitch offsets
-		const yawQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawOffset);
-		const pitchQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), pitchOffset);
-
-		// Apply the rotations
-		direction.applyQuaternion(yawQuaternion);
-		direction.applyQuaternion(pitchQuaternion);
-
-		return direction.normalize();
-	}
-
-	private findIntersectionOnPlayerWithOffset(
-		playerObject: THREE.Object3D,
-		offsetDirection: THREE.Vector3,
-	): THREE.Intersection | null {
-		// Set the raycaster with the offset direction
-		this.raycaster.set(this.camera.position, offsetDirection);
-
-		const intersects = this.raycaster.intersectObject(playerObject, true);
-		if (intersects.length > 0) {
-			return intersects[0]; // Return the first intersection point
-		}
-		return null;
-	}
-
-	public static setMap(map: THREE.Mesh) {
-		this.map = map;
-	}
+    private entityScene: THREE.Scene;
+    private playersToRender: PlayerToRender[];
+    private possumMesh: THREE.Mesh | undefined;
+    private loader: GLTFLoader;
+    private dracoLoader: DRACOLoader;
+
+    private sphere: THREE.Mesh;
+    private sphereScene: THREE.Scene;
+
+    private raycaster: THREE.Raycaster;
+    private camera: THREE.Camera;
+    private scene: THREE.Scene
+
+    private isAnimating: { [id: number]: boolean };
+    private animationPhase: { [id: number]: number };
+    private previousVelocity: { [id: number]: number };
+    private lastRunningYOffset: { [id: number]: number };
+
+    private groundTruthPositions: { [id: number]: THREE.Vector3 };
+
+    private networking: Networking;
+    private localPlayer: Player;
+    private deltaTime: number = 0; // Initialize deltaTime to avoid Deno error
+    private static minVelocityToAnimate = 0.1;
+    private static map: THREE.Mesh = new THREE.Mesh();
+
+    private crosshairVec = new THREE.Vector2();
+
+    constructor(
+        networking: Networking,
+        localPlayer: Player,
+        raycaster: THREE.Raycaster,
+        camera: THREE.Camera,
+        scene: THREE.Scene
+    ) {
+        this.networking = networking;
+        this.localPlayer = localPlayer;
+        this.raycaster = raycaster;
+        this.camera = camera;
+        this.scene = scene;
+
+        this.entityScene = new THREE.Scene();
+
+        this.sphere = new THREE.Mesh(new THREE.SphereGeometry(.6), new THREE.MeshBasicMaterial({color: 0xffffff}));
+        this.sphere.geometry.computeBoundsTree();
+        this.sphereScene = new THREE.Scene();
+
+        this.loader = new GLTFLoader();
+        this.dracoLoader = new DRACOLoader();
+        this.dracoLoader.setDecoderPath('/draco/');
+        this.loader.setDRACOLoader(this.dracoLoader);
+
+        this.possumMesh = undefined;
+        this.loader.load(
+            'models/simplified_possum.glb',
+            (gltf) => {
+                console.time("Computing possum BVH");
+                (<THREE.Mesh>gltf.scene.children[0]).geometry.computeBoundsTree();
+                console.timeEnd("Computing possum BVH");
+                this.possumMesh = (<THREE.Mesh>gltf.scene.children[0]);
+            },
+            undefined,
+            () => {
+                console.log('possum loading error');
+            }
+        );
+
+        this.playersToRender = [];
+
+        this.isAnimating = {};
+        this.animationPhase = {};
+        this.previousVelocity = {};
+        this.lastRunningYOffset = {};
+
+        this.groundTruthPositions = {};
+    }
+
+    public getEntityScene(): THREE.Scene {
+        return this.entityScene;
+    }
+
+    public update(deltaTime: number): void {
+        this.deltaTime = deltaTime;
+        this.updateRemotePlayers();
+    }
+
+    private updateRemotePlayers(): void {
+        if (!this.possumMesh) return;
+
+        const remotePlayerData: RemotePlayer[] = this.networking.getRemotePlayerData();
+        const localPlayerId = this.localPlayer.id;
+
+        // First, remove all players that should be hidden
+        this.playersToRender = this.playersToRender.filter((player) => {
+            const remotePlayer = remotePlayerData.find(rp => rp.id === player.id);
+            const shouldHide =
+                !remotePlayer || // Player no longer exists
+                remotePlayer.id === localPlayerId || // Is local player
+                remotePlayer.id === this.localPlayer.playerSpectating || // Is being spectated
+                remotePlayer.playerSpectating !== -1; // Is spectating someone
+
+            if (shouldHide) {
+                // Remove the player's objects from scenes
+                this.entityScene.remove(player.object);
+                this.entityScene.remove(player.nameLabel);
+                this.sphereScene.remove(player.sphere);
+
+                // Clean up associated data
+                delete this.groundTruthPositions[player.id];
+                delete this.isAnimating[player.id];
+                delete this.animationPhase[player.id];
+                delete this.previousVelocity[player.id];
+                delete this.lastRunningYOffset[player.id];
+            }
+
+            return !shouldHide;
+        });
+
+        // Then, update or add remaining valid players
+        remotePlayerData.forEach((remotePlayer) => {
+            // Skip if player should be hidden
+            if (remotePlayer.id === localPlayerId ||
+                remotePlayer.id === this.localPlayer.playerSpectating ||
+                remotePlayer.playerSpectating !== -1) {
+                return;
+            }
+
+            const playerDataWithQuaternion: RemotePlayerData = {
+                ...remotePlayer,
+                quaternion: remotePlayer.quaternion || [0, 0, 0, 1],
+            };
+
+            const existingPlayer = this.playersToRender.find((player) => player.id === remotePlayer.id);
+            if (existingPlayer) {
+                this.updatePlayerPosition(existingPlayer.object, existingPlayer.sphere, playerDataWithQuaternion);
+            } else {
+                this.addNewPlayer(playerDataWithQuaternion);
+            }
+        });
+    }
+
+
+    private updatePlayerPosition(playerObject: THREE.Object3D, playerSphere: THREE.Object3D, remotePlayerData: RemotePlayerData): void {
+        const velocity = Math.sqrt(
+            Math.pow(remotePlayerData.velocity.x, 2) +
+            Math.pow(remotePlayerData.velocity.y, 2) +
+            Math.pow(remotePlayerData.velocity.z, 2)
+        );
+
+        const playerId = remotePlayerData.id;
+        const prevVelocity = this.previousVelocity[playerId] || 0;
+
+        if (prevVelocity <= RemotePlayerRenderer.minVelocityToAnimate && velocity > RemotePlayerRenderer.minVelocityToAnimate) {
+            this.isAnimating[playerId] = true;
+            this.animationPhase[playerId] = 0;
+        }
+
+        this.previousVelocity[playerId] = velocity;
+
+        // Get or initialize groundTruthPosition
+        if (!this.groundTruthPositions[playerId]) {
+            this.groundTruthPositions[playerId] = new THREE.Vector3(
+                remotePlayerData.position.x,
+                remotePlayerData.position.y,
+                remotePlayerData.position.z
+            );
+        }
+
+        const groundTruthPosition = this.groundTruthPositions[playerId];
+
+        // Apply velocity to groundTruthPosition
+        groundTruthPosition.x += remotePlayerData.velocity.x * this.deltaTime;
+        groundTruthPosition.y += remotePlayerData.velocity.y * this.deltaTime;
+        groundTruthPosition.z += remotePlayerData.velocity.z * this.deltaTime;
+
+        // If forced, set groundTruthPosition to remotePlayerData.position
+        if (remotePlayerData.forced) {
+            groundTruthPosition.set(
+                remotePlayerData.position.x,
+                remotePlayerData.position.y,
+                remotePlayerData.position.z
+            );
+        }
+
+        // Lerp groundTruthPosition towards remotePlayerData.position
+        groundTruthPosition.lerp(
+            new THREE.Vector3(
+                remotePlayerData.position.x,
+                remotePlayerData.position.y,
+                remotePlayerData.position.z
+            ),
+            0.1 * this.deltaTime * 60
+        );
+
+        // Set playerObject.position to groundTruthPosition.clone()
+        playerObject.position.copy(groundTruthPosition);
+        playerSphere.position.copy(groundTruthPosition.clone());
+
+        // Apply animation offsets (e.g., yOffset)
+        if (this.isAnimating[playerId]) {
+            const frequency = 25;
+            this.animationPhase[playerId] += this.deltaTime * frequency;
+
+            const amplitude = 0.08;
+            const yOffset = amplitude * (1 + Math.cos(this.animationPhase[playerId]));
+
+            playerObject.position.y += yOffset;
+            playerSphere.position.y += yOffset;
+            this.lastRunningYOffset[playerId] = yOffset;
+
+            if (velocity <= RemotePlayerRenderer.minVelocityToAnimate && Math.cos(this.animationPhase[playerId]) <= 0) {
+                this.isAnimating[playerId] = false;
+                this.lastRunningYOffset[playerId] = 0;
+            }
+        } else {
+            this.lastRunningYOffset[playerId] = 0;
+        }
+
+        // Apply scared effect
+        const scaredLevel = 1 - Math.pow(remotePlayerData.health / this.networking.getServerInfo().playerMaxHealth, 2); // 0-1
+        playerObject.position.x += (Math.random() - 0.5) * 0.05 * scaredLevel;
+        playerObject.position.y += (Math.random() - 0.5) * 0.05 * scaredLevel;
+        playerObject.position.z += (Math.random() - 0.5) * 0.05 * scaredLevel;
+
+        // Apply quaternion slerp as before
+        const targetQuaternion = new THREE.Quaternion(
+            remotePlayerData.quaternion[0],
+            remotePlayerData.quaternion[1],
+            remotePlayerData.quaternion[2],
+            remotePlayerData.quaternion[3]
+        );
+        const rotationQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), Math.PI / 2);
+        targetQuaternion.multiply(rotationQuaternion);
+
+        playerObject.quaternion.slerp(targetQuaternion, 0.5 * this.deltaTime * 60);
+
+        // Update the position of the name label
+        const player = this.playersToRender.find(p => p.id === remotePlayerData.id);
+        if (player) {
+            player.nameLabel.position.set(
+                playerObject.position.x,
+                playerObject.position.y + 0.40, // Adjust the Y offset as needed
+                playerObject.position.z
+            );
+            player.nameLabel.lookAt(this.camera.position);
+
+            // Check if the name has changed
+            if (player.name !== remotePlayerData.name) {
+                player.name = remotePlayerData.name; // Update stored name
+                // Remove old label
+                this.entityScene.remove(player.nameLabel);
+                // Create and add new label
+                player.nameLabel = this.createTextSprite(remotePlayerData.name.toString());
+                player.nameLabel.position.set(
+                    playerObject.position.x,
+                    playerObject.position.y + 0.40,
+                    playerObject.position.z
+                );
+                this.entityScene.add(player.nameLabel);
+            }
+        }
+    }
+
+    private addNewPlayer(remotePlayerData: RemotePlayerData): void {
+        const object = this.possumMesh!.clone();
+        const sphere = this.sphere.clone();
+
+        // Create a text sprite for the player's name
+        const nameLabel = this.createTextSprite(remotePlayerData.name.toString());
+
+        const newPlayer: PlayerToRender = {
+            id: remotePlayerData.id,
+            object: object,
+            objectUUID: object.uuid,
+            sphere: sphere,
+            nameLabel: nameLabel,
+            name: remotePlayerData.name,
+        };
+
+        this.playersToRender.push(newPlayer);
+        this.entityScene.add(newPlayer.object);
+        this.sphereScene.add(newPlayer.sphere);
+        this.entityScene.add(newPlayer.nameLabel);
+
+        // Initialize groundTruthPosition for the new player
+        this.groundTruthPositions[remotePlayerData.id] = new THREE.Vector3(
+            remotePlayerData.position.x,
+            remotePlayerData.position.y,
+            remotePlayerData.position.z
+        );
+    }
+
+    private removeInactivePlayers(remotePlayerData: RemotePlayer[]): void {
+        this.playersToRender = this.playersToRender.filter((player) => {
+            const isActive = remotePlayerData.some((remotePlayer) => remotePlayer.id === player.id);
+            if (!isActive) {
+                this.entityScene.remove(player.object);
+                this.entityScene.remove(player.nameLabel);
+                this.sphereScene.remove(player.sphere);
+                // Remove associated data for the player
+                delete this.groundTruthPositions[player.id];
+                delete this.isAnimating[player.id];
+                delete this.animationPhase[player.id];
+                delete this.previousVelocity[player.id];
+                delete this.lastRunningYOffset[player.id];
+            }
+            return isActive;
+        });
+    }
+
+    private createTextSprite(text: string): THREE.Sprite {
+        text = text.replace(/&[0123456789abcdef]/g, ''); // Remove color codes
+        const canvas = document.createElement('canvas');
+        const context = canvas.getContext('2d')!;
+        const fontSize = 64;
+        context.font = `${fontSize}px Comic Sans MS`;
+
+        // Measure the text width and set canvas size accordingly
+        const textWidth = context.measureText(text).width;
+        canvas.width = textWidth * 2; // Increase resolution
+        canvas.height = fontSize * 2; // Increase resolution
+
+        // Redraw the text on the canvas
+        context.font = `${fontSize}px Comic Sans MS`;
+        context.fillStyle = 'rgba(255,255,255,1)';
+        context.textAlign = 'center';
+        context.textBaseline = 'middle';
+        context.fillText(text, canvas.width / 2, canvas.height / 2);
+
+        const texture = new THREE.CanvasTexture(canvas);
+        const spriteMaterial = new THREE.SpriteMaterial({ map: texture });
+        const sprite = new THREE.Sprite(spriteMaterial);
+
+        // Adjust the sprite scale to match the canvas aspect ratio
+        sprite.scale.set((textWidth / fontSize ) * 0.4 , 0.4, 0.4);
+
+        return sprite;
+    }
+
+
+    public getRemotePlayerIDsInCrosshair(): number[] {
+        const shotVectors = this.getShotVectorsToPlayersInCrosshair();
+        const playerIDs = shotVectors.map(shot => shot.playerID);
+        return playerIDs;
+    }
+
+    public getShotVectorsToPlayersInCrosshair(): { playerID: number, vector: THREE.Vector3, hitPoint: THREE.Vector3 }[] {
+        const shotVectors: { playerID: number, vector: THREE.Vector3, hitPoint: THREE.Vector3 }[] = [];
+        const objectsInCrosshair = this.getPlayersInCrosshairWithWalls();
+
+        for (const object of objectsInCrosshair) {
+            for (const player of this.playersToRender) {
+                if (player.objectUUID === object.uuid) {
+                    // Find the intersection point on the player
+                    const intersection = this.findIntersectionOnPlayer(object);
+                    if (intersection) {
+                        const vector = new THREE.Vector3().subVectors(intersection.point, this.camera.position);
+                        const hitPoint = intersection.point.clone(); // World coordinates of the hit
+                        shotVectors.push({ playerID: player.id, vector, hitPoint });
+                    }
+                    break;
+                }
+            }
+        }
+
+        return shotVectors;
+    }
+
+    private findIntersectionOnPlayer(playerObject: THREE.Object3D): THREE.Intersection | null {
+        this.raycaster.setFromCamera(this.crosshairVec, this.camera);
+
+        const intersects = this.raycaster.intersectObject(playerObject, true);
+        if (intersects.length > 0) {
+            return intersects[0]; // Return the first intersection point
+        }
+        return null;
+    }
+
+    private getPlayersInCrosshairWithWalls(): THREE.Object3D[] {
+        this.raycaster.setFromCamera(this.crosshairVec, this.camera);
+
+        const playerIntersects = this.raycaster.intersectObjects(this.entityScene.children);
+        this.raycaster.firstHitOnly = true;
+        const wallIntersects = this.raycaster.intersectObjects([RemotePlayerRenderer.map]);
+        this.raycaster.firstHitOnly = false;
+
+        const filteredIntersects = playerIntersects.filter((playerIntersect) => {
+            for (const wallIntersect of wallIntersects) {
+                if (wallIntersect.distance < playerIntersect.distance) {
+                    return false;
+                }
+            }
+            return true;
+        });
+
+        return filteredIntersects.map((intersect) => intersect.object);
+    }
+
+    public getPlayerSpheresInCrosshairWithWalls(): THREE.Object3D[] {
+        this.raycaster.setFromCamera(this.crosshairVec, this.camera);
+
+        this.sphereScene.updateMatrixWorld();
+
+        this.raycaster.firstHitOnly = true;
+        const playerIntersects = this.raycaster.intersectObjects(this.sphereScene.children);
+        const wallIntersects = this.raycaster.intersectObjects([RemotePlayerRenderer.map]);
+        this.raycaster.firstHitOnly = false;
+
+        const filteredIntersects = playerIntersects.filter((playerIntersect) => {
+            for (const wallIntersect of wallIntersects) {
+                if (wallIntersect.distance < playerIntersect.distance) {
+                    return false;
+                }
+            }
+            return true;
+        });
+
+        return filteredIntersects.map((intersect) => intersect.object);
+    }
+
+    public getShotVectorsToPlayersWithOffset(yawOffset: number, pitchOffset: number): { playerID: number, vector: THREE.Vector3, hitPoint: THREE.Vector3 }[] {
+        const shotVectors: { playerID: number, vector: THREE.Vector3, hitPoint: THREE.Vector3 }[] = [];
+        const offsetDirection = this.calculateOffsetDirection(yawOffset, pitchOffset);
+
+        // Set the raycaster with the offset direction
+        this.raycaster.set(this.camera.position, offsetDirection);
+
+        // Intersect with all potential targets (players and walls)
+        const playerIntersects = this.raycaster.intersectObjects(this.playersToRender.map(p => p.object), true);
+        this.raycaster.firstHitOnly = true;
+        const wallIntersects = this.raycaster.intersectObjects([RemotePlayerRenderer.map]);
+        this.raycaster.firstHitOnly = false;
+
+        // Filter player intersections based on wall intersections
+        const filteredPlayerIntersects = playerIntersects.filter((playerIntersect) => {
+            for (const wallIntersect of wallIntersects) {
+                if (wallIntersect.distance < playerIntersect.distance) {
+                    return false; // A wall is blocking the player
+                }
+            }
+            return true; // No wall is blocking the player
+        });
+
+        // Process the filtered player intersections
+        for (const intersect of filteredPlayerIntersects) {
+            const player = this.playersToRender.find(p => p.object === intersect.object);
+            if (player) {
+                const vector = new THREE.Vector3().subVectors(intersect.point, this.camera.position);
+                const hitPoint = intersect.point.clone(); // World coordinates of the hit
+                shotVectors.push({ playerID: player.id, vector, hitPoint });
+            }
+        }
+
+        return shotVectors;
+    }
+
+
+    private calculateOffsetDirection(yawOffset: number, pitchOffset: number): THREE.Vector3 {
+        // Get the camera's current direction
+        const direction = new THREE.Vector3();
+        this.camera.getWorldDirection(direction);
+
+        // Create a quaternion for the yaw and pitch offsets
+        const yawQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(0, 1, 0), yawOffset);
+        const pitchQuaternion = new THREE.Quaternion().setFromAxisAngle(new THREE.Vector3(1, 0, 0), pitchOffset);
+
+        // Apply the rotations
+        direction.applyQuaternion(yawQuaternion);
+        direction.applyQuaternion(pitchQuaternion);
+
+        return direction.normalize();
+    }
+
+    private findIntersectionOnPlayerWithOffset(playerObject: THREE.Object3D, offsetDirection: THREE.Vector3): THREE.Intersection | null {
+        // Set the raycaster with the offset direction
+        this.raycaster.set(this.camera.position, offsetDirection);
+
+        const intersects = this.raycaster.intersectObject(playerObject, true);
+        if (intersects.length > 0) {
+            return intersects[0]; // Return the first intersection point
+        }
+        return null;
+    }
+
+    public static setMap(map: THREE.Mesh) {
+        this.map = map;
+    }
 }
diff --git a/src/client/core/Renderer.ts b/src/client/core/Renderer.ts
index 6a46c166..58fd3b8d 100644
--- a/src/client/core/Renderer.ts
+++ b/src/client/core/Renderer.ts
@@ -1,415 +1,401 @@
 import * as THREE from 'three';
 import { Networking, type RemotePlayer } from './Networking.ts';
 import { Player } from './Player.ts';
-import { ChatOverlay } from '../ui/ChatOverlay.ts';
+import { ChatOverlay } from "../ui/ChatOverlay.ts";
 import { RemotePlayerRenderer } from './RemotePlayerRenderer.ts';
-import { InputHandler } from '../input/InputHandler.ts';
-import { SettingsManager } from './SettingsManager.ts';
-import { CollisionManager } from '../input/CollisionManager.ts';
+import {InputHandler} from "../input/InputHandler.ts";
+import {SettingsManager} from "./SettingsManager.ts";
+import {CollisionManager} from "../input/CollisionManager.ts";
 
 export class Renderer {
-	private clock: THREE.Clock;
-	private deltaTime: number = 0;
-	private chatOverlay: ChatOverlay;
-	private scene: THREE.Scene;
-	private camera: THREE.PerspectiveCamera;
-	private renderer: THREE.WebGLRenderer;
-	private heldItemScene: THREE.Scene;
-	private heldItemCamera: THREE.PerspectiveCamera;
-	private ambientLight: THREE.AmbientLight;
-	private framerate: number;
-	private framesInFramerateSample: number;
-	private sampleOn: number;
-	private lastFramerateCalculation: number;
-	private networking: Networking;
-	private localPlayer: Player;
-	private raycaster: THREE.Raycaster;
-	public scaredLevel: number = 0;
-	private lastPlayerHealth: number = 100;
-	private knockbackVector: THREE.Vector3 = new THREE.Vector3();
-	private bobCycle: number;
-	private lastCameraRoll: number;
-
-	public crosshairIsFlashing: boolean = false;
-	public lastShotSomeoneTimestamp: number = 0;
-	public playerHitMarkers: { hitPoint: THREE.Vector3; shotVector: THREE.Vector3; timestamp: number }[] = [];
-	private healthIndicatorScene: THREE.Scene;
-	private healthIndicatorCamera: THREE.PerspectiveCamera;
-	private screenPixelsInGamePixel: number = 1;
-	private inventoryMenuScene: THREE.Scene;
-	private inventoryMenuCamera: THREE.OrthographicCamera;
-	private remotePlayerRenderer: RemotePlayerRenderer;
-	private inputHandler!: InputHandler;
-	private collisionManager!: CollisionManager;
-
-	private spectateGroundTruthPosition: THREE.Vector3 | null = null;
-
-	constructor(container: HTMLElement, networking: Networking, localPlayer: Player, chatOverlay: ChatOverlay) {
-		this.networking = networking;
-		this.localPlayer = localPlayer;
-		this.chatOverlay = chatOverlay;
-
-		this.clock = new THREE.Clock();
-		this.scene = new THREE.Scene();
-		this.camera = new THREE.PerspectiveCamera(90, globalThis.innerWidth / globalThis.innerHeight, 0.01, 1000);
-		this.renderer = new THREE.WebGLRenderer();
-		//document.body.appendChild(this.renderer.domElement);
-		container.appendChild(this.renderer.domElement);
-		this.renderer.domElement.style.imageRendering = 'pixelated';
-		this.renderer.setAnimationLoop(null);
-
-		// Create a new scene and camera for the held item
-		this.heldItemScene = new THREE.Scene();
-		this.heldItemCamera = new THREE.PerspectiveCamera(90, globalThis.innerWidth / globalThis.innerHeight, 0.01, 1000);
-		this.heldItemCamera.position.set(0, 0, 5);
-		this.heldItemCamera.lookAt(0, 0, 0);
-
-		// Create a new scene and camera for the health indicator
-		this.healthIndicatorScene = new THREE.Scene();
-		this.healthIndicatorCamera = new THREE.PerspectiveCamera(70, 1, 0.01, 1000);
-		this.healthIndicatorCamera.position.set(0, 0, 0);
-		this.healthIndicatorCamera.lookAt(0, 0, 1);
-
-		this.inventoryMenuScene = new THREE.Scene();
-		this.inventoryMenuCamera = new THREE.OrthographicCamera(-0.5, 0.5, 2.5, -2.5, 0.01, 10);
-		this.inventoryMenuCamera.position.set(0, 0, 5);
-		this.inventoryMenuCamera.lookAt(0, 0, 0);
-		this.inventoryMenuScene.add(this.inventoryMenuCamera);
-		this.inventoryMenuScene.add(new THREE.AmbientLight(0xffffff, 0.5));
-
-		// Ambient lights
-		this.ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
-		const ambientLight2 = new THREE.AmbientLight(0xffffff, 0.5);
-		const ambientLight3 = new THREE.AmbientLight(0xffffff, 0.5); // Ambient light for remote players scene
-
-		this.scene.add(this.ambientLight);
-		this.heldItemScene.add(ambientLight2);
-
-		// Fog settings
-		this.scene.fog = new THREE.FogExp2('#111111', 0.05);
-		this.heldItemScene.fog = new THREE.FogExp2('#111111', 0.05);
-		this.healthIndicatorScene.fog = new THREE.FogExp2('#111111', 0.05); // Add fog to health indicator scene
-
-		this.framerate = 0;
-		this.framesInFramerateSample = 30;
-		this.sampleOn = 0;
-		this.lastFramerateCalculation = 0;
-
-		this.bobCycle = 0;
-		this.lastCameraRoll = 0;
-
-		this.raycaster = new THREE.Raycaster();
-
-		// Initialize remotePlayerRenderer
-		this.remotePlayerRenderer = new RemotePlayerRenderer(
-			this.networking,
-			this.localPlayer,
-			this.raycaster,
-			this.camera,
-			this.scene,
-		);
-		this.remotePlayerRenderer.getEntityScene().fog = new THREE.FogExp2('#111111', 0.1); // Add fog to remote players scene
-		this.remotePlayerRenderer.getEntityScene().add(ambientLight3); // Add ambient light to remote players scene
-		this.renderer.domElement.style.touchAction = 'none';
-		this.renderer.domElement.style.position = 'absolute';
-		this.onWindowResize();
-		globalThis.addEventListener('resize', this.onWindowResize.bind(this), false);
-		globalThis.addEventListener('orientationchange', this.onWindowResize.bind(this), false);
-	}
-
-	public onFrame(localPlayer: Player) {
-		this.deltaTime = this.clock.getDelta();
-
-		// Ensure the renderer clears the buffers before the first render
-		this.renderer.autoClear = true;
-
-		// Render the main scene
-		this.renderer.render(this.scene, this.camera);
-
-		// Prevent clearing the buffers in subsequent renders
-		this.renderer.autoClear = false;
-
-		// Update and render remote players
-		this.remotePlayerRenderer.update(this.deltaTime);
-		this.renderer.render(this.remotePlayerRenderer.getEntityScene(), this.camera);
-
-		// Render the held item scene normally (full screen)
-		this.renderer.render(this.heldItemScene, this.heldItemCamera);
-
-		// Set up the scissor and viewport for the health indicator scene rendering
-		const screenWidth = globalThis.innerWidth;
-		const screenHeight = globalThis.innerHeight;
-
-		const healthIndicatorWidth = 60; // native
-		const healthIndicatorHeight = healthIndicatorWidth; // 1:1 aspect ratio
-
-		// Set up scissor and viewport for a region from (0, 0) to (50, 50)
-		this.renderer.setScissorTest(true);
-		this.renderer.setScissor(
-			2 * this.screenPixelsInGamePixel,
-			screenHeight - (healthIndicatorHeight + 1 + this.chatOverlay.getDebugTextHeight()) * this.screenPixelsInGamePixel,
-			healthIndicatorWidth * this.screenPixelsInGamePixel,
-			healthIndicatorHeight * this.screenPixelsInGamePixel,
-		);
-		this.renderer.setViewport(
-			2 * this.screenPixelsInGamePixel,
-			screenHeight - (healthIndicatorHeight + 1 + this.chatOverlay.getDebugTextHeight()) * this.screenPixelsInGamePixel,
-			healthIndicatorWidth * this.screenPixelsInGamePixel,
-			healthIndicatorHeight * this.screenPixelsInGamePixel,
-		);
-
-		// Render the health indicator scene
-		this.renderer.render(this.healthIndicatorScene, this.healthIndicatorCamera);
-
-		// Render inventory view
-		const inventoryWidth = 20;
-		const inventoryHeight = inventoryWidth * 5;
-		this.renderer.setScissorTest(true);
-		this.renderer.setScissor(
-			screenWidth - (inventoryWidth + 4) * this.screenPixelsInGamePixel,
-			screenHeight / 2 - (inventoryHeight / 2) * this.screenPixelsInGamePixel,
-			inventoryWidth * this.screenPixelsInGamePixel,
-			inventoryHeight * this.screenPixelsInGamePixel,
-		);
-		this.renderer.setViewport(
-			screenWidth - (inventoryWidth + 4) * this.screenPixelsInGamePixel,
-			screenHeight / 2 - (inventoryHeight / 2) * this.screenPixelsInGamePixel,
-			inventoryWidth * this.screenPixelsInGamePixel,
-			inventoryHeight * this.screenPixelsInGamePixel,
-		);
-		this.renderer.render(this.inventoryMenuScene, this.inventoryMenuCamera);
-
-		// Reset scissor test and viewport after rendering the health indicator
-		this.renderer.setScissorTest(false);
-		this.renderer.setViewport(0, 0, screenWidth, screenHeight);
-
-		// Restore autoClear to true if necessary
-		this.renderer.autoClear = true;
-
-		if (localPlayer.playerSpectating !== -1) {
-			const remotePlayer = this.networking.getRemotePlayerData().find((player) =>
-				player.id === localPlayer.playerSpectating
-			);
-			if (remotePlayer !== undefined) {
-				// Initialize ground truth position if not set
-				if (!this.spectateGroundTruthPosition) {
-					this.spectateGroundTruthPosition = new THREE.Vector3(
-						remotePlayer.position.x,
-						remotePlayer.position.y,
-						remotePlayer.position.z,
-					);
-				}
-
-				// Update ground truth position based on velocity
-				this.spectateGroundTruthPosition.x += remotePlayer.velocity.x * this.deltaTime;
-				this.spectateGroundTruthPosition.y += remotePlayer.velocity.y * this.deltaTime;
-				this.spectateGroundTruthPosition.z += remotePlayer.velocity.z * this.deltaTime;
-
-				// If forced update, set directly to remote position
-				if (remotePlayer.forced) {
-					this.spectateGroundTruthPosition.set(
-						remotePlayer.position.x,
-						remotePlayer.position.y,
-						remotePlayer.position.z,
-					);
-				}
-
-				// Lerp ground truth position towards actual position
-				this.spectateGroundTruthPosition.lerp(
-					new THREE.Vector3(
-						remotePlayer.position.x,
-						remotePlayer.position.y,
-						remotePlayer.position.z,
-					),
-					0.1 * this.deltaTime * 60,
-				);
-
-				// Update camera position and rotation
-				this.camera.position.copy(this.spectateGroundTruthPosition);
-
-				// Simple quaternion slerp
-				this.camera.quaternion.slerp(
-					new THREE.Quaternion(
-						remotePlayer.lookQuaternion[0],
-						remotePlayer.lookQuaternion[1],
-						remotePlayer.lookQuaternion[2],
-						remotePlayer.lookQuaternion[3],
-					),
-					0.3 * this.deltaTime * 60,
-				);
-			}
-		} else {
-			// Reset spectate position when not spectating
-			this.spectateGroundTruthPosition = null;
-			this.camera.position.copy(localPlayer.position);
-			this.camera.setRotationFromQuaternion(this.localPlayer.lookQuaternion);
-		}
-
-		this.camera.position.add(this.knockbackVector);
-		this.knockbackVector.lerp(new THREE.Vector3(), 0.05 * this.deltaTime * 60);
-
-		if (this.localPlayer.health < this.lastPlayerHealth) {
-			const remotePlayer: RemotePlayer | undefined = this.networking.getRemotePlayerData().find((player) =>
-				player.id === this.localPlayer.idLastDamagedBy
-			);
-			if (remotePlayer !== undefined) {
-				//console.log("Player was damaged by " + remotePlayer.name);
-				const diff = new THREE.Vector3().subVectors(this.localPlayer.position, remotePlayer.position);
-				this.knockbackVector.copy(diff.normalize().multiplyScalar(0.2));
-			}
-		}
-
-		const shakeAmount = 0.08 * Math.pow(this.scaredLevel, 5);
-		this.camera.position.add(
-			new THREE.Vector3(
-				(Math.random() - 0.5) * shakeAmount,
-				(Math.random() - 0.5) * shakeAmount,
-				(Math.random() - 0.5) * shakeAmount,
-			),
-		);
-		this.camera.rotation.x += (Math.random() - 0.5) * shakeAmount * 0.12;
-		this.camera.rotation.y += (Math.random() - 0.5) * shakeAmount * 0.12;
-		this.camera.rotation.z += (Math.random() - 0.5) * shakeAmount * 0.12;
-
-		this.heldItemCamera.rotation.set(
-			(Math.random() - 0.5) * shakeAmount,
-			(Math.random() - 0.5) * shakeAmount,
-			(Math.random() - 0.5) * shakeAmount,
-		);
-
-		this.lastPlayerHealth = this.localPlayer.health;
-
-		const vel = Math.sqrt(
-			Math.pow(this.localPlayer.inputVelocity.x, 2) + Math.pow(this.localPlayer.inputVelocity.z, 2),
-		);
-
-		if (vel == 0 || this.collisionManager.isPlayerInAir() || this.localPlayer.playerSpectating !== -1) {
-			this.bobCycle = 0;
-		} else {
-			this.bobCycle += this.deltaTime * 4.8 * vel;
-			this.camera.position.y = this.camera.position.y +
-				(Math.sin(this.bobCycle) * .03 * SettingsManager.settings.viewBobbingStrength);
-			//console.log(this.camera.position.y);
-		}
-
-		let newHandX = Math.sin(this.bobCycle / 1.9) * .02 * SettingsManager.settings.viewBobbingStrength;
-		let newHandY = -(Math.sin(this.bobCycle) * .07 * SettingsManager.settings.viewBobbingStrength);
-		let newHandZ = Math.sin(this.bobCycle / 1.8) * .015 * SettingsManager.settings.viewBobbingStrength;
-		newHandY += localPlayer.velocity.y * 0.04 * SettingsManager.settings.viewBobbingStrength; //move hand up when falling, down when jumping
-
-		//banana lags behind player slightly
-		const playerVelocity = new THREE.Vector3().copy(localPlayer.velocity);
-		playerVelocity.applyQuaternion(localPlayer.lookQuaternion.clone().invert());
-		newHandX += playerVelocity.x * 0.02 * SettingsManager.settings.viewBobbingStrength;
-		newHandZ -= -playerVelocity.z * 0.02 * SettingsManager.settings.viewBobbingStrength;
-
-		if (this.inputHandler.getAim()) {
-			newHandX *= 0.25;
-			newHandY *= 0.25;
-			newHandZ *= 0.25;
-		}
-
-		this.heldItemCamera.position.lerp(new THREE.Vector3(newHandX, newHandY, 5 + newHandZ), 0.15 * this.deltaTime * 60);
-
-		const maxRollAmount = this.inputHandler.getInputX() * -.007 * SettingsManager.settings.viewBobbingStrength;
-		const maxRollSpeed = this.deltaTime * .4;
-		let roll: number = this.lastCameraRoll;
-		roll = Renderer.approachNumber(roll, maxRollSpeed, maxRollAmount);
-		const euler = new THREE.Euler().setFromQuaternion(this.camera.quaternion, 'YXZ');
-		euler.z += roll;
-		this.lastCameraRoll = roll;
-
-		this.camera.quaternion.setFromEuler(euler);
-
-		this.updateFramerate();
-	}
-
-	private updateFramerate() {
-		this.sampleOn++;
-		if (this.sampleOn >= this.framesInFramerateSample) {
-			this.framerate = this.framesInFramerateSample / (Date.now() / 1000 - this.lastFramerateCalculation);
-			this.sampleOn = 0;
-			this.lastFramerateCalculation = Date.now() / 1000;
-		}
-	}
-
-	public getFramerate(): number {
-		return this.framerate;
-	}
-
-	public getScene(): THREE.Scene {
-		return this.scene;
-	}
-
-	public getCamera(): THREE.PerspectiveCamera {
-		return this.camera;
-	}
-
-	public getHeldItemScene(): THREE.Scene {
-		return this.heldItemScene;
-	}
-
-	public getHealthIndicatorScene(): THREE.Scene {
-		return this.healthIndicatorScene;
-	}
-
-	public getInventoryMenuScene(): THREE.Scene {
-		return this.inventoryMenuScene;
-	}
-
-	public getInventoryMenuCamera(): THREE.OrthographicCamera {
-		return this.inventoryMenuCamera;
-	}
-
-	private onWindowResize() {
-		this.camera.aspect = globalThis.innerWidth / globalThis.innerHeight;
-		this.camera.updateProjectionMatrix();
-		this.renderer.setSize(globalThis.innerWidth, globalThis.innerHeight);
-		this.renderer.setPixelRatio(200 / globalThis.innerHeight);
-
-		this.screenPixelsInGamePixel = globalThis.innerHeight / 200;
-		// Update held item camera aspect ratio
-		this.heldItemCamera.aspect = globalThis.innerWidth / globalThis.innerHeight;
-		this.heldItemCamera.updateProjectionMatrix();
-	}
-
-	public getShotVectorsToPlayersInCrosshair(): { playerID: number; vector: THREE.Vector3; hitPoint: THREE.Vector3 }[] {
-		return this.remotePlayerRenderer.getShotVectorsToPlayersInCrosshair();
-	}
-
-	public getPlayerSpheresInCrosshairWithWalls() {
-		return this.remotePlayerRenderer.getPlayerSpheresInCrosshairWithWalls();
-	}
-
-	public getShotVectorsToPlayersWithOffset(
-		yawOffset: number,
-		pitchOffset: number,
-	): { playerID: number; vector: THREE.Vector3; hitPoint: THREE.Vector3 }[] {
-		return this.remotePlayerRenderer.getShotVectorsToPlayersWithOffset(yawOffset, pitchOffset);
-	}
-
-	public getEntityScene(): THREE.Scene {
-		return this.remotePlayerRenderer.getEntityScene();
-	}
-
-	public setInputHandler(inputHandler: InputHandler) {
-		this.inputHandler = inputHandler;
-	}
-
-	public setCollisionManager(collisionManager: CollisionManager) {
-		this.collisionManager = collisionManager;
-	}
-
-	private static approachNumber(input: number, step: number, approach: number): number {
-		if (input == approach) return approach;
-		let output: number;
-		if (input > approach) {
-			output = input - step;
-			if (output <= approach) return approach;
-		} else {
-			output = input + step;
-			if (output >= approach) return approach;
-		}
-		return output;
-	}
+    private clock: THREE.Clock;
+    private deltaTime: number = 0;
+    private chatOverlay: ChatOverlay;
+    private scene: THREE.Scene;
+    private camera: THREE.PerspectiveCamera;
+    private renderer: THREE.WebGLRenderer;
+    private heldItemScene: THREE.Scene;
+    private heldItemCamera: THREE.PerspectiveCamera;
+    private ambientLight: THREE.AmbientLight;
+    private framerate: number;
+    private framesInFramerateSample: number;
+    private sampleOn: number;
+    private lastFramerateCalculation: number;
+    private networking: Networking;
+    private localPlayer: Player;
+    private raycaster: THREE.Raycaster;
+    public scaredLevel: number = 0;
+    private lastPlayerHealth: number = 100;
+    private knockbackVector: THREE.Vector3 = new THREE.Vector3();
+    private bobCycle: number;
+    private lastCameraRoll: number
+
+    public crosshairIsFlashing: boolean = false;
+    public lastShotSomeoneTimestamp: number = 0;
+    public playerHitMarkers: { hitPoint: THREE.Vector3, shotVector: THREE.Vector3, timestamp: number }[] = [];
+    private healthIndicatorScene: THREE.Scene;
+    private healthIndicatorCamera: THREE.PerspectiveCamera;
+    private screenPixelsInGamePixel: number = 1;
+    private inventoryMenuScene: THREE.Scene;
+    private inventoryMenuCamera: THREE.OrthographicCamera;
+    private remotePlayerRenderer: RemotePlayerRenderer;
+    private inputHandler!: InputHandler;
+    private collisionManager!: CollisionManager;
+
+    private spectateGroundTruthPosition: THREE.Vector3 | null = null;
+
+
+    constructor(container: HTMLElement, networking: Networking, localPlayer: Player, chatOverlay: ChatOverlay) {
+        this.networking = networking;
+        this.localPlayer = localPlayer;
+        this.chatOverlay = chatOverlay;
+
+        this.clock = new THREE.Clock();
+        this.scene = new THREE.Scene();
+        this.camera = new THREE.PerspectiveCamera(90, globalThis.innerWidth / globalThis.innerHeight, 0.01, 1000);
+        this.renderer = new THREE.WebGLRenderer();
+        //document.body.appendChild(this.renderer.domElement);
+        container.appendChild(this.renderer.domElement);
+        this.renderer.domElement.style.imageRendering = 'pixelated';
+        this.renderer.setAnimationLoop(null);
+
+        // Create a new scene and camera for the held item
+        this.heldItemScene = new THREE.Scene();
+        this.heldItemCamera = new THREE.PerspectiveCamera(90, globalThis.innerWidth / globalThis.innerHeight, 0.01, 1000);
+        this.heldItemCamera.position.set(0, 0, 5);
+        this.heldItemCamera.lookAt(0, 0, 0);
+
+        // Create a new scene and camera for the health indicator
+        this.healthIndicatorScene = new THREE.Scene();
+        this.healthIndicatorCamera = new THREE.PerspectiveCamera(70, 1, 0.01, 1000);
+        this.healthIndicatorCamera.position.set(0, 0, 0);
+        this.healthIndicatorCamera.lookAt(0, 0, 1);
+
+        this.inventoryMenuScene = new THREE.Scene();
+        this.inventoryMenuCamera = new THREE.OrthographicCamera(-0.5, 0.5, 2.5, -2.5, 0.01, 10);
+        this.inventoryMenuCamera.position.set(0, 0, 5);
+        this.inventoryMenuCamera.lookAt(0, 0, 0);
+        this.inventoryMenuScene.add(this.inventoryMenuCamera);
+        this.inventoryMenuScene.add(new THREE.AmbientLight(0xffffff, 0.5));
+
+        // Ambient lights
+        this.ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
+        const ambientLight2 = new THREE.AmbientLight(0xffffff, 0.5);
+        const ambientLight3 = new THREE.AmbientLight(0xffffff, 0.5); // Ambient light for remote players scene
+
+        this.scene.add(this.ambientLight);
+        this.heldItemScene.add(ambientLight2);
+
+        // Fog settings
+        this.scene.fog = new THREE.FogExp2('#111111', 0.05);
+        this.heldItemScene.fog = new THREE.FogExp2('#111111', 0.05);
+        this.healthIndicatorScene.fog = new THREE.FogExp2('#111111', 0.05); // Add fog to health indicator scene
+
+        this.framerate = 0;
+        this.framesInFramerateSample = 30;
+        this.sampleOn = 0;
+        this.lastFramerateCalculation = 0;
+
+        this.bobCycle = 0;
+        this.lastCameraRoll = 0;
+
+        this.raycaster = new THREE.Raycaster();
+
+        // Initialize remotePlayerRenderer
+        this.remotePlayerRenderer = new RemotePlayerRenderer(
+            this.networking,
+            this.localPlayer,
+            this.raycaster,
+            this.camera,
+            this.scene
+        );
+        this.remotePlayerRenderer.getEntityScene().fog = new THREE.FogExp2('#111111', 0.1); // Add fog to remote players scene
+        this.remotePlayerRenderer.getEntityScene().add(ambientLight3); // Add ambient light to remote players scene
+        this.renderer.domElement.style.touchAction = 'none';
+        this.renderer.domElement.style.position = 'absolute';
+        this.onWindowResize();
+        globalThis.addEventListener('resize', this.onWindowResize.bind(this), false);
+        globalThis.addEventListener('orientationchange', this.onWindowResize.bind(this), false);
+    }
+
+    public onFrame(localPlayer: Player) {
+        this.deltaTime = this.clock.getDelta();
+
+        // Ensure the renderer clears the buffers before the first render
+        this.renderer.autoClear = true;
+
+        // Render the main scene
+        this.renderer.render(this.scene, this.camera);
+
+        // Prevent clearing the buffers in subsequent renders
+        this.renderer.autoClear = false;
+
+        // Update and render remote players
+        this.remotePlayerRenderer.update(this.deltaTime);
+        this.renderer.render(this.remotePlayerRenderer.getEntityScene(), this.camera);
+
+        // Render the held item scene normally (full screen)
+        this.renderer.render(this.heldItemScene, this.heldItemCamera);
+
+        // Set up the scissor and viewport for the health indicator scene rendering
+        const screenWidth = globalThis.innerWidth;
+        const screenHeight = globalThis.innerHeight;
+
+        const healthIndicatorWidth = 60; // native
+        const healthIndicatorHeight = healthIndicatorWidth; // 1:1 aspect ratio
+
+        // Set up scissor and viewport for a region from (0, 0) to (50, 50)
+        this.renderer.setScissorTest(true);
+        this.renderer.setScissor(
+            2 * this.screenPixelsInGamePixel,
+            screenHeight - (healthIndicatorHeight + 1 + this.chatOverlay.getDebugTextHeight()) * this.screenPixelsInGamePixel,
+            healthIndicatorWidth * this.screenPixelsInGamePixel,
+            healthIndicatorHeight * this.screenPixelsInGamePixel
+        );
+        this.renderer.setViewport(
+            2 * this.screenPixelsInGamePixel,
+            screenHeight - (healthIndicatorHeight + 1 + this.chatOverlay.getDebugTextHeight()) * this.screenPixelsInGamePixel,
+            healthIndicatorWidth * this.screenPixelsInGamePixel,
+            healthIndicatorHeight * this.screenPixelsInGamePixel
+        );
+
+        // Render the health indicator scene
+        this.renderer.render(this.healthIndicatorScene, this.healthIndicatorCamera);
+
+        // Render inventory view
+        const inventoryWidth = 20;
+        const inventoryHeight = inventoryWidth * 5;
+        this.renderer.setScissorTest(true);
+        this.renderer.setScissor(
+            screenWidth - (inventoryWidth + 4) * this.screenPixelsInGamePixel,
+            screenHeight / 2 - (inventoryHeight / 2) * this.screenPixelsInGamePixel,
+            inventoryWidth * this.screenPixelsInGamePixel,
+            inventoryHeight * this.screenPixelsInGamePixel
+        );
+        this.renderer.setViewport(
+            screenWidth - (inventoryWidth + 4) * this.screenPixelsInGamePixel,
+            screenHeight / 2 - (inventoryHeight / 2) * this.screenPixelsInGamePixel,
+            inventoryWidth * this.screenPixelsInGamePixel,
+            inventoryHeight * this.screenPixelsInGamePixel
+        );
+        this.renderer.render(this.inventoryMenuScene, this.inventoryMenuCamera);
+
+        // Reset scissor test and viewport after rendering the health indicator
+        this.renderer.setScissorTest(false);
+        this.renderer.setViewport(0, 0, screenWidth, screenHeight);
+
+
+        // Restore autoClear to true if necessary
+        this.renderer.autoClear = true;
+
+        if(localPlayer.playerSpectating !== -1) {
+            const remotePlayer = this.networking.getRemotePlayerData().find((player) => player.id === localPlayer.playerSpectating);
+            if(remotePlayer !== undefined) {
+                // Initialize ground truth position if not set
+                if (!this.spectateGroundTruthPosition) {
+                    this.spectateGroundTruthPosition = new THREE.Vector3(
+                        remotePlayer.position.x,
+                        remotePlayer.position.y,
+                        remotePlayer.position.z
+                    );
+                }
+
+                // Update ground truth position based on velocity
+                this.spectateGroundTruthPosition.x += remotePlayer.velocity.x * this.deltaTime;
+                this.spectateGroundTruthPosition.y += remotePlayer.velocity.y * this.deltaTime;
+                this.spectateGroundTruthPosition.z += remotePlayer.velocity.z * this.deltaTime;
+
+                // If forced update, set directly to remote position
+                if (remotePlayer.forced) {
+                    this.spectateGroundTruthPosition.set(
+                        remotePlayer.position.x,
+                        remotePlayer.position.y,
+                        remotePlayer.position.z
+                    );
+                }
+
+                // Lerp ground truth position towards actual position
+                this.spectateGroundTruthPosition.lerp(
+                    new THREE.Vector3(
+                        remotePlayer.position.x,
+                        remotePlayer.position.y,
+                        remotePlayer.position.z
+                    ),
+                    0.1 * this.deltaTime * 60
+                );
+
+                // Update camera position and rotation
+                this.camera.position.copy(this.spectateGroundTruthPosition);
+
+                // Simple quaternion slerp
+                this.camera.quaternion.slerp(new THREE.Quaternion(
+                    remotePlayer.lookQuaternion[0],
+                    remotePlayer.lookQuaternion[1],
+                    remotePlayer.lookQuaternion[2],
+                    remotePlayer.lookQuaternion[3]
+                ), 0.3 * this.deltaTime * 60);
+            }
+        } else {
+            // Reset spectate position when not spectating
+            this.spectateGroundTruthPosition = null;
+            this.camera.position.copy(localPlayer.position);
+            this.camera.setRotationFromQuaternion(this.localPlayer.lookQuaternion);
+        }
+
+
+
+
+
+        this.camera.position.add(this.knockbackVector);
+        this.knockbackVector.lerp(new THREE.Vector3(), 0.05 * this.deltaTime * 60);
+
+
+        if(this.localPlayer.health < this.lastPlayerHealth) {
+            const remotePlayer: RemotePlayer | undefined = this.networking.getRemotePlayerData().find((player) => player.id === this.localPlayer.idLastDamagedBy);
+            if(remotePlayer !== undefined) {
+                //console.log("Player was damaged by " + remotePlayer.name);
+                const diff = new THREE.Vector3().subVectors(this.localPlayer.position, remotePlayer.position);
+                this.knockbackVector.copy(diff.normalize().multiplyScalar(0.2));
+            }
+        }
+
+
+        const shakeAmount = 0.08 * Math.pow(this.scaredLevel,5);
+        this.camera.position.add(new THREE.Vector3((Math.random()-0.5) * shakeAmount, (Math.random()-0.5) *shakeAmount, (Math.random()-0.5) * shakeAmount));
+        this.camera.rotation.x += (Math.random()-0.5) * shakeAmount * 0.12;
+        this.camera.rotation.y += (Math.random()-0.5) * shakeAmount * 0.12;
+        this.camera.rotation.z += (Math.random()-0.5) * shakeAmount * 0.12;
+
+        this.heldItemCamera.rotation.set((Math.random()-0.5) * shakeAmount, (Math.random()-0.5) * shakeAmount, (Math.random()-0.5) * shakeAmount );
+
+        this.lastPlayerHealth = this.localPlayer.health;
+
+        const vel = Math.sqrt(Math.pow(this.localPlayer.inputVelocity.x,2) + Math.pow(this.localPlayer.inputVelocity.z,2))
+
+        if(vel == 0 || this.collisionManager.isPlayerInAir() || this.localPlayer.playerSpectating !== -1) {
+            this.bobCycle = 0;
+        } else {
+            this.bobCycle += this.deltaTime * 4.8 * vel;
+            this.camera.position.y = this.camera.position.y + (Math.sin(this.bobCycle) * .03 * SettingsManager.settings.viewBobbingStrength);
+            //console.log(this.camera.position.y);
+        }
+
+        let newHandX = Math.sin(this.bobCycle / 1.9) * .02 * SettingsManager.settings.viewBobbingStrength;
+        let newHandY = -(Math.sin(this.bobCycle) * .07 * SettingsManager.settings.viewBobbingStrength);
+        let newHandZ = Math.sin(this.bobCycle / 1.8) * .015 * SettingsManager.settings.viewBobbingStrength;
+        newHandY += localPlayer.velocity.y * 0.04 * SettingsManager.settings.viewBobbingStrength; //move hand up when falling, down when jumping
+
+        //banana lags behind player slightly
+        const playerVelocity = new THREE.Vector3().copy(localPlayer.velocity);
+        playerVelocity.applyQuaternion(localPlayer.lookQuaternion.clone().invert());
+        newHandX += playerVelocity.x * 0.02 * SettingsManager.settings.viewBobbingStrength;
+        newHandZ -= -playerVelocity.z * 0.02 * SettingsManager.settings.viewBobbingStrength;
+
+        if(this.inputHandler.getAim()) {
+            newHandX *= 0.25;
+            newHandY *= 0.25;
+            newHandZ *= 0.25;
+        }
+
+        this.heldItemCamera.position.lerp(new THREE.Vector3(newHandX, newHandY, 5 + newHandZ),0.15 * this.deltaTime * 60);
+
+        const maxRollAmount = this.inputHandler.getInputX() * -.007 * SettingsManager.settings.viewBobbingStrength;
+        const maxRollSpeed = this.deltaTime * .4;
+        let roll: number = this.lastCameraRoll;
+        roll = Renderer.approachNumber(roll, maxRollSpeed, maxRollAmount);
+        const euler = new THREE.Euler().setFromQuaternion(this.camera.quaternion, 'YXZ');
+        euler.z += roll;
+        this.lastCameraRoll = roll;
+
+        this.camera.quaternion.setFromEuler(euler);
+
+        this.updateFramerate();
+    }
+
+    private updateFramerate() {
+        this.sampleOn++;
+        if (this.sampleOn >= this.framesInFramerateSample) {
+            this.framerate = this.framesInFramerateSample / (Date.now() / 1000 - this.lastFramerateCalculation);
+            this.sampleOn = 0;
+            this.lastFramerateCalculation = Date.now() / 1000;
+        }
+    }
+
+    public getFramerate(): number {
+        return this.framerate;
+    }
+
+    public getScene(): THREE.Scene {
+        return this.scene;
+    }
+
+    public getCamera(): THREE.PerspectiveCamera {
+        return this.camera;
+    }
+
+    public getHeldItemScene(): THREE.Scene {
+        return this.heldItemScene;
+    }
+
+    public getHealthIndicatorScene(): THREE.Scene {
+        return this.healthIndicatorScene;
+    }
+
+    public getInventoryMenuScene(): THREE.Scene {
+        return this.inventoryMenuScene;
+    }
+
+    public getInventoryMenuCamera(): THREE.OrthographicCamera {
+        return this.inventoryMenuCamera;
+    }
+
+    private onWindowResize() {
+        this.camera.aspect = globalThis.innerWidth / globalThis.innerHeight;
+        this.camera.updateProjectionMatrix();
+        this.renderer.setSize(globalThis.innerWidth, globalThis.innerHeight);
+        this.renderer.setPixelRatio(200 / globalThis.innerHeight);
+
+        this.screenPixelsInGamePixel = globalThis.innerHeight / 200;
+        // Update held item camera aspect ratio
+        this.heldItemCamera.aspect = globalThis.innerWidth / globalThis.innerHeight;
+        this.heldItemCamera.updateProjectionMatrix();
+    }
+
+
+    public getShotVectorsToPlayersInCrosshair(): { playerID: number, vector: THREE.Vector3, hitPoint: THREE.Vector3 }[] {
+        return this.remotePlayerRenderer.getShotVectorsToPlayersInCrosshair();
+    }
+
+    public getPlayerSpheresInCrosshairWithWalls() {
+        return this.remotePlayerRenderer.getPlayerSpheresInCrosshairWithWalls();
+    }
+
+    public getShotVectorsToPlayersWithOffset(yawOffset: number, pitchOffset: number): { playerID: number, vector: THREE.Vector3, hitPoint: THREE.Vector3 }[] {
+        return this.remotePlayerRenderer.getShotVectorsToPlayersWithOffset(yawOffset, pitchOffset);
+    }
+
+    public getEntityScene(): THREE.Scene {
+        return this.remotePlayerRenderer.getEntityScene();
+    }
+
+    public setInputHandler(inputHandler: InputHandler) {
+        this.inputHandler = inputHandler;
+    }
+
+    public setCollisionManager(collisionManager: CollisionManager) {
+        this.collisionManager = collisionManager;
+    }
+
+    private static approachNumber(input: number, step: number, approach: number): number {
+        if (input == approach) {return approach;}
+        let output: number;
+        if (input > approach) {
+            output = input - step;
+            if (output <= approach) {return  approach;}
+        } else {
+            output = input + step;
+            if (output >= approach) {return  approach;}
+        }
+        return output;
+    }
 }
diff --git a/src/client/core/SettingsManager.ts b/src/client/core/SettingsManager.ts
index 0e028ef7..6a326dd5 100644
--- a/src/client/core/SettingsManager.ts
+++ b/src/client/core/SettingsManager.ts
@@ -1,39 +1,39 @@
 export class SettingsManager {
-	static settings: Settings;
+    static settings: Settings;
 
-	static {
-		SettingsManager.reset();
-		const settingsJson = localStorage.getItem('settings');
-		if (settingsJson) SettingsManager.settings = JSON.parse(settingsJson);
-	}
+    static {
+        SettingsManager.reset()
+        const settingsJson = localStorage.getItem('settings');
+        if (settingsJson) SettingsManager.settings = JSON.parse(settingsJson);
+    }
 
-	public static write(): void {
-		localStorage.setItem('settings', JSON.stringify(SettingsManager.settings));
-	}
+    public static write(): void {
+        localStorage.setItem('settings', JSON.stringify(SettingsManager.settings));
+    }
 
-	public static reset() {
-		SettingsManager.settings = {
-			sense: 1,
-			controllerSense: 1,
-			name: null,
-			crosshairColor: 'rgb(0,255,255)',
-			crosshairType: 1,
-			viewBobbingStrength: 1,
-			doPrettyText: false,
-		};
-	}
+    public static reset() {
+        SettingsManager.settings = {
+            sense: 1,
+            controllerSense: 1,
+            name: null,
+            crosshairColor: 'rgb(0,255,255)',
+            crosshairType: 1,
+            viewBobbingStrength: 1,
+            doPrettyText: false,
+        };
+    }
 
-	constructor() {
-		throw Error('Settings class is static.');
-	}
+    constructor() {
+        throw Error('Settings class is static.');
+    }
 }
 
 interface Settings {
-	sense: number;
-	controllerSense: number;
-	name: null | string;
-	crosshairColor: string;
-	crosshairType: number;
-	viewBobbingStrength: number;
-	doPrettyText: boolean;
-}
+    sense: number;
+    controllerSense: number;
+    name: null | string;
+    crosshairColor: string;
+    crosshairType: number;
+    viewBobbingStrength: number;
+    doPrettyText: boolean;
+}
\ No newline at end of file
diff --git a/src/client/input/CollisionManager.ts b/src/client/input/CollisionManager.ts
index fe855d13..4cd8e336 100644
--- a/src/client/input/CollisionManager.ts
+++ b/src/client/input/CollisionManager.ts
@@ -1,150 +1,142 @@
 import * as THREE from 'three';
 import { Player } from '../core/Player.ts';
-import {
-	acceleratedRaycast,
-	computeBoundsTree,
-	disposeBoundsTree,
-	MeshBVH,
-	StaticGeometryGenerator,
-} from 'three-mesh-bvh';
-import { InputHandler } from './InputHandler.ts';
-import { RemotePlayerRenderer } from '../core/RemotePlayerRenderer.ts';
+import { acceleratedRaycast, computeBoundsTree, disposeBoundsTree, StaticGeometryGenerator, MeshBVH } from 'three-mesh-bvh';
+import { InputHandler } from "./InputHandler.ts";
+import { RemotePlayerRenderer} from "../core/RemotePlayerRenderer.ts";
 
 THREE.Mesh.prototype.raycast = acceleratedRaycast;
 THREE.BufferGeometry.prototype.computeBoundsTree = computeBoundsTree;
 THREE.BufferGeometry.prototype.disposeBoundsTree = disposeBoundsTree;
 
 export class CollisionManager {
-	private clock: THREE.Clock;
-	private readonly colliderSphere: THREE.Sphere;
-	private readonly deltaVec: THREE.Vector3;
-	private readonly prevPosition: THREE.Vector3;
-	public static mapLoaded: boolean = false;
-	private static colliderGeom?: THREE.BufferGeometry; // Mark as possibly undefined
-	private inputHandler: InputHandler;
-	private static readonly maxAngle: number = Math.cos(45 * Math.PI / 180);
-	private readonly triNormal: THREE.Vector3;
-	private readonly upVector: THREE.Vector3;
-	private coyoteTime: number;
-	private jumped: boolean;
-	private collided: boolean;
-
-	constructor(inputHandler: InputHandler) {
-		this.inputHandler = inputHandler;
-		this.clock = new THREE.Clock();
-		this.colliderSphere = new THREE.Sphere(new THREE.Vector3(), .2);
-		this.deltaVec = new THREE.Vector3();
-		this.prevPosition = new THREE.Vector3();
-		this.triNormal = new THREE.Vector3();
-		this.upVector = new THREE.Vector3(0, 1, 0);
-		this.coyoteTime = 0;
-		this.jumped = false;
-		this.collided = false;
-	}
-
-	public collisionPeriodic(localPlayer: Player) {
-		if (!CollisionManager.mapLoaded || !CollisionManager.colliderGeom || !CollisionManager.colliderGeom.boundsTree) {
-			return; // Add checks
-		}
-		let deltaTime: number = this.clock.getDelta();
-		let steps: number = 1;
-		while (deltaTime >= 1 / 120) {
-			deltaTime = deltaTime / 2;
-			steps = steps * 2;
-		}
-		for (let i = 0; i < steps; i++) {
-			this.physics(localPlayer, deltaTime);
-		}
-	}
-
-	private physics(localPlayer: Player, deltaTime: number) {
-		this.prevPosition.copy(localPlayer.position);
-		const jump: boolean = this.inputHandler.jump;
-
-		localPlayer.gravity += deltaTime * -30;
-		localPlayer.inputVelocity.y += localPlayer.gravity;
-		localPlayer.inputVelocity.y = (localPlayer.inputVelocity.y + this.inputHandler.prevInputVelocity.y) * .25;
-		localPlayer.position.add(localPlayer.inputVelocity.clone().multiplyScalar(deltaTime));
-
-		const bvh: MeshBVH | undefined = CollisionManager.colliderGeom?.boundsTree;
-		if (!bvh) return; // Ensure bvh exists
-
-		this.colliderSphere.center = localPlayer.position.clone();
-
-		this.collided = false;
-
-		bvh.shapecast({
-			intersectsBounds: (box: THREE.Box3) => {
-				return box.intersectsSphere(this.colliderSphere);
-			},
-
-			intersectsTriangle: (tri: THREE.Triangle) => {
-				// Get delta between the closest point and center
-				tri.closestPointToPoint(this.colliderSphere.center, this.deltaVec);
-				this.deltaVec.sub(this.colliderSphere.center);
-				const distance: number = this.deltaVec.length();
-
-				if (distance < this.colliderSphere.radius) {
-					// Move the sphere position to be outside the triangle
-					const radius: number = this.colliderSphere.radius;
-					const depth: number = distance - radius;
-					this.deltaVec.multiplyScalar(1 / distance);
-
-					tri.getNormal(this.triNormal);
-					const angle: number = this.triNormal.dot(this.upVector);
-
-					if (angle >= CollisionManager.maxAngle) {
-						localPlayer.position.addScaledVector(this.deltaVec, depth);
-						localPlayer.inputVelocity.y = 0;
-						localPlayer.gravity = 0;
-						this.coyoteTime = 0;
-						this.collided = true;
-					} else {
-						localPlayer.position.addScaledVector(this.deltaVec, depth);
-					}
-				}
-
-				this.colliderSphere.center = localPlayer.position.clone();
-				return false;
-			},
-
-			boundsTraverseOrder: (box: THREE.Box3) => {
-				return box.distanceToPoint(this.colliderSphere.center) - this.colliderSphere.radius;
-			},
-		});
-
-		if (!this.collided) {
-			this.coyoteTime += deltaTime;
-			if (jump && this.coyoteTime < 6 / 60 && !this.jumped) {
-				localPlayer.gravity = 8;
-				this.jumped = true;
-			}
-		} else {
-			if (jump) {
-				localPlayer.gravity = 8;
-			} else {
-				this.jumped = false;
-			}
-		}
-		if (!(deltaTime == 0)) {
-			localPlayer.velocity.copy(localPlayer.position.clone().sub(this.prevPosition).divideScalar(deltaTime));
-		}
-	}
-
-	public static staticGeometry(group: THREE.Group) {
-		//if (!this.mapLoaded) {
-		console.time('Building static geometry BVH');
-		const staticGenerator = new StaticGeometryGenerator(group);
-		staticGenerator.attributes = ['position'];
-		this.colliderGeom = staticGenerator.generate();
-		this.colliderGeom.computeBoundsTree({ maxDepth: 1000000, maxLeafTris: 4 });
-		RemotePlayerRenderer.setMap(new THREE.Mesh(this.colliderGeom));
-		this.mapLoaded = true;
-		console.timeEnd('Building static geometry BVH');
-		// }
-	}
-
-	public isPlayerInAir(): boolean {
-		return !this.collided;
-	}
-}
+    private clock: THREE.Clock;
+    private readonly colliderSphere: THREE.Sphere;
+    private readonly deltaVec: THREE.Vector3;
+    private readonly prevPosition: THREE.Vector3;
+    public static mapLoaded: boolean = false;
+    private static colliderGeom?: THREE.BufferGeometry; // Mark as possibly undefined
+    private inputHandler: InputHandler;
+    private static readonly maxAngle: number = Math.cos(45 * Math.PI / 180);
+    private readonly triNormal: THREE.Vector3;
+    private readonly upVector: THREE.Vector3;
+    private coyoteTime: number;
+    private jumped: boolean;
+    private collided: boolean;
+
+    constructor(inputHandler: InputHandler) {
+        this.inputHandler = inputHandler;
+        this.clock = new THREE.Clock();
+        this.colliderSphere = new THREE.Sphere(new THREE.Vector3(), .2);
+        this.deltaVec = new THREE.Vector3();
+        this.prevPosition = new THREE.Vector3();
+        this.triNormal = new THREE.Vector3();
+        this.upVector = new THREE.Vector3(0, 1, 0)
+        this.coyoteTime = 0;
+        this.jumped = false;
+        this.collided = false;
+    }
+
+    public collisionPeriodic(localPlayer: Player) {
+        if (!CollisionManager.mapLoaded || !CollisionManager.colliderGeom || !CollisionManager.colliderGeom.boundsTree) return; // Add checks
+        let deltaTime: number = this.clock.getDelta();
+        let steps: number = 1;
+        while (deltaTime >= 1 / 120) {
+            deltaTime = deltaTime / 2
+            steps = steps * 2;
+        }
+        for (let i = 0; i < steps; i++) {
+            this.physics(localPlayer, deltaTime);
+        }
+    }
+
+    private physics(localPlayer: Player, deltaTime: number) {
+        this.prevPosition.copy(localPlayer.position);
+        const jump: boolean = this.inputHandler.jump;
+
+        localPlayer.gravity += deltaTime * -30;
+        localPlayer.inputVelocity.y += localPlayer.gravity;
+        localPlayer.inputVelocity.y = (localPlayer.inputVelocity.y + this.inputHandler.prevInputVelocity.y) * .25;
+        localPlayer.position.add(localPlayer.inputVelocity.clone().multiplyScalar(deltaTime));
+
+        const bvh: MeshBVH | undefined = CollisionManager.colliderGeom?.boundsTree;
+        if (!bvh) return; // Ensure bvh exists
+
+        this.colliderSphere.center = localPlayer.position.clone();
+
+        this.collided = false;
+
+        bvh.shapecast({
+            intersectsBounds: (box: THREE.Box3) => {
+                return box.intersectsSphere(this.colliderSphere);
+            },
+
+            intersectsTriangle: (tri: THREE.Triangle) => {
+                // Get delta between the closest point and center
+                tri.closestPointToPoint(this.colliderSphere.center, this.deltaVec);
+                this.deltaVec.sub(this.colliderSphere.center);
+                const distance: number = this.deltaVec.length();
+
+                if (distance < this.colliderSphere.radius) {
+                    // Move the sphere position to be outside the triangle
+                    const radius: number = this.colliderSphere.radius;
+                    const depth: number = distance - radius;
+                    this.deltaVec.multiplyScalar(1 / distance);
+
+                    tri.getNormal(this.triNormal);
+                    const angle: number = this.triNormal.dot(this.upVector);
+
+                    if (angle >= CollisionManager.maxAngle) {
+                        localPlayer.position.addScaledVector(this.deltaVec, depth);
+                        localPlayer.inputVelocity.y = 0;
+                        localPlayer.gravity = 0;
+                        this.coyoteTime = 0;
+                        this.collided = true;
+                    } else {
+                        localPlayer.position.addScaledVector(this.deltaVec, depth);
+                    }
+                }
+
+                this.colliderSphere.center = localPlayer.position.clone();
+                return false;
+            },
+
+            boundsTraverseOrder: (box: THREE.Box3) => {
+                return box.distanceToPoint(this.colliderSphere.center) - this.colliderSphere.radius;
+            }
+        });
+
+        if (!this.collided) {
+            this.coyoteTime += deltaTime;
+            if (jump && this.coyoteTime < 6 / 60 && !this.jumped) {
+                localPlayer.gravity = 8;
+                this.jumped = true;
+            }
+        } else {
+            if (jump) {
+                localPlayer.gravity = 8;
+            } else {
+                this.jumped = false;
+            }
+        }
+        if (!(deltaTime == 0)) {
+            localPlayer.velocity.copy(localPlayer.position.clone().sub(this.prevPosition).divideScalar(deltaTime));
+        }
+    }
+
+    public static staticGeometry(group: THREE.Group) {
+        //if (!this.mapLoaded) {
+            console.time("Building static geometry BVH");
+            const staticGenerator = new StaticGeometryGenerator(group);
+            staticGenerator.attributes = ['position'];
+            this.colliderGeom = staticGenerator.generate();
+            this.colliderGeom.computeBoundsTree({maxDepth: 1000000, maxLeafTris: 4});
+            RemotePlayerRenderer.setMap(new THREE.Mesh(this.colliderGeom));
+            this.mapLoaded = true;
+            console.timeEnd("Building static geometry BVH");
+       // }
+    }
+
+    public isPlayerInAir(): boolean {
+        return !this.collided;
+    }
+}
\ No newline at end of file
diff --git a/src/client/input/HeldItemInput.ts b/src/client/input/HeldItemInput.ts
index 062b2aa1..916b4869 100644
--- a/src/client/input/HeldItemInput.ts
+++ b/src/client/input/HeldItemInput.ts
@@ -1,11 +1,11 @@
 export class HeldItemInput {
-	public leftClick: boolean = false;
-	public rightClick: boolean = false;
-	public shiftKey: boolean = false;
+    public leftClick: boolean = false;
+    public rightClick: boolean = false;
+    public shiftKey: boolean = false;
 
-	constructor(leftClick: boolean = false, rightClick: boolean = false, shiftKey: boolean = false) {
-		this.leftClick = leftClick;
-		this.rightClick = rightClick;
-		this.shiftKey = shiftKey;
-	}
-}
+    constructor(leftClick: boolean = false, rightClick: boolean = false, shiftKey: boolean = false) {
+        this.leftClick = leftClick;
+        this.rightClick = rightClick;
+        this.shiftKey = shiftKey;
+    }
+}
\ No newline at end of file
diff --git a/src/client/input/InputHandler.ts b/src/client/input/InputHandler.ts
index f3fbba1f..b50c4c3b 100644
--- a/src/client/input/InputHandler.ts
+++ b/src/client/input/InputHandler.ts
@@ -2,359 +2,342 @@ import * as THREE from 'three';
 import { PointerLockControls } from './PointerLockControl.ts';
 import { Renderer } from '../core/Renderer.ts';
 import { Player } from '../core/Player.ts';
-import { SettingsManager } from '../core/SettingsManager.ts';
+import {SettingsManager} from "../core/SettingsManager.ts";
 
 export class InputHandler {
-	private readonly gameIndex: number;
-	private mouse: PointerLockControls;
-	private gamepad: Gamepad | null = null;
-	private readonly gamepadEuler;
-	private clock: THREE.Clock;
-	private keys: { [key: string]: boolean } = {};
-	private leftMouseDown: boolean = false;
-	private rightMouseDown: boolean = false;
-	private renderer: Renderer;
-	private readonly localPlayer: Player;
-	private inputX: number = 0;
-	private inputZ: number = 0;
-	public jump = false;
-	public prevInputVelocity: THREE.Vector3;
-	private scrollClicksSinceLastCheck: number = 0;
-	private readonly gamepadInputs: GamepadInputs;
-	private shoot: boolean = false;
-	private aim: boolean = false;
-	public nameSettingActive: boolean = false;
-	private touchJoyX: number = 0;
-	private touchJoyY: number = 0;
-	private touchLookX: number = 0;
-	private touchLookY: number = 0;
-	private inventoryIterationTouched: boolean = false;
-	private touchButtons: number[] = [];
-
-	constructor(renderer: Renderer, localPlayer: Player, nextGameIndex: number) {
-		this.renderer = renderer;
-		this.localPlayer = localPlayer;
-		this.prevInputVelocity = new THREE.Vector3();
-		this.gamepadEuler = new THREE.Euler(0, 0, 0, 'YXZ');
-
-		this.clock = new THREE.Clock();
-		this.mouse = new PointerLockControls(this.localPlayer, document.body);
-
-		this.gamepadInputs = new GamepadInputs();
-
-		this.gameIndex = nextGameIndex;
-
-		if (!navigator.getGamepads()) {
-			console.log('Browser does not support Gamepad API.');
-		}
-
-		this.setupEventListeners();
-	}
-
-	private setupEventListeners() {
-		document.addEventListener('keydown', this.onKeyDown.bind(this));
-		document.addEventListener('keyup', this.onKeyUp.bind(this));
-		document.addEventListener('mousedown', this.onMouseDown.bind(this));
-		document.addEventListener('mouseup', this.onMouseUp.bind(this));
-		document.addEventListener('mouseleave', this.onMouseUp.bind(this));
-		document.addEventListener('blur', this.deregisterAllKeys.bind(this), false);
-		document.addEventListener('pointerlockchange', this.deregisterAllKeys.bind(this), false);
-		document.addEventListener('visibilitychange', this.deregisterAllKeys.bind(this), false);
-
-		document.addEventListener('click', () => {
-			this.mouse.lock();
-		});
-
-		document.addEventListener('contextmenu', (event) => {
-			event.preventDefault();
-		});
-
-		document.addEventListener('wheel', this.processScroll.bind(this));
-	}
-
-	private processScroll(e: WheelEvent) {
-		if (e.deltaY >= 4) {
-			this.scrollClicksSinceLastCheck++;
-		}
-		if (e.deltaY <= -4) {
-			this.scrollClicksSinceLastCheck--;
-		}
-	}
-
-	public getScrollClicks() {
-		const clicks = this.scrollClicksSinceLastCheck;
-		this.scrollClicksSinceLastCheck = 0;
-		return clicks;
-	}
-
-	public handleInputs() {
-		const deltaTime: number = this.clock.getDelta();
-		const deltaTimeAcceleration = this.localPlayer.acceleration * deltaTime;
-
-		let dist = 1;
-		let speedMultiplier: number = 1;
-		this.jump = false;
-		this.aim = false;
-		this.shoot = false;
-
-		const oldInputZ = this.inputZ;
-		const oldInputX = this.inputX;
-
-		this.gamepad = navigator.getGamepads()[this.gameIndex];
-		if (this.gamepad) {
-			if (this.gamepad.connected) {
-				this.updateGamepadInputArray(this.gamepad);
-				this.gamepadEuler.setFromQuaternion(this.localPlayer.lookQuaternion);
-				if (Math.abs(this.gamepadInputs.leftJoyX) >= .1) {
-					this.inputX += deltaTimeAcceleration * this.gamepadInputs.leftJoyX;
-				}
-				if (Math.abs(this.gamepadInputs.leftJoyY) >= .1) {
-					this.inputZ += deltaTimeAcceleration * this.gamepadInputs.leftJoyY;
-				}
-				const vectorLength = Math.sqrt(
-					(this.gamepadInputs.leftJoyX * this.gamepadInputs.leftJoyX) +
-						(this.gamepadInputs.leftJoyY * this.gamepadInputs.leftJoyY),
-				);
-				if (vectorLength >= .1) speedMultiplier = Math.min(Math.max(vectorLength, 0), 1);
-				if (this.gamepadInputs.A) this.jump = true;
-				if (this.gamepadInputs.leftTrigger > .5) this.aim = true;
-				if (this.gamepadInputs.rightTrigger > .5) this.shoot = true;
-				const aimAdjust = this.calculateAimAssist();
-				this.gamepadEuler.y -= this.gamepadInputs.rightJoyX * SettingsManager.settings.controllerSense * deltaTime *
-					aimAdjust * 4;
-				this.gamepadEuler.x -= this.gamepadInputs.rightJoyY * SettingsManager.settings.controllerSense * deltaTime *
-					aimAdjust * 4;
-				this.gamepadEuler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.gamepadEuler.x));
-				this.localPlayer.lookQuaternion.setFromEuler(this.gamepadEuler);
-			}
-		}
-
-		//touch joystick controls
-		if (Math.hypot(this.touchJoyY, this.touchJoyX) > 0.1) {
-			const touchVectorLength = Math.hypot(this.touchJoyX, this.touchJoyY);
-			speedMultiplier = Math.min(Math.max(touchVectorLength, 0), 1);
-			this.inputX += deltaTimeAcceleration * this.touchJoyX;
-			this.inputZ += deltaTimeAcceleration * this.touchJoyY;
-		}
-
-		const touchSensitivity = 0.03; // Adjust sensitivity as needed
-		this.gamepadEuler.setFromQuaternion(this.localPlayer.lookQuaternion);
-
-		if (!this.localPlayer.chatActive && !this.nameSettingActive) {
-			if (this.getKey('w')) this.inputZ -= deltaTimeAcceleration;
-			if (this.getKey('s')) this.inputZ += deltaTimeAcceleration;
-			if (this.getKey('a')) this.inputX -= deltaTimeAcceleration;
-			if (this.getKey('d')) this.inputX += deltaTimeAcceleration;
-			const aimAdjust = this.calculateAimAssist();
-			if (this.getKey('arrowright')) {
-				this.gamepadEuler.y -= SettingsManager.settings.controllerSense * deltaTime * aimAdjust * 4;
-			}
-			if (this.getKey('arrowleft')) {
-				this.gamepadEuler.y += SettingsManager.settings.controllerSense * deltaTime * aimAdjust * 4;
-			}
-			if (this.getKey('arrowup')) {
-				this.gamepadEuler.x += SettingsManager.settings.controllerSense * deltaTime * aimAdjust * 4;
-			}
-			if (this.getKey('arrowdown')) {
-				this.gamepadEuler.x -= SettingsManager.settings.controllerSense * deltaTime * aimAdjust * 4;
-			}
-			if (this.getKey(' ')) this.jump = true;
-		}
-
-		this.gamepadEuler.y -= this.touchLookX * touchSensitivity;
-		this.gamepadEuler.x -= this.touchLookY * touchSensitivity;
-		this.gamepadEuler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.gamepadEuler.x));
-		this.localPlayer.lookQuaternion.setFromEuler(this.gamepadEuler);
-
-		switch (this.inputZ - oldInputZ) {
-			case 0:
-				this.inputZ = InputHandler.approachZero(this.inputZ, deltaTimeAcceleration);
-		}
-
-		switch (this.inputX - oldInputX) {
-			case 0:
-				this.inputX = InputHandler.approachZero(this.inputX, deltaTimeAcceleration);
-		}
-
-		if (this.localPlayer.health <= 0) dist = 0; //don't allow movement when health = 0
-
-		this.prevInputVelocity.copy(this.localPlayer.inputVelocity);
-
-		this.localPlayer.inputVelocity.z = dist * this.inputZ;
-		this.localPlayer.inputVelocity.x = dist * this.inputX;
-		this.localPlayer.inputVelocity.y = 0;
-		this.localPlayer.inputVelocity.clampLength(0, this.localPlayer.speed * speedMultiplier);
-		this.localPlayer.inputVelocity.y = this.prevInputVelocity.y;
-		this.inputZ = this.localPlayer.inputVelocity.z;
-		this.inputX = this.localPlayer.inputVelocity.x;
-
-		const euler = new THREE.Euler().setFromQuaternion(this.localPlayer.lookQuaternion, 'YXZ');
-		euler.x = 0;
-		euler.z = 0;
-		this.localPlayer.quaternion.setFromEuler(euler);
-		this.localPlayer.inputVelocity.applyQuaternion(this.localPlayer.quaternion);
-
-		if (this.leftMouseDown || this.touchButtons.includes(0)) this.shoot = true;
-		if (this.rightMouseDown) this.aim = true;
-
-		if (this.localPlayer.playerSpectating !== -1) {
-			this.inputX = 0;
-			this.inputZ = 0;
-			this.jump = false;
-			this.shoot = false;
-			this.aim = false;
-		}
-	}
-
-	public getKey(key: string): boolean {
-		return this.keys[key];
-	}
-
-	public setTouchJoyInput(x: number, y: number) {
-		this.touchJoyX = x;
-		this.touchJoyY = y;
-	}
-
-	public setLastTouchLookDelta(x: number, y: number) {
-		this.touchLookX = x;
-		this.touchLookY = y;
-	}
-	public setButtonsHeld(buttons: number[]) {
-		this.touchButtons = buttons;
-		this.jump = this.jump || buttons.includes(-1);
-		this.inventoryIterationTouched = buttons.includes(1);
-	}
-
-	public getInventoryIterationTouched() {
-		return this.inventoryIterationTouched;
-	}
-
-	private onKeyDown(event: KeyboardEvent) {
-		//event.preventDefault();
-		if (event.key === 'Tab' || event.key === "'" || event.key === '/') event.preventDefault();
-		const key = event.key.toLowerCase();
-		this.keys[key] = true;
-
-		if (!this.localPlayer.chatActive && !this.nameSettingActive) {
-			if (key === 'c') {
-				this.leftMouseDown = true;
-			} else if (key === 'z') {
-				this.rightMouseDown = true;
-			}
-		}
-	}
-
-	private onKeyUp(event: KeyboardEvent) {
-		const key = event.key.toLowerCase();
-		this.keys[key] = false;
-
-		if (!this.localPlayer.chatActive && !this.nameSettingActive) {
-			if (key === 'c') {
-				this.leftMouseDown = false;
-			} else if (key === 'z') {
-				this.rightMouseDown = false;
-			}
-		}
-	}
-
-	private onMouseDown(event: MouseEvent) {
-		if (event.button === 0) {
-			this.leftMouseDown = true;
-		} else if (event.button === 2) {
-			this.rightMouseDown = true;
-		}
-	}
-
-	private onMouseUp(event: MouseEvent) {
-		if (event.button === 0) {
-			this.leftMouseDown = false;
-		} else if (event.button === 2) {
-			this.rightMouseDown = false;
-		}
-	}
-
-	public getShoot() {
-		return this.shoot;
-	}
-
-	public getAim() {
-		return this.aim;
-	}
-
-	public getGamepadInputs(): GamepadInputs {
-		return this.gamepadInputs;
-	}
-
-	public deregisterAllKeys() {
-		const locked = document.pointerLockElement === document.body;
-		if (!locked) {
-			this.keys = {};
-		}
-	}
-
-	public getInputX() {
-		return this.inputX;
-	}
-
-	public getInputZ() {
-		return this.inputZ;
-	}
-
-	private static approachZero(input: number, step: number): number {
-		if (input == 0) return 0;
-		let sign: number = 1;
-		if (input < 0) sign = -1;
-		const output: number = Math.abs(input) - step;
-		if (output <= 0) return 0;
-		return sign * output;
-	}
-
-	private updateGamepadInputArray(gamepad: Gamepad) {
-		if (gamepad.axes[4]) {
-			this.gamepadInputs.leftTrigger = gamepad.axes[4];
-			this.gamepadInputs.rightTrigger = gamepad.axes[5];
-		} else {
-			this.gamepadInputs.leftTrigger = gamepad.buttons[6].value;
-			this.gamepadInputs.rightTrigger = gamepad.buttons[7].value;
-		}
-		this.gamepadInputs.leftJoyX = gamepad.axes[0];
-		this.gamepadInputs.leftJoyY = gamepad.axes[1];
-		this.gamepadInputs.A = gamepad.buttons[0].pressed;
-		this.gamepadInputs.rightJoyX = gamepad.axes[2];
-		this.gamepadInputs.rightJoyY = gamepad.axes[3];
-		this.gamepadInputs.leftShoulder = gamepad.buttons[4].pressed;
-		this.gamepadInputs.rightShoulder = gamepad.buttons[5].pressed;
-	}
-
-	private calculateAimAssist(): number {
-		if (this.gamepad) {
-			if ((Math.abs(this.gamepadInputs.rightJoyX) >= .1 || Math.abs(this.gamepadInputs.rightJoyY) >= .1)) {
-				if (this.renderer.getPlayerSpheresInCrosshairWithWalls().length > 0) {
-					return .5;
-				}
-			}
-		} else if (
-			this.getKey('arrowup') || this.getKey('arrowdown') || this.getKey('arrowleft') || this.getKey('arrowright')
-		) {
-			if (this.renderer.getPlayerSpheresInCrosshairWithWalls().length > 0) {
-				return .5;
-			}
-		}
-		return 1;
-	}
+    private readonly gameIndex: number;
+    private mouse: PointerLockControls;
+    private gamepad: (Gamepad | null) = null;
+    private readonly gamepadEuler ;
+    private clock: THREE.Clock;
+    private keys: { [key: string]: boolean } = {};
+    private leftMouseDown: boolean = false;
+    private rightMouseDown: boolean = false;
+    private renderer: Renderer;
+    private readonly localPlayer: Player;
+    private inputX: number = 0;
+    private inputZ: number = 0;
+    public  jump = false;
+    public prevInputVelocity: THREE.Vector3;
+    private scrollClicksSinceLastCheck: number = 0;
+    private readonly gamepadInputs: GamepadInputs;
+    private shoot: boolean = false;
+    private aim: boolean = false;
+    public nameSettingActive: boolean = false;
+    private touchJoyX: number = 0;
+    private touchJoyY: number = 0;
+    private touchLookX: number = 0;
+    private touchLookY: number = 0;
+    private inventoryIterationTouched: boolean = false;
+    private touchButtons: number[] = [];
+
+    constructor(renderer: Renderer, localPlayer: Player, nextGameIndex: number) {
+        this.renderer = renderer;
+        this.localPlayer = localPlayer;
+        this.prevInputVelocity = new THREE.Vector3();
+        this.gamepadEuler = new THREE.Euler(0, 0, 0, 'YXZ');
+
+        this.clock = new THREE.Clock();
+        this.mouse = new PointerLockControls(this.localPlayer, document.body);
+
+        this.gamepadInputs = new GamepadInputs();
+
+        this.gameIndex = nextGameIndex;
+
+        if(!navigator.getGamepads()) {
+            console.log("Browser does not support Gamepad API.")
+        }
+
+        this.setupEventListeners();
+    }
+
+    private setupEventListeners() {
+        document.addEventListener('keydown', this.onKeyDown.bind(this));
+        document.addEventListener('keyup', this.onKeyUp.bind(this));
+        document.addEventListener('mousedown', this.onMouseDown.bind(this));
+        document.addEventListener('mouseup', this.onMouseUp.bind(this));
+        document.addEventListener('mouseleave', this.onMouseUp.bind(this));
+        document.addEventListener('blur', this.deregisterAllKeys.bind(this), false);
+        document.addEventListener('pointerlockchange', this.deregisterAllKeys.bind(this), false);
+        document.addEventListener('visibilitychange', this.deregisterAllKeys.bind(this), false);
+
+        document.addEventListener('click', () => {
+            this.mouse.lock();
+        });
+
+        document.addEventListener('contextmenu', (event) => {event.preventDefault();});
+
+        document.addEventListener('wheel', this.processScroll.bind(this));
+    }
+
+    private processScroll(e :WheelEvent) {
+        if(e.deltaY >= 4)
+            this.scrollClicksSinceLastCheck++;
+        if(e.deltaY <= -4)
+            this.scrollClicksSinceLastCheck--;
+    }
+
+    public getScrollClicks() {
+        const clicks = this.scrollClicksSinceLastCheck;
+        this.scrollClicksSinceLastCheck = 0;
+        return clicks;
+
+    }
+
+    public handleInputs() {
+        const deltaTime: number = this.clock.getDelta();
+        const deltaTimeAcceleration = this.localPlayer.acceleration * deltaTime;
+
+        let dist = 1;
+        let speedMultiplier: number = 1;
+        this.jump = false;
+        this.aim = false;
+        this.shoot = false;
+
+        const oldInputZ = this.inputZ;
+        const oldInputX = this.inputX;
+
+        this.gamepad = navigator.getGamepads()[this.gameIndex];
+        if(this.gamepad) {
+            if(this.gamepad.connected) {
+                this.updateGamepadInputArray(this.gamepad);
+                this.gamepadEuler.setFromQuaternion(this.localPlayer.lookQuaternion);
+                if (Math.abs(this.gamepadInputs.leftJoyX) >= .1) this.inputX += deltaTimeAcceleration * this.gamepadInputs.leftJoyX;
+                if (Math.abs(this.gamepadInputs.leftJoyY) >= .1) this.inputZ += deltaTimeAcceleration * this.gamepadInputs.leftJoyY;
+                const vectorLength = Math.sqrt((this.gamepadInputs.leftJoyX * this.gamepadInputs.leftJoyX) + (this.gamepadInputs.leftJoyY * this.gamepadInputs.leftJoyY));
+                if (vectorLength >= .1) speedMultiplier = Math.min(Math.max(vectorLength, 0), 1);
+                if (this.gamepadInputs.A) this.jump = true;
+                if (this.gamepadInputs.leftTrigger > .5) this.aim = true;
+                if (this.gamepadInputs.rightTrigger > .5) this.shoot = true;
+                const aimAdjust = this.calculateAimAssist();
+                this.gamepadEuler.y -= this.gamepadInputs.rightJoyX * SettingsManager.settings.controllerSense * deltaTime * aimAdjust * 4;
+                this.gamepadEuler.x -= this.gamepadInputs.rightJoyY * SettingsManager.settings.controllerSense * deltaTime * aimAdjust * 4;
+                this.gamepadEuler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.gamepadEuler.x));
+                this.localPlayer.lookQuaternion.setFromEuler(this.gamepadEuler);
+            }
+        }
+
+        //touch joystick controls
+        if(Math.hypot(this.touchJoyY, this.touchJoyX) > 0.1) {
+            const touchVectorLength = Math.hypot(this.touchJoyX, this.touchJoyY);
+            speedMultiplier = Math.min(Math.max(touchVectorLength, 0), 1);
+            this.inputX += deltaTimeAcceleration * this.touchJoyX;
+            this.inputZ += deltaTimeAcceleration * this.touchJoyY;
+        }
+
+
+
+
+        const touchSensitivity = 0.03; // Adjust sensitivity as needed
+        this.gamepadEuler.setFromQuaternion(this.localPlayer.lookQuaternion);
+
+        if (!this.localPlayer.chatActive && !this.nameSettingActive) {
+            if (this.getKey('w')) this.inputZ -= deltaTimeAcceleration;
+            if (this.getKey('s')) this.inputZ += deltaTimeAcceleration;
+            if (this.getKey('a')) this.inputX -= deltaTimeAcceleration;
+            if (this.getKey('d')) this.inputX += deltaTimeAcceleration;
+            const aimAdjust = this.calculateAimAssist();
+            if (this.getKey('arrowright')) this.gamepadEuler.y -= SettingsManager.settings.controllerSense * deltaTime * aimAdjust * 4;
+            if (this.getKey('arrowleft')) this.gamepadEuler.y += SettingsManager.settings.controllerSense * deltaTime * aimAdjust * 4;
+            if (this.getKey('arrowup')) this.gamepadEuler.x += SettingsManager.settings.controllerSense * deltaTime * aimAdjust * 4;
+            if (this.getKey('arrowdown')) this.gamepadEuler.x -= SettingsManager.settings.controllerSense * deltaTime * aimAdjust * 4;
+            if (this.getKey(' ')) this.jump = true;
+
+        }
+
+        this.gamepadEuler.y -= this.touchLookX * touchSensitivity;
+        this.gamepadEuler.x -= this.touchLookY * touchSensitivity;
+        this.gamepadEuler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.gamepadEuler.x));
+        this.localPlayer.lookQuaternion.setFromEuler(this.gamepadEuler);
+
+        switch (this.inputZ - oldInputZ) {
+            case 0:
+                this.inputZ = InputHandler.approachZero(this.inputZ, deltaTimeAcceleration);
+        }
+
+        switch (this.inputX - oldInputX) {
+            case 0:
+                this.inputX = InputHandler.approachZero(this.inputX, deltaTimeAcceleration);
+        }
+
+        if(this.localPlayer.health <= 0) dist = 0; //don't allow movement when health = 0
+
+       this.prevInputVelocity.copy(this.localPlayer.inputVelocity);
+
+        this.localPlayer.inputVelocity.z = dist * this.inputZ;
+        this.localPlayer.inputVelocity.x = dist * this.inputX;
+        this.localPlayer.inputVelocity.y = 0;
+        this.localPlayer.inputVelocity.clampLength(0, this.localPlayer.speed * speedMultiplier);
+        this.localPlayer.inputVelocity.y = this.prevInputVelocity.y;
+        this.inputZ = this.localPlayer.inputVelocity.z;
+        this.inputX = this.localPlayer.inputVelocity.x;
+
+        const euler = new THREE.Euler().setFromQuaternion(this.localPlayer.lookQuaternion, 'YXZ');
+        euler.x = 0;
+        euler.z = 0;
+        this.localPlayer.quaternion.setFromEuler(euler);
+        this.localPlayer.inputVelocity.applyQuaternion(this.localPlayer.quaternion);
+
+        if (this.leftMouseDown || this.touchButtons.includes(0)) this.shoot = true;
+        if (this.rightMouseDown) this.aim = true;
+
+        if(this.localPlayer.playerSpectating !== -1) {
+            this.inputX = 0;
+            this.inputZ = 0;
+            this.jump = false;
+            this.shoot = false;
+            this.aim = false;
+        }
+
+    }
+
+    public getKey(key: string):boolean {
+        return this.keys[key];
+    }
+
+    public setTouchJoyInput(x: number, y: number) {
+        this.touchJoyX = x;
+        this.touchJoyY = y;
+    }
+
+    public setLastTouchLookDelta(x: number, y: number) {
+        this.touchLookX = x;
+        this.touchLookY = y;
+    }
+    public setButtonsHeld(buttons: number[]) {
+        this.touchButtons = buttons;
+        this.jump = this.jump || buttons.includes(-1);
+        this.inventoryIterationTouched = buttons.includes(1);
+
+    }
+
+    public getInventoryIterationTouched() {
+        return this.inventoryIterationTouched;
+    }
+
+    private onKeyDown(event: KeyboardEvent) {
+        //event.preventDefault();
+        if(event.key === 'Tab' || event.key === "'"|| event.key === '/') event.preventDefault();
+        const key = event.key.toLowerCase();
+        this.keys[key] = true;
+
+        if (!this.localPlayer.chatActive && !this.nameSettingActive) {
+            if (key === 'c') {
+                this.leftMouseDown = true;
+            } else if (key === 'z') {
+                this.rightMouseDown = true;
+            }
+        }
+    }
+
+    private onKeyUp(event: KeyboardEvent) {
+        const key = event.key.toLowerCase();
+        this.keys[key] = false;
+
+        if (!this.localPlayer.chatActive && !this.nameSettingActive) {
+            if (key === 'c') {
+                this.leftMouseDown = false;
+            } else if (key === 'z') {
+                this.rightMouseDown = false;
+            }
+        }
+    }
+
+    private onMouseDown(event: MouseEvent) {
+        if (event.button === 0) {
+            this.leftMouseDown = true;
+        } else if (event.button === 2) {
+            this.rightMouseDown = true;
+        }
+    }
+
+    private onMouseUp(event: MouseEvent) {
+        if (event.button === 0) {
+            this.leftMouseDown = false;
+        } else if (event.button === 2) {
+            this.rightMouseDown = false;
+        }
+    }
+
+    public getShoot() {
+        return this.shoot;
+    }
+
+    public getAim() {
+        return this.aim;
+    }
+
+    public getGamepadInputs(): GamepadInputs {
+        return this.gamepadInputs;
+    }
+
+    public deregisterAllKeys(){
+        const locked = document.pointerLockElement === document.body;
+        if(!locked)
+            this.keys = {};
+    }
+
+    public getInputX() {
+        return this.inputX;
+    }
+
+    public getInputZ() {
+        return this.inputZ;
+    }
+
+    private static approachZero(input: number, step: number): number {
+        if (input == 0) {return 0;}
+        let sign: number = 1;
+        if (input < 0) {sign = -1;}
+        const output: number = Math.abs(input) - step;
+        if (output <= 0) {return  0;}
+        return sign * output;
+    }
+
+    private updateGamepadInputArray(gamepad: Gamepad) {
+        if (gamepad.axes[4]) {
+            this.gamepadInputs.leftTrigger = gamepad.axes[4];
+            this.gamepadInputs.rightTrigger = gamepad.axes[5];
+        } else {
+            this.gamepadInputs.leftTrigger = gamepad.buttons[6].value;
+            this.gamepadInputs.rightTrigger = gamepad.buttons[7].value;
+        }
+        this.gamepadInputs.leftJoyX = gamepad.axes[0];
+        this.gamepadInputs.leftJoyY = gamepad.axes[1];
+        this.gamepadInputs.A = gamepad.buttons[0].pressed;
+        this.gamepadInputs.rightJoyX = gamepad.axes[2];
+        this.gamepadInputs.rightJoyY = gamepad.axes[3];
+        this.gamepadInputs.leftShoulder = gamepad.buttons[4].pressed
+        this.gamepadInputs.rightShoulder= gamepad.buttons[5].pressed
+    }
+
+    private calculateAimAssist(): number {
+        if(this.gamepad) {
+            if ((Math.abs(this.gamepadInputs.rightJoyX) >= .1 || Math.abs(this.gamepadInputs.rightJoyY) >= .1)) {
+                if (this.renderer.getPlayerSpheresInCrosshairWithWalls().length > 0) {
+                    return .5;
+                }
+            }
+        } else if (this.getKey('arrowup') || this.getKey('arrowdown') || this.getKey('arrowleft') || this.getKey('arrowright')) {
+            if (this.renderer.getPlayerSpheresInCrosshairWithWalls().length > 0) {
+                return .5;
+            }
+        }
+        return 1;
+    }
 }
 
 class GamepadInputs {
-	leftJoyX: number = 0;
-	leftJoyY: number = 0;
-	rightJoyX: number = 0;
-	rightJoyY: number = 0;
-	leftTrigger: number = 0;
-	rightTrigger: number = 0;
-	leftShoulder: boolean = false;
-	rightShoulder: boolean = false;
-	A: boolean = false;
-	B: boolean = false;
-	X: boolean = false;
-	Y: boolean = false;
+    leftJoyX: number = 0;
+    leftJoyY: number = 0;
+    rightJoyX: number = 0;
+    rightJoyY: number = 0;
+    leftTrigger: number= 0;
+    rightTrigger: number = 0;
+    leftShoulder: boolean = false;
+    rightShoulder: boolean = false;
+    A: boolean = false;
+    B: boolean = false;
+    X: boolean = false;
+    Y: boolean = false;
 }
diff --git a/src/client/input/PointerLockControl.ts b/src/client/input/PointerLockControl.ts
index b761c2bc..718ef384 100644
--- a/src/client/input/PointerLockControl.ts
+++ b/src/client/input/PointerLockControl.ts
@@ -1,92 +1,92 @@
 import * as THREE from 'three';
-import { Player } from '../core/Player.ts';
-import { SettingsManager } from '../core/SettingsManager.ts';
+import { Player } from "../core/Player.ts";
+import {SettingsManager} from "../core/SettingsManager.ts";
 
 // Define a custom event map interface
 interface PointerLockControlEventMap {
-	change: Event;
-	lock: Event;
-	unlock: Event;
+    change: Event;
+    lock: Event;
+    unlock: Event;
 }
 
 // Extend the EventDispatcher class to use our custom event map
 export class PointerLockControls extends THREE.EventDispatcher<PointerLockControlEventMap> {
-	public localPlayer: Player;
-	public domElement: Element;
-	public isLocked: boolean = false;
+    public localPlayer: Player;
+    public domElement: Element;
+    public isLocked: boolean = false;
 
-	constructor(localPlayer: Player, domElement: Element) {
-		super();
+    constructor(localPlayer: Player, domElement: Element) {
+        super();
 
-		if (domElement === undefined) {
-			console.warn('THREE.PointerLockControls: The second parameter "domElement" is now mandatory.');
-			domElement = document.body;
-		}
+        if (domElement === undefined) {
+            console.warn('THREE.PointerLockControls: The second parameter "domElement" is now mandatory.');
+            domElement = document.body;
+        }
 
-		this.localPlayer = localPlayer;
-		this.domElement = domElement;
+        this.localPlayer = localPlayer;
+        this.domElement = domElement;
 
-		this.connect();
-	}
+        this.connect();
+    }
 
-	public connect(): void {
-		document.addEventListener('mousemove', this.onMouseMove, false);
-		document.addEventListener('pointerlockchange', this.onPointerLockChange, false);
-		document.addEventListener('pointerlockerror', this.onPointerLockError, false);
-	}
+    public connect(): void {
+        document.addEventListener('mousemove', this.onMouseMove, false);
+        document.addEventListener('pointerlockchange', this.onPointerLockChange, false);
+        document.addEventListener('pointerlockerror', this.onPointerLockError, false);
+    }
 
-	public disconnect(): void {
-		document.removeEventListener('mousemove', this.onMouseMove, false);
-		document.removeEventListener('pointerlockchange', this.onPointerLockChange, false);
-		document.removeEventListener('pointerlockerror', this.onPointerLockError, false);
-	}
+    public disconnect(): void {
+        document.removeEventListener('mousemove', this.onMouseMove, false);
+        document.removeEventListener('pointerlockchange', this.onPointerLockChange, false);
+        document.removeEventListener('pointerlockerror', this.onPointerLockError, false);
+    }
 
-	public dispose(): void {
-		this.disconnect();
-	}
+    public dispose(): void {
+        this.disconnect();
+    }
 
-	public getObject(): Player {
-		return this.localPlayer;
-	}
+    public getObject(): Player {
+        return this.localPlayer;
+    }
 
-	public getDirection = (v: THREE.Vector3): THREE.Vector3 => {
-		return v.copy(new THREE.Vector3(0, 0, -1)).applyQuaternion(this.localPlayer.lookQuaternion);
-	};
+    public getDirection = (v: THREE.Vector3): THREE.Vector3 => {
+        return v.copy(new THREE.Vector3(0, 0, -1)).applyQuaternion(this.localPlayer.lookQuaternion);
+    };
 
-	public lock(): void {
-		this.domElement.requestPointerLock();
-	}
+    public lock(): void {
+        this.domElement.requestPointerLock();
+    }
 
-	public unlock(): void {
-		document.exitPointerLock();
-	}
+    public unlock(): void {
+        document.exitPointerLock();
+    }
 
-	private onMouseMove = (event: MouseEvent): void => {
-		if (!this.isLocked) return;
+    private onMouseMove = (event: MouseEvent): void => {
+        if (!this.isLocked) return;
 
-		// deno-lint-ignore no-explicit-any
-		const movementX = event.movementX || (event as any).mozMovementX || (event as any).webkitMovementX || 0;
-		// deno-lint-ignore no-explicit-any
-		const movementY = event.movementY || (event as any).mozMovementY || (event as any).webkitMovementY || 0;
+        // deno-lint-ignore no-explicit-any
+        const movementX = event.movementX || (event as any).mozMovementX || (event as any).webkitMovementX || 0;
+        // deno-lint-ignore no-explicit-any
+        const movementY = event.movementY || (event as any).mozMovementY || (event as any).webkitMovementY || 0;
 
-		const euler = new THREE.Euler(0, 0, 0, 'YXZ');
-		euler.setFromQuaternion(this.localPlayer.lookQuaternion);
+        const euler = new THREE.Euler(0, 0, 0, 'YXZ');
+        euler.setFromQuaternion(this.localPlayer.lookQuaternion);
 
-		euler.y -= movementX * SettingsManager.settings.sense * .002;
-		euler.x -= movementY * SettingsManager.settings.sense * .002;
+        euler.y -= movementX * SettingsManager.settings.sense * .002;
+        euler.x -= movementY * SettingsManager.settings.sense * .002;
 
-		euler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, euler.x));
+        euler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, euler.x));
 
-		this.localPlayer.lookQuaternion.setFromEuler(euler);
+        this.localPlayer.lookQuaternion.setFromEuler(euler);
 
-		// this.dispatchEvent({ type: 'change' });
-	};
+       // this.dispatchEvent({ type: 'change' });
+    };
 
-	private onPointerLockChange = (): void => {
-		this.isLocked = document.pointerLockElement === this.domElement;
-	};
+    private onPointerLockChange = (): void => {
+        this.isLocked = document.pointerLockElement === this.domElement;
+    };
 
-	private onPointerLockError = (): void => {
-		console.error('THREE.PointerLockControls: Unable to use Pointer Lock API');
-	};
+    private onPointerLockError = (): void => {
+        console.error('THREE.PointerLockControls: Unable to use Pointer Lock API');
+    };
 }
diff --git a/src/client/input/TouchInputHandler.ts b/src/client/input/TouchInputHandler.ts
index 36f1103c..ffb6bbf8 100644
--- a/src/client/input/TouchInputHandler.ts
+++ b/src/client/input/TouchInputHandler.ts
@@ -1,174 +1,190 @@
-import { InputHandler } from './InputHandler.ts';
-import { ChatOverlay } from '../ui/ChatOverlay.ts';
+import {InputHandler} from "./InputHandler.ts";
+import {ChatOverlay} from "../ui/ChatOverlay.ts";
 
 export class TouchInputHandler {
-	public static joystickRadius = 30;
-
-	private lastTouchTimestamp: number = 0;
-	private inputHandler: InputHandler;
-	private chatOverlay: ChatOverlay;
-
-	private joystickX: number = 0;
-	private joystickY: number = 0;
-
-	private joystickInputX: number = 0;
-	private joystickInputY: number = 0;
-
-	private joystickFingerId: number = -1;
-	private lookFingerId: number = -1;
-
-	private lastLookX: number = 0;
-	private lastLookY: number = 0;
-
-	private lastLookChangeX: number = 0;
-	private lastLookChangeY: number = 0;
-
-	private buttonsHeld: { button: number; fingerId: number }[] = [];
-
-	constructor(inputHandler: InputHandler, chatOverlay: ChatOverlay) {
-		this.inputHandler = inputHandler;
-		this.chatOverlay = chatOverlay;
-		this.setupEventListeners();
-	}
-
-	public onFrame() {
-		this.chatOverlay.setLastTouchTimestamp(this.lastTouchTimestamp);
-		this.chatOverlay.setTouchJoystickEngaged(this.joystickFingerId !== -1);
-		this.chatOverlay.setJoystickPosition(this.joystickX, this.joystickY);
-		this.chatOverlay.setJoystickInput(this.joystickInputX, this.joystickInputY);
-		this.chatOverlay.setButtonsHeld(this.buttonsHeld.map((button) => button.button));
-
-		this.inputHandler.setTouchJoyInput(this.joystickInputX, this.joystickInputY);
-		this.inputHandler.setLastTouchLookDelta(this.lastLookChangeX, this.lastLookChangeY);
-		this.inputHandler.setButtonsHeld(this.buttonsHeld.map((button) => button.button));
-		this.lastLookChangeX = 0;
-		this.lastLookChangeY = 0;
-		if (this.joystickFingerId == -1) {
-			this.joystickInputX = 0;
-			this.joystickInputY = 0;
-		}
-	}
-
-	private setupEventListeners() {
-		document.addEventListener('touchstart', this.onTouchStart.bind(this), false);
-		document.addEventListener('touchmove', this.onTouchMove.bind(this), false);
-		document.addEventListener('touchend', this.onTouchEnd.bind(this), false);
-	}
-
-	private onTouchStart(event: TouchEvent) {
-		this.lastTouchTimestamp = Date.now() / 1000;
-
-		for (let i = 0; i < event.touches.length; i++) {
-			const pixelRatio = this.getPixelRatio();
-			const touchX = event.touches[i].clientX * pixelRatio;
-			const touchY = event.touches[i].clientY * pixelRatio;
-			// console.log(touchX, touchY);
-
-			if (this.joystickFingerId == -1) {
-				if (touchX < globalThis.innerWidth * pixelRatio / 2.5) {
-					this.joystickFingerId = event.touches[i].identifier;
-					this.joystickX = touchX;
-					this.joystickY = touchY;
-					continue;
-				}
-			}
-			if (this.lookFingerId == -1) {
-				if (touchX > globalThis.innerWidth * pixelRatio / 2.5 && touchX < globalThis.innerWidth * pixelRatio - 30) {
-					this.lookFingerId = event.touches[i].identifier;
-					this.lastLookX = touchX;
-					this.lastLookY = touchY;
-					continue;
-				}
-			}
-		}
-		this.onTouchMove(event);
-	}
-
-	private onTouchMove(event: TouchEvent) {
-		this.lastTouchTimestamp = Date.now() / 1000;
-
-		for (let i = 0; i < event.touches.length; i++) {
-			const pixelRatio = this.getPixelRatio();
-			const touchX = event.touches[i].clientX * pixelRatio;
-			const touchY = event.touches[i].clientY * pixelRatio;
-
-			if (event.touches[i].identifier === this.joystickFingerId) {
-				this.joystickInputX = (touchX - this.joystickX) / TouchInputHandler.joystickRadius;
-				this.joystickInputY = (touchY - this.joystickY) / TouchInputHandler.joystickRadius;
-
-				const mag = Math.sqrt(this.joystickInputX * this.joystickInputX + this.joystickInputY * this.joystickInputY);
-				const dir = Math.atan2(this.joystickInputY, this.joystickInputX);
-				if (mag > 1) {
-					this.joystickInputX = Math.cos(dir);
-					this.joystickInputY = Math.sin(dir);
-				}
-				continue;
-			}
-
-			if (event.touches[i].identifier === this.lookFingerId) {
-				this.lastLookChangeX = touchX - this.lastLookX;
-				this.lastLookChangeY = touchY - this.lastLookY;
-
-				this.lastLookX = touchX;
-				this.lastLookY = touchY;
-				continue;
-			}
-
-			if (touchX > globalThis.innerWidth * pixelRatio - 30) {
-				const buttonClosestTo = Math.round((touchY - 100) / 30);
-
-				let found = false;
-				for (let j = 0; j < this.buttonsHeld.length; j++) {
-					if (this.buttonsHeld[j].button === buttonClosestTo) {
-						this.buttonsHeld[j].fingerId = event.touches[i].identifier;
-						found = true;
-					}
-				}
-				if (!found) {
-					this.buttonsHeld.push({ button: buttonClosestTo, fingerId: event.touches[i].identifier });
-				}
-			}
-		}
-	}
-
-	private onTouchEnd(event: TouchEvent) {
-		if (event.touches.length === 0) {
-			this.joystickFingerId = -1;
-			this.lookFingerId = -1;
-		}
-		for (let i = 0; i < event.changedTouches.length; i++) {
-			if (event.changedTouches[i].identifier === this.joystickFingerId) {
-				this.joystickFingerId = -1;
-			}
-			if (event.changedTouches[i].identifier === this.lookFingerId) {
-				this.lookFingerId = -1;
-			}
-
-			const touchY = event.changedTouches[i].clientY * this.getPixelRatio();
-			const buttonClosestTo = Math.round((touchY - 100) / 30);
-
-			// Iterate backwards to safely remove elements
-			for (let j = this.buttonsHeld.length - 1; j >= 0; j--) {
-				if (
-					this.buttonsHeld[j].fingerId === event.changedTouches[i].identifier ||
-					this.buttonsHeld[j].button === buttonClosestTo
-				) {
-					this.buttonsHeld.splice(j, 1);
-				}
-			}
-		}
-	}
-
-	public getButtonState(button: number): boolean {
-		for (let i = 0; i < this.buttonsHeld.length; i++) {
-			if (this.buttonsHeld[i].button === button && this.buttonsHeld[i].fingerId !== -1) {
-				return true;
-			}
-		}
-		return false;
-	}
-
-	private getPixelRatio(): number {
-		return 200 / globalThis.innerHeight;
-	}
+    public static joystickRadius = 30;
+
+    private lastTouchTimestamp: number = 0;
+    private inputHandler: InputHandler;
+    private chatOverlay: ChatOverlay;
+
+    private joystickX: number = 0;
+    private joystickY: number = 0;
+
+    private joystickInputX: number = 0;
+    private joystickInputY: number = 0;
+
+    private joystickFingerId: number = -1;
+    private lookFingerId: number = -1;
+
+    private lastLookX: number = 0;
+    private lastLookY: number = 0;
+
+    private lastLookChangeX: number = 0;
+    private lastLookChangeY: number = 0;
+
+    private buttonsHeld: {button:number, fingerId: number}[] = [];
+
+
+    constructor(inputHandler: InputHandler, chatOverlay: ChatOverlay) {
+        this.inputHandler = inputHandler;
+        this.chatOverlay = chatOverlay
+        this.setupEventListeners();
+
+
+    }
+
+    public onFrame() {
+        this.chatOverlay.setLastTouchTimestamp(this.lastTouchTimestamp);
+        this.chatOverlay.setTouchJoystickEngaged(this.joystickFingerId !== -1);
+        this.chatOverlay.setJoystickPosition(this.joystickX, this.joystickY);
+        this.chatOverlay.setJoystickInput(this.joystickInputX, this.joystickInputY);
+        this.chatOverlay.setButtonsHeld(this.buttonsHeld.map((button) => button.button));
+
+        this.inputHandler.setTouchJoyInput(this.joystickInputX, this.joystickInputY);
+        this.inputHandler.setLastTouchLookDelta(this.lastLookChangeX, this.lastLookChangeY);
+        this.inputHandler.setButtonsHeld(this.buttonsHeld.map((button) => button.button));
+        this.lastLookChangeX = 0; this.lastLookChangeY = 0;
+        if(this.joystickFingerId == -1) {
+            this.joystickInputX = 0;
+            this.joystickInputY = 0;
+        }
+    }
+
+    private setupEventListeners() {
+        document.addEventListener('touchstart', this.onTouchStart.bind(this), false);
+        document.addEventListener('touchmove', this.onTouchMove.bind(this), false);
+        document.addEventListener('touchend', this.onTouchEnd.bind(this), false);
+    }
+
+    private onTouchStart(event: TouchEvent) {
+        this.lastTouchTimestamp = Date.now()/1000;
+
+        for (let i = 0; i < event.touches.length; i++) {
+
+            const pixelRatio = this.getPixelRatio();
+            const touchX = event.touches[i].clientX * pixelRatio;
+            const touchY = event.touches[i].clientY * pixelRatio;
+           // console.log(touchX, touchY);
+
+            if(this.joystickFingerId == -1){
+                if (touchX < globalThis.innerWidth * pixelRatio / 2.5) {
+
+                    this.joystickFingerId = event.touches[i].identifier;
+                    this.joystickX = touchX;
+                    this.joystickY = touchY;
+                    continue;
+                }
+            }
+            if(this.lookFingerId == -1){
+                if (touchX > globalThis.innerWidth * pixelRatio / 2.5 && touchX < globalThis.innerWidth * pixelRatio - 30) {
+                    this.lookFingerId = event.touches[i].identifier;
+                    this.lastLookX = touchX;
+                    this.lastLookY = touchY;
+                    continue;
+                }
+            }
+
+
+        }
+        this.onTouchMove(event);
+    }
+
+    private onTouchMove(event: TouchEvent) {
+        this.lastTouchTimestamp = Date.now()/1000;
+
+        for (let i = 0; i < event.touches.length; i++) {
+
+            const pixelRatio = this.getPixelRatio();
+            const touchX = event.touches[i].clientX * pixelRatio;
+            const touchY = event.touches[i].clientY * pixelRatio;
+
+            if (event.touches[i].identifier === this.joystickFingerId) {
+                this.joystickInputX = (touchX - this.joystickX) / TouchInputHandler.joystickRadius;
+                this.joystickInputY = (touchY - this.joystickY) / TouchInputHandler.joystickRadius;
+
+                const mag = Math.sqrt(this.joystickInputX * this.joystickInputX + this.joystickInputY * this.joystickInputY);
+                const dir = Math.atan2(this.joystickInputY, this.joystickInputX);
+                if(mag > 1) {
+                    this.joystickInputX = Math.cos(dir);
+                    this.joystickInputY = Math.sin(dir);
+                }
+                continue;
+            }
+
+            if (event.touches[i].identifier === this.lookFingerId) {
+
+                this.lastLookChangeX = touchX - this.lastLookX;
+                this.lastLookChangeY = touchY - this.lastLookY;
+
+                this.lastLookX = touchX;
+                this.lastLookY = touchY;
+                continue;
+            }
+
+            if(touchX > globalThis.innerWidth * pixelRatio - 30) {
+                const buttonClosestTo = Math.round((touchY - 100) / 30);
+
+                let found = false;
+                for(let j = 0; j < this.buttonsHeld.length; j++)
+                    if(this.buttonsHeld[j].button === buttonClosestTo){
+                        this.buttonsHeld[j].fingerId = event.touches[i].identifier;
+                        found = true;
+                    }
+                if(!found)
+                    this.buttonsHeld.push({button: buttonClosestTo, fingerId: event.touches[i].identifier});
+
+
+
+            }
+
+
+
+
+        }
+
+    }
+
+    private onTouchEnd(event: TouchEvent) {
+        if(event.touches.length === 0) {
+            this.joystickFingerId = -1;
+            this.lookFingerId = -1;
+        }
+        for (let i = 0; i < event.changedTouches.length; i++) {
+            if (event.changedTouches[i].identifier === this.joystickFingerId) {
+                this.joystickFingerId = -1;
+            }
+            if (event.changedTouches[i].identifier === this.lookFingerId) {
+                this.lookFingerId = -1;
+            }
+
+            const touchY = event.changedTouches[i].clientY * this.getPixelRatio();
+            const buttonClosestTo = Math.round((touchY - 100) / 30);
+
+            // Iterate backwards to safely remove elements
+            for (let j = this.buttonsHeld.length - 1; j >= 0; j--) {
+                if (this.buttonsHeld[j].fingerId === event.changedTouches[i].identifier || this.buttonsHeld[j].button === buttonClosestTo) {
+                    this.buttonsHeld.splice(j, 1);
+                }
+            }
+        }
+
+
+    }
+
+    public getButtonState(button: number): boolean {
+        for(let i = 0; i < this.buttonsHeld.length; i++)
+            if(this.buttonsHeld[i].button === button && this.buttonsHeld[i].fingerId !== -1)
+                return true;
+        return false;
+
+    }
+
+    private getPixelRatio(): number {
+        return 200 / globalThis.innerHeight;
+    }
+
+
+
+
 }
diff --git a/src/client/items/BananaGun.ts b/src/client/items/BananaGun.ts
index 81a54ada..9fba619e 100644
--- a/src/client/items/BananaGun.ts
+++ b/src/client/items/BananaGun.ts
@@ -1,9 +1,9 @@
-import { ItemBase, ItemType } from './ItemBase.ts';
-import { HeldItemInput } from '../input/HeldItemInput.ts';
+import {ItemBase, ItemType} from './ItemBase.ts';
+import {HeldItemInput} from '../input/HeldItemInput.ts';
 import * as THREE from 'three';
-import { Renderer } from '../core/Renderer.ts';
-import { Networking } from '../core/Networking.ts';
-import { AssetManager } from '../core/AssetManager.ts';
+import {Renderer} from '../core/Renderer.ts';
+import {Networking} from '../core/Networking.ts';
+import { AssetManager } from "../core/AssetManager.ts";
 
 const firingDelay = 0.12;
 const firingDelayHeld = 0.225; //longer firing delay when mouse is held down
@@ -16,194 +16,196 @@ const scopedQuaternion = new THREE.Quaternion(0.64, 0.22, -0.69, -0.22);
 const inventoryQuaternionBase = new THREE.Quaternion(0, 0, 0, 1);
 
 export class BananaGun extends ItemBase {
-	private renderer!: Renderer;
-	private networking!: Networking;
-	private lastInput: HeldItemInput;
-	private lastFired: number;
-	private addedToHandScene: boolean;
-
-	// deno-lint-ignore constructor-super
-	constructor(renderer: Renderer, networking: Networking, index: number, itemType: ItemType) {
-		if (itemType === ItemType.WorldItem) {
-			super(itemType, renderer.getEntityScene(), renderer.getInventoryMenuScene(), index);
-		} else {
-			super(itemType, renderer.getHeldItemScene(), renderer.getInventoryMenuScene(), index);
-		}
-		this.renderer = renderer;
-		this.networking = networking;
-		this.lastInput = new HeldItemInput();
-		this.addedToHandScene = false;
-		this.lastFired = 0;
-	}
-
-	public override init() {
-		AssetManager.getInstance().loadAsset('models/simplified_banana_1.glb', (scene) => {
-			this.object = scene;
-			if (this.itemType === ItemType.InventoryItem) {
-				this.object.traverse((child) => {
-					if ((child as THREE.Mesh).isMesh) {
-						child.renderOrder = 999;
-						const mesh = child as THREE.Mesh;
-						if (Array.isArray(mesh.material)) {
-							mesh.material.forEach((mat) => mat.depthTest = false);
-						} else {
-							mesh.material.depthTest = false;
-						}
-					}
-				});
-			}
-
-			if (this.itemType === ItemType.WorldItem) {
-				this.object.scale.set(0.66, 0.66, 0.66);
-			}
-
-			this.inventoryMenuObject = this.object.clone();
-			this.inventoryMenuObject.scale.set(0.8, 0.8, 0.8);
-
-			if (this.itemType === ItemType.WorldItem) {
-				this.object.scale.set(0.45, 0.45, 0.45);
-			}
-		});
-	}
-
-	public override onFrame(input: HeldItemInput, selectedIndex: number) {
-		if (!this.object) return;
-		const deltaTime = this.clock.getDelta();
-		this.timeAccum += deltaTime;
-		this.angleAccum += deltaTime;
-
-		if (this.itemType === ItemType.WorldItem) {
-			this.worldOnFrame(deltaTime);
-		} else if (this.itemType === ItemType.InventoryItem) {
-			this.inventoryOnFrame(deltaTime, selectedIndex);
-			this.handOnFrame(deltaTime, input);
-		}
-	}
-
-	// No need to override worldOnFrame if default behavior is sufficient
-	// If specific behavior is needed, you can override it here
-
-	public override inventoryOnFrame(deltaTime: number, selectedIndex: number) {
-		if (!this.addedToInventoryItemScenes) {
-			this.inventoryMenuScene.add(this.inventoryMenuObject);
-			this.addedToInventoryItemScenes = true;
-		}
-
-		this.angleAccum += deltaTime;
-		this.inventoryMenuObject.position.set(0, this.index, 0);
-
-		const targetQuaternion = inventoryQuaternionBase.clone();
-		if (this.index === selectedIndex) {
-			rotateAroundWorldAxis(targetQuaternion, new THREE.Vector3(0, 1, 0), this.angleAccum * 4);
-			this.showInHand();
-		} else {
-			this.hideInHand();
-		}
-		this.inventoryMenuObject.quaternion.slerp(targetQuaternion, 0.1 * 60 * deltaTime);
-	}
-
-	public override handOnFrame(deltaTime: number, input: HeldItemInput) {
-		if (!this.object) return;
-
-		if (this.shownInHand && !this.addedToHandScene) {
-			this.scene.add(this.object);
-			this.addedToHandScene = true;
-		}
-
-		if (this.shownInHand && Date.now() / 1000 - this.shownInHandTimestamp > showInHandDelay) {
-			this.handleInput(input, deltaTime);
-		} else {
-			this.handPosition.lerp(hiddenPosition, 0.1 * 60 * deltaTime);
-			this.object.position.copy(this.handPosition);
-			// Remove the object after it has slid out of view
-			if (this.handPosition.distanceTo(hiddenPosition) < 0.1) {
-				if (this.addedToHandScene) {
-					this.scene.remove(this.object);
-					this.addedToHandScene = false;
-				}
-			}
-		}
-
-		// Update crosshair flashing based on last shot timestamp
-		this.renderer.crosshairIsFlashing = Date.now() / 1000 - this.renderer.lastShotSomeoneTimestamp < 0.05;
-	}
-
-	private handleInput(input: HeldItemInput, deltaTime: number) {
-		if (input.rightClick) {
-			moveTowardsPos(this.handPosition, scopedPosition, 0.3 * deltaTime * 60);
-		} else {
-			moveTowardsPos(this.handPosition, unscopedPosition, 0.1 * deltaTime * 60);
-		}
-
-		this.object.position.copy(this.handPosition);
-
-		moveTowardsRot(this.object.quaternion, scopedQuaternion, 0.1 * deltaTime * 60);
-
-		if (input.leftClick && (!this.lastInput.leftClick || Date.now() / 1000 - this.lastFired > firingDelayHeld)) {
-			if (Date.now() / 1000 - this.lastFired > firingDelay) {
-				this.lastFired = Date.now() / 1000;
-				this.shootBanana();
-				this.handPosition.add(new THREE.Vector3(0, 0, 0.6));
-				rotateAroundWorldAxis(this.object.quaternion, new THREE.Vector3(1, 0, 0), Math.PI / 16);
-			}
-		}
-
-		this.lastInput = input;
-	}
-
-	public override showInHand() {
-		if (this.shownInHand) return;
-		this.shownInHand = true;
-		this.shownInHandTimestamp = Date.now() / 1000;
-		if (!this.addedToHandScene && this.object) {
-			this.scene.add(this.object);
-			this.addedToHandScene = true;
-		}
-	}
-
-	public override hideInHand() {
-		if (!this.shownInHand) return;
-		this.shownInHand = false;
-	}
-	public itemDepleted(): boolean {
-		return false;
-	}
-
-	private shootBanana() {
-		const processShots = () => {
-			const shotVectors = this.renderer.getShotVectorsToPlayersInCrosshair();
-			if (shotVectors.length > 0) {
-				for (const shot of shotVectors) {
-					const { playerID, hitPoint } = shot;
-					this.networking.applyDamage(playerID, 17);
-					this.renderer.playerHitMarkers.push({ hitPoint: hitPoint, shotVector: shot.vector, timestamp: -1 });
-				}
-				this.renderer.lastShotSomeoneTimestamp = Date.now() / 1000;
-			}
-		};
-
-		if (typeof requestIdleCallback === 'function') {
-			requestIdleCallback(processShots, { timeout: 150 });
-		} else {
-			setTimeout(processShots, 0);
-		}
-	}
-
-	// Method to set world position when used as WorldItem
-	public override setWorldPosition(vector: THREE.Vector3) {
-		super.setWorldPosition(vector);
-	}
+    private renderer!: Renderer;
+    private networking!: Networking;
+    private lastInput: HeldItemInput;
+    private lastFired: number;
+    private addedToHandScene: boolean;
+
+    // deno-lint-ignore constructor-super
+    constructor(renderer: Renderer, networking: Networking, index: number, itemType: ItemType) {
+        if(itemType === ItemType.WorldItem)
+            super(itemType, renderer.getEntityScene(), renderer.getInventoryMenuScene(), index);
+        else
+            super(itemType, renderer.getHeldItemScene(), renderer.getInventoryMenuScene(), index);
+        this.renderer = renderer;
+        this.networking = networking;
+        this.lastInput = new HeldItemInput();
+        this.addedToHandScene = false;
+        this.lastFired = 0;
+
+
+    }
+
+    public override init() {
+        AssetManager.getInstance().loadAsset('models/simplified_banana_1.glb', (scene) => {
+            this.object = scene;
+            if (this.itemType === ItemType.InventoryItem) {
+                this.object.traverse((child) => {
+                    if ((child as THREE.Mesh).isMesh) {
+                        child.renderOrder = 999;
+                        const mesh = child as THREE.Mesh;
+                        if (Array.isArray(mesh.material)) {
+                            mesh.material.forEach(mat => mat.depthTest = false);
+                        } else {
+                            mesh.material.depthTest = false;
+                        }
+                    }
+                });
+            }
+
+            if(this.itemType === ItemType.WorldItem)
+                this.object.scale.set(0.66, 0.66, 0.66);
+
+            this.inventoryMenuObject = this.object.clone();
+            this.inventoryMenuObject.scale.set(0.8, 0.8, 0.8);
+
+            if(this.itemType === ItemType.WorldItem)
+                this.object.scale.set(0.45, 0.45, 0.45);
+        });
+    }
+
+
+    public override onFrame(input: HeldItemInput, selectedIndex: number) {
+        if (!this.object) return;
+        const deltaTime = this.clock.getDelta();
+        this.timeAccum += deltaTime;
+        this.angleAccum += deltaTime;
+
+        if (this.itemType === ItemType.WorldItem) {
+            this.worldOnFrame(deltaTime);
+        } else if (this.itemType === ItemType.InventoryItem) {
+            this.inventoryOnFrame(deltaTime, selectedIndex);
+            this.handOnFrame(deltaTime, input);
+        }
+    }
+
+    // No need to override worldOnFrame if default behavior is sufficient
+    // If specific behavior is needed, you can override it here
+
+    public override inventoryOnFrame(deltaTime: number, selectedIndex: number) {
+        if (!this.addedToInventoryItemScenes) {
+            this.inventoryMenuScene.add(this.inventoryMenuObject);
+            this.addedToInventoryItemScenes = true;
+        }
+
+        this.angleAccum += deltaTime;
+        this.inventoryMenuObject.position.set(0, this.index, 0);
+
+        const targetQuaternion = inventoryQuaternionBase.clone();
+        if (this.index === selectedIndex) {
+            rotateAroundWorldAxis(targetQuaternion, new THREE.Vector3(0, 1, 0), this.angleAccum * 4);
+            this.showInHand();
+        } else {
+            this.hideInHand();
+        }
+        this.inventoryMenuObject.quaternion.slerp(targetQuaternion, 0.1 * 60 * deltaTime);
+    }
+
+    public override handOnFrame(deltaTime: number, input: HeldItemInput) {
+        if (!this.object) return;
+
+        if (this.shownInHand && !this.addedToHandScene) {
+            this.scene.add(this.object);
+            this.addedToHandScene = true;
+        }
+
+        if (this.shownInHand && Date.now() / 1000 - this.shownInHandTimestamp > showInHandDelay) {
+            this.handleInput(input, deltaTime);
+        } else {
+            this.handPosition.lerp(hiddenPosition, 0.1 * 60 * deltaTime);
+            this.object.position.copy(this.handPosition);
+            // Remove the object after it has slid out of view
+            if (this.handPosition.distanceTo(hiddenPosition) < 0.1) {
+                if (this.addedToHandScene) {
+                    this.scene.remove(this.object);
+                    this.addedToHandScene = false;
+                }
+            }
+        }
+
+        // Update crosshair flashing based on last shot timestamp
+        this.renderer.crosshairIsFlashing = Date.now() / 1000 - this.renderer.lastShotSomeoneTimestamp < 0.05;
+    }
+
+    private handleInput(input: HeldItemInput, deltaTime: number) {
+        if (input.rightClick)
+            moveTowardsPos(this.handPosition, scopedPosition, 0.3 * deltaTime * 60);
+        else
+            moveTowardsPos(this.handPosition, unscopedPosition, 0.1 * deltaTime * 60);
+
+        this.object.position.copy(this.handPosition);
+
+        moveTowardsRot(this.object.quaternion, scopedQuaternion, 0.1 * deltaTime * 60);
+
+        if (input.leftClick && (!this.lastInput.leftClick || Date.now() / 1000 - this.lastFired > firingDelayHeld)) {
+            if (Date.now() / 1000 - this.lastFired > firingDelay) {
+                this.lastFired = Date.now() / 1000;
+                this.shootBanana();
+                this.handPosition.add(new THREE.Vector3(0, 0, 0.6));
+                rotateAroundWorldAxis(this.object.quaternion, new THREE.Vector3(1, 0, 0), Math.PI / 16);
+            }
+        }
+
+        this.lastInput = input;
+    }
+
+    public override showInHand() {
+        if (this.shownInHand) return;
+        this.shownInHand = true;
+        this.shownInHandTimestamp = Date.now() / 1000;
+        if (!this.addedToHandScene && this.object) {
+            this.scene.add(this.object);
+            this.addedToHandScene = true;
+        }
+    }
+
+    public override hideInHand() {
+        if (!this.shownInHand) return;
+        this.shownInHand = false;
+    }
+    public itemDepleted(): boolean {
+        return false;
+    }
+
+    private shootBanana() {
+        const processShots = () => {
+            const shotVectors = this.renderer.getShotVectorsToPlayersInCrosshair();
+            if (shotVectors.length > 0) {
+                for (const shot of shotVectors) {
+                    const { playerID, hitPoint } = shot;
+                    this.networking.applyDamage(playerID, 17);
+                    this.renderer.playerHitMarkers.push({hitPoint: hitPoint, shotVector: shot.vector, timestamp: -1});
+                }
+                this.renderer.lastShotSomeoneTimestamp = Date.now() / 1000;
+            }
+        };
+
+        if (typeof requestIdleCallback === 'function') {
+            requestIdleCallback(processShots, { timeout: 150 });
+        } else {
+            setTimeout(processShots, 0);
+        }
+    }
+
+
+
+
+    // Method to set world position when used as WorldItem
+    public override setWorldPosition(vector: THREE.Vector3) {
+        super.setWorldPosition(vector);
+    }
 }
 
 function rotateAroundWorldAxis(source: THREE.Quaternion, axis: THREE.Vector3, angle: number) {
-	const rotationQuat = new THREE.Quaternion().setFromAxisAngle(axis, angle);
-	source.multiplyQuaternions(rotationQuat, source);
+    const rotationQuat = new THREE.Quaternion().setFromAxisAngle(axis, angle);
+    source.multiplyQuaternions(rotationQuat, source);
 }
 
 function moveTowardsPos(source: THREE.Vector3, target: THREE.Vector3, frac: number) {
-	source.lerp(target, frac);
+    source.lerp(target, frac);
 }
 
 function moveTowardsRot(source: THREE.Quaternion, target: THREE.Quaternion, frac: number) {
-	source.slerp(target, frac);
+    source.slerp(target, frac);
 }
diff --git a/src/client/items/FishGun.ts b/src/client/items/FishGun.ts
index 42d5ab23..010bc07b 100644
--- a/src/client/items/FishGun.ts
+++ b/src/client/items/FishGun.ts
@@ -1,10 +1,10 @@
-import { ItemBase, ItemType } from './ItemBase.ts';
-import { HeldItemInput } from '../input/HeldItemInput.ts';
+import {ItemBase, ItemType} from './ItemBase.ts';
+import {HeldItemInput} from '../input/HeldItemInput.ts';
 
 import * as THREE from 'three';
-import { Renderer } from '../core/Renderer.ts';
-import { Networking } from '../core/Networking.ts';
-import { AssetManager } from '../core/AssetManager.ts';
+import {Renderer} from '../core/Renderer.ts';
+import {Networking} from '../core/Networking.ts';
+import { AssetManager } from "../core/AssetManager.ts";
 
 const firingDelay = 0.45;
 const firingDelayHeld = 0.45; //longer firing delay when mouse is held down
@@ -13,235 +13,241 @@ const showInHandDelay = 0.1;
 const scopedPosition = new THREE.Vector3(0, -0.6, 3.5);
 const unscopedPosition = new THREE.Vector3(0.75, -0.9, 3.2);
 const hiddenPosition = new THREE.Vector3(0.85, -2.7, 3.2);
-const scopedQuaternion = new THREE.Quaternion(0, 0.707, 0, 0.707);
+const scopedQuaternion = new THREE.Quaternion(0,0.707,0,0.707);
 const inventoryQuaternionBase = new THREE.Quaternion(0, 0, 0, 1);
 
 export class FishGun extends ItemBase {
-	private renderer!: Renderer;
-	private networking!: Networking;
-	private lastInput: HeldItemInput;
-	private lastFired: number;
-	private addedToHandScene: boolean;
-
-	// deno-lint-ignore constructor-super
-	constructor(renderer: Renderer, networking: Networking, index: number, itemType: ItemType) {
-		if (itemType === ItemType.WorldItem) {
-			super(itemType, renderer.getEntityScene(), renderer.getInventoryMenuScene(), index);
-		} else {
-			super(itemType, renderer.getHeldItemScene(), renderer.getInventoryMenuScene(), index);
-		}
-		this.renderer = renderer;
-		this.networking = networking;
-		this.lastInput = new HeldItemInput();
-		this.addedToHandScene = false;
-		this.lastFired = 0;
-	}
-
-	public override init() {
-		AssetManager.getInstance().loadAsset('models/simplified_fish.glb', (scene) => {
-			this.object = scene;
-			if (this.itemType === ItemType.InventoryItem) {
-				this.object.traverse((child) => {
-					if ((child as THREE.Mesh).isMesh) {
-						child.renderOrder = 999;
-						const mesh = child as THREE.Mesh;
-						if (Array.isArray(mesh.material)) {
-							mesh.material.forEach((mat) => mat.depthTest = false);
-						} else {
-							mesh.material.depthTest = false;
-						}
-					}
-				});
-				if (this.itemType === ItemType.InventoryItem) {
-					this.object.scale.set(1.5, 1.5, 1.5);
-				}
-			}
-
-			this.inventoryMenuObject = this.object.clone();
-			this.inventoryMenuObject.scale.set(0.8, 0.8, 0.8);
-
-			if (this.itemType === ItemType.WorldItem) {
-				this.object.scale.set(0.45, 0.45, 0.45);
-			}
-		});
-	}
-
-	public override onFrame(input: HeldItemInput, selectedIndex: number) {
-		if (!this.object) return;
-		const deltaTime = this.clock.getDelta();
-		this.timeAccum += deltaTime;
-		this.angleAccum += deltaTime;
-
-		if (this.itemType === ItemType.WorldItem) {
-			this.worldOnFrame(deltaTime);
-		} else if (this.itemType === ItemType.InventoryItem) {
-			this.inventoryOnFrame(deltaTime, selectedIndex);
-			this.handOnFrame(deltaTime, input);
-		}
-	}
-
-	// No need to override worldOnFrame if default behavior is sufficient
-	// If specific behavior is needed, you can override it here
-
-	public override inventoryOnFrame(deltaTime: number, selectedIndex: number) {
-		if (!this.addedToInventoryItemScenes) {
-			this.inventoryMenuScene.add(this.inventoryMenuObject);
-			this.addedToInventoryItemScenes = true;
-		}
-
-		this.angleAccum += deltaTime;
-		this.inventoryMenuObject.position.set(0, this.index, 0);
-
-		const targetQuaternion = inventoryQuaternionBase.clone();
-		if (this.index === selectedIndex) {
-			rotateAroundWorldAxis(targetQuaternion, new THREE.Vector3(0, 1, 0), this.angleAccum * 4);
-			this.showInHand();
-		} else {
-			this.hideInHand();
-		}
-		this.inventoryMenuObject.quaternion.slerp(targetQuaternion, 0.1 * 60 * deltaTime);
-	}
-
-	public override handOnFrame(deltaTime: number, input: HeldItemInput) {
-		if (!this.object) return;
-
-		if (this.shownInHand && !this.addedToHandScene) {
-			this.scene.add(this.object);
-			this.addedToHandScene = true;
-		}
-
-		if (this.shownInHand && Date.now() / 1000 - this.shownInHandTimestamp > showInHandDelay) {
-			this.handleInput(input, deltaTime);
-		} else {
-			this.handPosition.lerp(hiddenPosition, 0.1 * 60 * deltaTime);
-			this.object.position.copy(this.handPosition);
-			// Remove the object after it has slid out of view
-			if (this.handPosition.distanceTo(hiddenPosition) < 0.1) {
-				if (this.addedToHandScene) {
-					this.scene.remove(this.object);
-					this.addedToHandScene = false;
-				}
-			}
-		}
-
-		// Update crosshair flashing based on last shot timestamp
-		this.renderer.crosshairIsFlashing = Date.now() / 1000 - this.renderer.lastShotSomeoneTimestamp < 0.05;
-	}
-
-	private handleInput(input: HeldItemInput, deltaTime: number) {
-		if (input.rightClick) {
-			moveTowardsPos(this.handPosition, scopedPosition, 0.3 * deltaTime * 60);
-		} else {
-			moveTowardsPos(this.handPosition, unscopedPosition, 0.1 * deltaTime * 60);
-		}
-
-		this.object.position.copy(this.handPosition);
-
-		moveTowardsRot(this.object.quaternion, scopedQuaternion, 0.1 * deltaTime * 60);
-
-		if (input.leftClick && (!this.lastInput.leftClick || Date.now() / 1000 - this.lastFired > firingDelayHeld)) {
-			if (Date.now() / 1000 - this.lastFired > firingDelay) {
-				this.lastFired = Date.now() / 1000;
-				this.shootFish();
-				this.handPosition.add(new THREE.Vector3(0, 0, 2));
-				rotateAroundWorldAxis(this.object.quaternion, new THREE.Vector3(1, 0, 0), Math.PI / 16);
-			}
-		}
-
-		this.lastInput = input;
-	}
-
-	public override showInHand() {
-		if (this.shownInHand) return;
-		this.shownInHand = true;
-		this.shownInHandTimestamp = Date.now() / 1000;
-		if (!this.addedToHandScene && this.object) {
-			this.scene.add(this.object);
-			this.addedToHandScene = true;
-		}
-	}
-
-	public override hideInHand() {
-		if (!this.shownInHand) return;
-		this.shownInHand = false;
-	}
-	public itemDepleted(): boolean {
-		return false;
-	}
-
-	private shootFish() {
-		const totalShots = 25;
-		let processedShots = 0;
-		const TIMEOUT = 150;
-
-		const processShots = (deadline?: IdleDeadline) => {
-			const timeRemaining = deadline ? deadline.timeRemaining() : 16;
-
-			while (processedShots < totalShots && timeRemaining > 0) {
-				const shotVectors = this.renderer.getShotVectorsToPlayersWithOffset(
-					(Math.random() - 0.5) * 0.30,
-					(Math.random() - 0.5) * 0.30,
-				);
-				if (shotVectors.length > 0) {
-					for (const shot of shotVectors) {
-						const { playerID, hitPoint } = shot;
-						this.networking.applyDamage(playerID, 3);
-						this.renderer.playerHitMarkers.push({
-							hitPoint: hitPoint,
-							shotVector: shot.vector,
-							timestamp: -1,
-						});
-					}
-					this.renderer.lastShotSomeoneTimestamp = Date.now() / 1000;
-				}
-				processedShots++;
-			}
-
-			// If we still have shots to process, schedule the next batch
-			if (processedShots < totalShots) {
-				if (typeof requestIdleCallback === 'function') {
-					const idleCallbackId = requestIdleCallback(processShots, { timeout: TIMEOUT });
-
-					// Ensure completion within timeout
-					setTimeout(() => {
-						cancelIdleCallback(idleCallbackId);
-						processShots();
-					}, TIMEOUT);
-				} else {
-					setTimeout(() => processShots(), 0);
-				}
-			}
-		};
-
-		// Initial call
-		if (typeof requestIdleCallback === 'function') {
-			const idleCallbackId = requestIdleCallback(processShots, { timeout: TIMEOUT });
-
-			// Ensure first batch starts within timeout
-			setTimeout(() => {
-				cancelIdleCallback(idleCallbackId);
-				processShots();
-			}, TIMEOUT);
-		} else {
-			setTimeout(() => processShots(), 0);
-		}
-	}
-
-	// Method to set world position when used as WorldItem
-	public override setWorldPosition(vector: THREE.Vector3) {
-		super.setWorldPosition(vector);
-	}
+    private renderer!: Renderer;
+    private networking!: Networking;
+    private lastInput: HeldItemInput;
+    private lastFired: number;
+    private addedToHandScene: boolean;
+
+    // deno-lint-ignore constructor-super
+    constructor(renderer: Renderer, networking: Networking, index: number, itemType: ItemType) {
+        if(itemType === ItemType.WorldItem)
+            super(itemType, renderer.getEntityScene(), renderer.getInventoryMenuScene(), index);
+        else
+            super(itemType, renderer.getHeldItemScene(), renderer.getInventoryMenuScene(), index);
+        this.renderer = renderer;
+        this.networking = networking;
+        this.lastInput = new HeldItemInput();
+        this.addedToHandScene = false;
+        this.lastFired = 0;
+    }
+
+    public override init() {
+        AssetManager.getInstance().loadAsset('models/simplified_fish.glb', (scene) => {
+            this.object = scene;
+            if (this.itemType === ItemType.InventoryItem) {
+                this.object.traverse((child) => {
+                    if ((child as THREE.Mesh).isMesh) {
+                        child.renderOrder = 999;
+                        const mesh = child as THREE.Mesh;
+                        if (Array.isArray(mesh.material)) {
+                            mesh.material.forEach(mat => mat.depthTest = false);
+                        } else {
+                            mesh.material.depthTest = false;
+                        }
+                    }
+                });
+                if(this.itemType === ItemType.InventoryItem)
+                    this.object.scale.set(1.5, 1.5, 1.5);
+            }
+
+            this.inventoryMenuObject = this.object.clone();
+            this.inventoryMenuObject.scale.set(0.8, 0.8, 0.8);
+
+            if(this.itemType === ItemType.WorldItem)
+                this.object.scale.set(0.45, 0.45, 0.45);
+        });
+    }
+
+
+
+    public override onFrame(input: HeldItemInput, selectedIndex: number) {
+        if (!this.object) return;
+        const deltaTime = this.clock.getDelta();
+        this.timeAccum += deltaTime;
+        this.angleAccum += deltaTime;
+
+        if (this.itemType === ItemType.WorldItem) {
+            this.worldOnFrame(deltaTime);
+        } else if (this.itemType === ItemType.InventoryItem) {
+            this.inventoryOnFrame(deltaTime, selectedIndex);
+            this.handOnFrame(deltaTime, input);
+        }
+    }
+
+    // No need to override worldOnFrame if default behavior is sufficient
+    // If specific behavior is needed, you can override it here
+
+    public override inventoryOnFrame(deltaTime: number, selectedIndex: number) {
+        if (!this.addedToInventoryItemScenes) {
+            this.inventoryMenuScene.add(this.inventoryMenuObject);
+            this.addedToInventoryItemScenes = true;
+        }
+
+        this.angleAccum += deltaTime;
+        this.inventoryMenuObject.position.set(0, this.index, 0);
+
+        const targetQuaternion = inventoryQuaternionBase.clone();
+        if (this.index === selectedIndex) {
+            rotateAroundWorldAxis(targetQuaternion, new THREE.Vector3(0, 1, 0), this.angleAccum * 4);
+            this.showInHand();
+        } else {
+            this.hideInHand();
+        }
+        this.inventoryMenuObject.quaternion.slerp(targetQuaternion, 0.1 * 60 * deltaTime);
+    }
+
+    public override handOnFrame(deltaTime: number, input: HeldItemInput) {
+        if (!this.object) return;
+
+        if (this.shownInHand && !this.addedToHandScene) {
+            this.scene.add(this.object);
+            this.addedToHandScene = true;
+        }
+
+        if (this.shownInHand && Date.now() / 1000 - this.shownInHandTimestamp > showInHandDelay) {
+            this.handleInput(input, deltaTime);
+        } else {
+            this.handPosition.lerp(hiddenPosition, 0.1 * 60 * deltaTime);
+            this.object.position.copy(this.handPosition);
+            // Remove the object after it has slid out of view
+            if (this.handPosition.distanceTo(hiddenPosition) < 0.1) {
+                if (this.addedToHandScene) {
+                    this.scene.remove(this.object);
+                    this.addedToHandScene = false;
+                }
+            }
+        }
+
+        // Update crosshair flashing based on last shot timestamp
+        this.renderer.crosshairIsFlashing = Date.now() / 1000 - this.renderer.lastShotSomeoneTimestamp < 0.05;
+    }
+
+    private handleInput(input: HeldItemInput, deltaTime: number) {
+        if (input.rightClick)
+            moveTowardsPos(this.handPosition, scopedPosition, 0.3 * deltaTime * 60);
+        else
+            moveTowardsPos(this.handPosition, unscopedPosition, 0.1 * deltaTime * 60);
+
+        this.object.position.copy(this.handPosition);
+
+        moveTowardsRot(this.object.quaternion, scopedQuaternion, 0.1 * deltaTime * 60);
+
+        if (input.leftClick && (!this.lastInput.leftClick || Date.now() / 1000 - this.lastFired > firingDelayHeld)) {
+            if (Date.now() / 1000 - this.lastFired > firingDelay) {
+                this.lastFired = Date.now() / 1000;
+                this.shootFish();
+                this.handPosition.add(new THREE.Vector3(0, 0, 2));
+                rotateAroundWorldAxis(this.object.quaternion, new THREE.Vector3(1, 0, 0), Math.PI / 16);
+            }
+        }
+
+        this.lastInput = input;
+    }
+
+    public override showInHand() {
+        if (this.shownInHand) return;
+        this.shownInHand = true;
+        this.shownInHandTimestamp = Date.now() / 1000;
+        if (!this.addedToHandScene && this.object) {
+            this.scene.add(this.object);
+            this.addedToHandScene = true;
+        }
+    }
+
+    public override hideInHand() {
+        if (!this.shownInHand) return;
+        this.shownInHand = false;
+    }
+    public itemDepleted(): boolean {
+        return false;
+    }
+
+    private shootFish() {
+        const totalShots = 25;
+        let processedShots = 0;
+        const TIMEOUT = 150;
+
+        const processShots = (deadline?: IdleDeadline) => {
+            const timeRemaining = deadline ? deadline.timeRemaining() : 16;
+
+            while (processedShots < totalShots && timeRemaining > 0) {
+                const shotVectors = this.renderer.getShotVectorsToPlayersWithOffset(
+                    (Math.random() - 0.5) * 0.30,
+                    (Math.random() - 0.5) * 0.30
+                );
+                if (shotVectors.length > 0) {
+                    for (const shot of shotVectors) {
+                        const { playerID, hitPoint } = shot;
+                        this.networking.applyDamage(playerID, 3);
+                        this.renderer.playerHitMarkers.push({
+                            hitPoint: hitPoint,
+                            shotVector: shot.vector,
+                            timestamp: -1,
+                        });
+                    }
+                    this.renderer.lastShotSomeoneTimestamp = Date.now() / 1000;
+                }
+                processedShots++;
+            }
+
+            // If we still have shots to process, schedule the next batch
+            if (processedShots < totalShots) {
+                if (typeof requestIdleCallback === 'function') {
+                    const idleCallbackId = requestIdleCallback(processShots, { timeout: TIMEOUT });
+
+                    // Ensure completion within timeout
+                    setTimeout(() => {
+                        cancelIdleCallback(idleCallbackId);
+                        processShots();
+                    }, TIMEOUT);
+                } else {
+                    setTimeout(() => processShots(), 0);
+                }
+            }
+        };
+
+        // Initial call
+        if (typeof requestIdleCallback === 'function') {
+            const idleCallbackId = requestIdleCallback(processShots, { timeout: TIMEOUT });
+
+            // Ensure first batch starts within timeout
+            setTimeout(() => {
+                cancelIdleCallback(idleCallbackId);
+                processShots();
+            }, TIMEOUT);
+        } else {
+            setTimeout(() => processShots(), 0);
+        }
+    }
+
+
+
+
+
+
+
+
+
+    // Method to set world position when used as WorldItem
+    public override setWorldPosition(vector: THREE.Vector3) {
+        super.setWorldPosition(vector);
+    }
 }
 
 function rotateAroundWorldAxis(source: THREE.Quaternion, axis: THREE.Vector3, angle: number) {
-	const rotationQuat = new THREE.Quaternion().setFromAxisAngle(axis, angle);
-	source.multiplyQuaternions(rotationQuat, source);
+    const rotationQuat = new THREE.Quaternion().setFromAxisAngle(axis, angle);
+    source.multiplyQuaternions(rotationQuat, source);
 }
 
 function moveTowardsPos(source: THREE.Vector3, target: THREE.Vector3, frac: number) {
-	source.lerp(target, frac);
+    source.lerp(target, frac);
 }
 
 function moveTowardsRot(source: THREE.Quaternion, target: THREE.Quaternion, frac: number) {
-	source.slerp(target, frac);
+    source.slerp(target, frac);
 }
diff --git a/src/client/items/ItemBase.ts b/src/client/items/ItemBase.ts
index 54f00704..49ff6b0b 100644
--- a/src/client/items/ItemBase.ts
+++ b/src/client/items/ItemBase.ts
@@ -4,152 +4,158 @@ import * as THREE from 'three';
 const showInHandDelay = 0.1;
 
 export class ItemBase {
-	protected timeAccum: number = 0;
-	protected clock: THREE.Clock = new THREE.Clock();
-
-	protected object!: THREE.Object3D;
-	protected itemType: ItemType;
-
-	protected scene: THREE.Scene; // The scene to put the item in
-
-	protected inventoryMenuScene: THREE.Scene; //Inventory menu scene
-	protected inventoryMenuObject!: THREE.Object3D; //The object shown in the inventory menu (he do spin)
-	protected index: number; //The index of the item in the inventory
-	protected shownInHand: boolean = false;
-	protected angleAccum: number = 0;
-	protected handPosition: THREE.Vector3 = new THREE.Vector3(0.85, -0.8, 3.2);
-	protected shownInHandTimestamp: number = 0;
-
-	constructor(itemType: ItemType, scene: THREE.Scene, inventoryMenuScene: THREE.Scene, index: number) {
-		this.itemType = itemType;
-		this.scene = scene;
-		this.inventoryMenuScene = inventoryMenuScene;
-		this.index = index;
-
-		this.init();
-	}
-
-	protected init() {
-		// Init should be responsible for creating object and inventoryMenuObject
-		// For this class, we'll just create a simple cube
-		const geometry = new THREE.BoxGeometry(0.5, 0.5, 0.5);
-		const material = new THREE.MeshBasicMaterial({ color: 0x00ff00 });
-		this.object = new THREE.Mesh(geometry, material);
-		this.inventoryMenuObject = this.object.clone();
-
-		if (this.itemType === ItemType.InventoryItem) {
-			this.object.traverse((child) => {
-				if ((child as THREE.Mesh).isMesh) {
-					child.renderOrder = 999;
-					const applyDepthTest = (material: THREE.Material | THREE.Material[]) => {
-						if (Array.isArray(material)) {
-							material.forEach((mat) => applyDepthTest(mat)); // Recursively handle array elements
-						} else {
-							material.depthTest = false;
-						}
-					};
-					const mesh = child as THREE.Mesh;
-					applyDepthTest(mesh.material);
-				}
-			});
-		}
-	}
-
-	onFrame(input: HeldItemInput | undefined, selectedIndex: number | undefined) {
-		if (!this.object) return; //return if object hasn't loaded
-		const deltaTime = this.clock.getDelta();
-		this.timeAccum += deltaTime;
-
-		if (this.itemType === ItemType.WorldItem) {
-			this.worldOnFrame(deltaTime);
-		}
-		if (this.itemType === ItemType.InventoryItem && selectedIndex !== undefined && input !== undefined) {
-			this.inventoryOnFrame(deltaTime, selectedIndex);
-			this.handOnFrame(deltaTime, input);
-		}
-	}
-
-	/** -- World Items -- */
-	protected addedToWorldScene: boolean = false;
-	protected worldPosition: THREE.Vector3 = new THREE.Vector3();
-
-	protected worldOnFrame(deltaTime: number) { // This function is called every frame for world items
-		if (!this.addedToWorldScene) {
-			this.scene.add(this.object);
-			this.addedToWorldScene = true;
-		}
-		this.object.position.copy(this.worldPosition);
-		this.object.position.add(new THREE.Vector3(0, Math.sin(this.timeAccum * 2) * 0.1, 0));
-		this.object.rotation.y += deltaTime * 2;
-	}
-
-	setWorldPosition(vector: THREE.Vector3) {
-		this.worldPosition = vector;
-	}
-
-	/** -- Inventory Items -- */
-	protected addedToInventoryItemScenes: boolean = false;
-
-	protected inventoryOnFrame(deltaTime: number, selectedIndex: number) {
-		if (!this.addedToInventoryItemScenes) {
-			this.scene.add(this.object);
-			this.inventoryMenuScene.add(this.inventoryMenuObject);
-		}
-		this.angleAccum += deltaTime;
-		this.inventoryMenuObject.position.set(0, this.index, 0);
-
-		const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(0, 0, 0));
-		if (this.index === selectedIndex) {
-			rotateAroundWorldAxis(targetQuaternion, new THREE.Vector3(0, 1, 0), this.angleAccum * 6);
-			this.showInHand();
-		} else {
-			this.hideInHand();
-		}
-		rotateAroundWorldAxis(targetQuaternion, new THREE.Vector3(1, 0, 0), Math.PI / 4);
-		this.inventoryMenuObject.quaternion.slerp(targetQuaternion, 0.1 * 60 * deltaTime);
-	}
-
-	protected handOnFrame(deltaTime: number, input: HeldItemInput) {
-		if (this.shownInHand && Date.now() / 1000 - this.shownInHandTimestamp > showInHandDelay) {
-			this.handPosition.lerp(heldPosition, 0.1 * 60 * deltaTime);
-		} else {
-			this.handPosition.lerp(hiddenPosition, 0.1 * 60 * deltaTime);
-		}
-
-		this.object.position.copy(this.handPosition);
-		if (this.shownInHand && input.leftClick) {
-			this.object.position.add(new THREE.Vector3(Math.random() * 0.2, Math.random() * 0.2, Math.random() * 0.2));
-			this.object.quaternion.slerp(new THREE.Quaternion().random(), 0.1);
-		}
-	}
-
-	protected showInHand() {
-		if (this.shownInHand) return;
-		this.shownInHand = true;
-		this.shownInHandTimestamp = Date.now() / 1000;
-	}
-
-	protected hideInHand() {
-		if (!this.shownInHand) return;
-		this.shownInHand = false;
-	}
-
-	public destroy() {
-		this.scene.remove(this.object);
-		this.inventoryMenuScene.remove(this.inventoryMenuObject);
-	}
+    protected timeAccum:number = 0;
+    protected clock:THREE.Clock = new THREE.Clock();
+
+    protected object!: THREE.Object3D;
+    protected itemType: ItemType;
+
+    protected scene: THREE.Scene; // The scene to put the item in
+
+    protected inventoryMenuScene: THREE.Scene; //Inventory menu scene
+    protected inventoryMenuObject!:THREE.Object3D; //The object shown in the inventory menu (he do spin)
+    protected index:number; //The index of the item in the inventory
+    protected shownInHand:boolean = false;
+    protected angleAccum: number = 0;
+    protected handPosition:THREE.Vector3 = new THREE.Vector3(0.85, -0.8, 3.2);
+    protected shownInHandTimestamp:number = 0;
+
+
+    constructor(itemType:ItemType, scene:THREE.Scene, inventoryMenuScene:THREE.Scene, index:number){
+        this.itemType = itemType;
+        this.scene = scene;
+        this.inventoryMenuScene = inventoryMenuScene;
+        this.index = index;
+
+        this.init();
+    }
+
+    protected init() {
+        // Init should be responsible for creating object and inventoryMenuObject
+        // For this class, we'll just create a simple cube
+        const geometry = new THREE.BoxGeometry(0.5,0.5,0.5);
+        const material = new THREE.MeshBasicMaterial({color: 0x00ff00});
+        this.object = new THREE.Mesh(geometry, material);
+        this.inventoryMenuObject = this.object.clone();
+
+        if(this.itemType === ItemType.InventoryItem)
+            this.object.traverse((child) => {
+                if ((child as THREE.Mesh).isMesh) {
+                    child.renderOrder = 999;
+                    const applyDepthTest = (material: THREE.Material | THREE.Material[]) => {
+                        if (Array.isArray(material))
+                            material.forEach((mat) => applyDepthTest(mat));  // Recursively handle array elements
+                        else
+                            material.depthTest = false;
+                    };
+                    const mesh = child as THREE.Mesh;
+                    applyDepthTest(mesh.material);
+                }
+            });
+    }
+
+    onFrame(input: HeldItemInput | undefined, selectedIndex: number | undefined) {
+        if(!this.object) return; //return if object hasn't loaded
+        const deltaTime = this.clock.getDelta();
+        this.timeAccum += deltaTime;
+
+        if(this.itemType === ItemType.WorldItem)
+            this.worldOnFrame(deltaTime);
+        if(this.itemType === ItemType.InventoryItem && selectedIndex !== undefined && input !== undefined){
+            this.inventoryOnFrame(deltaTime, selectedIndex);
+            this.handOnFrame(deltaTime, input);
+        }
+    }
+
+
+    /** -- World Items -- */
+    protected addedToWorldScene:boolean = false;
+    protected worldPosition:THREE.Vector3 = new THREE.Vector3();
+
+
+    protected worldOnFrame(deltaTime:number){ // This function is called every frame for world items
+        if(!this.addedToWorldScene){
+            this.scene.add(this.object);
+            this.addedToWorldScene = true;
+
+        }
+        this.object.position.copy(this.worldPosition);
+        this.object.position.add(new THREE.Vector3(0, Math.sin(this.timeAccum*2) * 0.1, 0));
+        this.object.rotation.y += deltaTime * 2;
+
+    }
+
+    setWorldPosition(vector:THREE.Vector3){
+        this.worldPosition = vector;
+    }
+
+    /** -- Inventory Items -- */
+    protected addedToInventoryItemScenes:boolean = false;
+
+    protected inventoryOnFrame(deltaTime:number, selectedIndex:number){
+        if(!this.addedToInventoryItemScenes){
+            this.scene.add(this.object);
+            this.inventoryMenuScene.add(this.inventoryMenuObject);
+        }
+        this.angleAccum+=deltaTime;
+        this.inventoryMenuObject.position.set(0, this.index, 0);
+
+        const targetQuaternion = new THREE.Quaternion().setFromEuler(new THREE.Euler(0,0,0));
+        if(this.index === selectedIndex){
+            rotateAroundWorldAxis(targetQuaternion, new THREE.Vector3(0,1,0), this.angleAccum * 6);
+            this.showInHand();
+        }else{
+            this.hideInHand();
+        }
+        rotateAroundWorldAxis(targetQuaternion, new THREE.Vector3(1,0,0), Math.PI / 4);
+        this.inventoryMenuObject.quaternion.slerp(targetQuaternion, 0.1 * 60 * deltaTime);
+    }
+
+    protected handOnFrame(deltaTime:number, input:HeldItemInput){
+        if(this.shownInHand && Date.now() / 1000 - this.shownInHandTimestamp > showInHandDelay)
+            this.handPosition.lerp(heldPosition, 0.1 * 60 *deltaTime);
+        else
+            this.handPosition.lerp(hiddenPosition, 0.1 * 60 *deltaTime);
+
+        this.object.position.copy(this.handPosition);
+        if(this.shownInHand && input.leftClick){
+            this.object.position.add(new THREE.Vector3(Math.random()*0.2, Math.random()*0.2, Math.random()*0.2));
+            this.object.quaternion.slerp(new THREE.Quaternion().random(),0.1);
+        }
+    }
+
+    protected showInHand(){
+        if(this.shownInHand) return;
+        this.shownInHand = true;
+        this.shownInHandTimestamp = Date.now() / 1000;
+
+    }
+
+    protected hideInHand(){
+        if(!this.shownInHand) return;
+        this.shownInHand = false;
+    }
+
+
+    public destroy(){
+        this.scene.remove(this.object);
+        this.inventoryMenuScene.remove(this.inventoryMenuObject);
+    }
+
+
+
 }
 const heldPosition = new THREE.Vector3(0.85, -0.8, 3.2);
 const hiddenPosition = new THREE.Vector3(0.85, -2.5, 3.2);
 export enum ItemType {
-	//TODO diagnose lint being weird here?
-	// eslint-disable-next-line no-unused-vars
-	WorldItem = 1,
-	// eslint-disable-next-line no-unused-vars
-	InventoryItem = 2,
+    //TODO diagnose lint being weird here?
+    // eslint-disable-next-line no-unused-vars
+    WorldItem = 1,
+    // eslint-disable-next-line no-unused-vars
+    InventoryItem = 2,
 }
 
 function rotateAroundWorldAxis(source: THREE.Quaternion, axis: THREE.Vector3, angle: number) {
-	const rotationQuat = new THREE.Quaternion().setFromAxisAngle(axis, angle);
-	source.multiplyQuaternions(rotationQuat, source);
-}
+    const rotationQuat = new THREE.Quaternion().setFromAxisAngle(axis, angle);
+    source.multiplyQuaternions(rotationQuat, source);
+}
\ No newline at end of file
diff --git a/src/client/main.ts b/src/client/main.ts
index 164a464e..65f1a9ed 100644
--- a/src/client/main.ts
+++ b/src/client/main.ts
@@ -1,4 +1,4 @@
 // import {Game} from "./core/Game.ts";
 //
 // const game = new Game();
-// game.start();
+// game.start();
\ No newline at end of file
diff --git a/src/client/ui/ChatOverlay.ts b/src/client/ui/ChatOverlay.ts
index 560c4644..d9c00925 100644
--- a/src/client/ui/ChatOverlay.ts
+++ b/src/client/ui/ChatOverlay.ts
@@ -1,948 +1,901 @@
-import { Player } from '../core/Player.ts';
-import { Renderer } from '../core/Renderer.ts';
-import { Networking } from '../core/Networking.ts';
-import { InputHandler } from '../input/InputHandler.ts';
-import { CommandManager } from '../core/CommandManager.ts';
-import { SettingsManager } from '../core/SettingsManager.ts';
-import { TouchInputHandler } from '../input/TouchInputHandler.ts';
+import {Player} from '../core/Player.ts';
+import {Renderer} from '../core/Renderer.ts';
+import {Networking} from '../core/Networking.ts';
+import {InputHandler} from '../input/InputHandler.ts';
+import {CommandManager} from "../core/CommandManager.ts";
+import {SettingsManager} from "../core/SettingsManager.ts";
+import {TouchInputHandler} from "../input/TouchInputHandler.ts";
 
 interface ChatMessage {
-	id: number;
-	message: string;
-	name: string;
-	timestamp: number;
+    id: number;
+    message: string;
+    name: string;
+    timestamp: number;
 }
 
 interface AnimatedGameMessage {
-	id: string; // Unique identifier
-	message: string;
-	state: 'animatingIn' | 'animatingOut' | 'idle';
-	animationProgress: number; // Ranges from 0 to 1
-	timestamp: number; // Time when the current animation state started
+    id: string; // Unique identifier
+    message: string;
+    state: 'animatingIn' | 'animatingOut' | 'idle';
+    animationProgress: number; // Ranges from 0 to 1
+    timestamp: number; // Time when the current animation state started
 }
 
 interface LineMessage {
-	currentMessage: AnimatedGameMessage | null;
-	pendingMessage: string | null;
+    currentMessage: AnimatedGameMessage | null;
+    pendingMessage: string | null;
 }
 
 const hitMarkerLifetime = 0.3;
 
 export class ChatOverlay {
-	private chatCanvas: HTMLCanvasElement;
-	private chatCtx: CanvasRenderingContext2D;
-	private chatMessages: ChatMessage[]; // Typed as ChatMessage[]
-	private chatMessageLifespan: number;
-	private charsToRemovePerSecond: number;
-	private maxMessagesOnScreen: number;
-	private nameSettingActive: boolean;
-	private localPlayer: Player;
-	private renderer!: Renderer;
-	private networking!: Networking;
-	private screenWidth: number;
-	private inputHandler!: InputHandler;
-	private debugTextHeight!: number;
-	private oldScreenWidth: number = 0;
-	private readonly commandManager: CommandManager;
-	private lastTouchTimestamp: number = 0;
-	private touchJoystickEngaged: boolean = false;
-	private joystickX: number = 0;
-	private joystickY: number = 0;
-	private joystickInputX: number = 0;
-	private joystickInputY: number = 0;
-	private buttonsHeld: number[] = [];
-	private lastRoutineMs = 0;
-
-	private offscreenCanvas: HTMLCanvasElement;
-	private offscreenCtx: CanvasRenderingContext2D;
-
-	public gameMessages: string[] = [];
-	private previousGameMessages: string[] = [];
-
-	// Removed animatedGameMessages in favor of per-line management
-	private lines: LineMessage[] = [];
-	private animationDuration: number = 1; // Adjusted for smoother animation
-
-	// Color code mapping
-	COLOR_CODES: { [key: string]: string } = {
-		'0': '#000000', // Black
-		'1': '#0000AA', // Dark Blue
-		'2': '#00AA00', // Dark Green
-		'3': '#00AAAA', // Dark Aqua
-		'4': '#AA0000', // Dark Red
-		'5': '#AA00AA', // Dark Purple
-		'6': '#FFAA00', // Gold
-		'7': '#AAAAAA', // Gray
-		'8': '#555555', // Dark Gray
-		'9': '#5555FF', // Blue
-		'a': '#55FF55', // Green
-		'b': '#55FFFF', // Aqua
-		'c': '#FF5555', // Red
-		'd': '#FF55FF', // Light Purple
-		'e': '#FFFF55', // Yellow
-		'f': '#FFFFFF', // White
-		'g': this.getRainbowColor(),
-	};
-
-	private getColorCode(code: string): string | false {
-		if (code === 'g') {
-			return this.getRainbowColor();
-		}
-		return this.COLOR_CODES[code] || false;
-	}
-
-	private getRainbowColor(): string {
-		const hue = (Date.now() / 20) % 360;
-		return `hsl(${hue}, 100%, 50%)`;
-	}
-
-	constructor(container: HTMLElement, localPlayer: Player) {
-		this.localPlayer = localPlayer;
-		this.chatCanvas = document.createElement('canvas');
-		this.chatCtx = this.chatCanvas.getContext('2d') as CanvasRenderingContext2D;
-		this.chatCtx.imageSmoothingEnabled = false;
-
-		this.chatCanvas.width = 400;
-		this.chatCanvas.height = 200;
-
-		this.chatMessages = [];
-		this.chatMessageLifespan = 40; // 40 seconds
-		this.charsToRemovePerSecond = 30;
-		this.maxMessagesOnScreen = 12;
-
-		this.nameSettingActive = false;
-		this.screenWidth = 100;
-
-		this.commandManager = new CommandManager(this.localPlayer, this);
-
-		this.setupEventListeners();
-
-		this.chatCanvas.style.position = 'absolute';
-		this.chatCanvas.style.display = 'block';
-		this.chatCanvas.style.zIndex = '100';
-		this.chatCanvas.style.top = '0';
-		this.chatCanvas.style.left = '0';
-
-		this.chatCanvas.style.height = '100vh';
-		document.body.style.margin = '0';
-		this.chatCanvas.style.imageRendering = 'pixelated';
-		this.chatCanvas.style.textRendering = 'pixelated';
-
-		this.chatCanvas.style.touchAction = 'none';
-
-		this.offscreenCanvas = document.createElement('canvas');
-		this.offscreenCtx = this.offscreenCanvas.getContext('2d') as CanvasRenderingContext2D;
-
-		// Initialize lines for per-line message management
-		this.lines = Array(this.maxMessagesOnScreen).fill(null).map(() => ({
-			currentMessage: null,
-			pendingMessage: null,
-		}));
-
-		//document.body.appendChild(this.chatCanvas);
-		container.appendChild(this.chatCanvas);
-
-		globalThis.addEventListener('resize', this.onWindowResize.bind(this));
-		globalThis.addEventListener('orientationchange', this.onWindowResize.bind(this));
-	}
-
-	public setRenderer(renderer: Renderer) {
-		this.renderer = renderer;
-	}
-
-	public setNetworking(networking: Networking) {
-		this.networking = networking;
-	}
-
-	public setInputHandler(inputHandler: InputHandler) {
-		this.inputHandler = inputHandler;
-	}
-
-	private setupEventListeners() {
-		document.addEventListener('keydown', this.onKeyDown.bind(this));
-	}
-
-	public onFrame() {
-		const startTime = Date.now();
-		const now = Date.now() / 1000;
-
-		this.gameMessages = this.localPlayer.gameMsgs;
-		this.detectGameMessagesChanges(now);
-		this.updateAnimatedGameMessages(now);
-
-		this.clearOldMessages();
-		this.chatCtx.clearRect(0, 0, this.chatCanvas.width, this.chatCanvas.height);
-		this.renderHitMarkers();
-		this.renderChatMessages();
-		this.renderGameText();
-		this.renderDebugText();
-		if (this.inputHandler.getKey('tab')) {
-			this.renderPlayerList();
-		}
-		this.renderEvil();
-		this.renderCrosshair();
-		this.renderTouchControls();
-
-		this.screenWidth = Math.floor(this.renderer.getCamera().aspect * 200);
-
-		if (this.oldScreenWidth !== this.screenWidth) {
-			//if(this.chatCanvas.width < this.screenWidth)
-			this.chatCanvas.width = this.screenWidth;
-			this.oldScreenWidth = this.screenWidth;
-		}
-
-		// this.chatCanvas.width = this.screenWidth;
-		// this.chatCtx.fillRect(0,0,10,10);
-
-		this.onWindowResize();
-
-		this.inputHandler.nameSettingActive = this.nameSettingActive;
-		if (Math.random() < 0.03) {
-			this.lastRoutineMs = Date.now() - startTime;
-		}
-	}
-
-	private onWindowResize() {
-		this.chatCanvas.style.width = globalThis.innerWidth + 'px';
-		this.chatCanvas.style.height = globalThis.innerHeight + 'px';
-	}
-
-	private renderChatMessages() {
-		const ctx = this.chatCtx;
-
-		this.offscreenCtx.font = '8px Tiny5';
-		this.offscreenCtx.fillStyle = 'white';
-
-		const usermsg = this.localPlayer.chatMsg;
-		let cursor = '';
-		if ((Date.now() / 1000) % 0.7 < 0.35) cursor = '|';
-
-		const linesToRender: string[] = [];
-		const pixOffsets: number[] = [];
-		const messagesBeingTyped = this.networking.getMessagesBeingTyped();
-
-		for (let i = 0; i < this.chatMessages.length; i++) {
-			let msg = this.chatMessages[i].message;
-			const name = this.chatMessages[i].name;
-			if (name.length > 0) msg = `${name}: ${msg}`;
-
-			const duplicateFromPlayerData = messagesBeingTyped.includes(msg);
-
-			let charsToRemove = Date.now() / 1000 - this.chatMessages[i].timestamp - this.chatMessageLifespan;
-			charsToRemove = Math.max(0, charsToRemove * this.charsToRemovePerSecond);
-			charsToRemove = Math.floor(charsToRemove);
-
-			let removedSubstring = '';
-			let remainingMsg = msg;
-			if (charsToRemove > 0) {
-				let charsRemoved = 0;
-				while (charsRemoved < charsToRemove && remainingMsg.length > 0) {
-					const char = remainingMsg.charAt(0);
-					removedSubstring += char;
-					remainingMsg = remainingMsg.substring(1);
-					charsRemoved++;
-				}
-			}
-
-			if (!duplicateFromPlayerData) {
-				linesToRender.push(remainingMsg);
-				pixOffsets.push(this.offscreenCtx.measureText(removedSubstring).width);
-			}
-		}
-
-		for (const msg of messagesBeingTyped) {
-			linesToRender.push(msg + cursor);
-			pixOffsets.push(0);
-		}
-
-		if (this.localPlayer.chatActive) {
-			if (this.localPlayer.chatMsg.startsWith('>')) {
-				linesToRender.push('&2' + usermsg + cursor);
-			} else {
-				linesToRender.push(usermsg + cursor);
-			}
-			pixOffsets.push(0);
-		}
-
-		if (this.nameSettingActive) {
-			linesToRender.push('Enter your name: ' + usermsg + cursor);
-			pixOffsets.push(0);
-			this.localPlayer.name = usermsg + cursor;
-			if (this.localPlayer.name.length == 0) this.localPlayer.name = ' ';
-		}
-
-		const wrappedLines: string[] = [];
-		const lineOrigins: number[] = [];
-		const isFirstWrappedLine: boolean[] = [];
-
-		for (let i = 0; i < linesToRender.length; i++) {
-			const wrapped = this.doTextWrapping(this.offscreenCtx, [linesToRender[i]], this.screenWidth - 10);
-			for (let j = 0; j < wrapped.length; j++) {
-				wrappedLines.push(wrapped[j]);
-				lineOrigins.push(i);
-				isFirstWrappedLine.push(j === 0);
-			}
-		}
-
-		const totalLines = wrappedLines.length;
-		for (let i = 0; i < totalLines; i++) {
-			const lineIndex = totalLines - i - 1;
-			const text = wrappedLines[lineIndex];
-			const originIndex = lineOrigins[lineIndex];
-			const pixOffset = isFirstWrappedLine[lineIndex] ? pixOffsets[originIndex] : 0;
-
-			this.renderPixelText(text, 3 + pixOffset, 200 - 20 - 8 * i, 'white');
-		}
-
-		if ((usermsg !== '' && this.localPlayer.chatActive) || this.nameSettingActive) {
-			ctx.fillStyle = 'rgba(145,142,118,0.3)';
-			let width = ctx.measureText(usermsg).width;
-			if (this.nameSettingActive) {
-				width = ctx.measureText('Enter your name: ' + usermsg).width;
-			}
-			ctx.fillRect(2, 200 - 20 - 7, width + 1, 9);
-		}
-	}
-
-	private renderPrettyText(text: string, x: number, y: number, defaultColor: string) {
-		let currentX = x;
-		const segments: { text: string; color: string }[] = [];
-		let currentColor = defaultColor;
-		let currentSegment = '';
-
-		// Parse color codes and split into segments
-		for (let i = 0; i < text.length; i++) {
-			if (text[i] === '&' && i + 1 < text.length && this.getColorCode(text[i + 1])) {
-				if (currentSegment) {
-					segments.push({ text: currentSegment, color: currentColor });
-				}
-				currentColor = <string> this.getColorCode(text[i + 1]);
-				currentSegment = '';
-				i++; // Skip the color code character
-			} else {
-				currentSegment += text[i];
-			}
-		}
-
-		if (currentSegment) {
-			segments.push({ text: currentSegment, color: currentColor });
-		}
-
-		// Render each segment
-		for (const segment of segments) {
-			this.offscreenCtx.font = '8px Tiny5';
-			const textMetrics = this.offscreenCtx.measureText(segment.text);
-			const textWidth = Math.max(Math.ceil(textMetrics.width), 1);
-			const textHeight = 8;
-
-			if (this.offscreenCanvas.width !== textWidth || this.offscreenCanvas.height !== textHeight) {
-				this.offscreenCanvas.width = textWidth;
-				this.offscreenCanvas.height = textHeight;
-			}
-
-			this.offscreenCtx.clearRect(0, 0, textWidth, textHeight);
-			this.offscreenCtx.font = '8px Tiny5';
-			this.offscreenCtx.fillStyle = segment.color;
-			this.offscreenCtx.fillText(segment.text, 0, textHeight - 1);
-
-			const imageData = this.offscreenCtx.getImageData(0, 0, textWidth, textHeight);
-			const data = imageData.data;
-
-			for (let i = 0; i < data.length; i += 4) {
-				data[i + 3] = data[i + 3] > 170 ? 255 : 0;
-			}
-
-			this.offscreenCtx.putImageData(imageData, 0, 0);
-			this.chatCtx.drawImage(this.offscreenCanvas, currentX, y - textHeight + 1);
-			currentX += textWidth;
-		}
-	}
-
-	private renderUglyText(text: string, x: number, y: number, defaultColor: string) {
-		let currentX = x;
-		let currentColor = defaultColor;
-		let currentSegment = '';
-
-		for (let i = 0; i < text.length; i++) {
-			if (text[i] === '&' && i + 1 < text.length && this.getColorCode(text[i + 1])) {
-				if (currentSegment) {
-					this.chatCtx.font = '8px Tiny5';
-					this.chatCtx.fillStyle = currentColor;
-					this.chatCtx.fillText(currentSegment, currentX, y);
-					currentX += this.chatCtx.measureText(currentSegment).width;
-				}
-				currentColor = <string> this.getColorCode(text[i + 1]);
-				currentSegment = '';
-				i++; // Skip the color code character
-			} else {
-				currentSegment += text[i];
-			}
-		}
-
-		if (currentSegment) {
-			this.chatCtx.font = '8px Tiny5';
-			this.chatCtx.fillStyle = currentColor;
-			this.chatCtx.fillText(currentSegment, currentX, y);
-		}
-	}
-
-	private renderPixelText(text: string, x: number, y: number, color: string) {
-		if (SettingsManager.settings.doPrettyText) {
-			this.renderPrettyText(text, x, y, color);
-		} else {
-			this.renderUglyText(text, x, y, color);
-		}
-	}
-
-	private renderDebugText() {
-		const ctx = this.chatCtx;
-		ctx.font = '8px Tiny5';
-		ctx.fillStyle = 'teal';
-
-		const linesToRender = [];
-		const framerate = this.renderer.getFramerate();
-
-		if (this.localPlayer.latency >= 999) {
-			linesToRender.push('disconnected :(');
-		}
-
-		//const playerX = Math.round(this.localPlayer.position.x);
-
-		linesToRender.push(
-			'candiru ' + this.localPlayer.gameVersion + ' @ ' + Math.round(framerate) + 'fps, ' +
-				Math.round(this.localPlayer.latency) + 'ms',
-		);
-		//linesToRender.push('connected to: ' + this.networking.getServerInfo().name);
-		//linesToRender.push('players: ' + this.networking.getServerInfo().currentPlayers + '/' + this.networking.getServerInfo().maxPlayers);
-		//linesToRender.push('map: ' + this.networking.getServerInfo().mapName);
-		//linesToRender.push('mode: ' + this.networking.getServerInfo().gameMode);
-		//linesToRender.push('serverVersion: ' + this.networking.getServerInfo().version);
-		//linesToRender.push('tickRate: ' + this.networking.getServerInfo().tickRate);
-		//linesToRender.push('playerMaxHealth: ' + this.networking.getServerInfo().playerMaxHealth);
-		//linesToRender.push('health: ' + this.localPlayer.health);
-
-		for (const msg of this.localPlayer.gameMsgs2) {
-			linesToRender.push(msg);
-		}
-
-		//linesToRender.push('routineTime: ' + this.lastRoutineMs + 'ms');
-
-		for (let i = 0; i < linesToRender.length; i++) {
-			this.renderPixelText(linesToRender[i], 2, 7 + 7 * i, 'teal');
-		}
-
-		this.debugTextHeight = 7 * linesToRender.length;
-	}
-
-	private detectGameMessagesChanges(now: number) {
-		const current = this.gameMessages;
-
-		for (let i = 0; i < this.maxMessagesOnScreen; i++) {
-			const line = this.lines[i];
-			const currentMessage = current[i] || null;
-
-			if (!line.currentMessage) {
-				if (currentMessage) {
-					line.currentMessage = {
-						id: this.generateUniqueId(),
-						message: currentMessage,
-						state: 'animatingIn',
-						animationProgress: 0,
-						timestamp: now,
-					};
-				}
-				continue;
-			}
-
-			if (currentMessage !== line.currentMessage.message) {
-				if (line.currentMessage.state === 'idle') {
-					line.currentMessage.state = 'animatingOut';
-					line.currentMessage.timestamp = now;
-					line.pendingMessage = currentMessage;
-				} else {
-					line.pendingMessage = currentMessage;
-				}
-			}
-		}
-
-		this.previousGameMessages = [...current];
-	}
-
-	private updateAnimatedGameMessages(now: number) {
-		for (let i = 0; i < this.maxMessagesOnScreen; i++) {
-			const line = this.lines[i];
-			if (!line.currentMessage) continue; // Early return if null
-
-			const elapsed = now - line.currentMessage.timestamp;
-			let progress = Math.min(elapsed / this.animationDuration, 1);
-			progress = this.easeOut(progress);
-
-			line.currentMessage.animationProgress = progress;
-
-			if (line.currentMessage.state === 'animatingOut' && progress >= 1) {
-				// Remove the message after fade-out
-				if (line.pendingMessage) {
-					line.currentMessage = {
-						id: this.generateUniqueId(),
-						message: line.pendingMessage,
-						state: 'animatingIn',
-						animationProgress: 0,
-						timestamp: now,
-					};
-					line.pendingMessage = null;
-				} else {
-					line.currentMessage = null;
-				}
-				continue;
-			}
-
-			if (line.currentMessage.state === 'animatingIn' && progress >= 1) {
-				line.currentMessage.state = 'idle';
-				line.currentMessage.animationProgress = 1;
-			}
-		}
-	}
-
-	private renderGameText() {
-		const ctx = this.chatCtx;
-		ctx.font = '8px Tiny5';
-		const centerY = this.chatCanvas.height / 2 + 48;
-
-		for (let i = 0; i < this.maxMessagesOnScreen; i++) {
-			const line = this.lines[i];
-			if (!line.currentMessage) continue;
-
-			let visibleText = line.currentMessage.message;
-
-			// Check if we should skip animation
-			const shouldSkipAnimation = line.currentMessage.state === 'animatingOut' &&
-				line.pendingMessage !== null &&
-				line.currentMessage.message.includes('seconds') &&
-				line.pendingMessage.includes('seconds');
-
-			if (shouldSkipAnimation && line.pendingMessage) {
-				// Directly update to the pending message
-				line.currentMessage = {
-					id: this.generateUniqueId(),
-					message: line.pendingMessage,
-					state: 'idle',
-					animationProgress: 1,
-					timestamp: Date.now() / 1000,
-				};
-				line.pendingMessage = null;
-				visibleText = line.currentMessage.message;
-			} else if (line.currentMessage.state === 'animatingIn' || line.currentMessage.state === 'animatingOut') {
-				visibleText = this.getVisibleText(
-					line.currentMessage.message,
-					line.currentMessage.state,
-					line.currentMessage.animationProgress,
-				);
-			}
-
-			// Calculate the actual width of the rendered text, including color codes
-			const textWidth = this.getRenderedTextWidth(visibleText);
-			const x = Math.floor((this.screenWidth - textWidth) / 2);
-			const y = Math.floor(centerY + (i * 10));
-
-			this.renderPixelText(visibleText, x, y, 'white');
-		}
-	}
-
-	private getRenderedTextWidth(text: string): number {
-		let totalWidth = 0;
-		let currentSegment = '';
-		const ctx = this.chatCtx;
-
-		for (let i = 0; i < text.length; i++) {
-			if (text[i] === '&' && i + 1 < text.length && this.getColorCode(text[i + 1])) {
-				// Measure the current segment before switching color
-				if (currentSegment) {
-					totalWidth += ctx.measureText(currentSegment).width;
-					currentSegment = '';
-				}
-				i++; // Skip the color code character
-			} else {
-				currentSegment += text[i];
-			}
-		}
-
-		// Measure the last segment
-		if (currentSegment) {
-			totalWidth += ctx.measureText(currentSegment).width;
-		}
-
-		return totalWidth;
-	}
-
-	private getVisibleText(message: string, state: 'animatingIn' | 'animatingOut' | 'idle', progress: number): string {
-		if (state === 'idle') {
-			return message;
-		}
-
-		const length = message.length;
-		if (length === 0) return '';
-
-		let charsToShow = 0;
-
-		if (state === 'animatingIn') {
-			charsToShow = Math.floor(progress * length);
-			charsToShow = Math.min(charsToShow, length); // Ensure it doesn't exceed the message length
-			return message.substring(0, charsToShow);
-		} else if (state === 'animatingOut') {
-			charsToShow = Math.floor((1 - progress) * length);
-			charsToShow = Math.max(charsToShow, 0); // Ensure it doesn't go below zero
-			return message.substring(0, charsToShow);
-		}
-
-		return message;
-	}
-
-	private easeOut(progress: number): number {
-		return 1 - Math.pow(1 - progress, 1.5);
-	}
-
-	public renderTouchControls() {
-		if (Date.now() / 1000 - this.lastTouchTimestamp > 10) return;
-		if (this.touchJoystickEngaged) {
-			// Draw circle for movement
-			this.chatCtx.fillStyle = 'rgba(255,255,255,0.25)';
-			this.chatCtx.beginPath();
-			this.chatCtx.arc(this.joystickX, this.joystickY, TouchInputHandler.joystickRadius, 0, 2 * Math.PI);
-			this.chatCtx.fill();
-
-			// Smaller circle for joystick-- offset from center
-			this.chatCtx.fillStyle = 'rgba(255,255,255,0.5)';
-			this.chatCtx.beginPath();
-			this.chatCtx.arc(
-				this.joystickX + this.joystickInputX * TouchInputHandler.joystickRadius,
-				this.joystickY + this.joystickInputY * TouchInputHandler.joystickRadius,
-				10,
-				0,
-				2 * Math.PI,
-			);
-			this.chatCtx.fill();
-		}
-
-		// Draw rounded square center right for jumping
-		const squareWidth = 24;
-		const squareHeight = 24;
-		const cornerRadius = 6;
-		const x = this.chatCanvas.width - squareWidth - 12; // 12px from the right edge
-		let y = (this.chatCanvas.height - squareHeight) / 2; // Center vertically
-
-		this.drawButton(x, y, squareWidth, squareHeight, cornerRadius, '●', 1, 0);
-		y -= squareHeight + 4;
-		this.drawButton(x, y, squareWidth, squareHeight, cornerRadius, '↑', 1, -1);
-		y += squareHeight + 4;
-		y += squareHeight + 4;
-		this.drawButton(x, y, squareWidth, squareHeight, cornerRadius, '[]', 1, 1);
-	}
-
-	public setButtonsHeld(buttons: number[]) {
-		this.buttonsHeld = buttons;
-	}
-
-	private drawButton(
-		x: number,
-		y: number,
-		width: number,
-		height: number,
-		cornerRadius: number,
-		text: string,
-		textOffset: number,
-		index: number,
-	) {
-		if (this.buttonsHeld.includes(index)) {
-			this.chatCtx.fillStyle = 'rgba(100,100,100,0.3)';
-		} else {
-			this.chatCtx.fillStyle = 'rgba(255,255,255,0.15)';
-		}
-
-		this.drawRoundedSquare(x, y, width, height, cornerRadius);
-		// Draw character inside square
-		this.chatCtx.fillStyle = 'rgba(0,0,0,0.5)';
-		this.chatCtx.font = '16px Tiny5';
-		const textWidth = this.chatCtx.measureText(text).width;
-		this.chatCtx.fillText(
-			text,
-			Math.floor(x + width / 2 - textWidth / 2 + textOffset),
-			Math.floor(y + height / 2 + 16 / 2 - 2),
-		);
-	}
-
-	private drawRoundedSquare(x: number, y: number, width: number, height: number, cornerRadius: number) {
-		this.chatCtx.beginPath();
-		this.chatCtx.moveTo(x + cornerRadius, y);
-		this.chatCtx.lineTo(x + width - cornerRadius, y);
-		this.chatCtx.quadraticCurveTo(x + width, y, x + width, y + cornerRadius);
-		this.chatCtx.lineTo(x + width, y + height - cornerRadius);
-		this.chatCtx.quadraticCurveTo(x + width, y + height, x + width - cornerRadius, y + height);
-		this.chatCtx.lineTo(x + cornerRadius, y + height);
-		this.chatCtx.quadraticCurveTo(x, y + height, x, y + height - cornerRadius);
-		this.chatCtx.lineTo(x, y + cornerRadius);
-		this.chatCtx.quadraticCurveTo(x, y, x + cornerRadius, y);
-		this.chatCtx.closePath();
-		this.chatCtx.fill();
-	}
-
-	public setLastTouchTimestamp(timestamp: number) {
-		this.lastTouchTimestamp = timestamp;
-	}
-
-	public setTouchJoystickEngaged(value: boolean) {
-		this.touchJoystickEngaged = value;
-	}
-
-	public setJoystickPosition(x: number, y: number) {
-		this.joystickX = x;
-		this.joystickY = y;
-	}
-
-	public setJoystickInput(x: number, y: number) {
-		this.joystickInputX = x;
-		this.joystickInputY = y;
-	}
-
-	public renderHitMarkers() {
-		const numDots = 10; // Number of dots to render around each hit point
-
-		for (let i = this.renderer.playerHitMarkers.length - 1; i >= 0; i--) {
-			if (this.renderer.playerHitMarkers[i].timestamp === -1) {
-				this.renderer.playerHitMarkers[i].timestamp = Date.now() / 1000; // Set timestamp if not set
-			}
-
-			const timeSinceHit = Date.now() / 1000 - this.renderer.playerHitMarkers[i].timestamp;
-			const lifePercent = timeSinceHit / hitMarkerLifetime;
-
-			if (timeSinceHit > hitMarkerLifetime) {
-				this.renderer.playerHitMarkers.splice(i, 1);
-				continue;
-			}
-
-			const hitVec = this.renderer.playerHitMarkers[i].hitPoint;
-			const projected = hitVec.clone().project(this.renderer.getCamera());
-			const projectedX = Math.round((projected.x + 1) * this.screenWidth / 2);
-			const projectedY = Math.round((-projected.y + 1) * 200 / 2);
-
-			if (projected.z < 1) {
-				this.chatCtx.fillStyle = 'rgba(255,0,0,' + (1 - Math.pow(lifePercent, 1.25)) + ')';
-
-				// Calculate sizeMultiplier
-				const sizeMultiplier = 1 + 2 / this.renderer.playerHitMarkers[i].shotVector.length();
-
-				// Calculate and render dots
-				const radius = Math.pow(lifePercent, 0.7) * 7 * sizeMultiplier; // Radius of the circle in which dots are placed
-				for (let j = 0; j < numDots; j++) {
-					const angle = (Math.PI * 2 / numDots) * j;
-					const dotX = Math.round(projectedX + radius * Math.cos(angle));
-					const dotY = Math.round(projectedY + radius * Math.sin(angle));
-
-					this.chatCtx.fillRect(dotX, dotY, 1, 1); // Render a 1px by 1px dot
-				}
-			}
-		}
-	}
-
-	public getDebugTextHeight(): number {
-		return this.debugTextHeight;
-	}
-
-	private renderPlayerList() {
-		const ctx = this.chatCtx;
-		const linesToRender: string[] = [];
-		const colorsToRender: string[] = [];
-		const playerData = this.networking.getRemotePlayerData();
-
-		linesToRender.push(playerData.length + ' online - ' + Math.round(this.localPlayer.latency) + 'ms');
-		colorsToRender.push('white');
-		for (let i = 0; i < playerData.length; i++) {
-			linesToRender.push(playerData[i].name);
-			if (playerData[i].latency > 200) {
-				colorsToRender.push('red');
-			} else if (playerData[i].latency > 50) {
-				colorsToRender.push('orange');
-			} else {
-				colorsToRender.push('green');
-			}
-		}
-
-		ctx.font = '8px Tiny5';
-
-		let longestLinePix = 0;
-		for (let i = 0; i < linesToRender.length; i++) {
-			longestLinePix = Math.max(longestLinePix, ctx.measureText(linesToRender[i]).width);
-		}
-
-		ctx.fillStyle = 'rgba(0,0,0,0.5)';
-		ctx.fillRect(
-			Math.floor(this.screenWidth / 2 - longestLinePix / 2),
-			4,
-			longestLinePix + 3,
-			linesToRender.length * 7 + 2,
-		);
-
-		for (let i = 0; i < linesToRender.length; i++) {
-			this.renderPixelText(
-				linesToRender[i],
-				Math.floor(this.screenWidth / 2 - longestLinePix / 2 + 2),
-				11 + 7 * i,
-				colorsToRender[i],
-			);
-		}
-	}
-
-	private renderEvil() {
-		const ctx = this.chatCtx;
-		if (Date.now() / 1000 - this.networking.getDamagedTimestamp() < 0.05) {
-			ctx.fillStyle = 'rgba(255,0,0,0.1)';
-			ctx.fillRect(0, 0, this.chatCanvas.width, this.chatCanvas.height);
-		}
-	}
-
-	private renderCrosshair() {
-		const ctx = this.chatCtx;
-		ctx.fillStyle = SettingsManager.settings.crosshairColor;
-		if (this.renderer.crosshairIsFlashing) {
-			ctx.fillStyle = '#FF0000';
-		}
-		switch (SettingsManager.settings.crosshairType) {
-			case 0:
-				ctx.fillRect(Math.floor(this.screenWidth / 2), 100 - 3, 1, 7);
-				ctx.fillRect(Math.floor(this.screenWidth / 2 - 3), 100, 7, 1);
-				break;
-			case 1:
-				ctx.fillRect(Math.floor(this.screenWidth / 2), 100, 1, 1);
-				break;
-		}
-	}
-
-	private onKeyDown(e: KeyboardEvent) {
-		if (e.key === 'Backspace' && (this.localPlayer.chatActive || this.nameSettingActive)) {
-			this.localPlayer.chatMsg = this.localPlayer.chatMsg.slice(0, -1);
-			return;
-		}
-
-		if (e.key === 'Enter') {
-			if (this.localPlayer.chatActive) {
-				if (!this.commandManager.runCmd(this.localPlayer.chatMsg)) {
-					this.networking.sendMessage(this.localPlayer.chatMsg);
-				}
-			}
-			if (this.nameSettingActive) {
-				this.localPlayer.name = this.localPlayer.chatMsg.toString();
-				SettingsManager.settings.name = this.localPlayer.chatMsg.toString();
-				SettingsManager.write();
-			}
-			this.localPlayer.chatMsg = '';
-			this.localPlayer.chatActive = false;
-			this.nameSettingActive = false;
-		}
-
-		if (e.key === 'Escape' || e.key === 'Enter') {
-			this.localPlayer.chatMsg = '';
-			this.localPlayer.chatActive = false;
-			this.nameSettingActive = false;
-		}
-
-		if ((this.localPlayer.chatActive) && e.key.length === 1 && this.localPlayer.chatMsg.length < 300) {
-			this.localPlayer.chatMsg += e.key;
-		}
-
-		if ((this.nameSettingActive) && e.key.length === 1 && this.localPlayer.chatMsg.length < 42) {
-			this.localPlayer.chatMsg += e.key;
-		}
-
-		if (e.key.toLowerCase() === 't' && !this.nameSettingActive) {
-			if (this.localPlayer.name.length > 0) this.localPlayer.chatActive = true;
-			else this.nameSettingActive = true;
-		}
-
-		if (e.key === '/' && !this.nameSettingActive && !this.localPlayer.chatActive) {
-			if (this.localPlayer.name.length > 0) {
-				this.localPlayer.chatActive = true;
-				this.localPlayer.chatMsg = '/';
-			} else {
-				this.nameSettingActive = true;
-			}
-		}
-
-		if (e.key.toLowerCase() === 'n' && !this.localPlayer.chatActive) {
-			this.nameSettingActive = true;
-		}
-	}
-
-	public addChatMessage(msg: { id: number; name: string; message: string }) {
-		const chatMessage: ChatMessage = {
-			id: msg.id,
-			name: msg.name,
-			message: msg.message,
-			timestamp: Date.now() / 1000,
-		};
-		this.chatMessages.push(chatMessage);
-	}
-
-	private clearOldMessages() {
-		for (let i = 0; i < this.chatMessages.length; i++) {
-			if (Date.now() / 1000 - this.chatMessages[i].timestamp > this.chatMessageLifespan + 5) {
-				this.chatMessages.splice(i, 1);
-			}
-		}
-
-		for (let i = this.chatMessages.length - 1; i >= 0; i--) {
-			if (i < this.chatMessages.length - this.maxMessagesOnScreen) {
-				this.chatMessages[i].timestamp = Math.min(
-					Date.now() / 1000 - this.chatMessageLifespan,
-					this.chatMessages[i].timestamp,
-				);
-			}
-		}
-	}
-
-	private doTextWrapping(
-		ctx: CanvasRenderingContext2D,
-		text: string[],
-		maxWidth: number,
-		initialOffset: number = 0,
-	): string[] {
-		ctx.font = '8px Tiny5';
-		const resultLines: string[] = [];
-
-		for (const line of text) {
-			if (line === '' || ctx.measureText(line).width <= maxWidth) {
-				resultLines.push(line);
-				continue;
-			}
-
-			const words = line.split(' ');
-			let currentLine = '';
-			let isFirstLine = true;
-
-			for (const word of words) {
-				const testLine = currentLine ? `${currentLine} ${word}` : word;
-				const testWidth = ctx.measureText(testLine).width;
-
-				const availableWidth = isFirstLine ? maxWidth - initialOffset : maxWidth;
-
-				if (testWidth <= availableWidth) {
-					currentLine = testLine;
-				} else {
-					if (currentLine) {
-						resultLines.push(currentLine);
-					}
-					currentLine = word;
-					isFirstLine = false;
-				}
-			}
-
-			if (currentLine) {
-				resultLines.push(currentLine);
-			}
-		}
-
-		return resultLines;
-	}
-
-	private generateUniqueId(): string {
-		return Math.random().toString(36).substr(2, 9);
-	}
-}
+    private chatCanvas: HTMLCanvasElement;
+    private chatCtx: CanvasRenderingContext2D;
+    private chatMessages: ChatMessage[]; // Typed as ChatMessage[]
+    private chatMessageLifespan: number;
+    private charsToRemovePerSecond: number;
+    private maxMessagesOnScreen: number;
+    private nameSettingActive: boolean;
+    private localPlayer: Player;
+    private renderer!: Renderer;
+    private networking!: Networking;
+    private screenWidth: number;
+    private inputHandler!: InputHandler;
+    private debugTextHeight!: number;
+    private oldScreenWidth: number = 0;
+    private readonly commandManager: CommandManager;
+    private lastTouchTimestamp: number = 0;
+    private touchJoystickEngaged: boolean = false;
+    private joystickX: number = 0;
+    private joystickY: number = 0;
+    private joystickInputX: number = 0;
+    private joystickInputY: number = 0;
+    private buttonsHeld: number[] = [];
+    private lastRoutineMs = 0;
+
+    private offscreenCanvas: HTMLCanvasElement;
+    private offscreenCtx: CanvasRenderingContext2D;
+
+    public gameMessages: string[] = [];
+    private previousGameMessages: string[] = [];
+
+    // Removed animatedGameMessages in favor of per-line management
+    private lines: LineMessage[] = [];
+    private animationDuration: number = 1; // Adjusted for smoother animation
+
+    // Color code mapping
+    COLOR_CODES: { [key: string]: string } = {
+        '0': '#000000',    // Black
+        '1': '#0000AA',    // Dark Blue
+        '2': '#00AA00',    // Dark Green
+        '3': '#00AAAA',    // Dark Aqua
+        '4': '#AA0000',    // Dark Red
+        '5': '#AA00AA',    // Dark Purple
+        '6': '#FFAA00',    // Gold
+        '7': '#AAAAAA',    // Gray
+        '8': '#555555',    // Dark Gray
+        '9': '#5555FF',    // Blue
+        'a': '#55FF55',    // Green
+        'b': '#55FFFF',    // Aqua
+        'c': '#FF5555',    // Red
+        'd': '#FF55FF',    // Light Purple
+        'e': '#FFFF55',    // Yellow
+        'f': '#FFFFFF',    // White
+        'g': this.getRainbowColor()
+    };
+
+    private getColorCode(code: string): string | false {
+        if (code === 'g') {
+            return this.getRainbowColor();
+        }
+        return this.COLOR_CODES[code] || false;
+    }
+
+
+    private getRainbowColor(): string {
+        const hue = (Date.now() / 20) % 360;
+        return `hsl(${hue}, 100%, 50%)`;
+    }
+
+
+    constructor(container: HTMLElement, localPlayer: Player) {
+        this.localPlayer = localPlayer;
+        this.chatCanvas = document.createElement('canvas');
+        this.chatCtx = this.chatCanvas.getContext('2d') as CanvasRenderingContext2D;
+        this.chatCtx.imageSmoothingEnabled = false;
+
+
+        this.chatCanvas.width = 400;
+        this.chatCanvas.height = 200;
+
+        this.chatMessages = [];
+        this.chatMessageLifespan = 40; // 40 seconds
+        this.charsToRemovePerSecond = 30;
+        this.maxMessagesOnScreen = 12;
+
+        this.nameSettingActive = false;
+        this.screenWidth = 100;
+
+        this.commandManager = new CommandManager(this.localPlayer, this);
+
+        this.setupEventListeners();
+
+        this.chatCanvas.style.position = 'absolute';
+        this.chatCanvas.style.display = 'block';
+        this.chatCanvas.style.zIndex = '100';
+        this.chatCanvas.style.top = '0';
+        this.chatCanvas.style.left = '0';
+
+        this.chatCanvas.style.height = '100vh';
+        document.body.style.margin = '0';
+        this.chatCanvas.style.imageRendering = 'pixelated';
+        this.chatCanvas.style.textRendering = 'pixelated';
+
+        this.chatCanvas.style.touchAction = 'none';
+
+        this.offscreenCanvas = document.createElement('canvas');
+        this.offscreenCtx = this.offscreenCanvas.getContext('2d') as CanvasRenderingContext2D;
+
+        // Initialize lines for per-line message management
+        this.lines = Array(this.maxMessagesOnScreen).fill(null).map(() => ({
+            currentMessage: null,
+            pendingMessage: null
+        }));
+
+        //document.body.appendChild(this.chatCanvas);
+        container.appendChild(this.chatCanvas);
+
+        globalThis.addEventListener('resize', this.onWindowResize.bind(this));
+        globalThis.addEventListener('orientationchange', this.onWindowResize.bind(this));
+    }
+
+    public setRenderer(renderer: Renderer) {
+        this.renderer = renderer;
+    }
+
+    public setNetworking(networking: Networking) {
+        this.networking = networking;
+    }
+
+    public setInputHandler(inputHandler: InputHandler) {
+        this.inputHandler = inputHandler;
+    }
+
+    private setupEventListeners() {
+        document.addEventListener('keydown', this.onKeyDown.bind(this));
+    }
+
+    public onFrame() {
+        const startTime = Date.now();
+        const now = Date.now() / 1000;
+
+        this.gameMessages = this.localPlayer.gameMsgs;
+        this.detectGameMessagesChanges(now);
+        this.updateAnimatedGameMessages(now);
+
+        this.clearOldMessages();
+        this.chatCtx.clearRect(0, 0, this.chatCanvas.width, this.chatCanvas.height);
+        this.renderHitMarkers();
+        this.renderChatMessages();
+        this.renderGameText();
+        this.renderDebugText();
+        if (this.inputHandler.getKey('tab'))
+            this.renderPlayerList();
+        this.renderEvil();
+        this.renderCrosshair();
+        this.renderTouchControls();
+
+        this.screenWidth = Math.floor(this.renderer.getCamera().aspect * 200);
+
+        if (this.oldScreenWidth !== this.screenWidth) {
+            //if(this.chatCanvas.width < this.screenWidth)
+            this.chatCanvas.width = this.screenWidth;
+            this.oldScreenWidth = this.screenWidth;
+        }
+
+        // this.chatCanvas.width = this.screenWidth;
+        // this.chatCtx.fillRect(0,0,10,10);
+
+        this.onWindowResize();
+
+        this.inputHandler.nameSettingActive = this.nameSettingActive;
+        if (Math.random() < 0.03)
+            this.lastRoutineMs = Date.now() - startTime;
+    }
+
+    private onWindowResize() {
+        this.chatCanvas.style.width = globalThis.innerWidth + 'px';
+        this.chatCanvas.style.height = globalThis.innerHeight + 'px';
+    }
+
+    private renderChatMessages() {
+        const ctx = this.chatCtx;
+
+        this.offscreenCtx.font = '8px Tiny5';
+        this.offscreenCtx.fillStyle = 'white';
+
+        const usermsg = this.localPlayer.chatMsg;
+        let cursor = '';
+        if ((Date.now() / 1000) % 0.7 < 0.35) cursor = '|';
+
+        const linesToRender: string[] = [];
+        const pixOffsets: number[] = [];
+        const messagesBeingTyped = this.networking.getMessagesBeingTyped();
+
+        for (let i = 0; i < this.chatMessages.length; i++) {
+            let msg = this.chatMessages[i].message;
+            const name = this.chatMessages[i].name;
+            if (name.length > 0) msg = `${name}: ${msg}`;
+
+            const duplicateFromPlayerData = messagesBeingTyped.includes(msg);
+
+            let charsToRemove = Date.now() / 1000 - this.chatMessages[i].timestamp - this.chatMessageLifespan;
+            charsToRemove = Math.max(0, charsToRemove * this.charsToRemovePerSecond);
+            charsToRemove = Math.floor(charsToRemove);
+
+            let removedSubstring = '';
+            let remainingMsg = msg;
+            if (charsToRemove > 0) {
+                let charsRemoved = 0;
+                while (charsRemoved < charsToRemove && remainingMsg.length > 0) {
+                    const char = remainingMsg.charAt(0);
+                    removedSubstring += char;
+                    remainingMsg = remainingMsg.substring(1);
+                    charsRemoved++;
+                }
+            }
+
+            if (!duplicateFromPlayerData) {
+                linesToRender.push(remainingMsg);
+                pixOffsets.push(this.offscreenCtx.measureText(removedSubstring).width);
+            }
+        }
+
+        for (const msg of messagesBeingTyped) {
+            linesToRender.push(msg + cursor);
+            pixOffsets.push(0);
+        }
+
+        if (this.localPlayer.chatActive) {
+            if (this.localPlayer.chatMsg.startsWith('>'))
+                linesToRender.push('&2' + usermsg + cursor);
+            else
+                linesToRender.push(usermsg + cursor);
+            pixOffsets.push(0);
+        }
+
+        if (this.nameSettingActive) {
+            linesToRender.push('Enter your name: ' + usermsg + cursor);
+            pixOffsets.push(0);
+            this.localPlayer.name = usermsg + cursor;
+            if (this.localPlayer.name.length == 0) this.localPlayer.name = ' ';
+        }
+
+        const wrappedLines: string[] = [];
+        const lineOrigins: number[] = [];
+        const isFirstWrappedLine: boolean[] = [];
+
+        for (let i = 0; i < linesToRender.length; i++) {
+            const wrapped = this.doTextWrapping(this.offscreenCtx, [linesToRender[i]], this.screenWidth - 10);
+            for (let j = 0; j < wrapped.length; j++) {
+                wrappedLines.push(wrapped[j]);
+                lineOrigins.push(i);
+                isFirstWrappedLine.push(j === 0);
+            }
+        }
+
+        const totalLines = wrappedLines.length;
+        for (let i = 0; i < totalLines; i++) {
+            const lineIndex = totalLines - i - 1;
+            const text = wrappedLines[lineIndex];
+            const originIndex = lineOrigins[lineIndex];
+            const pixOffset = isFirstWrappedLine[lineIndex] ? pixOffsets[originIndex] : 0;
+
+            this.renderPixelText(text, 3 + pixOffset, 200 - 20 - 8 * i, 'white');
+        }
+
+        if ((usermsg !== '' && this.localPlayer.chatActive) || this.nameSettingActive) {
+            ctx.fillStyle = 'rgba(145,142,118,0.3)';
+            let width = ctx.measureText(usermsg).width;
+            if (this.nameSettingActive) {
+                width = ctx.measureText('Enter your name: ' + usermsg).width;
+            }
+            ctx.fillRect(2, 200 - 20 - 7, width + 1, 9);
+        }
+    }
+
+    private renderPrettyText(text: string, x: number, y: number, defaultColor: string) {
+        let currentX = x;
+        const segments: { text: string, color: string }[] = [];
+        let currentColor = defaultColor;
+        let currentSegment = '';
+
+        // Parse color codes and split into segments
+        for (let i = 0; i < text.length; i++) {
+            if (text[i] === '&' && i + 1 < text.length && this.getColorCode(text[i + 1])) {
+                if (currentSegment) {
+                    segments.push({ text: currentSegment, color: currentColor });
+                }
+                currentColor = <string>this.getColorCode(text[i + 1]);
+                currentSegment = '';
+                i++; // Skip the color code character
+            } else {
+                currentSegment += text[i];
+            }
+        }
+
+        if (currentSegment) {
+            segments.push({ text: currentSegment, color: currentColor });
+        }
+
+        // Render each segment
+        for (const segment of segments) {
+            this.offscreenCtx.font = '8px Tiny5';
+            const textMetrics = this.offscreenCtx.measureText(segment.text);
+            const textWidth = Math.max(Math.ceil(textMetrics.width), 1);
+            const textHeight = 8;
+
+            if (this.offscreenCanvas.width !== textWidth || this.offscreenCanvas.height !== textHeight) {
+                this.offscreenCanvas.width = textWidth;
+                this.offscreenCanvas.height = textHeight;
+            }
+
+            this.offscreenCtx.clearRect(0, 0, textWidth, textHeight);
+            this.offscreenCtx.font = '8px Tiny5';
+            this.offscreenCtx.fillStyle = segment.color;
+            this.offscreenCtx.fillText(segment.text, 0, textHeight - 1);
+
+            const imageData = this.offscreenCtx.getImageData(0, 0, textWidth, textHeight);
+            const data = imageData.data;
+
+            for (let i = 0; i < data.length; i += 4) {
+                data[i + 3] = data[i + 3] > 170 ? 255 : 0;
+            }
+
+            this.offscreenCtx.putImageData(imageData, 0, 0);
+            this.chatCtx.drawImage(this.offscreenCanvas, currentX, y - textHeight + 1);
+            currentX += textWidth;
+        }
+    }
+
+    private renderUglyText(text: string, x: number, y: number, defaultColor: string) {
+        let currentX = x;
+        let currentColor = defaultColor;
+        let currentSegment = '';
+
+        for (let i = 0; i < text.length; i++) {
+            if (text[i] === '&' && i + 1 < text.length && this.getColorCode(text[i + 1])) {
+                if (currentSegment) {
+                    this.chatCtx.font = '8px Tiny5';
+                    this.chatCtx.fillStyle = currentColor;
+                    this.chatCtx.fillText(currentSegment, currentX, y);
+                    currentX += this.chatCtx.measureText(currentSegment).width;
+                }
+                currentColor = <string>this.getColorCode(text[i + 1]);
+                currentSegment = '';
+                i++; // Skip the color code character
+            } else {
+                currentSegment += text[i];
+            }
+        }
+
+        if (currentSegment) {
+            this.chatCtx.font = '8px Tiny5';
+            this.chatCtx.fillStyle = currentColor;
+            this.chatCtx.fillText(currentSegment, currentX, y);
+        }
+    }
+
+    private renderPixelText(text: string, x: number, y: number, color: string) {
+        if (SettingsManager.settings.doPrettyText)
+            this.renderPrettyText(text, x, y, color);
+        else
+            this.renderUglyText(text, x, y, color);
+    }
+
+    private renderDebugText() {
+        const ctx = this.chatCtx;
+        ctx.font = '8px Tiny5';
+        ctx.fillStyle = 'teal';
+
+        const linesToRender = [];
+        const framerate = this.renderer.getFramerate();
+
+        if (this.localPlayer.latency >= 999)
+            linesToRender.push('disconnected :(');
+
+        //const playerX = Math.round(this.localPlayer.position.x);
+
+        linesToRender.push('candiru ' + this.localPlayer.gameVersion + ' @ ' + Math.round(framerate) + 'fps, ' + Math.round(this.localPlayer.latency) + 'ms');
+        //linesToRender.push('connected to: ' + this.networking.getServerInfo().name);
+        //linesToRender.push('players: ' + this.networking.getServerInfo().currentPlayers + '/' + this.networking.getServerInfo().maxPlayers);
+        //linesToRender.push('map: ' + this.networking.getServerInfo().mapName);
+        //linesToRender.push('mode: ' + this.networking.getServerInfo().gameMode);
+        //linesToRender.push('serverVersion: ' + this.networking.getServerInfo().version);
+        //linesToRender.push('tickRate: ' + this.networking.getServerInfo().tickRate);
+        //linesToRender.push('playerMaxHealth: ' + this.networking.getServerInfo().playerMaxHealth);
+        //linesToRender.push('health: ' + this.localPlayer.health);
+
+        for(const msg of this.localPlayer.gameMsgs2)
+            linesToRender.push(msg)
+
+        //linesToRender.push('routineTime: ' + this.lastRoutineMs + 'ms');
+
+        for (let i = 0; i < linesToRender.length; i++) {
+            this.renderPixelText(linesToRender[i], 2, 7 + 7 * i, 'teal');
+        }
+
+        this.debugTextHeight = 7 * linesToRender.length;
+    }
+
+    private detectGameMessagesChanges(now: number) {
+        const current = this.gameMessages;
+
+        for (let i = 0; i < this.maxMessagesOnScreen; i++) {
+            const line = this.lines[i];
+            const currentMessage = current[i] || null;
+
+            if (!line.currentMessage) {
+                if (currentMessage) {
+                    line.currentMessage = {
+                        id: this.generateUniqueId(),
+                        message: currentMessage,
+                        state: 'animatingIn',
+                        animationProgress: 0,
+                        timestamp: now,
+                    };
+                }
+                continue;
+            }
+
+            if (currentMessage !== line.currentMessage.message) {
+                if (line.currentMessage.state === 'idle') {
+                    line.currentMessage.state = 'animatingOut';
+                    line.currentMessage.timestamp = now;
+                    line.pendingMessage = currentMessage;
+                } else {
+                    line.pendingMessage = currentMessage;
+                }
+            }
+        }
+
+        this.previousGameMessages = [...current];
+    }
+
+
+    private updateAnimatedGameMessages(now: number) {
+        for (let i = 0; i < this.maxMessagesOnScreen; i++) {
+            const line = this.lines[i];
+            if (!line.currentMessage) continue; // Early return if null
+
+            const elapsed = now - line.currentMessage.timestamp;
+            let progress = Math.min(elapsed / this.animationDuration, 1);
+            progress = this.easeOut(progress);
+
+            line.currentMessage.animationProgress = progress;
+
+            if (line.currentMessage.state === 'animatingOut' && progress >= 1) {
+                // Remove the message after fade-out
+                if (line.pendingMessage) {
+                    line.currentMessage = {
+                        id: this.generateUniqueId(),
+                        message: line.pendingMessage,
+                        state: 'animatingIn',
+                        animationProgress: 0,
+                        timestamp: now,
+                    };
+                    line.pendingMessage = null;
+                } else {
+                    line.currentMessage = null;
+                }
+                continue;
+            }
+
+            if (line.currentMessage.state === 'animatingIn' && progress >= 1) {
+                line.currentMessage.state = 'idle';
+                line.currentMessage.animationProgress = 1;
+            }
+        }
+    }
+
+
+    private renderGameText() {
+        const ctx = this.chatCtx;
+        ctx.font = '8px Tiny5';
+        const centerY = this.chatCanvas.height / 2 + 48;
+
+        for (let i = 0; i < this.maxMessagesOnScreen; i++) {
+            const line = this.lines[i];
+            if (!line.currentMessage) continue;
+
+            let visibleText = line.currentMessage.message;
+
+            // Check if we should skip animation
+            const shouldSkipAnimation =
+                line.currentMessage.state === 'animatingOut' &&
+                line.pendingMessage !== null &&
+                line.currentMessage.message.includes('seconds') &&
+                line.pendingMessage.includes('seconds');
+
+            if (shouldSkipAnimation && line.pendingMessage) {
+                // Directly update to the pending message
+                line.currentMessage = {
+                    id: this.generateUniqueId(),
+                    message: line.pendingMessage,
+                    state: 'idle',
+                    animationProgress: 1,
+                    timestamp: Date.now() / 1000,
+                };
+                line.pendingMessage = null;
+                visibleText = line.currentMessage.message;
+            } else if (line.currentMessage.state === 'animatingIn' || line.currentMessage.state === 'animatingOut') {
+                visibleText = this.getVisibleText(line.currentMessage.message, line.currentMessage.state, line.currentMessage.animationProgress);
+            }
+
+            // Calculate the actual width of the rendered text, including color codes
+            const textWidth = this.getRenderedTextWidth(visibleText);
+            const x = Math.floor((this.screenWidth - textWidth) / 2);
+            const y = Math.floor(centerY + (i * 10));
+
+            this.renderPixelText(visibleText, x, y, 'white');
+        }
+    }
+
+
+    private getRenderedTextWidth(text: string): number {
+        let totalWidth = 0;
+        let currentSegment = '';
+        const ctx = this.chatCtx;
+
+        for (let i = 0; i < text.length; i++) {
+            if (text[i] === '&' && i + 1 < text.length && this.getColorCode(text[i + 1])) {
+                // Measure the current segment before switching color
+                if (currentSegment) {
+                    totalWidth += ctx.measureText(currentSegment).width;
+                    currentSegment = '';
+                }
+                i++; // Skip the color code character
+            } else {
+                currentSegment += text[i];
+            }
+        }
+
+        // Measure the last segment
+        if (currentSegment) {
+            totalWidth += ctx.measureText(currentSegment).width;
+        }
+
+        return totalWidth;
+    }
+
+
+
+
+    private getVisibleText(message: string, state: 'animatingIn' | 'animatingOut' | 'idle', progress: number): string {
+        if (state === 'idle') {
+            return message;
+        }
+
+        const length = message.length;
+        if (length === 0) return '';
+
+        let charsToShow = 0;
+
+        if (state === 'animatingIn') {
+            charsToShow = Math.floor(progress * length);
+            charsToShow = Math.min(charsToShow, length); // Ensure it doesn't exceed the message length
+            return message.substring(0, charsToShow);
+        } else if (state === 'animatingOut') {
+            charsToShow = Math.floor((1 - progress) * length);
+            charsToShow = Math.max(charsToShow, 0); // Ensure it doesn't go below zero
+            return message.substring(0, charsToShow);
+        }
+
+        return message;
+    }
+
+    private easeOut(progress: number): number {
+        return 1 - Math.pow(1 - progress, 1.5);
+    }
+
+    public renderTouchControls() {
+        if (Date.now() / 1000 - this.lastTouchTimestamp > 10) return;
+        if (this.touchJoystickEngaged) {
+            // Draw circle for movement
+            this.chatCtx.fillStyle = 'rgba(255,255,255,0.25)';
+            this.chatCtx.beginPath();
+            this.chatCtx.arc(this.joystickX, this.joystickY, TouchInputHandler.joystickRadius, 0, 2 * Math.PI);
+            this.chatCtx.fill();
+
+            // Smaller circle for joystick-- offset from center
+            this.chatCtx.fillStyle = 'rgba(255,255,255,0.5)';
+            this.chatCtx.beginPath();
+            this.chatCtx.arc(
+                this.joystickX + this.joystickInputX * TouchInputHandler.joystickRadius,
+                this.joystickY + this.joystickInputY * TouchInputHandler.joystickRadius,
+                10,
+                0,
+                2 * Math.PI
+            );
+            this.chatCtx.fill();
+        }
+
+        // Draw rounded square center right for jumping
+        const squareWidth = 24;
+        const squareHeight = 24;
+        const cornerRadius = 6;
+        const x = this.chatCanvas.width - squareWidth - 12; // 12px from the right edge
+        let y = (this.chatCanvas.height - squareHeight) / 2; // Center vertically
+
+        this.drawButton(x, y, squareWidth, squareHeight, cornerRadius, '●', 1, 0);
+        y -= squareHeight + 4;
+        this.drawButton(x, y, squareWidth, squareHeight, cornerRadius, '↑', 1, -1);
+        y += squareHeight + 4;
+        y += squareHeight + 4;
+        this.drawButton(x, y, squareWidth, squareHeight, cornerRadius, '[]', 1, 1);
+    }
+
+    public setButtonsHeld(buttons: number[]) {
+        this.buttonsHeld = buttons;
+    }
+
+    private drawButton(x: number, y: number, width: number, height: number, cornerRadius: number, text: string, textOffset: number, index: number) {
+        if (this.buttonsHeld.includes(index))
+            this.chatCtx.fillStyle = 'rgba(100,100,100,0.3)';
+        else
+            this.chatCtx.fillStyle = 'rgba(255,255,255,0.15)';
+
+        this.drawRoundedSquare(x, y, width, height, cornerRadius);
+        // Draw character inside square
+        this.chatCtx.fillStyle = 'rgba(0,0,0,0.5)';
+        this.chatCtx.font = '16px Tiny5';
+        const textWidth = this.chatCtx.measureText(text).width;
+        this.chatCtx.fillText(text, Math.floor(x + width / 2 - textWidth / 2 + textOffset), Math.floor(y + height / 2 + 16 / 2 - 2));
+    }
+
+    private drawRoundedSquare(x: number, y: number, width: number, height: number, cornerRadius: number) {
+        this.chatCtx.beginPath();
+        this.chatCtx.moveTo(x + cornerRadius, y);
+        this.chatCtx.lineTo(x + width - cornerRadius, y);
+        this.chatCtx.quadraticCurveTo(x + width, y, x + width, y + cornerRadius);
+        this.chatCtx.lineTo(x + width, y + height - cornerRadius);
+        this.chatCtx.quadraticCurveTo(x + width, y + height, x + width - cornerRadius, y + height);
+        this.chatCtx.lineTo(x + cornerRadius, y + height);
+        this.chatCtx.quadraticCurveTo(x, y + height, x, y + height - cornerRadius);
+        this.chatCtx.lineTo(x, y + cornerRadius);
+        this.chatCtx.quadraticCurveTo(x, y, x + cornerRadius, y);
+        this.chatCtx.closePath();
+        this.chatCtx.fill();
+    }
+
+    public setLastTouchTimestamp(timestamp: number) {
+        this.lastTouchTimestamp = timestamp;
+    }
+
+    public setTouchJoystickEngaged(value: boolean) {
+        this.touchJoystickEngaged = value;
+    }
+
+    public setJoystickPosition(x: number, y: number) {
+        this.joystickX = x;
+        this.joystickY = y;
+    }
+
+    public setJoystickInput(x: number, y: number) {
+        this.joystickInputX = x;
+        this.joystickInputY = y;
+    }
+
+    public renderHitMarkers() {
+        const numDots = 10; // Number of dots to render around each hit point
+
+        for (let i = this.renderer.playerHitMarkers.length - 1; i >= 0; i--) {
+            if (this.renderer.playerHitMarkers[i].timestamp === -1)
+                this.renderer.playerHitMarkers[i].timestamp = Date.now() / 1000; // Set timestamp if not set
+
+            const timeSinceHit = Date.now() / 1000 - this.renderer.playerHitMarkers[i].timestamp;
+            const lifePercent = timeSinceHit / hitMarkerLifetime;
+
+            if (timeSinceHit > hitMarkerLifetime) {
+                this.renderer.playerHitMarkers.splice(i, 1);
+                continue;
+            }
+
+            const hitVec = this.renderer.playerHitMarkers[i].hitPoint;
+            const projected = hitVec.clone().project(this.renderer.getCamera());
+            const projectedX = Math.round((projected.x + 1) * this.screenWidth / 2);
+            const projectedY = Math.round((-projected.y + 1) * 200 / 2);
+
+            if (projected.z < 1) {
+                this.chatCtx.fillStyle = 'rgba(255,0,0,' + (1 - Math.pow(lifePercent, 1.25)) + ')';
+
+                // Calculate sizeMultiplier
+                const sizeMultiplier = 1 + 2 / this.renderer.playerHitMarkers[i].shotVector.length();
+
+                // Calculate and render dots
+                const radius = Math.pow(lifePercent, 0.7) * 7 * sizeMultiplier; // Radius of the circle in which dots are placed
+                for (let j = 0; j < numDots; j++) {
+                    const angle = (Math.PI * 2 / numDots) * j;
+                    const dotX = Math.round(projectedX + radius * Math.cos(angle));
+                    const dotY = Math.round(projectedY + radius * Math.sin(angle));
+
+                    this.chatCtx.fillRect(dotX, dotY, 1, 1); // Render a 1px by 1px dot
+                }
+            }
+        }
+    }
+
+    public getDebugTextHeight(): number {
+        return this.debugTextHeight;
+    }
+
+    private renderPlayerList() {
+        const ctx = this.chatCtx;
+        const linesToRender: string[] = [];
+        const colorsToRender: string[] = [];
+        const playerData = this.networking.getRemotePlayerData();
+
+        linesToRender.push(playerData.length + ' online - ' + Math.round(this.localPlayer.latency) + 'ms');
+        colorsToRender.push('white');
+        for (let i = 0; i < playerData.length; i++) {
+            linesToRender.push(playerData[i].name);
+            if (playerData[i].latency > 200)
+                colorsToRender.push('red');
+            else if (playerData[i].latency > 50)
+                colorsToRender.push('orange');
+            else
+                colorsToRender.push('green');
+        }
+
+        ctx.font = '8px Tiny5';
+
+        let longestLinePix = 0;
+        for (let i = 0; i < linesToRender.length; i++)
+            longestLinePix = Math.max(longestLinePix, ctx.measureText(linesToRender[i]).width);
+
+        ctx.fillStyle = 'rgba(0,0,0,0.5)';
+        ctx.fillRect(Math.floor(this.screenWidth / 2 - longestLinePix / 2), 4, longestLinePix + 3, linesToRender.length * 7 + 2);
+
+        for (let i = 0; i < linesToRender.length; i++) {
+            this.renderPixelText(linesToRender[i], Math.floor(this.screenWidth / 2 - longestLinePix / 2 + 2), 11 + 7 * i, colorsToRender[i]);
+        }
+    }
+
+    private renderEvil() {
+        const ctx = this.chatCtx;
+        if (Date.now() / 1000 - this.networking.getDamagedTimestamp() < 0.05) {
+            ctx.fillStyle = 'rgba(255,0,0,0.1)';
+            ctx.fillRect(0, 0, this.chatCanvas.width, this.chatCanvas.height);
+        }
+    }
+
+    private renderCrosshair() {
+        const ctx = this.chatCtx;
+        ctx.fillStyle = SettingsManager.settings.crosshairColor;
+        if (this.renderer.crosshairIsFlashing)
+            ctx.fillStyle = '#FF0000';
+        switch (SettingsManager.settings.crosshairType) {
+            case 0:
+                ctx.fillRect(Math.floor(this.screenWidth / 2), 100 - 3, 1, 7);
+                ctx.fillRect(Math.floor(this.screenWidth / 2 - 3), 100, 7, 1);
+                break;
+            case 1:
+                ctx.fillRect(Math.floor(this.screenWidth / 2), 100, 1, 1);
+                break;
+        }
+    }
+
+    private onKeyDown(e: KeyboardEvent) {
+        if (e.key === 'Backspace' && (this.localPlayer.chatActive || this.nameSettingActive)) {
+            this.localPlayer.chatMsg = this.localPlayer.chatMsg.slice(0, -1);
+            return;
+        }
+
+        if (e.key === 'Enter') {
+            if (this.localPlayer.chatActive) {
+                if (!this.commandManager.runCmd(this.localPlayer.chatMsg)) this.networking.sendMessage(this.localPlayer.chatMsg);
+            }
+            if (this.nameSettingActive) {
+                this.localPlayer.name = this.localPlayer.chatMsg.toString();
+                SettingsManager.settings.name = this.localPlayer.chatMsg.toString();
+                SettingsManager.write();
+            }
+            this.localPlayer.chatMsg = '';
+            this.localPlayer.chatActive = false;
+            this.nameSettingActive = false;
+        }
+
+        if (e.key === 'Escape' || e.key === 'Enter') {
+            this.localPlayer.chatMsg = '';
+            this.localPlayer.chatActive = false;
+            this.nameSettingActive = false;
+        }
+
+        if ((this.localPlayer.chatActive) && e.key.length === 1 && this.localPlayer.chatMsg.length < 300)
+            this.localPlayer.chatMsg += e.key;
+
+        if ((this.nameSettingActive) && e.key.length === 1 && this.localPlayer.chatMsg.length < 42)
+            this.localPlayer.chatMsg += e.key;
+
+        if (e.key.toLowerCase() === 't' && !this.nameSettingActive) {
+            if (this.localPlayer.name.length > 0) this.localPlayer.chatActive = true;
+            else this.nameSettingActive = true;
+        }
+
+        if (e.key === '/' && !this.nameSettingActive && !this.localPlayer.chatActive) {
+            if (this.localPlayer.name.length > 0) {
+                this.localPlayer.chatActive = true;
+                this.localPlayer.chatMsg = '/';
+            } else {
+                this.nameSettingActive = true;
+            }
+        }
+
+        if (e.key.toLowerCase() === 'n' && !this.localPlayer.chatActive)
+            this.nameSettingActive = true;
+    }
+
+    public addChatMessage(msg: { id: number; name: string; message: string; }) {
+        const chatMessage: ChatMessage = {
+            id: msg.id,
+            name: msg.name,
+            message: msg.message,
+            timestamp: Date.now() / 1000,
+        };
+        this.chatMessages.push(chatMessage);
+    }
+
+    private clearOldMessages() {
+        for (let i = 0; i < this.chatMessages.length; i++)
+            if (Date.now() / 1000 - this.chatMessages[i].timestamp > this.chatMessageLifespan + 5)
+                this.chatMessages.splice(i, 1);
+
+        for (let i = this.chatMessages.length - 1; i >= 0; i--) {
+            if (i < this.chatMessages.length - this.maxMessagesOnScreen)
+                this.chatMessages[i].timestamp = Math.min(Date.now() / 1000 - this.chatMessageLifespan, this.chatMessages[i].timestamp);
+        }
+    }
+
+    private doTextWrapping(ctx: CanvasRenderingContext2D, text: string[], maxWidth: number, initialOffset: number = 0): string[] {
+        ctx.font = '8px Tiny5';
+        const resultLines: string[] = [];
+
+        for (const line of text) {
+            if (line === '' || ctx.measureText(line).width <= maxWidth) {
+                resultLines.push(line);
+                continue;
+            }
+
+            const words = line.split(' ');
+            let currentLine = '';
+            let isFirstLine = true;
+
+            for (const word of words) {
+                const testLine = currentLine ? `${currentLine} ${word}` : word;
+                const testWidth = ctx.measureText(testLine).width;
+
+                const availableWidth = isFirstLine ? maxWidth - initialOffset : maxWidth;
+
+                if (testWidth <= availableWidth) {
+                    currentLine = testLine;
+                } else {
+                    if (currentLine) {
+                        resultLines.push(currentLine);
+                    }
+                    currentLine = word;
+                    isFirstLine = false;
+                }
+            }
+
+            if (currentLine) {
+                resultLines.push(currentLine);
+            }
+        }
+
+        return resultLines;
+    }
+
+    private generateUniqueId(): string {
+        return Math.random().toString(36).substr(2, 9);
+    }
+}
\ No newline at end of file
diff --git a/src/client/ui/HealthIndicator.ts b/src/client/ui/HealthIndicator.ts
index ecc18856..6883a783 100644
--- a/src/client/ui/HealthIndicator.ts
+++ b/src/client/ui/HealthIndicator.ts
@@ -1,134 +1,132 @@
 import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
 import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
 import * as THREE from 'three';
-import { Renderer } from '../core/Renderer.ts';
-import { Player } from '../core/Player.ts';
-import { Networking } from '../core/Networking.ts';
+import {Renderer} from "../core/Renderer.ts";
+import {Player} from "../core/Player.ts";
+import {Networking} from "../core/Networking.ts";
+
 
 const clock = new THREE.Clock();
 
 export class HealthIndicator {
-	private scene: THREE.Scene;
-	private possumObject!: THREE.Object3D;
-	private sceneAdded: boolean = false;
-	private targetQuaternion: THREE.Quaternion = new THREE.Quaternion(0, 0, 0, 1);
-	private targetPosition: THREE.Vector3 = new THREE.Vector3(0, 0, 0);
-	private rotatedAngle: number = 0;
-	private ambientLight: THREE.AmbientLight;
-	private lastHealth: number = 0;
-	private lastHealthChangeWasDamage: boolean = false;
-	private lightRGBI: number[] = [0, 0, 0, 0];
-
-	constructor(private renderer: Renderer, private localPlayer: Player, private networking: Networking) {
-		this.scene = renderer.getHealthIndicatorScene();
-		this.ambientLight = new THREE.AmbientLight(rgbToHex(0, 0, 0), 0);
-		this.scene.add(this.ambientLight);
-	}
-
-	public init() {
-		const loader = new GLTFLoader();
-		const dracoLoader = new DRACOLoader();
-		dracoLoader.setDecoderPath('/draco/');
-		loader.setDRACOLoader(dracoLoader);
-		loader.load(
-			'models/simplified_possum.glb',
-			(gltf) => {
-				this.possumObject = gltf.scene;
-				this.possumObject.traverse((child) => {
-					if ((child as THREE.Mesh).isMesh) {
-						child.renderOrder = 999;
-						const applyDepthTest = (material: THREE.Material | THREE.Material[]) => {
-							if (Array.isArray(material)) {
-								material.forEach((mat) => applyDepthTest(mat)); // Recursively handle array elements
-							} else {
-								material.depthTest = false;
-							}
-						};
-						const mesh = child as THREE.Mesh;
-						applyDepthTest(mesh.material);
-					}
-				});
-			},
-			undefined,
-			() => {
-				console.log('overlay possum loading error');
-			},
-		);
-	}
-
-	public onFrame() {
-		if (!this.possumObject) return;
-		if (!this.sceneAdded) {
-			this.scene.add(this.possumObject);
-			this.sceneAdded = true;
-		}
-
-		let maxHealth = this.networking.getServerInfo().playerMaxHealth;
-		if (maxHealth === 0) maxHealth = 0.001;
-
-		const deltaTime = clock.getDelta();
-		const scaredLevel = 1 - Math.pow(this.localPlayer.health / maxHealth, 1); //0-1
-		this.renderer.scaredLevel = scaredLevel;
-
-		this.targetPosition.copy(basePosition);
-		this.targetPosition.y += scaredLevel * 0.5 * Math.sin(1.1 * Math.PI * this.rotatedAngle);
-		this.targetPosition.y += (Math.random() - 0.5) * 0.2 * scaredLevel;
-		this.targetPosition.x += (Math.random() - 0.5) * 0.2 * scaredLevel;
-		this.targetPosition.z += (Math.random() - 0.5) * 0.2 * scaredLevel;
-
-		this.targetQuaternion.copy(baseQuaternion);
-		rotateAroundWorldAxis(
-			this.targetQuaternion,
-			new THREE.Vector3(0, 0, 1),
-			Math.PI - this.localPlayer.health * Math.PI / maxHealth,
-		);
-
-		this.rotatedAngle += 4 * deltaTime / (Math.max(0.001, (1 - scaredLevel) * 3));
-		rotateAroundWorldAxis(this.targetQuaternion, new THREE.Vector3(0, 1, 0), this.rotatedAngle);
-
-		moveTowardsPos(this.possumObject.position, this.targetPosition, 0.8 * deltaTime * 60);
-		moveTowardsRot(this.possumObject.quaternion, this.targetQuaternion, 0.5 * deltaTime * 60);
-
-		let targetRGBI: number[];
-
-		if (!this.lastHealthChangeWasDamage && this.localPlayer.health < maxHealth && this.rotatedAngle % 2 > 1) {
-			targetRGBI = [125, 255, 125, 1.2];
-		} else {
-			targetRGBI = [255, 255, 255, 0.5];
-		}
-
-		for (let i = 0; i < 4; i++) {
-			this.lightRGBI[i] = this.lightRGBI[i] + (targetRGBI[i] - this.lightRGBI[i]) * 0.4 * deltaTime * 60;
-		}
-		this.ambientLight.copy(
-			new THREE.AmbientLight(rgbToHex(this.lightRGBI[0], this.lightRGBI[1], this.lightRGBI[2]), this.lightRGBI[3]),
-		);
-
-		if (this.lastHealth < this.localPlayer.health) {
-			this.lastHealthChangeWasDamage = false;
-		} else if (this.lastHealth > this.localPlayer.health) {
-			this.lastHealthChangeWasDamage = true;
-		}
-		this.lastHealth = this.localPlayer.health;
-	}
+    private scene: THREE.Scene;
+    private possumObject!: THREE.Object3D;
+    private sceneAdded: boolean = false;
+    private targetQuaternion: THREE.Quaternion = new THREE.Quaternion(0,0,0,1);
+    private targetPosition: THREE.Vector3 = new THREE.Vector3(0,0,0);
+    private rotatedAngle:number = 0;
+    private ambientLight: THREE.AmbientLight;
+    private lastHealth:number = 0;
+    private lastHealthChangeWasDamage:boolean = false;
+    private lightRGBI:number[] = [0,0,0,0];
+
+    constructor(private renderer: Renderer, private localPlayer:Player, private networking: Networking) {
+        this.scene = renderer.getHealthIndicatorScene();
+        this.ambientLight = new THREE.AmbientLight(rgbToHex(0,0,0), 0);
+        this.scene.add(this.ambientLight);
+    }
+
+    public init() {
+        const loader = new GLTFLoader();
+        const dracoLoader = new DRACOLoader();
+        dracoLoader.setDecoderPath('/draco/');
+        loader.setDRACOLoader(dracoLoader);
+        loader.load(
+            'models/simplified_possum.glb',
+            (gltf) => {
+                this.possumObject = gltf.scene;
+                this.possumObject.traverse((child) => {
+                    if ((child as THREE.Mesh).isMesh) {
+                        child.renderOrder = 999;
+                        const applyDepthTest = (material: THREE.Material | THREE.Material[]) => {
+                            if (Array.isArray(material))
+                                material.forEach((mat) => applyDepthTest(mat));  // Recursively handle array elements
+                            else
+                                material.depthTest = false;
+                        };
+                        const mesh = child as THREE.Mesh;
+                        applyDepthTest(mesh.material);
+                    }
+                });
+            },
+            undefined,
+            () => {
+                console.log('overlay possum loading error');
+            }
+        );
+    }
+
+    public onFrame() {
+        if (!this.possumObject) return;
+        if (!this.sceneAdded) {
+            this.scene.add(this.possumObject);
+            this.sceneAdded = true;
+        }
+
+        let maxHealth = this.networking.getServerInfo().playerMaxHealth;
+        if(maxHealth === 0) maxHealth = 0.001;
+
+        const deltaTime = clock.getDelta();
+        const scaredLevel = 1-Math.pow(this.localPlayer.health / maxHealth,1); //0-1
+        this.renderer.scaredLevel = scaredLevel;
+
+        this.targetPosition.copy(basePosition);
+        this.targetPosition.y += scaredLevel * 0.5 * Math.sin(1.1 * Math.PI * this.rotatedAngle);
+        this.targetPosition.y += (Math.random() - 0.5 ) * 0.2 * scaredLevel;
+        this.targetPosition.x += (Math.random() - 0.5 ) * 0.2 * scaredLevel;
+        this.targetPosition.z += (Math.random() - 0.5 ) * 0.2 * scaredLevel;
+
+        this.targetQuaternion.copy(baseQuaternion);
+        rotateAroundWorldAxis(this.targetQuaternion, new THREE.Vector3(0, 0, 1), Math.PI - this.localPlayer.health * Math.PI / maxHealth);
+
+        this.rotatedAngle += 4 * deltaTime / (Math.max(0.001, (1-scaredLevel)*3));
+        rotateAroundWorldAxis(this.targetQuaternion, new THREE.Vector3(0, 1, 0),  this.rotatedAngle);
+
+        moveTowardsPos(this.possumObject.position, this.targetPosition, 0.8 * deltaTime * 60);
+        moveTowardsRot(this.possumObject.quaternion, this.targetQuaternion, 0.5 * deltaTime * 60);
+
+        let targetRGBI: number[];
+
+        if(!this.lastHealthChangeWasDamage && this.localPlayer.health < maxHealth && this.rotatedAngle % 2 > 1)
+            targetRGBI = [125,255,125,1.2];
+        else
+            targetRGBI = [255,255,255,0.5];
+
+
+        for(let i = 0; i < 4; i++)
+            this.lightRGBI[i] = this.lightRGBI[i] + (targetRGBI[i] - this.lightRGBI[i]) * 0.4 * deltaTime * 60;
+        this.ambientLight.copy(new THREE.AmbientLight(rgbToHex(this.lightRGBI[0], this.lightRGBI[1], this.lightRGBI[2]),this.lightRGBI[3]));
+
+        if(this.lastHealth<this.localPlayer.health)
+            this.lastHealthChangeWasDamage = false;
+        else if(this.lastHealth>this.localPlayer.health)
+            this.lastHealthChangeWasDamage = true;
+        this.lastHealth = this.localPlayer.health;
+    }
+
+
+
 }
 
+
+
+
 function rotateAroundWorldAxis(source: THREE.Quaternion, axis: THREE.Vector3, angle: number) {
-	const rotationQuat = new THREE.Quaternion().setFromAxisAngle(axis, angle);
-	source.multiplyQuaternions(rotationQuat, source);
+    const rotationQuat = new THREE.Quaternion().setFromAxisAngle(axis, angle);
+    source.multiplyQuaternions(rotationQuat, source);
 }
 
 function moveTowardsPos(source: THREE.Vector3, target: THREE.Vector3, frac: number) {
-	source.lerp(target, frac);
+    source.lerp(target, frac);
 }
 
 function moveTowardsRot(source: THREE.Quaternion, target: THREE.Quaternion, frac: number) {
-	source.slerp(target, frac);
+    source.slerp(target, frac);
 }
 
-function rgbToHex(r: number, g: number, b: number) {
-	return (r << 16) + (g << 8) + b;
+function rgbToHex(r:number, g:number, b:number) {
+    return (r << 16) + (g << 8) + b;
 }
 
 const basePosition = new THREE.Vector3(0, 0, 1.2);
-const baseQuaternion = new THREE.Quaternion(0, 0, 0, 1);
+const baseQuaternion = new THREE.Quaternion(0,0,0,1);
diff --git a/src/main.server.ts b/src/main.server.ts
index a73e6628..6a60a09e 100644
--- a/src/main.server.ts
+++ b/src/main.server.ts
@@ -6,27 +6,27 @@ import { renderApplication } from '@angular/platform-server';
 import { provideServerContext } from '@analogjs/router/server';
 import { ServerContext } from '@analogjs/router/tokens';
 
-import { AppComponent } from './app/app.component.ts';
-import { config } from './app/app.config.server.ts';
+import { AppComponent } from "./app/app.component.ts";
+import { config } from "./app/app.config.server.ts";
 
 if (Deno.env.has('PRODUCTION')) {
-	enableProdMode();
+  enableProdMode();
 }
 
 export function bootstrap() {
-	return bootstrapApplication(AppComponent, config);
+  return bootstrapApplication(AppComponent, config);
 }
 
 export default async function render(
-	url: string,
-	document: string,
-	serverContext: ServerContext,
+  url: string,
+  document: string,
+  serverContext: ServerContext
 ) {
-	const html = await renderApplication(bootstrap, {
-		document,
-		url,
-		platformProviders: [provideServerContext(serverContext)],
-	});
+  const html = await renderApplication(bootstrap, {
+    document,
+    url,
+    platformProviders: [provideServerContext(serverContext)],
+  });
 
-	return html;
+  return html;
 }
diff --git a/src/main.ts b/src/main.ts
index 98f615be..af4e47ab 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -1,7 +1,7 @@
 import 'zone.js';
 import { bootstrapApplication } from '@angular/platform-browser';
 
-import { AppComponent } from './app/app.component.ts';
-import { appConfig } from './app/app.config.ts';
+import { AppComponent } from "./app/app.component.ts";
+import { appConfig } from "./app/app.config.ts";
 
 bootstrapApplication(AppComponent, appConfig);
diff --git a/src/server/DataValidator.ts b/src/server/DataValidator.ts
index 62191261..153bd05f 100644
--- a/src/server/DataValidator.ts
+++ b/src/server/DataValidator.ts
@@ -1,78 +1,81 @@
-import { z } from 'https://deno.land/x/zod@v3.23.8/mod.ts';
-import { Player } from './models/Player.ts';
-import { ChatMessage } from './models/ChatMessage.ts';
-import { DamageRequest } from './models/DamageRequest.ts';
+import { z } from "https://deno.land/x/zod@v3.23.8/mod.ts";
+import { Player } from "./models/Player.ts";
+import { ChatMessage } from "./models/ChatMessage.ts";
+import { DamageRequest } from "./models/DamageRequest.ts";
+
+
 
 export class DataValidator {
-	private static SERVER_VERSION = '';
+    private static SERVER_VERSION = '';
+
+    public static async updateServerVersion() {
+        const versionFile = await Deno.readTextFile("public/gameVersion.json");
+        const versionData = JSON.parse(versionFile);
+        this.SERVER_VERSION = versionData.version;
+        return this.SERVER_VERSION;
+    }
 
-	public static async updateServerVersion() {
-		const versionFile = await Deno.readTextFile('public/gameVersion.json');
-		const versionData = JSON.parse(versionFile);
-		this.SERVER_VERSION = versionData.version;
-		return this.SERVER_VERSION;
-	}
+    public static getServerVersion() {
+        return this.SERVER_VERSION;
+    }
 
-	public static getServerVersion() {
-		return this.SERVER_VERSION;
-	}
 
-	private static vector3Schema = z.object({
-		x: z.number(),
-		y: z.number(),
-		z: z.number(),
-	}).strict();
+    private static vector3Schema = z.object({
+        x: z.number(),
+        y: z.number(),
+        z: z.number(),
+    }).strict();
 
-	private static playerDataSchema = z.object({
-		id: z.number(),
-		speed: z.number(),
-		acceleration: z.number(),
-		name: z.string().max(42),
-		gameVersion: z.string().refine((val) => val === this.SERVER_VERSION, {
-			message: `Game version must be ${this.SERVER_VERSION}`,
-		}),
-		position: DataValidator.vector3Schema,
-		velocity: DataValidator.vector3Schema,
-		inputVelocity: DataValidator.vector3Schema,
-		gravity: z.number(),
-		lookQuaternion: z.array(z.number()).length(4),
-		quaternion: z.array(z.number()).length(4),
-		chatActive: z.boolean(),
-		chatMsg: z.string().max(300),
-		latency: z.number(),
-		health: z.number(),
-		forced: z.boolean(),
-		forcedAcknowledged: z.boolean(),
-		updateTimestamp: z.number().optional(),
-		lastDamageTime: z.number().optional(),
-		inventory: z.array(z.number()),
-		idLastDamagedBy: z.number().optional(),
-		playerSpectating: z.number(),
-		gameMsgs: z.array(z.string()),
-		gameMsgs2: z.array(z.string()),
-	}).strict();
+    private static playerDataSchema = z.object({
+        id: z.number(),
+        speed: z.number(),
+        acceleration: z.number(),
+        name: z.string().max(42),
+        gameVersion: z.string().refine(val => val === this.SERVER_VERSION, {
+            message: `Game version must be ${this.SERVER_VERSION}`,
+        }),
+        position: DataValidator.vector3Schema,
+        velocity: DataValidator.vector3Schema,
+        inputVelocity: DataValidator.vector3Schema,
+        gravity: z.number(),
+        lookQuaternion: z.array(z.number()).length(4),
+        quaternion: z.array(z.number()).length(4),
+        chatActive: z.boolean(),
+        chatMsg: z.string().max(300),
+        latency: z.number(),
+        health: z.number(),
+        forced: z.boolean(),
+        forcedAcknowledged: z.boolean(),
+        updateTimestamp: z.number().optional(),
+        lastDamageTime: z.number().optional(),
+        inventory: z.array(z.number()),
+        idLastDamagedBy: z.number().optional(),
+        playerSpectating: z.number(),
+        gameMsgs: z.array(z.string()),
+        gameMsgs2: z.array(z.string()),
+    }).strict();
 
-	private static chatMsgSchema = z.object({
-		id: z.number(),
-		name: z.string().max(42),
-		message: z.string().max(300),
-	}).strict();
+    private static chatMsgSchema = z.object({
+        id: z.number(),
+        name: z.string().max(42),
+        message: z.string().max(300),
+    }).strict();
 
-	private static damageRequestSchema = z.object({
-		localPlayer: DataValidator.playerDataSchema,
-		targetPlayer: DataValidator.playerDataSchema,
-		damage: z.number(),
-	}).strict();
+    private static damageRequestSchema = z.object({
+        localPlayer: DataValidator.playerDataSchema,
+        targetPlayer: DataValidator.playerDataSchema,
+        damage: z.number(),
+    }).strict();
 
-	static validatePlayerData(data: Player) {
-		return DataValidator.playerDataSchema.safeParse(data);
-	}
+    static validatePlayerData(data: Player) {
+        return DataValidator.playerDataSchema.safeParse(data);
+    }
 
-	static validateChatMessage(data: ChatMessage) {
-		return DataValidator.chatMsgSchema.safeParse(data);
-	}
+    static validateChatMessage(data: ChatMessage) {
+        return DataValidator.chatMsgSchema.safeParse(data);
+    }
 
-	static validateDamageRequest(data: DamageRequest) {
-		return DataValidator.damageRequestSchema.safeParse(data);
-	}
+    static validateDamageRequest(data: DamageRequest) {
+        return DataValidator.damageRequestSchema.safeParse(data);
+    }
 }
diff --git a/src/server/GameEngine.ts b/src/server/GameEngine.ts
index c5cd63c7..486e9666 100644
--- a/src/server/GameEngine.ts
+++ b/src/server/GameEngine.ts
@@ -1,155 +1,162 @@
-import { ChatManager } from './managers/ChatManager.ts';
-import { DamageSystem } from './managers/DamageSystem.ts';
-import { ItemManager } from './managers/ItemManager.ts';
-import { PlayerManager } from './managers/PlayerManager.ts';
-import { Server } from 'https://deno.land/x/socket_io@0.2.0/mod.ts';
-import config from './config.ts';
-import { Vector3 } from './models/Vector3.ts';
-import { ServerInfo } from './models/ServerInfo.ts';
-import { DataValidator } from './DataValidator.ts';
-import { Player } from './models/Player.ts';
-import { Gamemode } from './gamemodes/Gamemode.ts';
-import { FFAGamemode } from './gamemodes/FFAGamemode.ts';
+import { ChatManager } from "./managers/ChatManager.ts";
+import { DamageSystem } from "./managers/DamageSystem.ts";
+import { ItemManager } from "./managers/ItemManager.ts";
+import { PlayerManager } from "./managers/PlayerManager.ts";
+import { Server } from "https://deno.land/x/socket_io@0.2.0/mod.ts";
+import config from "./config.ts";
+import { Vector3 } from "./models/Vector3.ts";
+import { ServerInfo } from "./models/ServerInfo.ts";
+import {DataValidator} from "./DataValidator.ts";
+import {Player} from "./models/Player.ts";
+import {Gamemode} from "./gamemodes/Gamemode.ts";
+import {FFAGamemode} from "./gamemodes/FFAGamemode.ts";
 
 export class GameEngine {
-	private lastPlayerTickTimestamp: number = Date.now() / 1000;
-	private lastItemUpdateTimestamp: number = Date.now() / 1000;
-	public playerUpdateSinceLastEmit: boolean = false;
-	private itemUpdateSinceLastEmit: boolean = false;
-	private serverInfo: ServerInfo = new ServerInfo();
-	public gamemode: Gamemode | false = false;
-
-	constructor(
-		public playerManager: PlayerManager,
-		private itemManager: ItemManager,
-		public chatManager: ChatManager,
-		private damageSystem: DamageSystem,
-		private io: Server,
-	) {}
-
-	start() {
-		setInterval(() => this.serverTick(), 1000 / config.server.tickRate);
-		setInterval(() => this.periodicCleanup(), config.server.cleanupInterval);
-		setInterval(() => this.emitServerInfo(), config.server.cleanupInterval);
-		this.initGamemode();
-	}
-
-	private serverTick() {
-		try {
-			const currentTime = Date.now() / 1000;
-			this.playerManager.regenerateHealth();
-			this.itemManager.tick(currentTime);
-			if (this.gamemode) this.gamemode.tick();
-
-			// Emit player data if there are updates or enough time has passed
-			if (this.playerUpdateSinceLastEmit || currentTime - this.lastPlayerTickTimestamp > 1 / config.server.tickRate) {
-				try {
-					this.io.emit('remotePlayerData', this.playerManager.getAllPlayers());
-					this.playerUpdateSinceLastEmit = false;
-					this.lastPlayerTickTimestamp = currentTime;
-				} catch (err) {
-					console.error('⚠ error emitting player data:', err);
-				}
-			}
-
-			// Emit item data if there are updates
-			if (this.itemUpdateSinceLastEmit || this.itemManager.hasUpdates()) {
-				try {
-					this.io.emit('worldItemData', this.itemManager.getAllItems());
-					this.itemUpdateSinceLastEmit = false;
-				} catch (err) {
-					console.error('⚠ error emitting item data:', err);
-				}
-			}
-		} catch (error) {
-			console.error('⚠ error in serverTick:', error);
-		}
-	}
-
-	public periodicCleanup() {
-		// for(const player of this.playerManager.getAllPlayers())
-		//         console.log(player.gameMsgs)
-
-		try {
-			const currentTime = Date.now() / 1000;
-			const players = this.playerManager.getAllPlayers();
-
-			players.forEach((player) => {
-				if (player.position.y < -150) {
-					player.health = 0;
-					player.velocity = new Vector3(0, 0, 0);
-					this.chatManager.broadcastChat(`${player.name} fell off :'(`);
-					console.log(`💔 ${player.name}(${player.id}) fell off the map`);
-				}
-
-				if (player.health <= 0) {
-					if (this.gamemode) this.gamemode.onPlayerDeath(player); //gamemode now handles
-					else this.playerManager.respawnPlayer(player);
-				}
-
-				if ((player.updateTimestamp || 0) + config.player.disconnectTime < currentTime) {
-					if (this.gamemode) this.gamemode.onPlayerDisconnect(player);
-					console.log(`🟠 ${player.name}(${player.id}) left`);
-					this.chatManager.broadcastChat(`${player.name} left`);
-					this.playerManager.removePlayer(player.id);
-				}
-			});
-
-			const playerData = this.playerManager.getAllPlayerData();
-			playerData.forEach((playerData) => {
-				for (let i = 0; i < playerData.extras.gameMsgsTimeouts.length; i++) {
-					if (
-						playerData.extras.gameMsgsTimeouts[i] &&
-						currentTime > playerData.extras.gameMsgsTimeouts[i] && playerData.extras.gameMsgsTimeouts[i] !== -1
-					) {
-						playerData.player.gameMsgs[i] = '';
-						playerData.extras.gameMsgsTimeouts[i] = -1;
-						this.playerUpdateSinceLastEmit = true;
-					}
-				}
-			});
-
-			const items = this.itemManager.getAllItems();
-			items.forEach((item) => {
-				if (item.vector.y < -5) {
-					this.itemManager.removeItem(item.id);
-					this.itemUpdateSinceLastEmit = true;
-				}
-			});
-
-			if (this.gamemode) this.gamemode.onPeriodicCleanup();
-		} catch (error) {
-			console.error('⚠ error in periodicCleanup:', error);
-		}
-	}
-
-	// Method to emit server info to all clients
-	public emitServerInfo() {
-		this.serverInfo.version = DataValidator.getServerVersion();
-		this.serverInfo.currentPlayers = this.playerManager.getAllPlayers().length;
-		this.io.emit('serverInfo', this.serverInfo);
-	}
-
-	public setGameMessage(player: Player, message: string, index: number, timeout?: number) {
-		player.gameMsgs[index] = message;
-		const extras = this.playerManager.getPlayerExtrasById(player.id);
-		if (timeout && timeout > 0 && extras) {
-			extras.gameMsgsTimeouts[index] = Date.now() / 1000 + timeout;
-		}
-	}
-
-	private initGamemode() {
-		try {
-			switch (config.game.mode) {
-				case 'ffa':
-					this.gamemode = new FFAGamemode(this);
-					break;
-				default:
-					console.log('⚠️ invalid gamemode supplied (check your config!)', config.game.mode);
-					break;
-			}
-		} catch (error) {
-			console.error('⚠ error initializing gamemode:', error);
-		}
-	}
+    private lastPlayerTickTimestamp: number = Date.now() / 1000;
+    private lastItemUpdateTimestamp: number = Date.now() / 1000;
+    public playerUpdateSinceLastEmit: boolean = false;
+    private itemUpdateSinceLastEmit: boolean = false;
+    private serverInfo: ServerInfo = new ServerInfo();
+    public gamemode: Gamemode | false = false;
+
+    constructor(
+        public playerManager: PlayerManager,
+        private itemManager: ItemManager,
+        public chatManager: ChatManager,
+        private damageSystem: DamageSystem,
+        private io: Server
+    ) {}
+
+    start() {
+        setInterval(() => this.serverTick(), 1000 / config.server.tickRate);
+        setInterval(() => this.periodicCleanup(), config.server.cleanupInterval);
+        setInterval(() => this.emitServerInfo(), config.server.cleanupInterval);
+        this.initGamemode();
+    }
+
+    private serverTick() {
+        try {
+            const currentTime = Date.now() / 1000;
+            this.playerManager.regenerateHealth();
+            this.itemManager.tick(currentTime);
+            if(this.gamemode) this.gamemode.tick();
+
+            // Emit player data if there are updates or enough time has passed
+            if (this.playerUpdateSinceLastEmit || currentTime - this.lastPlayerTickTimestamp > 1 / config.server.tickRate) {
+                try {
+                    this.io.emit('remotePlayerData', this.playerManager.getAllPlayers());
+                    this.playerUpdateSinceLastEmit = false;
+                    this.lastPlayerTickTimestamp = currentTime;
+                } catch (err) {
+                    console.error('⚠ error emitting player data:', err);
+                }
+            }
+
+            // Emit item data if there are updates
+            if (this.itemUpdateSinceLastEmit || this.itemManager.hasUpdates()) {
+                try {
+                    this.io.emit('worldItemData', this.itemManager.getAllItems());
+                    this.itemUpdateSinceLastEmit = false;
+                } catch (err) {
+                    console.error('⚠ error emitting item data:', err);
+                }
+            }
+        } catch (error) {
+            console.error('⚠ error in serverTick:', error);
+        }
+    }
+
+    public periodicCleanup() {
+        // for(const player of this.playerManager.getAllPlayers())
+        //         console.log(player.gameMsgs)
+
+        try {
+            const currentTime = Date.now() / 1000;
+            const players = this.playerManager.getAllPlayers();
+
+            players.forEach(player => {
+                if (player.position.y < -150) {
+                    player.health = 0;
+                    player.velocity = new Vector3(0, 0, 0);
+                    this.chatManager.broadcastChat(`${player.name} fell off :'(`);
+                    console.log(`💔 ${player.name}(${player.id}) fell off the map`);
+                }
+
+                if (player.health <= 0) {
+                    if(this.gamemode) this.gamemode.onPlayerDeath(player); //gamemode now handles
+                    else this.playerManager.respawnPlayer(player);
+                }
+
+                if ((player.updateTimestamp || 0) + config.player.disconnectTime < currentTime) {
+                    if(this.gamemode) this.gamemode.onPlayerDisconnect(player);
+                    console.log(`🟠 ${player.name}(${player.id}) left`);
+                    this.chatManager.broadcastChat(`${player.name} left`);
+                    this.playerManager.removePlayer(player.id);
+                }
+            });
+
+            const playerData = this.playerManager.getAllPlayerData();
+            playerData.forEach(playerData => {
+                for(let i = 0; i<playerData.extras.gameMsgsTimeouts.length; i++){
+                    if(playerData.extras.gameMsgsTimeouts[i] &&
+                        currentTime > playerData.extras.gameMsgsTimeouts[i] && playerData.extras.gameMsgsTimeouts[i] !== -1){
+
+                        playerData.player.gameMsgs[i] = '';
+                        playerData.extras.gameMsgsTimeouts[i] = -1;
+                        this.playerUpdateSinceLastEmit = true;
+
+                    }
+                }
+            });
+
+
+
+            const items = this.itemManager.getAllItems();
+            items.forEach(item => {
+                if (item.vector.y < -5) {
+                    this.itemManager.removeItem(item.id);
+                    this.itemUpdateSinceLastEmit = true;
+                }
+            });
+
+            if(this.gamemode) this.gamemode.onPeriodicCleanup();
+            
+        } catch (error) {
+            console.error('⚠ error in periodicCleanup:', error);
+        }
+    }
+
+    // Method to emit server info to all clients
+    public emitServerInfo() {
+        this.serverInfo.version = DataValidator.getServerVersion();
+        this.serverInfo.currentPlayers = this.playerManager.getAllPlayers().length;
+        this.io.emit('serverInfo', this.serverInfo);
+    }
+
+
+
+    public setGameMessage(player:Player, message:string, index:number, timeout?:number){
+        player.gameMsgs[index] = message;
+        const extras = this.playerManager.getPlayerExtrasById(player.id);
+        if(timeout && timeout > 0 && extras){
+            extras.gameMsgsTimeouts[index] = Date.now()/1000 + timeout;
+        }
+    }
+
+
+
+    private initGamemode(){
+        try {
+            switch(config.game.mode){
+                case 'ffa':
+                    this.gamemode = new FFAGamemode(this);
+                    break;
+                default:
+                    console.log('⚠️ invalid gamemode supplied (check your config!)', config.game.mode);
+                    break;
+            }
+        } catch (error) {
+            console.error('⚠ error initializing gamemode:', error);
+        }
+    }
 }
diff --git a/src/server/GameServer.ts b/src/server/GameServer.ts
index 674639e7..f597bc4c 100644
--- a/src/server/GameServer.ts
+++ b/src/server/GameServer.ts
@@ -1,7 +1,7 @@
-import { Application, Router, send } from '@oak/oak';
-import { Server, Socket } from 'https://deno.land/x/socket_io@0.2.0/mod.ts';
+import { Application, Router, send } from "@oak/oak";
+import { Server, Socket } from "https://deno.land/x/socket_io@0.2.0/mod.ts";
 import config from './config.ts';
-import { serve } from 'https://deno.land/std@0.150.0/http/server.ts';
+import { serve } from "https://deno.land/std@0.150.0/http/server.ts";
 
 import { GameEngine } from './GameEngine.ts';
 import { PlayerManager } from './managers/PlayerManager.ts';
@@ -9,153 +9,154 @@ import { ItemManager } from './managers/ItemManager.ts';
 import { ChatManager } from './managers/ChatManager.ts';
 import { DamageSystem } from './managers/DamageSystem.ts';
 import { MapData } from './models/MapData.ts';
-import { DataValidator } from './DataValidator.ts';
+import { DataValidator } from "./DataValidator.ts";
 
 export class GameServer {
-	router: Router = new Router();
-	app: Application = new Application();
-	io: Server = new Server();
-
-	gameEngine: GameEngine;
-	playerManager: PlayerManager;
-	itemManager: ItemManager;
-	chatManager: ChatManager;
-	damageSystem: DamageSystem;
-	mapData: MapData;
-
-	constructor() {
-		this.mapData = this.loadMapData();
-		this.playerManager = new PlayerManager(this.mapData);
-		this.chatManager = new ChatManager(this.io, this.playerManager);
-		this.itemManager = new ItemManager(this.mapData, this.playerManager, this.chatManager);
-		this.damageSystem = new DamageSystem(this.playerManager, this.chatManager);
-
-		this.playerManager.setItemManager(this.itemManager);
-
-		this.setupSocketIO();
-		this.setupRoutes();
-
-		this.gameEngine = new GameEngine(
-			this.playerManager,
-			this.itemManager,
-			this.chatManager,
-			this.damageSystem,
-			this.io,
-		);
-		this.itemManager.setGamemode(this.gameEngine.gamemode);
-		this.damageSystem.setGameEngine(this.gameEngine);
-		this.gameEngine.start();
-
-		DataValidator.updateServerVersion();
-		this.start();
-	}
-
-	private setupSocketIO() {
-		this.io.on('connection', (socket: Socket) => {
-			if (socket.connected) {
-				socket.on('error', (error) => {
-					console.error(`Socket error for ${socket.id}:`, error);
-				});
-
-				// deno-lint-ignore require-await
-				socket.on('playerData', async (data) => {
-					try {
-						const result = this.playerManager.addOrUpdatePlayer(data);
-						if (result.isNew && result.player) {
-							if (this.gameEngine.gamemode) this.gameEngine.gamemode.onPlayerConnect(result.player);
-							this.chatManager.broadcastChat(`${result.player.name} joined`);
-							console.log(`🟢 ${result.player.name}(${result.player.id}) joined`);
-							this.gameEngine.emitServerInfo();
-						}
-					} catch (err) {
-						console.error(`Error handling playerData:`, err);
-					}
-				});
-
-				// deno-lint-ignore require-await
-				socket.on('chatMsg', async (data) => {
-					try {
-						this.chatManager.handleChatMessage(data, socket);
-					} catch (err) {
-						console.error(`Error handling chat message:`, err);
-					}
-				});
-
-				socket.on('applyDamage', (data) => {
-					try {
-						this.damageSystem.handleDamageRequest(data);
-					} catch (err) {
-						console.error(`Error handling damage request:`, err);
-					}
-				});
-
-				socket.on('latencyTest', () => {
-					try {
-						socket.emit('latencyTest', 'response :)');
-					} catch (err) {
-						console.error(`Error handling latency test:`, err);
-					}
-				});
-
-				socket.on('disconnect', () => {
-					//console.log(`Socket disconnected: ${socket.id}, reason: ${reason}`); //reason is passed
-				});
-			}
-		});
-	}
-
-	private setupRoutes() {
-		this.router.get('/(.*)', async (context) => {
-			try {
-				await send(context, context.params[0], {
-					root: `${Deno.cwd()}/dist`,
-					index: 'index.html',
-				});
-			} catch {
-				try {
-					await send(context, 'index.html', {
-						root: `${Deno.cwd()}/dist`,
-					});
-				} catch (err) {
-					console.error('Error serving files:', err);
-					context.response.status = 500;
-					context.response.body = 'Internal Server Error';
-				}
-			}
-		});
-
-		this.app.use(this.router.routes());
-		this.app.use(this.router.allowedMethods());
-	}
-
-	private async start() {
-		try {
-			const handler = this.io.handler(async (req: Request) => {
-				try {
-					return await this.app.handle(req) || new Response(null, { status: 404 });
-				} catch (error) {
-					console.error('Request handler error:', error);
-					return new Response('Internal Server Error', { status: 500 });
-				}
-			});
-
-			await serve(handler, {
-				port: config.server.port,
-			});
-		} catch (error) {
-			console.error('Failed to start server:', error);
-			Deno.exit(1);
-		}
-	}
-
-	private loadMapData(): MapData {
-		try {
-			const mapJson = Deno.readTextFileSync(`./dist/maps/${config.server.defaultMap}/map.json`);
-			const mapObj = JSON.parse(mapJson);
-			return MapData.fromJSON(mapObj);
-		} catch (error) {
-			console.error('Failed to load map data:', error);
-			return new MapData('default_map', [], []);
-		}
-	}
+    router: Router = new Router();
+    app: Application = new Application();
+    io: Server = new Server();
+
+    gameEngine: GameEngine;
+    playerManager: PlayerManager;
+    itemManager: ItemManager;
+    chatManager: ChatManager;
+    damageSystem: DamageSystem;
+    mapData: MapData;
+
+    constructor() {
+        this.mapData = this.loadMapData();
+        this.playerManager = new PlayerManager(this.mapData);
+        this.chatManager = new ChatManager(this.io, this.playerManager);
+        this.itemManager = new ItemManager(this.mapData, this.playerManager, this.chatManager);
+        this.damageSystem = new DamageSystem(this.playerManager, this.chatManager);
+
+        this.playerManager.setItemManager(this.itemManager);
+
+        this.setupSocketIO();
+        this.setupRoutes();
+
+        this.gameEngine = new GameEngine(
+            this.playerManager,
+            this.itemManager,
+            this.chatManager,
+            this.damageSystem,
+            this.io
+        );
+        this.itemManager.setGamemode(this.gameEngine.gamemode);
+        this.damageSystem.setGameEngine(this.gameEngine);
+        this.gameEngine.start();
+
+        DataValidator.updateServerVersion();
+        this.start();
+    }
+
+    private setupSocketIO() {
+        this.io.on("connection", (socket: Socket) => {
+            if (socket.connected) {
+
+                socket.on("error", (error) => {
+                    console.error(`Socket error for ${socket.id}:`, error);
+                });
+
+                // deno-lint-ignore require-await
+                socket.on("playerData", async (data) => {
+                    try {
+                        const result = this.playerManager.addOrUpdatePlayer(data);
+                        if (result.isNew && result.player) {
+                            if(this.gameEngine.gamemode) this.gameEngine.gamemode.onPlayerConnect(result.player);
+                            this.chatManager.broadcastChat(`${result.player.name} joined`);
+                            console.log(`🟢 ${result.player.name}(${result.player.id}) joined`);
+                            this.gameEngine.emitServerInfo();
+                        }
+                    } catch (err) {
+                        console.error(`Error handling playerData:`, err);
+                    }
+                });
+
+                // deno-lint-ignore require-await
+                socket.on("chatMsg", async (data) => {
+                    try {
+                        this.chatManager.handleChatMessage(data, socket);
+                    } catch (err) {
+                        console.error(`Error handling chat message:`, err);
+                    }
+                });
+
+                socket.on("applyDamage", (data) => {
+                    try {
+                        this.damageSystem.handleDamageRequest(data);
+                    } catch (err) {
+                        console.error(`Error handling damage request:`, err);
+                    }
+                });
+
+                socket.on('latencyTest', () => {
+                    try {
+                        socket.emit('latencyTest', 'response :)');
+                    } catch (err) {
+                        console.error(`Error handling latency test:`, err);
+                    }
+                });
+
+                socket.on("disconnect", () => {
+                    //console.log(`Socket disconnected: ${socket.id}, reason: ${reason}`); //reason is passed
+                });
+            }
+        });
+    }
+
+    private setupRoutes() {
+        this.router.get("/(.*)", async (context) => {
+            try {
+                await send(context, context.params[0], {
+                    root: `${Deno.cwd()}/dist`,
+                    index: "index.html",
+                });
+            } catch {
+                try {
+                    await send(context, "index.html", {
+                        root: `${Deno.cwd()}/dist`,
+                    });
+                } catch (err) {
+                    console.error('Error serving files:', err);
+                    context.response.status = 500;
+                    context.response.body = "Internal Server Error";
+                }
+            }
+        });
+
+        this.app.use(this.router.routes());
+        this.app.use(this.router.allowedMethods());
+    }
+
+    private async start() {
+        try {
+            const handler = this.io.handler(async (req: Request) => {
+                try {
+                    return await this.app.handle(req) || new Response(null, { status: 404 });
+                } catch (error) {
+                    console.error('Request handler error:', error);
+                    return new Response('Internal Server Error', { status: 500 });
+                }
+            });
+
+            await serve(handler, {
+                port: config.server.port
+            });
+        } catch (error) {
+            console.error('Failed to start server:', error);
+            Deno.exit(1);
+        }
+    }
+
+    private loadMapData(): MapData {
+        try {
+            const mapJson = Deno.readTextFileSync(`./dist/maps/${config.server.defaultMap}/map.json`);
+            const mapObj = JSON.parse(mapJson);
+            return MapData.fromJSON(mapObj);
+        } catch (error) {
+            console.error("Failed to load map data:", error);
+            return new MapData('default_map', [], []);
+        }
+    }
 }
diff --git a/src/server/config.ts b/src/server/config.ts
index f040b7cb..98a9d726 100644
--- a/src/server/config.ts
+++ b/src/server/config.ts
@@ -1,90 +1,91 @@
 const defaults = {
-	// Server settings
-	SERVER_PORT: '3000',
-	SERVER_NAME: 'my-server',
-	SERVER_URL: 'https://example.com',
-	SERVER_DEFAULT_MAP: 'crackhouse_1',
-	SERVER_TICK_RATE: '15',
-	SERVER_CLEANUP_INTERVAL: '2000',
+    // Server settings
+    SERVER_PORT: '3000',
+    SERVER_NAME: 'my-server',
+    SERVER_URL: 'https://example.com',
+    SERVER_DEFAULT_MAP: 'crackhouse_1',
+    SERVER_TICK_RATE: '15',
+    SERVER_CLEANUP_INTERVAL: '2000',
 
-	// Player settings
-	PLAYER_DISCONNECT_TIME: '8',
-	PLAYER_AFK_KICK_TIME: '600',
-	PLAYER_MAX_HEALTH: '100',
-	PLAYER_BASE_INVENTORY: '[]',
+    // Player settings
+    PLAYER_DISCONNECT_TIME: '8',
+    PLAYER_AFK_KICK_TIME: '600',
+    PLAYER_MAX_HEALTH: '100',
+    PLAYER_BASE_INVENTORY: '[]',
 
-	//Game settings
-	GAME_MODE: 'ffa',
-	GAME_MAX_PLAYERS: '20',
+    //Game settings
+    GAME_MODE: 'ffa',
+    GAME_MAX_PLAYERS: '20',
 
-	// Health settings
-	HEALTH_REGEN_DELAY: '5',
-	HEALTH_REGEN_RATE: '3',
+    // Health settings
+    HEALTH_REGEN_DELAY: '5',
+    HEALTH_REGEN_RATE: '3',
 
-	//Item settings
-	MAX_ITEMS_IN_WORLD: '7',
-	ITEM_RESPAWN_TIME: '10',
+    //Item settings
+    MAX_ITEMS_IN_WORLD: '7',
+    ITEM_RESPAWN_TIME: '10',
 };
 
 async function updateEnvFile(defaults: Record<string, string>) {
-	const envPath = '.env';
-	const envExists = await Deno.stat(envPath).catch(() => false);
-	let currentEnv: Record<string, string> = {};
+    const envPath = '.env';
+    const envExists = await Deno.stat(envPath).catch(() => false);
+    let currentEnv: Record<string, string> = {};
 
-	if (envExists) {
-		const content = await Deno.readTextFile(envPath);
-		currentEnv = content.split('\n')
-			.filter((line) => line && !line.startsWith('#'))
-			.reduce((acc, line) => {
-				const [key, value] = line.split('=').map((s) => s.trim());
-				acc[key] = value.replace(/["']/g, '');
-				return acc;
-			}, {} as Record<string, string>);
-	}
+    if (envExists) {
+        const content = await Deno.readTextFile(envPath);
+        currentEnv = content.split('\n')
+            .filter(line => line && !line.startsWith('#'))
+            .reduce((acc, line) => {
+                const [key, value] = line.split('=').map(s => s.trim());
+                acc[key] = value.replace(/["']/g, '');
+                return acc;
+            }, {} as Record<string, string>);
+    }
 
-	const finalEnv = {
-		...defaults,
-		...currentEnv,
-	};
+    const finalEnv = {
+        ...defaults,
+        ...currentEnv
+    };
 
-	const envContent = Object.entries(finalEnv)
-		.map(([key, value]) => `${key}=${value}`)
-		.join('\n');
 
-	await Deno.writeTextFile(envPath, envContent);
-	return finalEnv;
+    const envContent = Object.entries(finalEnv)
+        .map(([key, value]) => `${key}=${value}`)
+        .join('\n');
+
+    await Deno.writeTextFile(envPath, envContent);
+    return finalEnv;
 }
 
 // Parse specific types from string values
 function parseConfig(env: Record<string, string>) {
-	return {
-		server: {
-			port: parseInt(env.SERVER_PORT),
-			name: env.SERVER_NAME,
-			url: env.SERVER_URL,
-			defaultMap: env.SERVER_DEFAULT_MAP,
-			tickRate: parseInt(env.SERVER_TICK_RATE),
-			cleanupInterval: parseInt(env.SERVER_CLEANUP_INTERVAL),
-		},
-		game: {
-			mode: env.GAME_MODE,
-			maxPlayers: parseInt(env.GAME_MAX_PLAYERS),
-		},
-		player: {
-			disconnectTime: parseInt(env.PLAYER_DISCONNECT_TIME),
-			afkKickTime: parseInt(env.PLAYER_AFK_KICK_TIME),
-			maxHealth: parseInt(env.PLAYER_MAX_HEALTH),
-			baseInventory: JSON.parse(env.PLAYER_BASE_INVENTORY) as number[],
-		},
-		health: {
-			regenDelay: parseInt(env.HEALTH_REGEN_DELAY),
-			regenRate: parseInt(env.HEALTH_REGEN_RATE),
-		},
-		items: {
-			maxItemsInWorld: parseInt(env.MAX_ITEMS_IN_WORLD),
-			respawnTime: parseInt(env.ITEM_RESPAWN_TIME),
-		},
-	};
+    return {
+        server: {
+            port: parseInt(env.SERVER_PORT),
+            name: env.SERVER_NAME,
+            url: env.SERVER_URL,
+            defaultMap: env.SERVER_DEFAULT_MAP,
+            tickRate: parseInt(env.SERVER_TICK_RATE),
+            cleanupInterval: parseInt(env.SERVER_CLEANUP_INTERVAL)
+        },
+        game: {
+            mode: env.GAME_MODE,
+            maxPlayers: parseInt(env.GAME_MAX_PLAYERS)
+        },
+        player: {
+            disconnectTime: parseInt(env.PLAYER_DISCONNECT_TIME),
+            afkKickTime: parseInt(env.PLAYER_AFK_KICK_TIME),
+            maxHealth: parseInt(env.PLAYER_MAX_HEALTH),
+            baseInventory: JSON.parse(env.PLAYER_BASE_INVENTORY) as number[]
+        },
+        health: {
+            regenDelay: parseInt(env.HEALTH_REGEN_DELAY),
+            regenRate: parseInt(env.HEALTH_REGEN_RATE)
+        },
+        items: {
+            maxItemsInWorld: parseInt(env.MAX_ITEMS_IN_WORLD),
+            respawnTime: parseInt(env.ITEM_RESPAWN_TIME)
+        }
+    };
 }
 
 const rawConfig = await updateEnvFile(defaults);
diff --git a/src/server/gamemodes/FFAGamemode.ts b/src/server/gamemodes/FFAGamemode.ts
index aff8ff67..70f61183 100644
--- a/src/server/gamemodes/FFAGamemode.ts
+++ b/src/server/gamemodes/FFAGamemode.ts
@@ -1,120 +1,124 @@
-import { GameEngine } from '../GameEngine.ts';
-import { Player } from '../models/Player.ts';
-import { Gamemode } from './Gamemode.ts';
-import config from '../config.ts';
+import {GameEngine} from "../GameEngine.ts";
+import {Player} from "../models/Player.ts";
+import {Gamemode} from "./Gamemode.ts";
+import config from "../config.ts";
+
 
 export class FFAGamemode extends Gamemode {
-	private spectateTimeouts: Map<Player, number> = new Map();
-	constructor(gameEngine: GameEngine) {
-		super(gameEngine);
-		this.init();
-	}
-
-	init(): void {
-		console.log('🐙 FFA Gamemode initialized');
-	}
-
-	tick(): void {
-		const currentTime = Date.now() / 1000;
-		for (const [player, timestamp] of this.spectateTimeouts) {
-			if (currentTime - timestamp > 10) {
-				this.gameEngine.playerManager.respawnPlayer(player);
-				player.playerSpectating = -1;
-				this.spectateTimeouts.delete(player);
-				this.gameEngine.setGameMessage(player, '', 0);
-			} else {
-				this.gameEngine.setGameMessage(
-					player,
-					'&crespawn in ' + Math.floor(10 + timestamp - currentTime) + ' seconds',
-					1,
-					0.5,
-				);
-			}
-			player.health = config.player.maxHealth;
-		}
-	}
-
-	onPeriodicCleanup(): void {
-		// send kill death stats to all players
-		for (const player of this.gameEngine.playerManager.getAllPlayers()) {
-			const extras = this.gameEngine.playerManager.getPlayerExtrasById(player.id);
-			if (extras) player.gameMsgs2 = ['&7' + extras.kills + ' kills, ' + extras.deaths + ' deaths'];
-		}
-	}
-
-	onPlayerConnect(_player: Player): void {
-	}
-
-	onPlayerDisconnect(_player: Player): void {
-	}
-
-	onPlayerDeath(player: Player): void {
-		const extras = this.gameEngine.playerManager.getPlayerExtrasById(player.id);
-		if (extras) {
-			extras.deaths++;
-			extras.killStreak = 0;
-		}
-
-		if (
-			player.lastDamageTime && player.idLastDamagedBy &&
-			Date.now() / 1000 - player.lastDamageTime < 5
-		) {
-			const killer = this.gameEngine.playerManager.getPlayerById(player.idLastDamagedBy);
-			if (killer) {
-				// Redirect spectators of the dead player to the killer
-				for (const otherPlayer of this.gameEngine.playerManager.getAllPlayers()) {
-					if (otherPlayer.playerSpectating === player.id) {
-						otherPlayer.playerSpectating = killer.id;
-						this.gameEngine.setGameMessage(otherPlayer, '&cspectating ' + killer.name, 0, 10);
-					}
-				}
-
-				// Set the dead player to spectate the killer
-				player.playerSpectating = player.idLastDamagedBy;
-				player.health = config.player.maxHealth;
-				this.gameEngine.playerManager.dropAllItems(player);
-
-				this.gameEngine.setGameMessage(player, '&cspectating ' + killer.name, 0, 10);
-				this.gameEngine.setGameMessage(player, '&crespawn in 10 seconds', 1, 2);
-				this.gameEngine.setGameMessage(killer, '&akilled ' + player.name, 0, 5);
-
-				// Add the dead player to the spectate timeout list
-				this.spectateTimeouts.set(player, Date.now() / 1000);
-				this.gameEngine.playerUpdateSinceLastEmit = true;
-
-				this.onPlayerKill(killer);
-			} else {
-				// Respawn the player if no killer is found
-				this.gameEngine.playerManager.respawnPlayer(player);
-			}
-		} else {
-			// Respawn the player if no valid killer is found
-			this.gameEngine.playerManager.respawnPlayer(player);
-		}
-	}
-
-	onPlayerKill(player: Player) {
-		const extras = this.gameEngine.playerManager.getPlayerExtrasById(player.id);
-		if (extras) {
-			extras.kills++;
-			extras.killStreak++;
-
-			let colorCode = '&a';
-			if (extras.killStreak >= 5) colorCode = '&b';
-			if (extras.killStreak >= 10) colorCode = '&6';
-			if (extras.killStreak >= 15) colorCode = '&g';
-
-			if (extras.killStreak >= 3) {
-				this.gameEngine.setGameMessage(player, colorCode + extras.killStreak + ' kill streak', 1, 5);
-			}
-			if (extras.killStreak >= 5) {
-				this.gameEngine.chatManager.broadcastChat(
-					colorCode + player.name + ' is on a ' + extras.killStreak + ' kill streak',
-				);
-			}
-		}
-	}
-
-	onItemPickup(_player: Player): void {
-	}
+
+    private spectateTimeouts: Map<Player,number> = new Map();
+    constructor(gameEngine: GameEngine) {
+        super(gameEngine);
+        this.init();
+    }
+
+    init(): void {
+        console.log('🐙 FFA Gamemode initialized');
+    }
+
+    tick(): void {
+        const currentTime = Date.now() / 1000;
+        for (const [player, timestamp] of this.spectateTimeouts) {
+            if (currentTime - timestamp > 10) {
+                this.gameEngine.playerManager.respawnPlayer(player);
+                player.playerSpectating = -1;
+                this.spectateTimeouts.delete(player);
+                this.gameEngine.setGameMessage(player, '', 0);
+            }else{
+                this.gameEngine.setGameMessage(player, '&crespawn in ' + Math.floor(10 + timestamp - currentTime)+ ' seconds', 1, 0.5);
+            }
+            player.health = config.player.maxHealth;
+
+
+        }
+    }
+
+    onPeriodicCleanup(): void {
+        // send kill death stats to all players
+        for (const player of this.gameEngine.playerManager.getAllPlayers()) {
+            const extras = this.gameEngine.playerManager.getPlayerExtrasById(player.id);
+            if(extras) player.gameMsgs2 = ['&7'+extras.kills + ' kills, ' + extras.deaths + ' deaths'];
+        }
+
+
+    }
+
+    onPlayerConnect(_player: Player): void {
+    }
+
+    onPlayerDisconnect(_player: Player): void {
+    }
+
+    onPlayerDeath(player: Player): void {
+        const extras = this.gameEngine.playerManager.getPlayerExtrasById(player.id);
+        if(extras) {extras.deaths++; extras.killStreak = 0;}
+
+        if (player.lastDamageTime && player.idLastDamagedBy &&
+            Date.now() / 1000 - player.lastDamageTime < 5) {
+            const killer = this.gameEngine.playerManager.getPlayerById(player.idLastDamagedBy);
+            if (killer) {
+                // Redirect spectators of the dead player to the killer
+                for (const otherPlayer of this.gameEngine.playerManager.getAllPlayers()) {
+                    if (otherPlayer.playerSpectating === player.id) {
+                        otherPlayer.playerSpectating = killer.id;
+                        this.gameEngine.setGameMessage(otherPlayer, '&cspectating ' + killer.name, 0, 10);
+                    }
+                }
+
+
+                // Set the dead player to spectate the killer
+                player.playerSpectating = player.idLastDamagedBy;
+                player.health = config.player.maxHealth;
+                this.gameEngine.playerManager.dropAllItems(player);
+
+                this.gameEngine.setGameMessage(player, '&cspectating ' + killer.name, 0, 10);
+                this.gameEngine.setGameMessage(player, '&crespawn in 10 seconds', 1, 2);
+                this.gameEngine.setGameMessage(killer, '&akilled ' + player.name, 0, 5);
+
+                // Add the dead player to the spectate timeout list
+                this.spectateTimeouts.set(player, Date.now() / 1000);
+                this.gameEngine.playerUpdateSinceLastEmit = true;
+
+                this.onPlayerKill(killer);
+
+
+            } else {
+                // Respawn the player if no killer is found
+                this.gameEngine.playerManager.respawnPlayer(player);
+            }
+        } else {
+            // Respawn the player if no valid killer is found
+            this.gameEngine.playerManager.respawnPlayer(player);
+        }
+
+
+    }
+
+    onPlayerKill(player:Player){
+        const extras = this.gameEngine.playerManager.getPlayerExtrasById(player.id);
+        if(extras) {
+            extras.kills++;
+            extras.killStreak++;
+
+            let colorCode = '&a';
+            if(extras.killStreak >= 5) colorCode = '&b';
+            if(extras.killStreak >= 10) colorCode = '&6';
+            if(extras.killStreak >= 15) colorCode = '&g';
+
+            if(extras.killStreak >= 3)
+                this.gameEngine.setGameMessage(player, colorCode+extras.killStreak+' kill streak', 1, 5);
+            if(extras.killStreak >= 5)
+                this.gameEngine.chatManager.broadcastChat(colorCode+player.name+' is on a '+extras.killStreak+' kill streak');
+
+
+        }
+    }
+
+
+
+
+    onItemPickup(_player: Player): void {
+    }
+
+
 }
diff --git a/src/server/gamemodes/Gamemode.ts b/src/server/gamemodes/Gamemode.ts
index 10326525..9df17da8 100644
--- a/src/server/gamemodes/Gamemode.ts
+++ b/src/server/gamemodes/Gamemode.ts
@@ -1,20 +1,24 @@
-import { GameEngine } from '../GameEngine.ts';
-import { Player } from '../models/Player.ts';
+import {GameEngine} from "../GameEngine.ts";
+import {Player} from "../models/Player.ts";
 
 export abstract class Gamemode {
-	constructor(protected gameEngine: GameEngine) {}
 
-	abstract init(): void;
+    constructor(protected gameEngine: GameEngine) {}
 
-	abstract tick(): void;
+    abstract init(): void;
 
-	abstract onPeriodicCleanup(): void;
+    abstract tick(): void;
 
-	abstract onPlayerConnect(player: Player): void;
+    abstract onPeriodicCleanup(): void;
 
-	abstract onPlayerDisconnect(player: Player): void;
+    abstract onPlayerConnect(player:Player): void;
 
-	abstract onPlayerDeath(player: Player): void;
+    abstract onPlayerDisconnect(player:Player): void;
 
-	abstract onItemPickup(player: Player): void;
-}
+    abstract onPlayerDeath(player:Player): void;
+
+    abstract onItemPickup(player:Player): void;
+
+
+
+}   
\ No newline at end of file
diff --git a/src/server/managers/ChatManager.ts b/src/server/managers/ChatManager.ts
index 825a80e6..329cd149 100644
--- a/src/server/managers/ChatManager.ts
+++ b/src/server/managers/ChatManager.ts
@@ -1,87 +1,86 @@
-import { Server, Socket } from 'https://deno.land/x/socket_io@0.2.0/mod.ts';
-import { DataValidator } from '../DataValidator.ts';
+import { Server, Socket } from "https://deno.land/x/socket_io@0.2.0/mod.ts";
+import { DataValidator } from "../DataValidator.ts";
 import { ChatMessage } from '../models/ChatMessage.ts';
-import { PlayerManager } from './PlayerManager.ts';
+import {PlayerManager} from "./PlayerManager.ts";
+
 
 export class ChatManager {
-	constructor(private io: Server, private playerManager: PlayerManager) {}
+    constructor(private io: Server, private playerManager:PlayerManager) {}
 
-	handleChatMessage(data: ChatMessage, socket: Socket) {
-		const { error } = DataValidator.validateChatMessage(data);
-		if (error) {
-			console.warn(`Invalid chat message: ${error.message}`);
-			return;
-		}
+    handleChatMessage(data: ChatMessage, socket: Socket) {
+        const { error } = DataValidator.validateChatMessage(data);
+        if (error) {
+            console.warn(`Invalid chat message: ${error.message}`);
+            return;
+        }
 
-		const isCommand = this.parseCommand(data.message, socket, data.id);
-		if (!isCommand) {
-			if (data.message.startsWith('>')) data.message = '&2' + data.message;
-			console.log(`💬 ${data.name}: ${data.message}`);
-			this.io.emit('chatMsg', data);
-		}
-	}
+        const isCommand = this.parseCommand(data.message, socket, data.id);
+        if (!isCommand) {
+            if(data.message.startsWith('>')) data.message = '&2'+data.message;
+            console.log(`💬 ${data.name}: ${data.message}`);
+            this.io.emit('chatMsg', data);
+        }
+    }
 
-	private parseCommand(message: string, socket: Socket, playerId: number): boolean {
-		if (message.charAt(0) !== '/') return false;
+    private parseCommand(message: string, socket: Socket, playerId: number): boolean {
+        if (message.charAt(0) !== '/') return false;
 
-		const args = message.slice(1).split(' ');
-		const command = args.shift()?.toLowerCase();
+        const args = message.slice(1).split(' ');
+        const command = args.shift()?.toLowerCase();
 
-		switch (command) {
-			case 'help':
-				this.whisperChatMessage(message + ` -> nah i'm good`, socket);
-				break;
-			case 'kill': {
-				const player = this.playerManager.getPlayerById(playerId);
-				if (player) {
-					this.playerManager.respawnPlayer(player);
-				}
-				this.broadcastChat(`${this.playerManager.getPlayerById(playerId)?.name} killed himself`);
-				break;
-			}
-			case 'thumbsup':
-				this.broadcastChat(`${this.playerManager.getPlayerById(playerId)?.name}: 👍`);
-				break;
-			case 'thumbsdown':
-				this.broadcastChat(`${this.playerManager.getPlayerById(playerId)?.name}: 👎`);
-				break;
-			case 'octopus':
-				this.broadcastChat(`${this.playerManager.getPlayerById(playerId)?.name}: 🐙`);
-				break;
-			case 'ping':
-				this.whisperChatMessage(message + ' -> pong!', socket);
-				break;
-			case 'version':
-				this.whisperChatMessage(message + ` -> candiru ${DataValidator.getServerVersion()}`, socket);
-				break;
-			case 'clear':
-				for (let i = 0; i < 25; i++) {
-					this.whisperChatMessage(' ', socket);
-				}
-				this.whisperChatMessage(message + ' -> cleared chat', socket);
-				break;
-			default:
-				this.whisperChatMessage(message + ' -> unknown command', socket);
-		}
+        switch (command) {
+            case 'help':
+                this.whisperChatMessage(message + ` -> nah i'm good`, socket);
+                break;
+            case 'kill':{
+                const player = this.playerManager.getPlayerById(playerId);
+                if(player)
+                    this.playerManager.respawnPlayer(player);
+                this.broadcastChat(`${this.playerManager.getPlayerById(playerId)?.name} killed himself`);
+               break;
+            }
+            case 'thumbsup':
+                this.broadcastChat(`${this.playerManager.getPlayerById(playerId)?.name}: 👍`);
+                break;
+            case 'thumbsdown':
+                this.broadcastChat(`${this.playerManager.getPlayerById(playerId)?.name}: 👎`);
+                break;
+            case 'octopus':
+                this.broadcastChat(`${this.playerManager.getPlayerById(playerId)?.name}: 🐙`);
+                break;
+            case 'ping':
+                this.whisperChatMessage(message + ' -> pong!', socket);
+                break;
+            case 'version':
+                this.whisperChatMessage(message + ` -> candiru ${DataValidator.getServerVersion()}`, socket);
+                break;
+            case 'clear':
+                for(let i = 0; i < 25; i++)
+                    this.whisperChatMessage(' ', socket);
+                this.whisperChatMessage(message + ' -> cleared chat', socket);
+                break;
+            default:
+                this.whisperChatMessage(message +' -> unknown command', socket);
+        }
 
-		return true;
-	}
+        return true;
+    }
 
-	broadcastChat(message: string) {
-		const chatMessage: ChatMessage = {
-			id: -1,
-			name: '',
-			message,
-		};
-		this.io.emit('chatMsg', chatMessage);
-	}
+    broadcastChat(message: string) {
+        const chatMessage: ChatMessage = {
+            id: -1,
+            name: '',
+            message,
+        };
+        this.io.emit('chatMsg', chatMessage);
+    }
 
-	whisperChatMessage(message: string, socket: Socket) {
-		const chatMessage: ChatMessage = {
-			id: -1,
-			name: '',
-			message,
-		};
-		socket.emit('chatMsg', chatMessage);
-	}
-}
+    whisperChatMessage(message: string, socket: Socket) {
+        const chatMessage: ChatMessage = {
+            id: -1,
+            name: '',
+            message,
+        };
+        socket.emit('chatMsg', chatMessage);
+    }
+}
\ No newline at end of file
diff --git a/src/server/managers/DamageSystem.ts b/src/server/managers/DamageSystem.ts
index 4f77d0b0..d079ac1f 100644
--- a/src/server/managers/DamageSystem.ts
+++ b/src/server/managers/DamageSystem.ts
@@ -1,68 +1,68 @@
-import { PlayerManager } from './PlayerManager.ts';
-import { ChatManager } from './ChatManager.ts';
-import { DamageRequest } from '../models/DamageRequest.ts';
-import { DataValidator } from '../DataValidator.ts';
-import { Vector3 } from '../models/Vector3.ts';
-import { GameEngine } from '../GameEngine.ts';
+import { PlayerManager } from "./PlayerManager.ts";
+import { ChatManager } from "./ChatManager.ts";
+import { DamageRequest } from "../models/DamageRequest.ts";
+import { DataValidator } from "../DataValidator.ts";
+import { Vector3 } from "../models/Vector3.ts";
+import {GameEngine} from "../GameEngine.ts";
 
 export class DamageSystem {
-	constructor(
-		private playerManager: PlayerManager,
-		private chatManager: ChatManager,
-	) {}
-	private gameEngine!: GameEngine;
-	public setGameEngine(gameEngine: GameEngine) {
-		this.gameEngine = gameEngine;
-	}
+    constructor(
+        private playerManager: PlayerManager,
+        private chatManager: ChatManager,
+    ) {}
+    private gameEngine!:GameEngine;
+    public setGameEngine(gameEngine:GameEngine){
+        this.gameEngine = gameEngine;
+    }
 
-	handleDamageRequest(data: DamageRequest) {
-		const validationResult = DataValidator.validateDamageRequest(data);
-		if (!validationResult.success) {
-			console.warn(`Invalid damage request: ${validationResult.error?.message}`);
-			return;
-		}
+    handleDamageRequest(data: DamageRequest) {
+        const validationResult = DataValidator.validateDamageRequest(data);
+        if (!validationResult.success) {
+            console.warn(`Invalid damage request: ${validationResult.error?.message}`);
+            return;
+        }
 
-		const targetPlayer = this.playerManager.getPlayerById(data.targetPlayer.id);
-		const localPlayer = this.playerManager.getPlayerById(data.localPlayer.id);
+        const targetPlayer = this.playerManager.getPlayerById(data.targetPlayer.id);
+        const localPlayer = this.playerManager.getPlayerById(data.localPlayer.id);
 
-		if (!targetPlayer || !localPlayer) {
-			console.warn('Target or local player not found.');
-			return;
-		}
+        if (!targetPlayer || !localPlayer) {
+            console.warn('Target or local player not found.');
+            return;
+        }
 
-		// Validate positions
-		const localPlayerSentPosition = data.localPlayer.position;
-		const localPlayerServerPosition = localPlayer.position;
-		const localDistance = Vector3.distanceTo(localPlayerSentPosition, localPlayerServerPosition);
+        // Validate positions
+        const localPlayerSentPosition = data.localPlayer.position;
+        const localPlayerServerPosition = localPlayer.position;
+        const localDistance = Vector3.distanceTo(localPlayerSentPosition, localPlayerServerPosition);
 
-		const targetPlayerSentPosition = data.targetPlayer.position;
-		const targetPlayerServerPosition = targetPlayer.position;
-		const targetDistance = Vector3.distanceTo(targetPlayerSentPosition, targetPlayerServerPosition);
+        const targetPlayerSentPosition = data.targetPlayer.position;
+        const targetPlayerServerPosition = targetPlayer.position;
+        const targetDistance = Vector3.distanceTo(targetPlayerSentPosition, targetPlayerServerPosition);
 
-		const MAX_DESYNC_DISTANCE = 1; // Threshold for considering positions in sync
+        const MAX_DESYNC_DISTANCE = 1; // Threshold for considering positions in sync
 
-		if (localDistance > MAX_DESYNC_DISTANCE || targetDistance > MAX_DESYNC_DISTANCE) {
-			//console.warn(`⚠️ Client out of sync - localDistance: ${localDistance}, targetDistance: ${targetDistance}`);
-			// Optionally, send a message back to the client
-			// this.chatManager.whisperChatMessage('⚠️ Shot not registered (client out of sync)', localPlayer.socket);
-			return;
-		}
+        if (localDistance > MAX_DESYNC_DISTANCE || targetDistance > MAX_DESYNC_DISTANCE) {
+            //console.warn(`⚠️ Client out of sync - localDistance: ${localDistance}, targetDistance: ${targetDistance}`);
+            // Optionally, send a message back to the client
+            // this.chatManager.whisperChatMessage('⚠️ Shot not registered (client out of sync)', localPlayer.socket);
+            return;
+        }
 
-		// Apply damage
-		targetPlayer.health -= data.damage;
-		targetPlayer.lastDamageTime = Date.now() / 1000;
-		targetPlayer.idLastDamagedBy = localPlayer.id;
+        // Apply damage
+        targetPlayer.health -= data.damage;
+        targetPlayer.lastDamageTime = Date.now() / 1000;
+        targetPlayer.idLastDamagedBy = localPlayer.id;
 
-		if (targetPlayer.health <= 0) {
-			const killerName = localPlayer.name;
-			const killedName = targetPlayer.name;
-			//this.chatManager.broadcastChat(`${killerName} &fkilled ${killedName}`);
-			console.log(`💔 ${killerName} killed ${killedName}`);
-			//this.playerManager.respawnPlayer(targetPlayer);
-			this.gameEngine.periodicCleanup();
-		}
+        if (targetPlayer.health <= 0) {
+            const killerName = localPlayer.name;
+            const killedName = targetPlayer.name;
+            //this.chatManager.broadcastChat(`${killerName} &fkilled ${killedName}`);
+            console.log(`💔 ${killerName} killed ${killedName}`);
+            //this.playerManager.respawnPlayer(targetPlayer);
+            this.gameEngine.periodicCleanup();
+        }
 
-		// Update player data
-		this.playerManager.addOrUpdatePlayer(targetPlayer);
-	}
-}
+        // Update player data
+        this.playerManager.addOrUpdatePlayer(targetPlayer);
+    }
+}
\ No newline at end of file
diff --git a/src/server/managers/GameMsgManager.ts b/src/server/managers/GameMsgManager.ts
index 7c8dd390..96ab7fa3 100644
--- a/src/server/managers/GameMsgManager.ts
+++ b/src/server/managers/GameMsgManager.ts
@@ -1,4 +1,4 @@
 export class GameMsgManager {
-	constructor() {
-	}
-}
+    constructor() {
+    }
+}
\ No newline at end of file
diff --git a/src/server/managers/ItemManager.ts b/src/server/managers/ItemManager.ts
index 78fcd2a2..3b75d9bb 100644
--- a/src/server/managers/ItemManager.ts
+++ b/src/server/managers/ItemManager.ts
@@ -1,122 +1,124 @@
 import { WorldItem } from '../models/WorldItem.ts';
 import { MapData } from '../models/MapData.ts';
 import { Vector3 } from '../models/Vector3.ts';
-import config from '../config.ts';
-import { PlayerManager } from './PlayerManager.ts';
-import { ChatManager } from './ChatManager.ts';
-import { Gamemode } from '../gamemodes/Gamemode.ts';
+import config from "../config.ts";
+import {PlayerManager} from "./PlayerManager.ts";
+import {ChatManager} from "./ChatManager.ts";
+import {Gamemode} from "../gamemodes/Gamemode.ts";
+
 
 export class ItemManager {
-	private worldItems: WorldItem[] = [];
-	private lastItemCreationTimestamp: number = Date.now() / 1000;
-	private itemUpdateFlag: boolean = false;
-	private gamemode: Gamemode | false = false;
-
-	constructor(private mapData: MapData, public playerManager: PlayerManager, private chatManager: ChatManager) {}
-
-	public setGamemode(gamemode: Gamemode | false) {
-		this.gamemode = gamemode;
-	}
-
-	tick(currentTime: number) {
-		this.checkForPickups();
-		if (currentTime - this.lastItemCreationTimestamp > config.items.respawnTime) {
-			this.createItem();
-			this.lastItemCreationTimestamp = currentTime;
-		}
-		// Additional item-related logic can be added here
-	}
-
-	createItem() {
-		if (!this.mapData) return;
-
-		const randomIndex = Math.floor(Math.random() * this.mapData.itemRespawnPoints.length);
-		const respawnPoint = this.mapData.itemRespawnPoints[randomIndex];
-		const newItem = new WorldItem(
-			new Vector3(respawnPoint.position.x, respawnPoint.position.y, respawnPoint.position.z),
-			respawnPoint.itemId,
-		);
-
-		if (this.isItemCloseToPoint(newItem.vector, 1)) return; // Another item is too close
-		if (this.worldItems.length >= config.items.maxItemsInWorld) return; // Max items reached
-
-		this.worldItems.push(newItem);
-		this.itemUpdateFlag = true;
-	}
-
-	pushItem(item: WorldItem) {
-		this.worldItems.push(item);
-		this.itemUpdateFlag = true;
-	}
-
-	checkForPickups() {
-		const players = this.playerManager.getAllPlayers();
-
-		for (const player of players) {
-			if (player.playerSpectating !== -1) continue;
-			if (player.health <= 0) continue;
-			const itemIndex = this.worldItems.findIndex((item) => Vector3.distanceTo(player.position, item.vector) < 0.5);
-
-			if (itemIndex === -1) continue;
-
-			const item = this.worldItems[itemIndex];
-			let shouldPickup = false;
-
-			switch (item.itemType) {
-				case 0: // Cube
-					player.inventory.push(0);
-					shouldPickup = true;
-					this.chatManager.broadcastChat(`${player.name} picked up [Object]!`);
-					console.log(`🍌 ${player.name} picked up cube!`);
-					break;
-
-				case 1: // Banana
-					if (!player.inventory.includes(1)) {
-						player.inventory.push(1);
-						shouldPickup = true;
-						console.log(`🍌 ${player.name} picked up banana!`);
-					}
-					break;
-
-				case 2: // Fish
-					if (!player.inventory.includes(2)) {
-						player.inventory.push(2);
-						shouldPickup = true;
-						console.log(`🍌 ${player.name} picked up fish!`);
-					}
-					break;
-			}
-
-			if (shouldPickup) {
-				if (this.gamemode) this.gamemode.onItemPickup(player);
-				this.worldItems.splice(itemIndex, 1);
-				this.itemUpdateFlag = true;
-			}
-		}
-	}
-
-	isItemCloseToPoint(vector: Vector3, distance: number): boolean {
-		return this.worldItems.some((item) => Vector3.distanceTo(item.vector, vector) < distance);
-	}
-
-	getAllItems(): WorldItem[] {
-		return this.worldItems;
-	}
-
-	removeItem(itemId: number) {
-		this.worldItems = this.worldItems.filter((item) => item.id !== itemId);
-		this.itemUpdateFlag = true;
-	}
-
-	hasUpdates(): boolean {
-		if (this.itemUpdateFlag) {
-			this.itemUpdateFlag = false;
-			return true;
-		}
-		return false;
-	}
-
-	triggerUpdateFlag() {
-		this.itemUpdateFlag = true;
-	}
-}
+    private worldItems: WorldItem[] = [];
+    private lastItemCreationTimestamp: number = Date.now() / 1000;
+    private itemUpdateFlag: boolean = false;
+    private gamemode: Gamemode | false = false;
+
+    constructor(private mapData: MapData, public playerManager:PlayerManager, private chatManager:ChatManager) {}
+
+    public setGamemode(gamemode: Gamemode | false) {this.gamemode = gamemode;}
+
+    tick(currentTime: number) {
+        this.checkForPickups();
+        if (currentTime - this.lastItemCreationTimestamp > config.items.respawnTime) {
+            this.createItem();
+            this.lastItemCreationTimestamp = currentTime;
+        }
+        // Additional item-related logic can be added here
+    }
+
+    createItem() {
+        if (!this.mapData) return;
+
+        const randomIndex = Math.floor(Math.random() * this.mapData.itemRespawnPoints.length);
+        const respawnPoint = this.mapData.itemRespawnPoints[randomIndex];
+        const newItem = new WorldItem(
+            new Vector3(respawnPoint.position.x, respawnPoint.position.y, respawnPoint.position.z),
+            respawnPoint.itemId
+        );
+
+        if (this.isItemCloseToPoint(newItem.vector, 1)) return; // Another item is too close
+        if (this.worldItems.length >= config.items.maxItemsInWorld) return; // Max items reached
+
+        this.worldItems.push(newItem);
+        this.itemUpdateFlag = true;
+    }
+
+    pushItem(item: WorldItem) {
+        this.worldItems.push(item);
+        this.itemUpdateFlag = true;
+    }
+
+    checkForPickups() {
+        const players = this.playerManager.getAllPlayers();
+
+        for (const player of players) {
+            if(player.playerSpectating !== -1) continue;
+            if(player.health <= 0) continue;
+            const itemIndex = this.worldItems.findIndex(item =>
+               Vector3.distanceTo(player.position, item.vector) < 0.5
+            );
+
+            if (itemIndex === -1) continue;
+
+            const item = this.worldItems[itemIndex];
+            let shouldPickup = false;
+
+            switch (item.itemType) {
+                case 0: // Cube
+                    player.inventory.push(0);
+                    shouldPickup = true;
+                    this.chatManager.broadcastChat(`${player.name} picked up [Object]!`);
+                    console.log(`🍌 ${player.name} picked up cube!`);
+                    break;
+
+                case 1: // Banana
+                    if (!player.inventory.includes(1)) {
+                        player.inventory.push(1);
+                        shouldPickup = true;
+                        console.log(`🍌 ${player.name} picked up banana!`);
+                    }
+                    break;
+
+                case 2: // Fish
+                    if (!player.inventory.includes(2)) {
+                        player.inventory.push(2);
+                        shouldPickup = true;
+                        console.log(`🍌 ${player.name} picked up fish!`);
+                    }
+                    break;
+            }
+
+            if (shouldPickup) {
+                if(this.gamemode) this.gamemode.onItemPickup(player);
+                this.worldItems.splice(itemIndex, 1);
+                this.itemUpdateFlag = true;
+            }
+        }
+    }
+
+
+    isItemCloseToPoint(vector: Vector3, distance: number): boolean {
+        return this.worldItems.some(item => Vector3.distanceTo(item.vector,vector) < distance);
+    }
+
+    getAllItems(): WorldItem[] {
+        return this.worldItems;
+    }
+
+    removeItem(itemId: number) {
+        this.worldItems = this.worldItems.filter(item => item.id !== itemId);
+        this.itemUpdateFlag = true;
+    }
+
+    hasUpdates(): boolean {
+        if (this.itemUpdateFlag) {
+            this.itemUpdateFlag = false;
+            return true;
+        }
+        return false;
+    }
+
+    triggerUpdateFlag(){
+        this.itemUpdateFlag = true;
+    }
+}
\ No newline at end of file
diff --git a/src/server/managers/PlayerManager.ts b/src/server/managers/PlayerManager.ts
index 86b4fa37..5e041515 100644
--- a/src/server/managers/PlayerManager.ts
+++ b/src/server/managers/PlayerManager.ts
@@ -1,171 +1,162 @@
-import { Player } from '../models/Player.ts';
+import { Player } from "../models/Player.ts";
 import { Vector3 } from '../models/Vector3.ts';
 import { Quaternion } from '../models/Quaternion.ts';
 import { DataValidator } from '../DataValidator.ts';
-import { MapData } from '../models/MapData.ts';
-import config from '../config.ts';
-import { WorldItem } from '../models/WorldItem.ts';
-import { ItemManager } from './ItemManager.ts';
-import { PlayerExtras } from '../models/PlayerExtras.ts';
+import { MapData } from "../models/MapData.ts";
+import config from "../config.ts";
+import { WorldItem } from "../models/WorldItem.ts";
+import { ItemManager } from "./ItemManager.ts";
+import {PlayerExtras} from "../models/PlayerExtras.ts";
 
 interface PlayerData {
-	player: Player;
-	extras: PlayerExtras;
+    player: Player;
+    extras: PlayerExtras;
 }
 
 export class PlayerManager {
-	private players: Map<number, PlayerData> = new Map();
-	private mapData: MapData;
-	private itemManager!: ItemManager;
-
-	constructor(mapData: MapData) {
-		this.mapData = mapData;
-	}
-
-	setItemManager(itemManager: ItemManager) {
-		this.itemManager = itemManager;
-	}
-
-	addOrUpdatePlayer(data: Player): { isNew: boolean; player?: Player } {
-		const { error } = DataValidator.validatePlayerData(data);
-		if (error) {
-			throw new Error(`⚠️ invalid player data `);
-		}
-
-		const existingPlayerData = this.players.get(data.id);
-		if (data.name.length < 1) data.name = 'possum' + data.id.toString().substring(0, 3);
-		if (data.chatMsg.startsWith('/admin ')) data.chatMsg = '/admin ' + data.chatMsg.substring(7).replace(/./g, '*');
-		if (data.chatMsg.startsWith('>')) data.chatMsg = '&2' + data.chatMsg;
-		if (!data.chatMsg.startsWith('&f')) data.chatMsg = '&f' + data.chatMsg;
-
-		if (existingPlayerData) {
-			// Handle forced acknowledgment
-			if (existingPlayerData.player.forced && !data.forcedAcknowledged) {
-				return { isNew: false };
-			}
-			if (existingPlayerData.player.forced && data.forcedAcknowledged) {
-				existingPlayerData.player.forced = false;
-			}
-
-			// Update existing player, preserving certain fields
-			data.health = existingPlayerData.player.health;
-			data.inventory = existingPlayerData.player.inventory;
-			data.lastDamageTime = existingPlayerData.player.lastDamageTime;
-			data.gameMsgs = existingPlayerData.player.gameMsgs;
-			data.gameMsgs2 = existingPlayerData.player.gameMsgs2;
-			data.playerSpectating = existingPlayerData.player.playerSpectating;
-			data.updateTimestamp = Date.now() / 1000;
-
-			const updatedData: PlayerData = {
-				player: data,
-				extras: existingPlayerData.extras,
-			};
-			this.players.set(data.id, updatedData);
-			return { isNew: false };
-		} else {
-			// New player
-			data.inventory = [...config.player.baseInventory];
-			const spawnPoint = this.getRandomSpawnPoint();
-			data.position = spawnPoint.vec;
-			data.health = config.player.maxHealth;
-			data.gameMsgs = [];
-			data.gameMsgs2 = [];
-			data.playerSpectating = -1;
-			data.lookQuaternion = [
-				spawnPoint.quaternion.x,
-				spawnPoint.quaternion.y,
-				spawnPoint.quaternion.z,
-				spawnPoint.quaternion.w,
-			];
-			data.forced = true;
-
-			const newPlayerData: PlayerData = {
-				player: data,
-				extras: new PlayerExtras(),
-			};
-			this.players.set(data.id, newPlayerData);
-			this.itemManager.triggerUpdateFlag();
-
-			return { isNew: true, player: data };
-		}
-	}
-
-	removePlayer(playerId: number) {
-		this.players.delete(playerId);
-	}
-
-	getAllPlayers(): Player[] {
-		return Array.from(this.players.values()).map((playerData) => playerData.player);
-	}
-
-	getPlayerById(playerId: number): Player | undefined {
-		const playerData = this.players.get(playerId);
-		return playerData?.player;
-	}
-
-	getPlayerDataById(playerId: number): PlayerData | undefined {
-		return this.players.get(playerId);
-	}
-
-	getPlayerExtrasById(playerId: number): PlayerExtras | undefined {
-		const playerData = this.players.get(playerId);
-		return playerData?.extras;
-	}
-
-	getAllPlayerData(): PlayerData[] {
-		return Array.from(this.players.values());
-	}
-
-	public dropAllItems(player: Player) {
-		for (let i = 0; i < player.inventory.length; i++) {
-			this.itemManager.pushItem(new WorldItem(player.position, player.inventory[i]));
-		}
-		player.inventory = [];
-	}
-
-	respawnPlayer(player: Player) {
-		const playerData = this.players.get(player.id);
-		if (!playerData) return;
-
-		const spawnPoint = this.getRandomSpawnPoint();
-		player.position = spawnPoint.vec;
-		player.lookQuaternion = [
-			spawnPoint.quaternion.x,
-			spawnPoint.quaternion.y,
-			spawnPoint.quaternion.z,
-			spawnPoint.quaternion.w,
-		];
-		player.health = config.player.maxHealth;
-		player.gravity = 0;
-		player.velocity = new Vector3(0, 0, 0);
-		player.forced = true;
-
-		const updatedPlayerData: PlayerData = {
-			player: player,
-			extras: playerData.extras,
-		};
-		this.players.set(player.id, updatedPlayerData);
-	}
-
-	regenerateHealth() {
-		const currentTime = Date.now() / 1000;
-		for (const playerData of this.players.values()) {
-			const player = playerData.player;
-			const lastDamage = player.lastDamageTime ?? 0;
-			if (player.health < config.player.maxHealth && (lastDamage + config.health.regenDelay < currentTime)) {
-				player.health += config.health.regenRate / config.server.tickRate;
-				if (player.health > config.player.maxHealth) player.health = config.player.maxHealth;
-			}
-		}
-	}
-
-	private getRandomSpawnPoint(): { vec: Vector3; quaternion: Quaternion } {
-		if (!this.mapData) {
-			return { vec: new Vector3(2, 1, 0), quaternion: new Quaternion(0, 0, 0, 1) };
-		}
-
-		const randomIndex = Math.floor(Math.random() * this.mapData.respawnPoints.length);
-		const respawnPoint = this.mapData.respawnPoints[randomIndex];
-		return { vec: respawnPoint.position, quaternion: respawnPoint.quaternion };
-	}
+    private players: Map<number, PlayerData> = new Map();
+    private mapData: MapData;
+    private itemManager!: ItemManager;
+
+    constructor(mapData: MapData) {
+        this.mapData = mapData;
+    }
+
+    setItemManager(itemManager: ItemManager) {
+        this.itemManager = itemManager
+    }
+
+    addOrUpdatePlayer(data: Player): { isNew: boolean; player?: Player } {
+        const { error } = DataValidator.validatePlayerData(data);
+        if (error) {
+            throw new Error(`⚠️ invalid player data `);
+        }
+
+        const existingPlayerData = this.players.get(data.id);
+        if (data.name.length < 1) data.name = 'possum' + data.id.toString().substring(0,3);
+        if(data.chatMsg.startsWith('/admin ')) data.chatMsg = '/admin ' + data.chatMsg.substring(7).replace(/./g, '*');
+        if(data.chatMsg.startsWith('>')) data.chatMsg = '&2'+data.chatMsg;
+        if(!data.chatMsg.startsWith('&f')) data.chatMsg = '&f'+data.chatMsg;
+
+        if (existingPlayerData) {
+            // Handle forced acknowledgment
+            if (existingPlayerData.player.forced && !data.forcedAcknowledged) {
+                return { isNew: false };
+            }
+            if (existingPlayerData.player.forced && data.forcedAcknowledged) {
+                existingPlayerData.player.forced = false;
+            }
+
+            // Update existing player, preserving certain fields
+            data.health = existingPlayerData.player.health;
+            data.inventory = existingPlayerData.player.inventory;
+            data.lastDamageTime = existingPlayerData.player.lastDamageTime;
+            data.gameMsgs = existingPlayerData.player.gameMsgs;
+            data.gameMsgs2 = existingPlayerData.player.gameMsgs2;
+            data.playerSpectating = existingPlayerData.player.playerSpectating;
+            data.updateTimestamp = Date.now() / 1000;
+
+            const updatedData: PlayerData = {
+                player: data,
+                extras: existingPlayerData.extras
+            };
+            this.players.set(data.id, updatedData);
+            return { isNew: false };
+        } else {
+            // New player
+            data.inventory = [...config.player.baseInventory];
+            const spawnPoint = this.getRandomSpawnPoint();
+            data.position = spawnPoint.vec;
+            data.health = config.player.maxHealth;
+            data.gameMsgs = [];
+            data.gameMsgs2 = [];
+            data.playerSpectating = -1;
+            data.lookQuaternion = [spawnPoint.quaternion.x, spawnPoint.quaternion.y, spawnPoint.quaternion.z, spawnPoint.quaternion.w];
+            data.forced = true;
+
+            const newPlayerData: PlayerData = {
+                player: data,
+                extras: new PlayerExtras()
+            };
+            this.players.set(data.id, newPlayerData);
+            this.itemManager.triggerUpdateFlag();
+
+            return { isNew: true, player: data };
+        }
+    }
+
+    removePlayer(playerId: number) {
+        this.players.delete(playerId);
+    }
+
+    getAllPlayers(): Player[] {
+        return Array.from(this.players.values()).map(playerData => playerData.player);
+    }
+
+    getPlayerById(playerId: number): Player | undefined {
+        const playerData = this.players.get(playerId);
+        return playerData?.player;
+    }
+
+    getPlayerDataById(playerId: number): PlayerData | undefined {
+        return this.players.get(playerId);
+    }
+
+    getPlayerExtrasById(playerId: number): PlayerExtras | undefined {
+        const playerData = this.players.get(playerId);
+        return playerData?.extras;
+    }
+
+    getAllPlayerData(): PlayerData[] {
+        return Array.from(this.players.values());
+    }
+
+    public dropAllItems(player:Player){
+        for(let i = 0; i < player.inventory.length; i++){
+            this.itemManager.pushItem(new WorldItem(player.position, player.inventory[i]));
+        }
+        player.inventory = [];
+
+    }
+
+    respawnPlayer(player: Player) {
+        const playerData = this.players.get(player.id);
+        if (!playerData) return;
+
+        const spawnPoint = this.getRandomSpawnPoint();
+        player.position = spawnPoint.vec;
+        player.lookQuaternion = [spawnPoint.quaternion.x, spawnPoint.quaternion.y, spawnPoint.quaternion.z, spawnPoint.quaternion.w];
+        player.health = config.player.maxHealth;
+        player.gravity = 0;
+        player.velocity = new Vector3(0, 0, 0);
+        player.forced = true;
+
+        const updatedPlayerData: PlayerData = {
+            player: player,
+            extras: playerData.extras
+        };
+        this.players.set(player.id, updatedPlayerData);
+    }
+
+    regenerateHealth() {
+        const currentTime = Date.now() / 1000;
+        for (const playerData of this.players.values()) {
+            const player = playerData.player;
+            const lastDamage = player.lastDamageTime ?? 0;
+            if (player.health < config.player.maxHealth && (lastDamage + config.health.regenDelay < currentTime)) {
+                player.health += config.health.regenRate / config.server.tickRate;
+                if (player.health > config.player.maxHealth) player.health = config.player.maxHealth;
+            }
+        }
+    }
+
+    private getRandomSpawnPoint(): { vec: Vector3; quaternion: Quaternion } {
+        if (!this.mapData) {
+            return { vec: new Vector3(2, 1, 0), quaternion: new Quaternion(0, 0, 0, 1) };
+        }
+
+        const randomIndex = Math.floor(Math.random() * this.mapData.respawnPoints.length);
+        const respawnPoint = this.mapData.respawnPoints[randomIndex];
+        return { vec: respawnPoint.position, quaternion: respawnPoint.quaternion };
+    }
 }
diff --git a/src/server/models/ChatMessage.ts b/src/server/models/ChatMessage.ts
index f2fe9379..c3e0ede1 100644
--- a/src/server/models/ChatMessage.ts
+++ b/src/server/models/ChatMessage.ts
@@ -1,5 +1,5 @@
 export interface ChatMessage {
-	id: number;
-	name: string;
-	message: string;
-}
+    id: number;
+    name: string;
+    message: string;
+}
\ No newline at end of file
diff --git a/src/server/models/DamageRequest.ts b/src/server/models/DamageRequest.ts
index 421b991f..40b94aa1 100644
--- a/src/server/models/DamageRequest.ts
+++ b/src/server/models/DamageRequest.ts
@@ -1,7 +1,7 @@
-import { Player } from './Player.ts';
+import { Player } from "./Player.ts";
 
 export interface DamageRequest {
-	localPlayer: Player;
-	targetPlayer: Player;
-	damage: number;
-}
+    localPlayer: Player;
+    targetPlayer: Player;
+    damage: number;
+}
\ No newline at end of file
diff --git a/src/server/models/ItemRespawnPoint.ts b/src/server/models/ItemRespawnPoint.ts
index 811fc3c2..d77b7c53 100644
--- a/src/server/models/ItemRespawnPoint.ts
+++ b/src/server/models/ItemRespawnPoint.ts
@@ -1,9 +1,9 @@
 import { Vector3 } from './Vector3.ts';
 
 export class ItemRespawnPoint {
-	constructor(
-		public position: Vector3,
-		public itemId: number,
-		public spawnChancePerTick: number,
-	) {}
-}
+    constructor(
+        public position: Vector3,
+        public itemId: number,
+        public spawnChancePerTick: number
+    ) {}
+}
\ No newline at end of file
diff --git a/src/server/models/MapData.ts b/src/server/models/MapData.ts
index ed2b665c..5ea4bb83 100644
--- a/src/server/models/MapData.ts
+++ b/src/server/models/MapData.ts
@@ -1,35 +1,33 @@
 import { RespawnPoint } from './RespawnPoint.ts';
 import { ItemRespawnPoint } from './ItemRespawnPoint.ts';
-import { Vector3 } from './Vector3.ts';
-import { Quaternion } from './Quaternion.ts';
+import { Vector3 } from "./Vector3.ts";
+import { Quaternion } from "./Quaternion.ts";
 
 export class MapData {
-	constructor(
-		public name: string,
-		public respawnPoints: RespawnPoint[],
-		public itemRespawnPoints: ItemRespawnPoint[],
-	) {}
+    constructor(
+        public name: string,
+        public respawnPoints: RespawnPoint[],
+        public itemRespawnPoints: ItemRespawnPoint[]
+    ) {}
 
-	static fromJSON(
-		json: { respawnPoints: RespawnPoint[]; itemRespawnPoints: ItemRespawnPoint[]; name: string },
-	): MapData {
-		const respawnPoints = json.respawnPoints.map(
-			(rp) =>
-				new RespawnPoint(
-					new Vector3(rp.position.x, rp.position.y, rp.position.z),
-					new Quaternion(rp.quaternion.x, rp.quaternion.y, rp.quaternion.z, rp.quaternion.w),
-				),
-		);
+    static fromJSON(json: { respawnPoints: RespawnPoint[]; itemRespawnPoints: ItemRespawnPoint[]; name: string; }): MapData {
+        const respawnPoints = json.respawnPoints.map(
+            (rp) =>
+                new RespawnPoint(
+                    new Vector3(rp.position.x, rp.position.y, rp.position.z),
+                    new Quaternion(rp.quaternion.x, rp.quaternion.y, rp.quaternion.z, rp.quaternion.w)
+                )
+        );
 
-		const itemRespawnPoints = json.itemRespawnPoints.map(
-			(irp) =>
-				new ItemRespawnPoint(
-					new Vector3(irp.position.x, irp.position.y, irp.position.z),
-					irp.itemId,
-					irp.spawnChancePerTick,
-				),
-		);
+        const itemRespawnPoints = json.itemRespawnPoints.map(
+            (irp) =>
+                new ItemRespawnPoint(
+                    new Vector3(irp.position.x, irp.position.y, irp.position.z),
+                    irp.itemId,
+                    irp.spawnChancePerTick
+                )
+        );
 
-		return new MapData(json.name, respawnPoints, itemRespawnPoints);
-	}
-}
+        return new MapData(json.name, respawnPoints, itemRespawnPoints);
+    }
+}
\ No newline at end of file
diff --git a/src/server/models/Player.ts b/src/server/models/Player.ts
index de4ab1f3..3f63639f 100644
--- a/src/server/models/Player.ts
+++ b/src/server/models/Player.ts
@@ -1,28 +1,29 @@
-import { Vector3 } from './Vector3.ts';
+import { Vector3 } from "./Vector3.ts";
 
 export interface Player {
-	id: number;
-	speed: number;
-	acceleration: number;
-	name: string;
-	gameVersion: string;
-	position: Vector3;
-	velocity: Vector3;
-	inputVelocity: Vector3;
-	gravity: number;
-	lookQuaternion: number[];
-	quaternion: number[];
-	chatActive: boolean;
-	chatMsg: string;
-	latency: number;
-	health: number;
-	forced: boolean;
-	forcedAcknowledged: boolean;
-	updateTimestamp?: number;
-	lastDamageTime?: number;
-	inventory: number[];
-	idLastDamagedBy?: number;
-	playerSpectating: number;
-	gameMsgs: string[];
-	gameMsgs2: string[];
-}
+    id: number;
+    speed: number;
+    acceleration: number;
+    name: string;
+    gameVersion: string;
+    position: Vector3;
+    velocity: Vector3;
+    inputVelocity: Vector3;
+    gravity: number;
+    lookQuaternion: number[];
+    quaternion: number[];
+    chatActive: boolean;
+    chatMsg: string;
+    latency: number;
+    health: number;
+    forced: boolean;
+    forcedAcknowledged: boolean;
+    updateTimestamp?: number;
+    lastDamageTime?: number;
+    inventory: number[];
+    idLastDamagedBy?: number;
+    playerSpectating:number;
+    gameMsgs:string[];
+    gameMsgs2:string[];
+
+}
\ No newline at end of file
diff --git a/src/server/models/PlayerExtras.ts b/src/server/models/PlayerExtras.ts
index f16eff18..610cf4e6 100644
--- a/src/server/models/PlayerExtras.ts
+++ b/src/server/models/PlayerExtras.ts
@@ -1,7 +1,8 @@
 export class PlayerExtras {
-	gameMsgsTimeouts: number[] = [];
-	kills: number = 0;
-	deaths: number = 0;
-	killStreak: number = 0;
-	points: number = 0;
-}
+gameMsgsTimeouts: number[] = [];
+kills: number = 0;
+deaths: number = 0;
+killStreak: number = 0;
+points: number = 0;
+
+}
\ No newline at end of file
diff --git a/src/server/models/Quaternion.ts b/src/server/models/Quaternion.ts
index dd755166..97216d34 100644
--- a/src/server/models/Quaternion.ts
+++ b/src/server/models/Quaternion.ts
@@ -1,3 +1,3 @@
 export class Quaternion {
-	constructor(public x: number, public y: number, public z: number, public w: number) {}
-}
+    constructor(public x: number, public y: number, public z: number, public w: number) {}
+}
\ No newline at end of file
diff --git a/src/server/models/RespawnPoint.ts b/src/server/models/RespawnPoint.ts
index c0ad5eae..e96e5e6e 100644
--- a/src/server/models/RespawnPoint.ts
+++ b/src/server/models/RespawnPoint.ts
@@ -2,5 +2,5 @@ import { Vector3 } from './Vector3.ts';
 import { Quaternion } from './Quaternion.ts';
 
 export class RespawnPoint {
-	constructor(public position: Vector3, public quaternion: Quaternion) {}
-}
+  constructor(public position: Vector3, public quaternion: Quaternion) {}
+}
\ No newline at end of file
diff --git a/src/server/models/ServerInfo.ts b/src/server/models/ServerInfo.ts
index 6dc1e906..a628942d 100644
--- a/src/server/models/ServerInfo.ts
+++ b/src/server/models/ServerInfo.ts
@@ -1,22 +1,22 @@
-import config from '../config.ts';
+import config from "../config.ts";
 
 export class ServerInfo {
-	public name: string;
-	public maxPlayers: number;
-	public currentPlayers: number;
-	public mapName: string;
-	public tickRate: number;
-	public version: string;
-	public gameMode: string;
-	public playerMaxHealth: number;
-	constructor() {
-		this.name = config.server.name;
-		this.maxPlayers = config.game.maxPlayers;
-		this.currentPlayers = 0;
-		this.mapName = config.server.defaultMap;
-		this.tickRate = config.server.tickRate;
-		this.version = '';
-		this.gameMode = config.game.mode;
-		this.playerMaxHealth = config.player.maxHealth;
-	}
-}
+    public name: string;
+    public maxPlayers: number;
+    public currentPlayers: number;
+    public mapName: string;
+    public tickRate: number;
+    public version: string;
+    public gameMode: string;
+    public playerMaxHealth: number;
+    constructor() {
+        this.name = config.server.name;
+        this.maxPlayers = config.game.maxPlayers;
+        this.currentPlayers = 0;
+        this.mapName = config.server.defaultMap;
+        this.tickRate = config.server.tickRate;
+        this.version = '';
+        this.gameMode = config.game.mode;
+        this.playerMaxHealth = config.player.maxHealth;
+    }
+}
\ No newline at end of file
diff --git a/src/server/models/Vector3.ts b/src/server/models/Vector3.ts
index 253af654..a8f44c1c 100644
--- a/src/server/models/Vector3.ts
+++ b/src/server/models/Vector3.ts
@@ -1,18 +1,18 @@
 export class Vector3 {
-	constructor(public x: number, public y: number, public z: number) {}
-	public distanceTo(other: Vector3): number {
-		return Math.sqrt(
-			Math.pow(this.x - other.x, 2) +
-				Math.pow(this.y - other.y, 2) +
-				Math.pow(this.z - other.z, 2),
-		);
-	}
+    constructor(public x: number, public y: number, public z: number) {}
+    public distanceTo(other: Vector3): number {
+        return Math.sqrt(
+            Math.pow(this.x - other.x, 2) +
+            Math.pow(this.y - other.y, 2) +
+            Math.pow(this.z - other.z, 2)
+        );
+    }
 
-	public static distanceTo(v1: Vector3, v2: Vector3): number {
-		return Math.sqrt(
-			Math.pow(v1.x - v2.x, 2) +
-				Math.pow(v1.y - v2.y, 2) +
-				Math.pow(v1.z - v2.z, 2),
-		);
-	}
-}
+    public static distanceTo(v1: Vector3, v2: Vector3): number {
+        return Math.sqrt(
+            Math.pow(v1.x - v2.x, 2) +
+            Math.pow(v1.y - v2.y, 2) +
+            Math.pow(v1.z - v2.z, 2)
+        );
+    }
+}
\ No newline at end of file
diff --git a/src/server/models/WorldItem.ts b/src/server/models/WorldItem.ts
index 43c3bf32..629e6b32 100644
--- a/src/server/models/WorldItem.ts
+++ b/src/server/models/WorldItem.ts
@@ -1,9 +1,9 @@
-import { Vector3 } from './Vector3.ts';
+import { Vector3 } from "./Vector3.ts";
 
 export class WorldItem {
-	public id: number;
+    public id: number;
 
-	constructor(public vector: Vector3, public itemType: number) {
-		this.id = Math.floor(Math.random() * 100000) + 1;
-	}
-}
+    constructor(public vector: Vector3, public itemType: number) {
+        this.id = Math.floor(Math.random() * 100000) + 1;
+    }
+}
\ No newline at end of file
diff --git a/src/styles.css b/src/styles.css
index 6d69c386..3852c971 100644
--- a/src/styles.css
+++ b/src/styles.css
@@ -5,85 +5,85 @@
 
 /* You can add global styles to this file, and also import other style files */
 :root {
-	font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
-	font-size: 16px;
-	line-height: 24px;
-	font-weight: 400;
+  font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
+  font-size: 16px;
+  line-height: 24px;
+  font-weight: 400;
 
-	color-scheme: light dark;
-	color: rgba(255, 255, 255, 0.87);
-	background-color: #242424;
+  color-scheme: light dark;
+  color: rgba(255, 255, 255, 0.87);
+  background-color: #242424;
 
-	font-synthesis: none;
-	text-rendering: optimizeLegibility;
-	-webkit-font-smoothing: antialiased;
-	-moz-osx-font-smoothing: grayscale;
-	-webkit-text-size-adjust: 100%;
+  font-synthesis: none;
+  text-rendering: optimizeLegibility;
+  -webkit-font-smoothing: antialiased;
+  -moz-osx-font-smoothing: grayscale;
+  -webkit-text-size-adjust: 100%;
 }
 
 a {
-	font-weight: 500;
-	color: #646cff;
-	text-decoration: inherit;
+  font-weight: 500;
+  color: #646cff;
+  text-decoration: inherit;
 }
 
 a:hover {
-	color: #535bf2;
+  color: #535bf2;
 }
 
 body {
-	margin: 0;
-	/*display: flex;*/
-	/*place-items: center;*/
-	min-width: 320px;
-	min-height: 100vh;
+  margin: 0;
+  /*display: flex;*/
+  /*place-items: center;*/
+  min-width: 320px;
+  min-height: 100vh;
 }
 
 h1 {
-	font-size: 3.2em;
-	line-height: 1.1;
+  font-size: 3.2em;
+  line-height: 1.1;
 }
 
 button {
-	border-radius: 8px;
-	border: 1px solid transparent;
-	padding: 0.6em 1.2em;
-	font-size: 1em;
-	font-weight: 500;
-	font-family: inherit;
-	background-color: #1a1a1a;
-	cursor: pointer;
-	transition: border-color 0.25s;
+  border-radius: 8px;
+  border: 1px solid transparent;
+  padding: 0.6em 1.2em;
+  font-size: 1em;
+  font-weight: 500;
+  font-family: inherit;
+  background-color: #1a1a1a;
+  cursor: pointer;
+  transition: border-color 0.25s;
 }
 
 button:hover {
-	border-color: #646cff;
+  border-color: #646cff;
 }
 
 button:focus,
 button:focus-visible {
-	outline: 4px auto -webkit-focus-ring-color;
+  outline: 4px auto -webkit-focus-ring-color;
 }
 
 .card {
-	padding: 2em;
+  padding: 2em;
 }
 
 .logo {
-	@apply box-content;
+  @apply box-content;
 }
 
 @media (prefers-color-scheme: light) {
-	:root {
-		color: #213547;
-		background-color: #ffffff;
-	}
+  :root {
+    color: #213547;
+    background-color: #ffffff;
+  }
 
-	a:hover {
-		color: #747bff;
-	}
+  a:hover {
+    color: #747bff;
+  }
 
-	button {
-		background-color: #f9f9f9;
-	}
+  button {
+    background-color: #f9f9f9;
+  }
 }