From 8e174a063a8641be5d3ee651466a484dc1484815 Mon Sep 17 00:00:00 2001
From: Dennis Postma <dennis@directonline.io>
Date: Tue, 11 Jun 2024 01:10:22 +0200
Subject: [PATCH] Worked on zone manager

---
 package-lock.json                             |   2 +
 .../assets/icons/zoneEditor/eraser-tool.svg   |   1 -
 public/assets/icons/zoneEditor/eraser.svg     |  44 ++++++
 public/assets/icons/zoneEditor/move.svg       |  78 ++++++++++
 public/assets/icons/zoneEditor/tiles.svg      |  33 ++--
 src/App.vue                                   |   3 +-
 src/assets/scss/_variables.scss               |   2 +-
 src/assets/scss/main.scss                     |  26 +++-
 src/components/Game.vue                       |  70 ++++-----
 src/components/World.vue                      |  46 +++---
 src/components/{game => gui}/Chat.vue         |   0
 src/components/{game => gui}/Hud.vue          |  53 +++++--
 src/components/{game => gui}/Menu.vue         |   0
 src/components/screens/Characters.vue         |  34 ++--
 src/components/screens/Login.vue              |  10 +-
 src/components/sprites/Character.vue          |  50 ++----
 src/components/utilities/Controls.vue         |   6 +-
 src/components/utilities/GmTools.vue          |  10 +-
 src/components/utilities/Modal.vue            |  41 +++--
 src/components/utilities/Notifications.vue    |  12 +-
 src/components/utilities/zoneEditor/Tiles.vue | 103 ++++++++++++
 .../utilities/zoneEditor/Toolbar.vue          | 125 +++++++++++++++
 .../utilities/zoneEditor/ZoneEditor.vue       | 146 +++++++-----------
 .../zoneEditor/ZoneEditorToolbar.vue          |  81 ----------
 src/services/authentication.ts                |   4 +-
 src/services/zone.ts                          |   4 +-
 src/stores/socket.ts                          |   6 -
 src/stores/zone.ts                            |  29 +---
 src/stores/zoneEditor.ts                      |  18 ++-
 29 files changed, 658 insertions(+), 379 deletions(-)
 delete mode 100644 public/assets/icons/zoneEditor/eraser-tool.svg
 create mode 100644 public/assets/icons/zoneEditor/eraser.svg
 create mode 100644 public/assets/icons/zoneEditor/move.svg
 rename src/components/{game => gui}/Chat.vue (100%)
 rename src/components/{game => gui}/Hud.vue (64%)
 rename src/components/{game => gui}/Menu.vue (100%)
 create mode 100644 src/components/utilities/zoneEditor/Tiles.vue
 create mode 100644 src/components/utilities/zoneEditor/Toolbar.vue
 delete mode 100644 src/components/utilities/zoneEditor/ZoneEditorToolbar.vue

diff --git a/package-lock.json b/package-lock.json
index f7d9b25..f91a0ff 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1221,6 +1221,7 @@
       "version": "0.11.14",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
       "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+      "deprecated": "Use @eslint/config-array instead",
       "dev": true,
       "license": "Apache-2.0",
       "dependencies": {
@@ -1274,6 +1275,7 @@
       "version": "2.0.3",
       "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
       "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+      "deprecated": "Use @eslint/object-schema instead",
       "dev": true,
       "license": "BSD-3-Clause"
     },
diff --git a/public/assets/icons/zoneEditor/eraser-tool.svg b/public/assets/icons/zoneEditor/eraser-tool.svg
deleted file mode 100644
index c0e1bf9..0000000
--- a/public/assets/icons/zoneEditor/eraser-tool.svg
+++ /dev/null
@@ -1 +0,0 @@
-<svg fill="none" height="25" viewBox="0 0 24 25" width="24" xmlns="http://www.w3.org/2000/svg"><g fill="#000"><path d="m11.671 20.8824h-2.342c-.668 0-1.296-.26-1.768-.732l-3-3c-.472-.472-.732-1.1-.732-1.768s.26-1.295.732-1.768l11.585-11.58497c.188-.188.52-.188.707 0l7 7c.195.195.195.512 0 .707l-10.414 10.41397c-.472.472-1.1.732-1.768.732zm4.829-17.79297-11.232 11.23297c-.283.283-.439.66-.439 1.061s.156.777.439 1.061l3 3c.284.283.66.439 1.061.439h2.343c.401 0 .777-.156 1.061-.439l10.06-10.06197z"/><path d="m15.5 17.8824c-.128 0-.256-.049-.354-.146l-7-7c-.195-.195-.195-.512 0-.707.195-.19497.512-.19497.707 0l7 7c.195.195.195.512 0 .707-.097.097-.225.146-.353.146z"/><path d="m5.5 20.8824h-5c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h5c.276 0 .5.224.5.5s-.224.5-.5.5z"/><path d="m14.5 22.8824h-9c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h9c.276 0 .5.224.5.5s-.224.5-.5.5z"/></g></svg>
\ No newline at end of file
diff --git a/public/assets/icons/zoneEditor/eraser.svg b/public/assets/icons/zoneEditor/eraser.svg
new file mode 100644
index 0000000..94962b4
--- /dev/null
+++ b/public/assets/icons/zoneEditor/eraser.svg
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 469.333 469.333" style="enable-background:new 0 0 469.333 469.333;" xml:space="preserve">
+<g>
+	<g>
+		<path d="M456.833,172.237L318.167,33.439c-8.061-8.068-19.109-12.103-30.159-12.105c-11.055-0.002-22.11,4.033-30.175,12.105
+			L12.5,279.006C4.437,287.076,0,297.794,0,309.201c0,11.407,4.406,22.094,12.594,30.289l95.51,93.318
+			c10.021,9.791,23.25,15.192,37.271,15.192h71.771c14.115,0,27.417-5.464,37.479-15.4l202.208-199.972
+			c8.063-8.07,12.5-18.789,12.5-30.195S464.896,180.308,456.833,172.237z M224.656,402.25c-2.052,2.021-4.646,3.083-7.51,3.083
+			h-71.771c-2.844,0-5.417-1.042-7.458-3.042l-95.25-92.958l110.708-110.708l137.844,137.854L224.656,402.25z"/>
+	</g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/public/assets/icons/zoneEditor/move.svg b/public/assets/icons/zoneEditor/move.svg
new file mode 100644
index 0000000..5946299
--- /dev/null
+++ b/public/assets/icons/zoneEditor/move.svg
@@ -0,0 +1,78 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 492.009 492.009" style="enable-background:new 0 0 492.009 492.009;" xml:space="preserve">
+<g>
+	<g>
+		<path d="M314.343,62.977L255.399,4.033c-2.672-2.672-6.236-4.04-9.92-4.032c-3.752-0.036-7.396,1.36-10.068,4.032l-57.728,57.728
+			c-5.408,5.408-5.408,14.2,0,19.604l7.444,7.444c5.22,5.22,14.332,5.22,19.556,0l22.1-22.148v81.388
+			c0,0.248,0.144,0.452,0.188,0.684c0.6,7.092,6.548,12.704,13.8,12.704h10.52c7.644,0,13.928-6.208,13.928-13.852v-9.088
+			c0-0.04,0-0.068,0-0.1V67.869l22.108,22.152c5.408,5.408,14.18,5.408,19.584,0l7.432-7.436
+			C319.751,77.173,319.751,68.377,314.343,62.977z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M314.335,409.437l-7.44-7.456c-5.22-5.228-14.336-5.228-19.564,0l-22.108,22.152v-70.216c0-0.04,0-0.064,0-0.1v-9.088
+			c0-7.648-6.288-14.16-13.924-14.16h-10.528c-7.244,0-13.192,5.756-13.796,12.856c-0.044,0.236-0.188,0.596-0.188,0.84v81.084
+			l-22.1-22.148c-5.224-5.224-14.356-5.224-19.58,0l-7.44,7.444c-5.4,5.404-5.392,14.2,0.016,19.608l57.732,57.724
+			c2.604,2.612,6.08,4.032,9.668,4.032h0.52c3.716,0,7.184-1.416,9.792-4.032l58.94-58.94
+			C319.743,423.633,319.743,414.841,314.335,409.437z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M147.251,226.781l-1.184,0h-7.948c-0.028,0-0.056,0-0.088,0h-69.88l22.152-22.032c2.612-2.608,4.048-6.032,4.048-9.74
+			c0-3.712-1.436-7.164-4.048-9.768l-7.444-7.428c-5.408-5.408-14.204-5.4-19.604,0.008l-58.944,58.94
+			c-2.672,2.668-4.1,6.248-4.028,9.92c-0.076,3.82,1.356,7.396,4.028,10.068l57.728,57.732c2.704,2.704,6.252,4.056,9.804,4.056
+			s7.1-1.352,9.804-4.056l7.44-7.44c2.612-2.608,4.052-6.092,4.052-9.8c0-3.712-1.436-7.232-4.052-9.836l-22.144-22.184h80.728
+			c0.244,0,0.644-0.06,0.876-0.104c7.096-0.6,12.892-6.468,12.892-13.716v-10.536C161.439,233.229,154.895,226.781,147.251,226.781z
+			"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M487.695,236.765l-58.944-58.936c-5.404-5.408-14.2-5.408-19.604,0l-7.436,7.444c-2.612,2.604-4.052,6.088-4.052,9.796
+			c0,3.712,1.436,7.072,4.052,9.68l22.148,22.032h-70.328c-0.036,0-0.064,0-0.096,0h-9.084c-7.644,0-13.78,6.444-13.78,14.084
+			v10.536c0,7.248,5.564,13.108,12.664,13.712c0.236,0.048,0.408,0.108,0.648,0.108h81.188l-22.156,22.18
+			c-2.608,2.604-4.048,6.116-4.048,9.816c0,3.716,1.436,7.208,4.048,9.816l7.448,7.444c2.7,2.704,6.248,4.06,9.8,4.06
+			s7.096-1.352,9.8-4.056l57.736-57.732c2.664-2.664,4.092-6.244,4.028-9.92C491.787,243.009,490.359,239.429,487.695,236.765z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M246.011,207.541c-21.204,0-38.456,17.252-38.456,38.46c0,21.204,17.252,38.46,38.456,38.46
+			c21.204,0,38.46-17.256,38.46-38.46C284.471,224.793,267.215,207.541,246.011,207.541z"/>
+	</g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/public/assets/icons/zoneEditor/tiles.svg b/public/assets/icons/zoneEditor/tiles.svg
index 26cad8a..5a815b3 100644
--- a/public/assets/icons/zoneEditor/tiles.svg
+++ b/public/assets/icons/zoneEditor/tiles.svg
@@ -1,18 +1,29 @@
 <?xml version="1.0" encoding="iso-8859-1"?>
 <!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
