diff --git a/package-lock.json b/package-lock.json
index e3c6998..f69a98f 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -213,9 +213,9 @@
       }
     },
     "node_modules/@types/express-serve-static-core": {
-      "version": "4.19.3",
-      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.3.tgz",
-      "integrity": "sha512-KOzM7MhcBFlmnlr/fzISFF5vGWVSvN6fTd4T+ExOt08bA/dA5kpSzY52nMsI1KDFmUREpJelPYyuslLRSjjgCg==",
+      "version": "4.19.5",
+      "resolved": "https://registry.npmjs.org/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz",
+      "integrity": "sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -250,9 +250,9 @@
       "license": "MIT"
     },
     "node_modules/@types/node": {
-      "version": "20.14.6",
-      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.6.tgz",
-      "integrity": "sha512-JbA0XIJPL1IiNnU7PFxDXyfAwcwVVrOoqyzzyQTyMeVhBzkJVMSkC1LlVsRQ2lpqiY4n6Bb9oCS6lzDKVQxbZw==",
+      "version": "20.14.8",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.8.tgz",
+      "integrity": "sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==",
       "license": "MIT",
       "dependencies": {
         "undici-types": "~5.26.4"
@@ -1257,9 +1257,9 @@
       }
     },
     "node_modules/nodemon": {
-      "version": "3.1.3",
-      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.3.tgz",
-      "integrity": "sha512-m4Vqs+APdKzDFpuaL9F9EVOF85+h070FnkHVEoU4+rmT6Vw0bmNl7s61VEkY/cJkL7RCv1p4urnUDUMrS5rk2w==",
+      "version": "3.1.4",
+      "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.4.tgz",
+      "integrity": "sha512-wjPBbFhtpJwmIeY2yP7QF+UKzPfltVGtfce1g/bB15/8vCGZj8uxD62b/b9M9/WVgme0NZudpownKN+c0plXlQ==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -1330,10 +1330,13 @@
       }
     },
     "node_modules/object-inspect": {
-      "version": "1.13.1",
-      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.1.tgz",
-      "integrity": "sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==",
+      "version": "1.13.2",
+      "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz",
+      "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==",
       "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
       "funding": {
         "url": "https://github.com/sponsors/ljharb"
       }
@@ -1824,9 +1827,9 @@
       }
     },
     "node_modules/typescript": {
-      "version": "5.4.5",
-      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz",
-      "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==",
+      "version": "5.5.2",
+      "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.2.tgz",
+      "integrity": "sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew==",
       "license": "Apache-2.0",
       "bin": {
         "tsc": "bin/tsc",
diff --git a/prisma/migrations/20240614234637_init/migration.sql b/prisma/migrations/20240621191014_init/migration.sql
similarity index 53%
rename from prisma/migrations/20240614234637_init/migration.sql
rename to prisma/migrations/20240621191014_init/migration.sql
index ade3ac0..aa985ad 100644
--- a/prisma/migrations/20240614234637_init/migration.sql
+++ b/prisma/migrations/20240621191014_init/migration.sql
@@ -1,3 +1,37 @@
+-- CreateTable
+CREATE TABLE `ObjectGroup` (
+    `id` INTEGER NOT NULL AUTO_INCREMENT,
+    `name` VARCHAR(191) NOT NULL,
+    `description` VARCHAR(191) NOT NULL,
+
+    PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `Objects` (
+    `id` VARCHAR(191) NOT NULL,
+    `objectGroupId` INTEGER NOT NULL,
+    `name` VARCHAR(191) NOT NULL,
+    `origin_x` INTEGER NOT NULL DEFAULT 0,
+    `origin_y` INTEGER NOT NULL DEFAULT 0,
+    `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+    `updatedAt` DATETIME(3) NOT NULL,
+
+    PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
+-- CreateTable
+CREATE TABLE `Item` (
+    `id` VARCHAR(191) NOT NULL,
+    `name` VARCHAR(191) NOT NULL,
+    `description` VARCHAR(191) NOT NULL,
+    `stackable` BOOLEAN NOT NULL DEFAULT false,
+    `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
+    `updatedAt` DATETIME(3) NOT NULL,
+
+    PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
 -- CreateTable
 CREATE TABLE `User` (
     `id` INTEGER NOT NULL AUTO_INCREMENT,
@@ -27,6 +61,16 @@ CREATE TABLE `Character` (
     PRIMARY KEY (`id`)
 ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
 
+-- CreateTable
+CREATE TABLE `CharacterItem` (
+    `id` INTEGER NOT NULL AUTO_INCREMENT,
+    `characterId` INTEGER NOT NULL,
+    `itemId` VARCHAR(191) NOT NULL,
+    `quantity` INTEGER NOT NULL,
+
+    PRIMARY KEY (`id`)
+) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+
 -- CreateTable
 CREATE TABLE `Zone` (
     `id` INTEGER NOT NULL AUTO_INCREMENT,
@@ -42,10 +86,10 @@ CREATE TABLE `Zone` (
 ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
 
 -- CreateTable
-CREATE TABLE `ZoneDecoration` (
+CREATE TABLE `ZoneObject` (
     `id` INTEGER NOT NULL AUTO_INCREMENT,
     `zoneId` INTEGER NOT NULL,
-    `type` INTEGER NOT NULL,
+    `objectId` VARCHAR(191) NOT NULL,
     `position_x` INTEGER NOT NULL,
     `position_y` INTEGER NOT NULL,
 
@@ -63,6 +107,9 @@ CREATE TABLE `Chat` (
     PRIMARY KEY (`id`)
 ) DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
 
+-- AddForeignKey
+ALTER TABLE `Objects` ADD CONSTRAINT `Objects_objectGroupId_fkey` FOREIGN KEY (`objectGroupId`) REFERENCES `ObjectGroup`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
 -- AddForeignKey
 ALTER TABLE `Character` ADD CONSTRAINT `Character_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
 
@@ -70,7 +117,16 @@ ALTER TABLE `Character` ADD CONSTRAINT `Character_userId_fkey` FOREIGN KEY (`use
 ALTER TABLE `Character` ADD CONSTRAINT `Character_zoneId_fkey` FOREIGN KEY (`zoneId`) REFERENCES `Zone`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
 
 -- AddForeignKey
-ALTER TABLE `ZoneDecoration` ADD CONSTRAINT `ZoneDecoration_zoneId_fkey` FOREIGN KEY (`zoneId`) REFERENCES `Zone`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+ALTER TABLE `CharacterItem` ADD CONSTRAINT `CharacterItem_characterId_fkey` FOREIGN KEY (`characterId`) REFERENCES `Character`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `CharacterItem` ADD CONSTRAINT `CharacterItem_itemId_fkey` FOREIGN KEY (`itemId`) REFERENCES `Item`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `ZoneObject` ADD CONSTRAINT `ZoneObject_zoneId_fkey` FOREIGN KEY (`zoneId`) REFERENCES `Zone`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
+
+-- AddForeignKey
+ALTER TABLE `ZoneObject` ADD CONSTRAINT `ZoneObject_objectId_fkey` FOREIGN KEY (`objectId`) REFERENCES `Objects`(`id`) ON DELETE RESTRICT ON UPDATE CASCADE;
 
 -- AddForeignKey
 ALTER TABLE `Chat` ADD CONSTRAINT `Chat_characterId_fkey` FOREIGN KEY (`characterId`) REFERENCES `Character`(`id`) ON DELETE CASCADE ON UPDATE CASCADE;
diff --git a/prisma/schema.prisma b/prisma/schema.prisma
index fbbf7a9..68d3990 100644
--- a/prisma/schema.prisma
+++ b/prisma/schema.prisma
@@ -19,6 +19,26 @@ datasource db {
   url      = env("DATABASE_URL")
 }
 
+model Objects {
+  id            String       @id @default(uuid())
+  name          String
+  origin_x      Int          @default(0)
+  origin_y      Int          @default(0)
+  createdAt     DateTime     @default(now())
+  updatedAt     DateTime     @updatedAt
+  ZoneObject    ZoneObject[]
+}
+
+model Item {
+  id          String          @id @default(uuid())
+  name        String
+  description String
+  stackable   Boolean         @default(false)
+  createdAt   DateTime        @default(now())
+  updatedAt   DateTime        @updatedAt
+  characters  CharacterItem[]
+}
+
 model User {
   id         Int         @id @default(autoincrement())
   username   String      @unique
@@ -27,42 +47,53 @@ model User {
 }
 
 model Character {
-  id         Int    @id @default(autoincrement())
+  id         Int             @id @default(autoincrement())
   userId     Int
-  user       User   @relation(fields: [userId], references: [id], onDelete: Cascade)
-  name       String @unique
-  hitpoints  Int    @default(100)
-  mana       Int    @default(100)
-  level      Int    @default(1)
-  experience Int    @default(0)
-  role       String @default("player")
+  user       User            @relation(fields: [userId], references: [id], onDelete: Cascade)
+  name       String          @unique
+  hitpoints  Int             @default(100)
+  mana       Int             @default(100)
+  level      Int             @default(1)
+  experience Int             @default(0)
+  role       String          @default("player")
   position_x Int
   position_y Int
   rotation   Int
   zoneId     Int
-  zone       Zone   @relation(fields: [zoneId], references: [id], onDelete: Cascade)
+  zone       Zone            @relation(fields: [zoneId], references: [id], onDelete: Cascade)
   chats      Chat[]
+  items      CharacterItem[]
+}
+
+model CharacterItem {
+  id          Int       @id @default(autoincrement())
+  characterId Int
+  character   Character @relation(fields: [characterId], references: [id], onDelete: Cascade)
+  itemId      String
+  item        Item      @relation(fields: [itemId], references: [id], onDelete: Cascade)
+  quantity    Int
 }
 
 model Zone {
-  id          Int              @id @default(autoincrement())
+  id          Int          @id @default(autoincrement())
   name        String
   width       Int
   height      Int
   tiles       Json
   walls       Json
-  decorations ZoneDecoration[]
+  zoneObjects ZoneObject[]
   characters  Character[]
   chats       Chat[]
-  createdAt   DateTime         @default(now())
-  updatedAt   DateTime         @updatedAt
+  createdAt   DateTime     @default(now())
+  updatedAt   DateTime     @updatedAt
 }
 
-model ZoneDecoration {
-  id         Int  @id @default(autoincrement())
+model ZoneObject {
+  id         Int     @id @default(autoincrement())
   zoneId     Int
-  zone       Zone @relation(fields: [zoneId], references: [id], onDelete: Cascade)
-  type       Int
+  zone       Zone    @relation(fields: [zoneId], references: [id], onDelete: Cascade)
+  objectId   String
+  object     Objects @relation(fields: [objectId], references: [id])
   position_x Int
   position_y Int
 }
diff --git a/public/.gitignore b/public/.gitignore
new file mode 100644
index 0000000..782bfe1
--- /dev/null
+++ b/public/.gitignore
@@ -0,0 +1,2 @@
+!.gitignore
+**
\ No newline at end of file
diff --git a/src/app/events/CharacterConnect.ts b/src/app/events/character/CharacterConnect.ts
similarity index 84%
rename from src/app/events/CharacterConnect.ts
rename to src/app/events/character/CharacterConnect.ts
index 556754c..0af0a03 100644
--- a/src/app/events/CharacterConnect.ts
+++ b/src/app/events/character/CharacterConnect.ts
@@ -1,6 +1,6 @@
 import { Socket, Server } from "socket.io";
-import {TSocket} from "../utilities/Types";
-import CharacterRepository from "../repositories/CharacterRepository";
+import {TSocket} from "../../utilities/Types";
+import CharacterRepository from "../../repositories/CharacterRepository";
 import {Character, User} from "@prisma/client";
 
 type SocketResponseT = {
diff --git a/src/app/events/CharacterCreate.ts b/src/app/events/character/CharacterCreate.ts
similarity index 87%
rename from src/app/events/CharacterCreate.ts
rename to src/app/events/character/CharacterCreate.ts
index ef351d5..3ce2c54 100644
--- a/src/app/events/CharacterCreate.ts
+++ b/src/app/events/character/CharacterCreate.ts
@@ -1,8 +1,8 @@
 import { Server } from "socket.io";
-import {TSocket} from "../utilities/Types";
+import {TSocket} from "../../utilities/Types";
 import {Character} from "@prisma/client";
-import CharacterRepository from "../repositories/CharacterRepository";
-import {ZCharacterCreate} from "../utilities/ZodTypes";
+import CharacterRepository from "../../repositories/CharacterRepository";
+import {ZCharacterCreate} from "../../utilities/ZodTypes";
 
 export default function (socket: TSocket, io: Server) {
     socket.on('character:create', async (data: any) => {
diff --git a/src/app/events/CharacterDelete.ts b/src/app/events/character/CharacterDelete.ts
similarity index 79%
rename from src/app/events/CharacterDelete.ts
rename to src/app/events/character/CharacterDelete.ts
index c3ffd3e..6b265a7 100644
--- a/src/app/events/CharacterDelete.ts
+++ b/src/app/events/character/CharacterDelete.ts
@@ -1,8 +1,8 @@
 import {Server} from "socket.io";
-import {TSocket} from "../utilities/Types";
+import {TSocket} from "../../utilities/Types";
 import {Character} from "@prisma/client";
-import CharacterRepository from "../repositories/CharacterRepository";
-import {ZCharacterDelete} from "../utilities/ZodTypes";
+import CharacterRepository from "../../repositories/CharacterRepository";
+import {ZCharacterDelete} from "../../utilities/ZodTypes";
 
 export default function (socket: TSocket, io: Server) {
     socket.on('character:delete', async (data: any) => {
diff --git a/src/app/events/CharacterList.ts b/src/app/events/character/CharacterList.ts
similarity index 83%
rename from src/app/events/CharacterList.ts
rename to src/app/events/character/CharacterList.ts
index cf8bc5e..b721fd4 100644
--- a/src/app/events/CharacterList.ts
+++ b/src/app/events/character/CharacterList.ts
@@ -1,7 +1,7 @@
 import { Socket, Server } from "socket.io";
-import {TSocket} from "../utilities/Types";
+import {TSocket} from "../../utilities/Types";
 import {Character} from "@prisma/client";
-import CharacterRepository from "../repositories/CharacterRepository";
+import CharacterRepository from "../../repositories/CharacterRepository";
 
 export default function CharacterList(socket: TSocket, io: Server) {
     socket.on('character:list', async (data: any) => {
diff --git a/src/app/events/CharacterMove.ts b/src/app/events/character/CharacterMove.ts
similarity index 85%
rename from src/app/events/CharacterMove.ts
rename to src/app/events/character/CharacterMove.ts
index 28e5537..669e14d 100644
--- a/src/app/events/CharacterMove.ts
+++ b/src/app/events/character/CharacterMove.ts
@@ -1,7 +1,7 @@
 import { Server } from "socket.io";
-import {TSocket} from "../utilities/Types";
-import CharacterRepository from "../repositories/CharacterRepository";
-import ZoneManager from "../ZoneManager";
+import {TSocket} from "../../utilities/Types";
+import CharacterRepository from "../../repositories/CharacterRepository";
+import ZoneManager from "../../ZoneManager";
 
 type SocketResponseT = {
     position_x: number,
diff --git a/src/app/events/CharacterZoneLeave.ts b/src/app/events/character/CharacterZoneLeave.ts
similarity index 88%
rename from src/app/events/CharacterZoneLeave.ts
rename to src/app/events/character/CharacterZoneLeave.ts
index c41cdfe..69dae87 100644
--- a/src/app/events/CharacterZoneLeave.ts
+++ b/src/app/events/character/CharacterZoneLeave.ts
@@ -1,7 +1,7 @@
 import { Server } from "socket.io";
-import {TSocket} from "../utilities/Types";
-import ZoneRepository from "../repositories/ZoneRepository";
-import ZoneManager from "../ZoneManager";
+import {TSocket} from "../../utilities/Types";
+import ZoneRepository from "../../repositories/ZoneRepository";
+import ZoneManager from "../../ZoneManager";
 import {Character, Zone} from "@prisma/client";
 
 /**
diff --git a/src/app/events/CharacterZoneRequest.ts b/src/app/events/character/CharacterZoneRequest.ts
similarity index 83%
rename from src/app/events/CharacterZoneRequest.ts
rename to src/app/events/character/CharacterZoneRequest.ts
index febd33a..76f1383 100644
--- a/src/app/events/CharacterZoneRequest.ts
+++ b/src/app/events/character/CharacterZoneRequest.ts
@@ -1,10 +1,10 @@
 import { Server } from "socket.io";
-import {TSocket} from "../utilities/Types";
-import ZoneRepository from "../repositories/ZoneRepository";
-import ZoneManager from "../ZoneManager";
+import {TSocket} from "../../utilities/Types";
+import ZoneRepository from "../../repositories/ZoneRepository";
+import ZoneManager from "../../ZoneManager";
 import {Character, Zone} from "@prisma/client";
 
-interface IZoneLoad {
+interface IPayload {
     zoneId: number;
 }
 
@@ -14,7 +14,7 @@ interface IZoneLoad {
  * @param io
  */
 export default function (socket: TSocket, io: Server) {
-    socket.on('character:zone:request', async (data: IZoneLoad) => {
+    socket.on('character:zone:request', async (data: IPayload) => {
         console.log(`---User ${socket.character?.id} has requested zone.`);
 
         if (!data.zoneId) {
diff --git a/src/app/events/gm/GmTileList.ts b/src/app/events/gm/GmTileList.ts
new file mode 100644
index 0000000..c068e0f
--- /dev/null
+++ b/src/app/events/gm/GmTileList.ts
@@ -0,0 +1,37 @@
+import { Server } from "socket.io";
+import {TSocket} from "../../utilities/Types";
+import fs from 'fs';
+
+interface IPayload {
+}
+
+/**
+ * Handle game master list tiles event
+ * @param socket
+ * @param io
+ */
+export default function (socket: TSocket, io: Server) {
+    socket.on('gm:tile:list', async (data: any, callback: (response: string[]) => void) => {
+
+        // get root path
+        const root_folder = process.cwd();
+        const folder = `${root_folder}/public/tiles`;
+
+        // list the files in the folder
+        let tiles: string[] = [];
+
+        fs.readdir(folder, (err, files) => {
+            if (err) {
+                console.log(err);
+                return;
+            }
+
+            files.forEach(file => {
+                tiles.push(file);
+            });
+
+            // send over the list of tiles to the socket
+            callback(tiles);
+        });
+    });
+}
\ No newline at end of file
diff --git a/src/app/events/gm/GmTileUpload.ts b/src/app/events/gm/GmTileUpload.ts
new file mode 100644
index 0000000..eb1dc49
--- /dev/null
+++ b/src/app/events/gm/GmTileUpload.ts
@@ -0,0 +1,33 @@
+import { Server } from "socket.io";
+import {TSocket} from "../../utilities/Types";
+import {writeFile} from "node:fs";
+import {randomUUID} from "node:crypto";
+
+
+interface IPayload {
+}
+
+/**
+ * Handle game master upload tile event
+ * @param socket
+ * @param io
+ */
+export default function (socket: TSocket, io: Server) {
+    socket.on('gm:tile:upload', async (data: any) => {
+
+        // get root path
+        const root_folder = process.cwd();
+        const public_folder = `${root_folder}/public`;
+
+        for (const key in data) {
+            const filename = randomUUID();
+            const path = `${public_folder}/tiles/${filename}.png`;
+            const tile = data[key];
+
+            // save the tile to the disk, for example
+            writeFile(path, tile, (err) => {
+                // pajeet INC
+            });
+        }
+    });
+}
\ No newline at end of file
diff --git a/src/app/events/GmZoneEditorZoneRequest.ts b/src/app/events/gm/GmZoneEditorZoneRequest.ts
similarity index 73%
rename from src/app/events/GmZoneEditorZoneRequest.ts
rename to src/app/events/gm/GmZoneEditorZoneRequest.ts
index 267ad41..519487d 100644
--- a/src/app/events/GmZoneEditorZoneRequest.ts
+++ b/src/app/events/gm/GmZoneEditorZoneRequest.ts
@@ -1,10 +1,10 @@
 import { Server } from "socket.io";
-import {TSocket} from "../utilities/Types";
-import ZoneRepository from "../repositories/ZoneRepository";
-import ZoneManager from "../ZoneManager";
+import {TSocket} from "../../utilities/Types";
+import ZoneRepository from "../../repositories/ZoneRepository";
+import ZoneManager from "../../ZoneManager";
 import {Character, Zone} from "@prisma/client";
 
-interface IZoneLoad {
+interface IPayload {
     zoneId: number;
 }
 
@@ -14,7 +14,7 @@ interface IZoneLoad {
  * @param io
  */
 export default function (socket: TSocket, io: Server) {
-    socket.on('gm:zone_editor:zone:request', async (data: IZoneLoad) => {
+    socket.on('gm:zone_editor:zone:request', async (data: IPayload) => {
         console.log(`---GM ${socket.character?.id} has requested zone via zone editor.`);
 
         if (!data.zoneId) {
diff --git a/src/app/events/GmZoneEditorZoneSave.ts b/src/app/events/gm/GmZoneEditorZoneSave.ts
similarity index 81%
rename from src/app/events/GmZoneEditorZoneSave.ts
rename to src/app/events/gm/GmZoneEditorZoneSave.ts
index d8b41e5..c25fbcb 100644
--- a/src/app/events/GmZoneEditorZoneSave.ts
+++ b/src/app/events/gm/GmZoneEditorZoneSave.ts
@@ -1,10 +1,10 @@
 import { Server } from "socket.io";
-import {TSocket} from "../utilities/Types";
-import ZoneRepository from "../repositories/ZoneRepository";
-import ZoneManager from "../ZoneManager";
+import {TSocket} from "../../utilities/Types";
+import ZoneRepository from "../../repositories/ZoneRepository";
+import ZoneManager from "../../ZoneManager";
 import {Character, Zone} from "@prisma/client";
 
-interface IZoneLoad {
+interface IPayload {
     zoneId: number;
     name: string;
     width: number;
@@ -19,7 +19,7 @@ interface IZoneLoad {
  * @param io
  */
 export default function (socket: TSocket, io: Server) {
-    socket.on('gm:zone_editor:zone:save', async (data: IZoneLoad) => {
+    socket.on('gm:zone_editor:zone:save', async (data: IPayload) => {
         console.log(`---GM ${socket.character?.id} has saved zone via zone editor.`);
 
         console.log(data);
diff --git a/src/app/utilities/Http.ts b/src/app/utilities/Http.ts
index 428bdee..3ef41af 100644
--- a/src/app/utilities/Http.ts
+++ b/src/app/utilities/Http.ts
@@ -8,8 +8,33 @@ import UserService from '../services/UserService';
 import jwt from "jsonwebtoken";
 import config from "./Config";
 import {loginAccountSchema, registerAccountSchema} from "./ZodTypes";
+import path from "path";
+
+function isValidAsset(assetName: string) {
+    const assetPath = path.join(__dirname, 'public', 'assets', assetName);
+    return assetPath.startsWith(path.join(__dirname, 'public', 'assets'));
+}
 
 async function addAuthRoutes(app: Application) {
+    app.get('/assets/:asset', (req: Request, res: Response) => {
+        const assetName = req.params.asset;
+
+        if (!isValidAsset(assetName)) {
+            return res.status(400).send('Invalid asset name');
+        }
+
+        const options = {
+            root: path.join(__dirname, 'public', 'assets'),
+        };
+
+        res.sendFile(assetName, options, (err) => {
+            if (err) {
+                console.error('Error sending file:', err);
+                res.status(500).send('Error downloading the asset');
+            }
+        });
+    });
+
     app.post('/login', async (req: Request, res: Response) => {
         const { username, password } = req.body;
 
diff --git a/src/server.ts b/src/server.ts
index 74d5fc2..20672ee 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -12,6 +12,7 @@ import ZoneManager from "./app/ZoneManager";
 import UserManager from "./app/UserManager";
 import {Authentication} from "./app/middleware/Authentication";
 import CommandManager from "./app/CommandManager";
+import {Dirent} from "node:fs";
 
 export class Server
 {
@@ -76,15 +77,26 @@ export class Server
     private async handleConnection(socket: TSocket) {
         const eventsPath = path.join(__dirname, 'app', 'events');
         try {
-            const files: string[] = await fs.promises.readdir(eventsPath);
-            for (const file of files) {
-                const module = await import(path.join(eventsPath, file));
-                module.default(socket, this.io);
-            }
+            await this.loadEventHandlers(eventsPath, socket);
         } catch (error: any) {
             throw new Error('[❌] Failed to load event handlers: ' + error.message);
         }
     }
+
+    private async loadEventHandlers(dir: string, socket: TSocket) {
+        const files: Dirent[] = await fs.promises.readdir(dir, { withFileTypes: true });
+
+        for (const file of files) {
+            const fullPath = path.join(dir, file.name);
+
+            if (file.isDirectory()) {
+                await this.loadEventHandlers(fullPath, socket);
+            } else if (file.isFile()) {
+                const module = await import(fullPath);
+                module.default(socket, this.io);
+            }
+        }
+    }
 }
 
 // Start the server