-<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
-	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 443.733 443.733" style="enable-background:new 0 0 443.733 443.733;" xml:space="preserve">
 <g>
 	<g>
-		<path d="M506.816,268.181l-145.984-87.595l18.219-11.584c3.072-1.963,4.928-5.355,4.928-9.003s-1.856-7.04-4.928-9.003
-			L261.718,76.331c-3.499-2.219-7.957-2.219-11.456,0l-117.333,74.667C129.856,152.96,128,156.352,128,160s1.856,7.04,4.928,9.003
-			l18.219,11.605L5.184,268.181c-3.2,1.92-5.184,5.376-5.184,9.109c-0.021,3.733,1.92,7.211,5.12,9.152l245.333,149.333
-			c1.707,1.045,3.627,1.557,5.547,1.557c1.92,0,3.84-0.512,5.547-1.557L506.88,286.443c3.2-1.963,5.12-5.419,5.12-9.152
-			C511.979,273.557,510.016,270.101,506.816,268.181z M256,97.984L353.472,160L256,222.016L158.528,160L256,97.984z
-			 M171.264,193.408l78.997,50.261c1.749,1.109,3.755,1.664,5.739,1.664c1.984,0,3.989-0.555,5.717-1.664l78.997-50.283
-			l14.059,8.448L256,264.704l-98.795-62.869L171.264,193.408z M31.296,277.355l105.472-63.253l99.371,63.232l-100.181,63.744
-			L31.296,277.355z M256,414.165l-99.733-60.715L256,289.984l99.733,63.467L256,414.165z M376.043,341.099l-100.181-63.765
-			l99.371-63.232l105.472,63.275L376.043,341.099z"/>
+		<path d="M187.733,0H17.067C7.641,0,0,7.641,0,17.067v170.667c0,9.426,7.641,17.067,17.067,17.067h170.667
+			c9.426,0,17.067-7.641,17.067-17.067V17.067C204.8,7.641,197.159,0,187.733,0z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M426.667,0H256c-9.426,0-17.067,7.641-17.067,17.067v170.667c0,9.426,7.641,17.067,17.067,17.067h170.667
+			c9.426,0,17.067-7.641,17.067-17.067V17.067C443.733,7.641,436.092,0,426.667,0z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M187.733,238.933H17.067C7.641,238.933,0,246.574,0,256v170.667c0,9.426,7.641,17.067,17.067,17.067h170.667
+			c9.426,0,17.067-7.641,17.067-17.067V256C204.8,246.574,197.159,238.933,187.733,238.933z"/>
+	</g>
+</g>
+<g>
+	<g>
+		<path d="M426.667,238.933H256c-9.426,0-17.067,7.641-17.067,17.067v170.667c0,9.426,7.641,17.067,17.067,17.067h170.667
+			c9.426,0,17.067-7.641,17.067-17.067V256C443.733,246.574,436.092,238.933,426.667,238.933z"/>
 	</g>
 </g>
 <g>
diff --git a/src/App.vue b/src/App.vue
index 10285f8..0a0551b 100644
--- a/src/App.vue
+++ b/src/App.vue
@@ -18,7 +18,8 @@ import Game from '@/components/Game.vue'
 const screen: Ref<string> = ref('login')
 const socket = useSocketStore()
 
-socket.$subscribe((mutation, state) => {
+socket.$subscribe(
+  (mutation, state) => {
     if (!state.connection) {
       screen.value = 'login'
     }
diff --git a/src/assets/scss/_variables.scss b/src/assets/scss/_variables.scss
index ad7825c..b7811f5 100644
--- a/src/assets/scss/_variables.scss
+++ b/src/assets/scss/_variables.scss
@@ -12,4 +12,4 @@ $blue-gray: #778899;
 $cyan: #368f8b;
 $dark-cyan: #376362;
 $light-cyan: #00b3b3;
-$green: #09ad19;
\ No newline at end of file
+$green: #09ad19;
diff --git a/src/assets/scss/main.scss b/src/assets/scss/main.scss
index c9bf4df..1353186 100644
--- a/src/assets/scss/main.scss
+++ b/src/assets/scss/main.scss
@@ -15,22 +15,34 @@ body {
   user-select: none; /* Standard syntax */
 }
 
-h1, h2, h3, h4, h5, h6, button, a {
-  font-family: "Poppins", serif;
+h1,
+h2,
+h3,
+h4,
+h5,
+h6,
+button,
+a {
+  font-family: 'Poppins', serif;
   color: $white;
   font-weight: 500;
 }
 
-p, span, li, label {
-  font-family: "Inter", serif;
+p,
+span,
+li,
+label {
+  font-family: 'Inter', serif;
   color: $white;
 }
-button, a {
+button,
+a {
   font-weight: 500;
   text-shadow: 0 4px 6px rgba($black, 0.25);
 }
 
-button, input {
+button,
+input {
   border: none;
   background-color: transparent;
 }
@@ -60,4 +72,4 @@ button {
 
 ::-webkit-scrollbar {
   display: none;
-}
\ No newline at end of file
+}
diff --git a/src/components/Game.vue b/src/components/Game.vue
index c2ca608..fe699a2 100644
--- a/src/components/Game.vue
+++ b/src/components/Game.vue
@@ -1,25 +1,18 @@
 <template>
   <div class="game-container">
-    <GmTools />
-    <div class="top-ui" v-if="!zone.getEditorIsOpen">
-      <Hud />
-    </div>
-
-    <Game :config="gameConfig" class="game" @create="createGame">
-      <Scene name="main" @preload="preloadScene" @create="createScene" @play="playScene">
-        <World />
+    <Game class="game" :config="gameConfig" @create="createGame">
+      <Scene name="main" @preload="preloadScene" @create="createScene">
+        <GmTools />
+        <div v-if="!zoneEditorStore.isActive">
+          <div class="top-ui"><Hud /></div>
+          <World />
+          <div class="bottom-ui"><Chat /> <Menubar /></div>
+        </div>
+        <div v-else>
+          <ZoneEditor />
+        </div>
       </Scene>
     </Game>
-
-    <div class="bottom-ui">
-      <div v-if="!zone.getEditorIsOpen">
-        <Chat />
-        <Menubar />
-      </div>
-      <div v-else>
-        <ZoneEditorToolbar />
-      </div>
-    </div>
   </div>
 </template>
 
@@ -27,25 +20,28 @@
 import 'phaser'
 import { Game, Scene } from 'phavuer'
 import World from '@/components/World.vue'
-import Pointer = Phaser.Input.Pointer
-import { useSocketStore } from '@/stores/socket'
-import Hud from '@/components/game/Hud.vue'
-import Chat from '@/components/game/Chat.vue'
-import Menubar from '@/components/game/Menu.vue'
+import Hud from '@/components/gui/Hud.vue'
+import Chat from '@/components/gui/Chat.vue'
+import Menubar from '@/components/gui/Menu.vue'
 import { onUnmounted } from 'vue'
-import ZoneEditor from '@/components/utilities/zoneEditor/ZoneEditor.vue'
-import Modal from '@/components/utilities/Modal.vue'
 import GmTools from '@/components/utilities/GmTools.vue'
-import { useZoneStore } from '@/stores/zone'
-import ZoneEditorToolbar from '@/components/utilities/zoneEditor/ZoneEditorToolbar.vue'
+import { useSocketStore } from '@/stores/socket'
+import { useZoneEditorStore } from '@/stores/zoneEditor'
+import Toolbar from '@/components/utilities/zoneEditor/Toolbar.vue'
+import ZoneEditor from '@/components/utilities/zoneEditor/ZoneEditor.vue'
 
 const socket = useSocketStore()
-const zone = useZoneStore()
+const zoneEditorStore = useZoneEditorStore()
 
 onUnmounted(() => {
   socket.disconnectSocket()
 })
 
+// on page close
+addEventListener('beforeunload', () => {
+  socket.disconnectSocket()
+})
+
 const gameConfig = {
   name: 'New Quest',
   width: window.innerWidth,
@@ -55,7 +51,7 @@ const gameConfig = {
 }
 
 const createGame = (game: Phaser.Game) => {
-  window.addEventListener('resize', () => {
+  addEventListener('resize', () => {
     game.scale.resize(window.innerWidth, window.innerHeight)
   })
 }
@@ -75,25 +71,23 @@ const preloadScene = (scene: Phaser.Scene) => {
   scene.load.spritesheet('characterW', '/assets/avatar/default/walk.png', { frameWidth: 36, frameHeight: 94 })
 }
 
-const playScene = (scene: Phaser.Scene) => {
-}
+const playScene = (scene: Phaser.Scene) => {}
 
 const createScene = (scene: Phaser.Scene) => {
   // Camera drag system
   let cam = scene.cameras.main
-  scene.input.on('pointermove', function (pointer: Pointer) {
+  scene.input.on('pointermove', function (pointer: Phaser.Input.Pointer) {
     if (!pointer.isDown) return
     cam.scrollX -= (pointer.x - pointer.prevPosition.x) / cam.zoom
     cam.scrollY -= (pointer.y - pointer.prevPosition.y) / cam.zoom
   })
 
   scene.anims.create({
-    key: "walk",
+    key: 'walk',
     frameRate: 7,
-    frames: scene.anims.generateFrameNumbers("characterW", { start: 0, end: 3 }),
-    repeat: -1,
-  });
-
+    frames: scene.anims.generateFrameNumbers('characterW', { start: 0, end: 3 }),
+    repeat: -1
+  })
 
   // const grid = scene.add.grid(0, 0, window.innerWidth, window.innerHeight, 64, 32, 0, 0, 0xff0000, 0.5).setOrigin(0, 0);
   //
@@ -132,4 +126,4 @@ const createScene = (scene: Phaser.Scene) => {
   bottom: 100px;
   height: 100px;
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/src/components/World.vue b/src/components/World.vue
index bbffa75..fdb185e 100644
--- a/src/components/World.vue
+++ b/src/components/World.vue
@@ -1,9 +1,9 @@
 <template>
-  <TilemapLayerC v-if="zoneStore.isLoaded" :tilemap="tileMap" :tileset="zoneStore.getTiles" ref="tilemapLayer" :layerIndex="0" :cull-padding-x="10" :cull-padding-y="10" />
+  <TilemapLayerC :tilemap="tileMap" :tileset="zoneStore.tiles" ref="tilemapLayer" :layerIndex="0" :cull-padding-x="10" :cull-padding-y="10" />
   <Controls :layer="layer" />
-<!--  <Container v-if="zoneStore.isLoaded && !zoneEditorStore.isLoaded">-->
-<!--    <Character :layer="layer" v-for="character in zoneStore.getCharacters" :key="character.id" :character="character" />-->
-<!--  </Container>-->
+  <Container>
+    <Character :layer="layer" v-for="character in zoneStore.characters" :key="character.id" :character="character" />
+  </Container>
 </template>
 
 <script setup lang="ts">
@@ -13,11 +13,10 @@ import TilemapLayer = Phaser.Tilemaps.TilemapLayer
 import { Container, TilemapLayer as TilemapLayerC, useScene } from 'phavuer'
 import Character from '@/components/sprites/Character.vue'
 import { type Character as CharacterType } from '@/types'
-import { onBeforeMount, ref, type Ref, watch } from 'vue'
+import { onBeforeMount, onBeforeUnmount, ref, type Ref, watch } from 'vue'
 import Controls from '@/components/utilities/Controls.vue'
 import { useSocketStore } from '@/stores/socket'
 import { useZoneStore } from '@/stores/zone'
-import { useZoneEditorStore } from '@/stores/zoneEditor'
 
 // Phavuer logic
 let scene = useScene()
@@ -41,44 +40,53 @@ scene.cameras.main.centerOn(centerX, centerY)
 
 // Multiplayer / server logics
 const zoneStore = useZoneStore()
-const zoneEditorStore = useZoneEditorStore()
 const socket = useSocketStore()
 
 // Watch for changes in the zoneStore and update the layer
-watch(() => zoneStore.tiles, () => {
+watch(
+  () => zoneStore.tiles,
+  () => {
     // @TODO : change to tiles for when loading other maps
-    zoneStore.getTiles.forEach((row, y) => row.forEach((tile, x) => layer.putTileAt(tile, x, y)))
-  }, { deep: true }
+    zoneStore.tiles.forEach((row, y) => row.forEach((tile, x) => layer.putTileAt(tile, x, y)))
+  },
+  { deep: true }
 )
 
 // Load the zone from the server
 onBeforeMount(() => {
-  socket.getConnection.emit('character:zone:load', {zoneId: socket.character.zoneId})
+  socket.connection.emit('character:zone:load', { zoneId: socket.character.zoneId })
 })
 
 // Listen for the zone event from the server and load the zone
-socket.getConnection.on('character:zone:load', (data) => {
+socket.connection.on('character:zone:load', (data) => {
   console.log('character:zone:load', data)
-  zoneStore.loadTiles(data.zone.tiles)
+  zoneStore.setTiles(data.zone.tiles)
 
-  let characters = data.characters;
-  zoneStore.setCharacters(characters);
+  let characters = data.characters
+  zoneStore.setCharacters(characters)
 })
 
 // Listen for player join events
-socket.getConnection.on('zone:character:join', (data: CharacterType) => {
+socket.connection.on('zone:character:join', (data: CharacterType) => {
   console.log('character:zone:join', data)
   zoneStore.addCharacter(data)
 })
 
 // Listen for user:disconnect
-socket.getConnection.on('user:disconnect', (data: CharacterType) => {
+socket.connection.on('user:disconnect', (data: CharacterType) => {
   zoneStore.removeCharacter(data)
 })
 
-socket.getConnection.on('character:moved', (data: CharacterType) => {
+socket.connection.on('character:moved', (data: CharacterType) => {
   console.log('character:moved', data)
-  zoneStore.updateCharacter(data);
+  zoneStore.updateCharacter(data)
+})
+
+onBeforeUnmount(() => {
+  socket.connection.off('character:zone:load')
+  socket.connection.off('zone:character:join')
+  socket.connection.off('user:disconnect')
+  socket.connection.off('character:moved')
 })
 
 /**
diff --git a/src/components/game/Chat.vue b/src/components/gui/Chat.vue
similarity index 100%
rename from src/components/game/Chat.vue
rename to src/components/gui/Chat.vue
diff --git a/src/components/game/Hud.vue b/src/components/gui/Hud.vue
similarity index 64%
rename from src/components/game/Hud.vue
rename to src/components/gui/Hud.vue
index 4267b3e..bb20de6 100644
--- a/src/components/game/Hud.vue
+++ b/src/components/gui/Hud.vue
@@ -6,16 +6,16 @@
     <div class="hud">
       <div class="stats">
         <div class="player-details">
-          <span class="player-name">{{ socket.getCharacter.name }}</span>
-          <span class="player-lvl">lvl. {{ socket.getCharacter.level }}</span>
+          <span class="player-name">{{ socket.character.name }}</span>
+          <span class="player-lvl">lvl. {{ socket.character.level }}</span>
         </div>
         <div class="bar">
           <label for="hp">HP</label>
-          <progress id="hp" :value="socket.getCharacter.hitpoints" max="100">{{ socket.getCharacter.hitpoints }}%</progress>
+          <progress id="hp" :value="socket.character.hitpoints" max="100">{{ socket.character.hitpoints }}%</progress>
         </div>
         <div class="bar">
           <label for="mp">MP</label>
-          <progress id="mp" :value="socket.getCharacter.mana" max="100">{{ socket.getCharacter.mana }}%</progress>
+          <progress id="mp" :value="socket.character.mana" max="100">{{ socket.character.mana }}%</progress>
         </div>
       </div>
     </div>
@@ -25,7 +25,7 @@
 <script setup lang="ts">
 import { useSocketStore } from '@/stores/socket'
 
-const socket = useSocketStore();
+const socket = useSocketStore()
 </script>
 
 <style scoped lang="scss">
@@ -35,7 +35,8 @@ const socket = useSocketStore();
   position: relative;
   left: -32px;
 
-  .hud, &::before {
+  .hud,
+  &::before {
     position: absolute;
     top: 32px;
     left: 32px;
@@ -74,12 +75,14 @@ const socket = useSocketStore();
       justify-content: center;
       padding: 0 20px 0 50px;
 
-      .player-details, .bar {
+      .player-details,
+      .bar {
         width: 100%;
         display: flex;
         align-items: center;
         justify-content: space-between;
-        span, label {
+        span,
+        label {
           font-size: 14px;
         }
         .player-name {
@@ -105,18 +108,40 @@ const socket = useSocketStore();
           &#hp {
             accent-color: $red;
             // Chrome, Safari, Edge, Opera
-            &::-webkit-progress-value { background: $red; border-radius: 8px; }
-            &::-webkit-progress-bar { background: $white; border-radius: 8px; border: 2px solid $white; }
+            &::-webkit-progress-value {
+              background: $red;
+              border-radius: 8px;
+            }
+            &::-webkit-progress-bar {
+              background: $white;
+              border-radius: 8px;
+              border: 2px solid $white;
+            }
             // Firefox
-            &::-moz-progress-bar { background: $red; border-radius: 8px; border: 2px solid $white;}
+            &::-moz-progress-bar {
+              background: $red;
+              border-radius: 8px;
+              border: 2px solid $white;
+            }
           }
           &#mp {
             accent-color: $light-blue;
             // Chrome, Safari, Edge, Opera
-            &::-webkit-progress-value { background: $light-blue; border-radius: 8px; }
-            &::-webkit-progress-bar { background: $white; border-radius: 8px; border: 2px solid $white;}
+            &::-webkit-progress-value {
+              background: $light-blue;
+              border-radius: 8px;
+            }
+            &::-webkit-progress-bar {
+              background: $white;
+              border-radius: 8px;
+              border: 2px solid $white;
+            }
             // Firefox
-            &::-moz-progress-bar { background: $light-blue; border-radius: 8px; border: 2px solid $white;}
+            &::-moz-progress-bar {
+              background: $light-blue;
+              border-radius: 8px;
+              border: 2px solid $white;
+            }
           }
         }
       }
diff --git a/src/components/game/Menu.vue b/src/components/gui/Menu.vue
similarity index 100%
rename from src/components/game/Menu.vue
rename to src/components/gui/Menu.vue
diff --git a/src/components/screens/Characters.vue b/src/components/screens/Characters.vue
index 9bfc8b3..53f387d 100644
--- a/src/components/screens/Characters.vue
+++ b/src/components/screens/Characters.vue
@@ -7,9 +7,9 @@
           <label :for="character.id">{{ character.name }}</label>
           <!-- @TODO : Add a confirmation dialog -->
           <button class="delete" @click="delete_character(character.id)">
-            <img draggable="false" src="/assets/icons/trashcan.svg">
+            <img draggable="false" src="/assets/icons/trashcan.svg" />
           </button>
-  
+
           <div class="sprite-container">
             <img draggable="false" src="/assets/avatar/default/0.png" />
           </div>
@@ -32,7 +32,7 @@
       <div class="button-wrapper" v-if="!isLoading">
         <button class="btn-cyan" :disabled="!selected_character" @click="select_character()">
           PLAY
-          <img draggable="false" src="/assets/icons/arrow.svg">
+          <img draggable="false" src="/assets/icons/arrow.svg" />
         </button>
       </div>
     </div>
@@ -60,51 +60,57 @@
 
 <script setup lang="ts">
 import { useSocketStore } from '@/stores/socket'
-import { onBeforeMount, onMounted, ref } from 'vue'
+import { onBeforeMount, onBeforeUnmount, onMounted, ref } from 'vue'
 import Modal from '@/components/utilities/Modal.vue'
-import {type Character as CharacterT} from '@/types'
+import { type Character as CharacterT } from '@/types'
 
 const isLoading = ref(true)
 const characters = ref([])
-const socket = useSocketStore();
+const socket = useSocketStore()
 
 // Fetch characters
-socket.getConnection.on('character:list', (data: any) => {
+socket.connection.on('character:list', (data: any) => {
   characters.value = data
 })
 
 onMounted(() => {
   // wait 1.5 sec
   setTimeout(() => {
-    socket.getConnection.emit('character:list')
+    socket.connection.emit('character:list')
     isLoading.value = false
   }, 1000)
-});
+})
 
 // Select character logics
 const selected_character = ref(null)
 function select_character() {
   if (!selected_character.value) return
-  socket.getConnection.emit('character:connect', { character_id: selected_character.value })
-  socket.getConnection.on('character:connect', (data: CharacterT) => socket.setCharacter(data))
+  socket.connection.emit('character:connect', { character_id: selected_character.value })
+  socket.connection.on('character:connect', (data: CharacterT) => socket.setCharacter(data))
 }
 
 // Delete character logics
 function delete_character(character_id: number) {
   if (!character_id) return
-  socket.getConnection.emit('character:delete', { character_id: character_id })
+  socket.connection.emit('character:delete', { character_id: character_id })
 }
 
 // Create character logics
 const isModalOpen = ref(false)
 const name = ref('')
 function create() {
-  socket.getConnection.on('character:create:success', (data: CharacterT) => {
+  socket.connection.on('character:create:success', (data: CharacterT) => {
     socket.setCharacter(data)
     isModalOpen.value = false
   })
-  socket.getConnection.emit('character:create', { name: name.value })
+  socket.connection.emit('character:create', { name: name.value })
 }
+
+onBeforeUnmount(() => {
+  socket.connection.off('character:list')
+  socket.connection.off('character:connect')
+  socket.connection.off('character:create:success')
+})
 </script>
 
 <style lang="scss">
diff --git a/src/components/screens/Login.vue b/src/components/screens/Login.vue
index 6202814..982dc71 100644
--- a/src/components/screens/Login.vue
+++ b/src/components/screens/Login.vue
@@ -30,14 +30,12 @@
 import { ref } from 'vue'
 import { login, register } from '@/services/authentication'
 import { useNotificationStore } from '@/stores/notifications'
-import ZoneEditor from '@/components/utilities/zoneEditor/ZoneEditor.vue'
-import Modal from '@/components/utilities/Modal.vue'
 import { useSocketStore } from '@/stores/socket'
 
 const bgm = ref('bgm')
 if (bgm.value.paused) {
-  window.addEventListener('click', () => bgm.value.play())
-  window.addEventListener('keydown', () => bgm.value.play())
+  addEventListener('click', () => bgm.value.play())
+  addEventListener('keydown', () => bgm.value.play())
 }
 
 const socket = useSocketStore()
@@ -61,7 +59,7 @@ async function loginFunc() {
   }
 
   socket.setToken(response.token)
-  socket.initConnection();
+  socket.initConnection()
 }
 
 async function registerFunc() {
@@ -80,7 +78,7 @@ async function registerFunc() {
   }
 
   socket.setToken(response.token)
-  socket.initConnection();
+  socket.initConnection()
 }
 </script>
 
diff --git a/src/components/sprites/Character.vue b/src/components/sprites/Character.vue
index a5a4ea5..07555d2 100644
--- a/src/components/sprites/Character.vue
+++ b/src/components/sprites/Character.vue
@@ -1,23 +1,7 @@
 <template>
   <Container>
-    <Rectangle
-      :x="tileToWorldX(layer, props.character?.position_x, props.character?.position_y)"
-      :y="tileToWorldY(layer, props.character?.position_x, props.character?.position_y)"
-      :origin-x="0.5"
-      :origin-y="10.5"
-      :fillColor="0xFFFFFF"
-      :width="74"
-      :height="8"
-    >
-      <Rectangle
-        :x="tileToWorldX(layer, props.character?.position_x, props.character?.position_y)"
-        :y="tileToWorldY(layer, props.character?.position_x, props.character?.position_y)"
-        :origin-x="0.5"
-        :origin-y="20.5"
-        :fillColor="0x09ad19"
-        :width="70"
-        :height="4"
-      />
+    <Rectangle :x="tileToWorldX(layer, props.character?.position_x, props.character?.position_y)" :y="tileToWorldY(layer, props.character?.position_x, props.character?.position_y)" :origin-x="0.5" :origin-y="10.5" :fillColor="0xffffff" :width="74" :height="8">
+      <Rectangle :x="tileToWorldX(layer, props.character?.position_x, props.character?.position_y)" :y="tileToWorldY(layer, props.character?.position_x, props.character?.position_y)" :origin-x="0.5" :origin-y="20.5" :fillColor="0x09ad19" :width="70" :height="4" />
     </Rectangle>
     <Text
       @create="createText"
@@ -29,20 +13,16 @@
       :style="{
         fontFamily: 'Helvetica, Arial',
         color: '#FFF',
-        fontSize: '14px',
+        fontSize: '14px'
       }"
     />
-    <Sprite
-      ref="sprite"
-      :x="tileToWorldX(layer, props.character?.position_x, props.character?.position_y)"
-      :y="tileToWorldY(layer, props.character?.position_x, props.character?.position_y)"
-      play="walk" />
+    <Sprite ref="sprite" :x="tileToWorldX(layer, props.character?.position_x, props.character?.position_y)" :y="tileToWorldY(layer, props.character?.position_x, props.character?.position_y)" play="walk" />
   </Container>
 </template>
 
 <script lang="ts" setup>
 import { Container, Rectangle, Sprite, Text, useScene } from 'phavuer'
-import { onMounted, ref } from 'vue'
+import { onBeforeMount, onMounted, ref } from 'vue'
 import { useSocketStore } from '@/stores/socket'
 import { type Character as CharacterT } from '@/types'
 import { getTile, tileToWorldX, tileToWorldXY, tileToWorldY } from '@/services/zone'
@@ -55,33 +35,32 @@ const props = defineProps({
 })
 
 const scene = useScene()
-const isSelf = props.character.id === socket.character.id;
+const isSelf = props.character.id === socket.character.id
 
 const createText = (text: Phaser.GameObjects.Text) => {
-text.setLetterSpacing(1.5);
+  text.setLetterSpacing(1.5)
 }
 
-onMounted(() => {
+onBeforeMount(() => {
   if (isSelf) setupSelf()
-});
+})
 
-function setupSelf()
-{
+function setupSelf() {
   scene.input.on(Phaser.Input.Events.POINTER_UP, onPointerClick)
 }
 
 function onPointerClick(pointer: Phaser.Input.Pointer) {
-  if (!isSelf) return;
+  if (!isSelf) return
 
   const px = scene.cameras.main.worldView.x + pointer.x
   const py = scene.cameras.main.worldView.y + pointer.y
 
   const pointer_tile = getTile(px, py, props.layer) as Phaser.Tilemaps.Tile
   if (!pointer_tile) {
-    return;
+    return
   }
 
-  socket.getConnection.emit('character:move', { position_x: pointer_tile.x, position_y: pointer_tile.y })
+  socket.connection.emit('character:move', { position_x: pointer_tile.x, position_y: pointer_tile.y })
 
   //Directions for player sprites + animations
   if (px < 0 && py > 0) {
@@ -95,9 +74,6 @@ function onPointerClick(pointer: Phaser.Input.Pointer) {
   }
 }
 
-// spacing
-
-
 /**
  * Resources:
  * https://www.youtube.com/watch?v=9sWrGohw9qo
diff --git a/src/components/utilities/Controls.vue b/src/components/utilities/Controls.vue
index 875a4c9..03eaf8e 100644
--- a/src/components/utilities/Controls.vue
+++ b/src/components/utilities/Controls.vue
@@ -4,7 +4,7 @@
 
 <script setup lang="ts">
 import { Image, useScene } from 'phavuer'
-import { ref } from 'vue'
+import { onBeforeUnmount, ref } from 'vue'
 import config from '@/config'
 import { getTile, tileToWorldXY } from '@/services/zone'
 
@@ -37,4 +37,8 @@ function onPointerMove(pointer: Phaser.Input.Pointer) {
 }
 
 scene.input.on(Phaser.Input.Events.POINTER_MOVE, onPointerMove)
+
+onBeforeUnmount(() => {
+  scene.input.off(Phaser.Input.Events.POINTER_MOVE, onPointerMove)
+})
 </script>
diff --git a/src/components/utilities/GmTools.vue b/src/components/utilities/GmTools.vue
index 3175a38..3d4243f 100644
--- a/src/components/utilities/GmTools.vue
+++ b/src/components/utilities/GmTools.vue
@@ -1,11 +1,11 @@
 <template>
-  <Modal :isModalOpen="true" :closable="false" :modal-width="200" :modal-height="260">
+  <Modal :isModalOpen="true" :closable="false" :is-resizable="false" :modal-width="200" :modal-height="260">
     <template #modalHeader>
       <h3 class="modal-title">GM tools</h3>
     </template>
     <template #modalBody>
       <div class="content">
-        <button class="btn-cyan w-full" type="button">Zone manager</button>
+        <button class="btn-cyan w-full" type="button" @click="() => zoneEditorStore.toggleActive()">Zone manager</button>
         <button class="btn-cyan w-full" type="button">Player manager</button>
         <button class="btn-cyan w-full" type="button">Item manager</button>
         <button class="btn-cyan w-full" type="button">NPC manager</button>
@@ -14,8 +14,10 @@
   </Modal>
 </template>
 <script setup lang="ts">
-import ZoneEditor from '@/components/utilities/zoneEditor/ZoneEditor.vue'
 import Modal from '@/components/utilities/Modal.vue'
+import { useZoneEditorStore } from '@/stores/zoneEditor'
+
+const zoneEditorStore = useZoneEditorStore()
 </script>
 
 <style lang="scss">
@@ -24,4 +26,4 @@ import Modal from '@/components/utilities/Modal.vue'
   flex-direction: column;
   gap: 0.8rem;
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/src/components/utilities/Modal.vue b/src/components/utilities/Modal.vue
index af4cfc4..1cf166c 100644
--- a/src/components/utilities/Modal.vue
+++ b/src/components/utilities/Modal.vue
@@ -4,7 +4,7 @@
       <div class="modal-header" @mousedown="startDrag">
         <slot name="modalHeader" />
         <div class="buttons">
-<!--          <button><img alt="resize" draggable="false" src="/assets/icons/modalFullscreen.svg" /></button>-->
+          <!--          <button><img alt="resize" draggable="false" src="/assets/icons/modalFullscreen.svg" /></button>-->
           <button @click="close" v-if="closable"><img alt="close" draggable="false" src="/assets/icons/close-button-white.svg" /></button>
         </div>
       </div>
@@ -45,7 +45,9 @@ const properties = defineProps({
   }
 })
 
-watch(() => properties.isModalOpen, (value) => {
+watch(
+  () => properties.isModalOpen,
+  (value) => {
     isModalOpenRef.value = value
   }
 )
@@ -122,12 +124,16 @@ const stopDrag = () => {
   isDragging.value = false
 }
 
-watch(() => properties.modalWidth, (value) => {
+watch(
+  () => properties.modalWidth,
+  (value) => {
     width.value = value
   }
 )
 
-watch(() => properties.modalHeight, (value) => {
+watch(
+  () => properties.modalHeight,
+  (value) => {
     height.value = value
   }
 )
@@ -148,14 +154,25 @@ onUnmounted(() => {
   removeEventListener('resize', handleResize)
 })
 
-
 // Make sure modal doesn't go off screen
-watch(() => x.value, (value) => {
-    if (value < 0) { x.value = 0 } else if (value + width.value > window.innerWidth) { x.value = window.innerWidth - width.value }
+watch(
+  () => x.value,
+  (value) => {
+    if (value < 0) {
+      x.value = 0
+    } else if (value + width.value > window.innerWidth) {
+      x.value = window.innerWidth - width.value
+    }
   }
 )
-watch(() => y.value, (value) => {
-    if (value < 0) { y.value = 0 } else if (value + height.value > window.innerHeight) { y.value = window.innerHeight - height.value }
+watch(
+  () => y.value,
+  (value) => {
+    if (value < 0) {
+      y.value = 0
+    } else if (value + height.value > window.innerHeight) {
+      y.value = window.innerHeight - height.value
+    }
   }
 )
 
@@ -264,12 +281,12 @@ function handleResize() {
 
         label {
           margin-bottom: 10px;
-          font-family: "Poppins";
+          font-family: 'Poppins';
         }
 
         input {
           max-width: 250px;
-          font-family: "Poppins";
+          font-family: 'Poppins';
           border: 1px solid $cyan;
           border-radius: 5px;
           background-color: rgba($white, 0.8);
@@ -296,4 +313,4 @@ function handleResize() {
     }
   }
 }
-</style>
\ No newline at end of file
+</style>
diff --git a/src/components/utilities/Notifications.vue b/src/components/utilities/Notifications.vue
index 8498e06..a08942c 100644
--- a/src/components/utilities/Notifications.vue
+++ b/src/components/utilities/Notifications.vue
@@ -15,7 +15,7 @@
 import { useNotificationStore } from '@/stores/notifications'
 import { useSocketStore } from '@/stores/socket'
 import Modal from '@/components/utilities/Modal.vue'
-import { onMounted, onUnmounted, ref, watch } from 'vue'
+import { onBeforeMount, onBeforeUnmount, watch } from 'vue'
 
 const notifications = useNotificationStore()
 const socket = useSocketStore()
@@ -32,14 +32,14 @@ function setupNotificationListener(connection: any) {
   })
 }
 
-onMounted(() => {
-  const connection = socket.getConnection
+onBeforeMount(() => {
+  const connection = socket.connection
   if (connection) {
     setupNotificationListener(connection)
   } else {
     // Watch for changes in the socket connection
     watch(
-      () => socket.getConnection,
+      () => socket.connection,
       (newConnection) => {
         if (newConnection) setupNotificationListener(newConnection)
       }
@@ -47,8 +47,8 @@ onMounted(() => {
   }
 })
 
-onUnmounted(() => {
-  const connection = socket.getConnection
+onBeforeUnmount(() => {
+  const connection = socket.connection
   if (connection) {
     connection.off('notification')
   }
diff --git a/src/components/utilities/zoneEditor/Tiles.vue b/src/components/utilities/zoneEditor/Tiles.vue
new file mode 100644
index 0000000..9e943e7
--- /dev/null
+++ b/src/components/utilities/zoneEditor/Tiles.vue
@@ -0,0 +1,103 @@
+<template>
+  <Teleport to="body">
+    <Modal v-if="isModalOpen" :isModalOpen="true" :modal-width="645" :modal-height="260">
+      <template #modalHeader>
+        <h3 class="modal-title">Tiles</h3>
+      </template>
+      <template #modalBody>
+        <canvas ref="canvas" :width="tileWidth" :height="tileHeight" style="display: none"></canvas>
+        <div class="tiles">
+          <img v-for="(tile, index) in tiles" :key="index" :src="tile" alt="Tile" @click="selectTile(index)" :class="{ selected: selectedTile === index }" />
+        </div>
+      </template>
+    </Modal>
+  </Teleport>
+</template>
+
+<script setup lang="ts">
+import { ref, onMounted, nextTick } from 'vue'
+import config from '@/config'
+import Modal from '@/components/utilities/Modal.vue'
+
+const tileWidth = config.tile_size.x
+const tileHeight = config.tile_size.y
+const tiles = ref<string[]>([])
+const selectedTile = ref<number | null>(null)
+const canvas = ref<HTMLCanvasElement | null>(null)
+const isModalOpen = ref(false)
+
+// Hardcoded image path
+const imagePath = '/assets/tiles/default.png'
+
+const loadImage = (src: string): Promise<HTMLImageElement> => {
+  return new Promise((resolve) => {
+    const img = new Image()
+    img.onload = () => resolve(img)
+    img.src = src
+  })
+}
+
+const splitTiles = (img: HTMLImageElement) => {
+  if (!canvas.value) {
+    console.error('Canvas not found')
+    return
+  }
+  const ctx = canvas.value.getContext('2d')
+  if (!ctx) {
+    console.error('Failed to get canvas context')
+    return
+  }
+
+  const tilesetWidth = img.width
+  const tilesetHeight = img.height
+  const columns = Math.floor(tilesetWidth / tileWidth)
+  const rows = Math.floor(tilesetHeight / tileHeight)
+
+  tiles.value = []
+  selectedTile.value = null
+
+  for (let row = 0; row < rows; row++) {
+    for (let col = 0; col < columns; col++) {
+      const x = col * tileWidth
+      const y = row * tileHeight
+
+      ctx.clearRect(0, 0, tileWidth, tileHeight)
+      ctx.drawImage(img, x, y, tileWidth, tileHeight, 0, 0, tileWidth, tileHeight)
+
+      const tileDataURL = canvas.value.toDataURL()
+      tiles.value.push(tileDataURL)
+    }
+  }
+}
+
+const selectTile = (index: number) => {
+  selectedTile.value = index
+}
+
+onMounted(async () => {
+  isModalOpen.value = true
+  const img = await loadImage(imagePath)
+  await nextTick()
+  splitTiles(img)
+})
+</script>
+
+<style lang="scss">
+.tiles {
+  display: flex;
+  flex-wrap: wrap;
+  gap: 10px;
+}
+
+.tiles img {
+  width: 64px;
+  height: 32px;
+  cursor: pointer;
+  border: 2px solid transparent;
+  transition: border 0.3s ease;
+}
+
+.tiles img.selected {
+  border: 2px solid #ff0000;
+}
+</style>
diff --git a/src/components/utilities/zoneEditor/Toolbar.vue b/src/components/utilities/zoneEditor/Toolbar.vue
new file mode 100644
index 0000000..e64785c
--- /dev/null
+++ b/src/components/utilities/zoneEditor/Toolbar.vue
@@ -0,0 +1,125 @@
+<template>
+  <div class="wrapper">
+    <div class="toolbar">
+      <div class="tools">
+        <button :class="{ active: activeTool === 'move' }" @click="activeTool = 'move'">
+          <img src="/assets/icons/zoneEditor/move.svg" alt="Eraser tool" />
+        </button>
+        <div class="divider"></div>
+        <button :class="{ active: activeTool === 'tiles' }" @click="activeTool = 'tiles'">
+          <img src="/assets/icons/zoneEditor/tiles.svg" alt="Eraser tool" />
+        </button>
+        <div class="divider"></div>
+        <button :class="{ active: activeTool === 'eraser' }" @click="activeTool = 'eraser'">
+          <img src="/assets/icons/zoneEditor/eraser.svg" alt="Eraser tool" />
+        </button>
+      </div>
+      <div class="buttons">
+        <button class="btn-cyan">Save</button>
+        <button class="btn-cyan">Load</button>
+        <button class="btn-cyan">Clear</button>
+        <button class="btn-cyan" @click="() => zoneEditorStore.toggleActive()">Exit</button>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { onBeforeUnmount, ref } from 'vue'
+import { useScene } from 'phavuer'
+import { getTile, tileToWorldXY } from '@/services/zone'
+import config from '@/config'
+import { useZoneStore } from '@/stores/zone'
+import { useZoneEditorStore } from '@/stores/zoneEditor'
+
+const zoneEditorStore = useZoneEditorStore()
+
+const props = defineProps({
+  layer: Phaser.Tilemaps.TilemapLayer
+})
+const scene = useScene()
+const activeTool = ref('move')
+const emit = defineEmits(['erase', 'move', 'tile'])
+
+function onPointerClick(pointer: Phaser.Input.Pointer) {
+  const px = scene.cameras.main.worldView.x + pointer.x
+  const py = scene.cameras.main.worldView.y + pointer.y
+
+  const pointer_tile = getTile(px, py, props.layer) as Phaser.Tilemaps.Tile
+  if (!pointer_tile) {
+    return
+  }
+
+  if (activeTool.value === 'eraser') {
+    emit('erase', pointer_tile)
+  }
+}
+
+scene.input.on(Phaser.Input.Events.POINTER_UP, onPointerClick)
+
+onBeforeUnmount(() => {
+  scene.input.off(Phaser.Input.Events.POINTER_UP, onPointerClick)
+})
+</script>
+
+<style scoped lang="scss">
+@import '@/assets/scss/main';
+
+.wrapper {
+  display: flex;
+  justify-content: center;
+  margin: 10px;
+}
+
+.toolbar {
+  position: fixed;
+  top: 20px;
+  border-radius: 5px;
+  opacity: 0.8;
+  display: flex;
+  background: $dark-gray;
+  border: 2px solid $cyan;
+  color: $light-gray;
+  padding: 5px;
+  min-width: 90%;
+  height: 40px;
+
+  .tools {
+    display: flex;
+    gap: 10px;
+
+    .divider {
+      width: 1px;
+      background: $cyan;
+    }
+
+    // vertical center
+    button {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+      &.active {
+        border-bottom: 3px solid $light-cyan;
+      }
+    }
+
+    img {
+      filter: invert(1);
+      width: 20px;
+      height: 20px;
+    }
+  }
+
+  .buttons {
+    display: flex;
+    gap: 10px;
+    margin-left: auto;
+
+    button {
+      padding-left: 15px;
+      padding-right: 15px;
+    }
+  }
+}
+</style>
diff --git a/src/components/utilities/zoneEditor/ZoneEditor.vue b/src/components/utilities/zoneEditor/ZoneEditor.vue
index ce6c7e4..a4a3f7a 100644
--- a/src/components/utilities/zoneEditor/ZoneEditor.vue
+++ b/src/components/utilities/zoneEditor/ZoneEditor.vue
@@ -1,99 +1,73 @@
 <template>
-  <div>
-    <canvas ref="tileCanvas" :width="tileWidth" :height="tileHeight" style="display: none;"></canvas>
-    <div class="tiles">
-      <img
-        v-for="(tile, index) in tiles"
-        :key="index"
-        :src="tile"
-        alt="Tile"
-        @click="selectTile(index)"
-        :class="{ selected: selectedTile === index }"
-      />
-    </div>
-  </div>
+  <TilemapLayerC :tilemap="tileMap" :tileset="zoneStore.tiles" ref="tilemapLayer" :layerIndex="0" :cull-padding-x="10" :cull-padding-y="10" />
+  <Controls :layer="layer" />
+  <Toolbar :layer="layer" @erase="erase" @tile="tile" />
+  <Tiles />
 </template>
 
 <script setup lang="ts">
-import { ref, onMounted } from 'vue';
 import config from '@/config'
+import Tileset = Phaser.Tilemaps.Tileset
+import TilemapLayer = Phaser.Tilemaps.TilemapLayer
+import { Container, TilemapLayer as TilemapLayerC, useScene } from 'phavuer'
+import { onBeforeMount, ref, type Ref, watch } from 'vue'
+import Controls from '@/components/utilities/Controls.vue'
+import { useSocketStore } from '@/stores/socket'
+import { useZoneStore } from '@/stores/zone'
+import Toolbar from '@/components/utilities/zoneEditor/Toolbar.vue'
+import Tiles from '@/components/utilities/zoneEditor/Tiles.vue'
 
-const tileWidth = config.tile_size.x;
-const tileHeight = config.tile_size.y;
-const tiles = ref([]);
-const selectedTile = ref(null);
-const tileCanvas = ref(null);
+// Phavuer logic
+let scene = useScene()
+let tilemapLayer = ref()
+let zoneData = new Phaser.Tilemaps.MapData({
+  width: 10, // @TODO : get this from the server
+  height: 10, // @TODO : get this from the server
+  tileWidth: config.tile_size.x,
+  tileHeight: config.tile_size.y,
+  orientation: Phaser.Tilemaps.Orientation.ISOMETRIC,
+  format: Phaser.Tilemaps.Formats.ARRAY_2D
+})
+let tileMap = new Phaser.Tilemaps.Tilemap(scene, zoneData)
+let tileset: Tileset = tileMap.addTilesetImage('default', 'tiles') as Tileset
+let layer: TilemapLayer = tileMap.createBlankLayer('layer', tileset, 0, config.tile_size.y) as TilemapLayer
 
-// Hardcoded image path
-const imagePath = '/assets/tiles/default.png';
+// center camera
+const centerY = (tileMap.height * tileMap.tileHeight) / 2
+const centerX = (tileMap.width * tileMap.tileWidth) / 2
+scene.cameras.main.centerOn(centerX, centerY)
 
-const loadImage = (src) => {
-  return new Promise((resolve) => {
-    const img = new Image();
-    img.onload = () => resolve(img);
-    img.src = src;
-  });
-};
+// Multiplayer / server logics
+const zoneStore = useZoneStore()
+const socket = useSocketStore()
 
-const splitTiles = (img) => {
-  const canvas = tileCanvas.value;
-  const ctx = canvas.getContext('2d');
+zoneStore.setTiles([
+  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+  [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
+  [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
+  [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
+  [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
+  [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
+  [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
+  [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
+  [0, 1, 1, 1, 1, 1, 1, 1, 1, 0],
+  [0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
+])
+zoneStore.tiles.forEach((row, y) => row.forEach((tile, x) => layer.putTileAt(tile, x, y)))
 
-  const tilesetWidth = img.width;
-  const tilesetHeight = img.height;
-  const columns = Math.floor(tilesetWidth / tileWidth);
-  const rows = Math.floor(tilesetHeight / tileHeight);
+// Watch for changes in the zoneStore and update the layer
+watch(
+  () => zoneStore.tiles,
+  () => {
+    // @TODO : change to tiles for when loading other maps
+    zoneStore.tiles.forEach((row, y) => row.forEach((tile, x) => layer.putTileAt(tile, x, y)))
+  },
+  { deep: true }
+)
 
-  tiles.value = [];
-  selectedTile.value = null;
+function erase(tile: Phaser.Tilemaps.Tile) {
+  layer.removeTileAt(tile.x, tile.y)
+}
 
-  for (let row = 0; row < rows; row++) {
-    for (let col = 0; col < columns; col++) {
-      const x = col * tileWidth;
-      const y = row * tileHeight;
-
-      ctx.clearRect(0, 0, tileWidth, tileHeight);
-      ctx.drawImage(img, x, y, tileWidth, tileHeight, 0, 0, tileWidth, tileHeight);
-
-      const tileDataURL = canvas.toDataURL();
-      tiles.value.push(tileDataURL);
-    }
-  }
-};
-
-const selectTile = (index) => {
-  selectedTile.value = index;
-};
-
-onMounted(async () => {
-  const img = await loadImage(imagePath);
-  splitTiles(img);
-});
-
-/**
- * Resources:
- * https://codepen.io/Xymota/pen/gOOyxWB
- * https://www.dynetisgames.com/2022/06/09/update-how-to-manage-big-isometric-maps-with-phaser-3-5/
- * https://stackoverflow.com/questions/11533606/javascript-splitting-a-tileset-image-to-be-stored-in-2d-image-array
- */
+function tile() {}
 </script>
-
-<style lang="scss">
-.tiles {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 10px;
-}
-
-.tiles img {
-  width: 64px;
-  height: 32px;
-  cursor: pointer;
-  border: 2px solid transparent;
-  transition: border 0.3s ease;
-}
-
-.tiles img.selected {
-  border: 2px solid #ff0000;
-}
-</style>
\ No newline at end of file
diff --git a/src/components/utilities/zoneEditor/ZoneEditorToolbar.vue b/src/components/utilities/zoneEditor/ZoneEditorToolbar.vue
deleted file mode 100644
index 6187f16..0000000
--- a/src/components/utilities/zoneEditor/ZoneEditorToolbar.vue
+++ /dev/null
@@ -1,81 +0,0 @@
-<template>
-  <div class="toolbar">
-    <div class="tools">
-      <button :class="{ active: activeTool === 'tiles' }" @click="activeTool = 'tiles'">
-        <img src="/assets/icons/zoneEditor/tiles.svg" alt="Eraser tool" />
-      </button>
-      <div class="divider"></div>
-      <button :class="{ active: activeTool === 'eraser' }" @click="activeTool = 'eraser'">
-        <img src="/assets/icons/zoneEditor/eraser-tool.svg" alt="Eraser tool" />
-      </button>
-    </div>
-    <div class="buttons">
-      <button class="btn-cyan">Save</button>
-      <button class="btn-cyan">Load</button>
-      <button class="btn-cyan">Clear</button>
-      <button class="btn-cyan">Exit</button>
-    </div>
-  </div>
-</template>
-
-<script setup lang="ts">
-import { ref } from 'vue'
-
-const activeTool = ref('tiles');
-</script>
-
-<style scoped lang="scss">
-@import '@/assets/scss/main';
-
-.toolbar {
-  position: absolute;
-  border-radius: 5px;
-  opacity: 0.8;
-  display: flex;
-  background: $dark-gray;
-  border: 2px solid $cyan;
-  color: $light-gray;
-  padding: 5px;
-  min-width: 100%;
-  height: 40px;
-
-  .tools {
-    display: flex;
-    gap: 10px;
-
-    .divider {
-      width: 1px;
-      background: $cyan;
-    }
-
-    // vertical center
-    button {
-      display: flex;
-      justify-content: center;
-      align-items: center;
-
-      &.active {
-        border-bottom: 3px solid $light-cyan;
-        border-radius: 5px;
-      }
-    }
-
-    img {
-      filter: invert(1);
-      width: 35px;
-      height: 35px;
-    }
-  }
-
-  .buttons {
-    display: flex;
-    gap: 10px;
-    margin-left: auto;
-
-    button {
-      padding-left: 10px;
-      padding-right: 10px;
-    }
-  }
-}
-</style>
\ No newline at end of file
diff --git a/src/services/authentication.ts b/src/services/authentication.ts
index c874e6d..566614b 100644
--- a/src/services/authentication.ts
+++ b/src/services/authentication.ts
@@ -7,7 +7,7 @@ export async function register(username: string, password: string, socketStore =
   try {
     const response = await axios.post(`${config.server_endpoint}/register`, { username, password })
     useCookies().set('token', response.data.token as string)
-    return { success: true, token: response.data.token}
+    return { success: true, token: response.data.token }
   } catch (error: any) {
     return { error: error.response.data.message }
   }
@@ -17,7 +17,7 @@ export async function login(username: string, password: string, socketStore = us
   try {
     const response = await axios.post(`${config.server_endpoint}/login`, { username, password })
     useCookies().set('token', response.data.token as string)
-    return { success: true, token: response.data.token}
+    return { success: true, token: response.data.token }
   } catch (error: any) {
     return { error: error.response.data.message }
   }
diff --git a/src/services/zone.ts b/src/services/zone.ts
index 3eb7203..f8c9cef 100644
--- a/src/services/zone.ts
+++ b/src/services/zone.ts
@@ -2,7 +2,7 @@ import config from '@/config'
 
 export function getTile(x: number, y: number, layer: Phaser.Tilemaps.TilemapLayer): Phaser.Tilemaps.Tile | undefined {
   const tile: Phaser.Tilemaps.Tile = layer.getTileAtWorldXY(x, y)
-  if (!tile) return undefined;
+  if (!tile) return undefined
   return tile
 }
 
@@ -22,4 +22,4 @@ export function tileToWorldXY(layer: Phaser.Tilemaps.TilemapLayer, pos_x: number
   const position_y = worldPoint.y
 
   return { position_x, position_y }
-}
\ No newline at end of file
+}
diff --git a/src/stores/socket.ts b/src/stores/socket.ts
index f23a15b..068460e 100644
--- a/src/stores/socket.ts
+++ b/src/stores/socket.ts
@@ -11,12 +11,6 @@ export const useSocketStore: StoreDefinition = defineStore('socket', {
     user: null as User | null,
     character: null as Character | null
   }),
-  getters: {
-    getToken: (state: any) => state.token as string,
-    getConnection: (state: any) => state.connection as Socket,
-    getUser: (state: any) => state.user as User,
-    getCharacter: (state: any) => state.character as Character
-  },
   actions: {
     initConnection() {
       this.connection = io(config.server_endpoint, {
diff --git a/src/stores/zone.ts b/src/stores/zone.ts
index 9e9b61f..c5079bd 100644
--- a/src/stores/zone.ts
+++ b/src/stores/zone.ts
@@ -3,21 +3,12 @@ import type { Character } from '@/types'
 
 export const useZoneStore = defineStore('zone', {
   state: () => ({
-    loaded: false,
-    tiles: undefined,
-    characters: [] as Character[],
-    editorIsOpen: true
+    tiles: [] as number[][],
+    characters: [] as Character[]
   }),
-  getters: {
-    isLoaded: (state) => state.loaded,
-    getTiles: (state) => state.tiles,
-    getCharacters: (state) => state.characters,
-    getEditorIsOpen: (state) => state.editorIsOpen
-  },
   actions: {
-    loadTiles(tiles: any) {
+    setTiles(tiles: number[][]) {
       this.tiles = tiles
-      this.loaded = true
     },
     setCharacters(characters: Character[]) {
       this.characters = characters
@@ -25,19 +16,13 @@ export const useZoneStore = defineStore('zone', {
     addCharacter(character: Character) {
       this.characters.push(character)
     },
-    removeCharacter(character: Character) {
-      this.characters = this.characters.filter((c: Character) => c.id !== character.id)
-    },
     updateCharacter(character: Character) {
       const index = this.characters.findIndex((c) => c.id === character.id)
-      if (index !== -1) {
-        this.characters[index] = character
-      } else {
-        console.error(`Character with id ${character.id} not found`)
-      }
+      if (index === -1) return
+      this.characters[index] = character
     },
-    setEditorIsOpen(isOpen: boolean) {
-      this.editorIsOpen = isOpen
+    removeCharacter(character: Character) {
+      this.characters = this.characters.filter((c: Character) => c.id !== character.id)
     }
   }
 })
diff --git a/src/stores/zoneEditor.ts b/src/stores/zoneEditor.ts
index d411022..f27d703 100644
--- a/src/stores/zoneEditor.ts
+++ b/src/stores/zoneEditor.ts
@@ -1,18 +1,20 @@
 import { defineStore } from 'pinia'
+import config from '@/config'
 
 export const useZoneEditorStore = defineStore('zoneEditor', {
   state: () => ({
-    loaded: false,
-    tiles: undefined,
+    active: true
   }),
   getters: {
-    isLoaded: (state) => state.loaded,
-    getTiles: (state) => state.tiles,
+    isActive: (state) => state.active
   },
   actions: {
-    loadTiles(tiles: any) {
-      this.tiles = tiles
-      this.loaded = true
-    },
+    toggleActive() {
+      this.active = !this.active
+    }
   }
 })
+
+/**
+ * [[0,0,0,0,0,0,0,0,0,0],[0,1,1,1,1,1,1,1,1,0],[0,1,1,1,1,1,1,1,1,0],[0,1,1,1,1,1,1,1,1,0],[0,1,1,1,1,1,1,1,1,0],[0,1,1,1,1,1,1,1,1,0],[0,1,1,1,1,1,1,1,1,0],[0,1,1,1,1,1,1,1,1,0],[0,1,1,1,1,1,1,1,1,0],[0,0,0,0,0,0,0,0,0,0]]
+ */