From 2fe6f8d9c072dcb5625840f9ab08727ceb51fc82 Mon Sep 17 00:00:00 2001
From: Dennis Postma <dennis@directonline.io>
Date: Thu, 4 Jul 2024 02:07:45 +0200
Subject: [PATCH] Worked on zone objects, tile tags and searching

---
 package-lock.json                             |  90 ++++++++-----
 public/assets/icons/zoneEditor/paint.svg      |   2 +
 src/components/forms/ChipsInput.vue           |  38 ++----
 src/components/utilities/GmPanel.vue          |   2 +-
 src/components/utilities/GmTools.vue          |   1 -
 .../utilities/assetManager/AssetManager.vue   |   4 +-
 .../assetManager/partials/ObjectDetails.vue   |  81 ++++++++++++
 .../assetManager/partials/ObjectList.vue      |  41 +++---
 .../assetManager/partials/TileDetails.vue     | 107 +++++++++++-----
 .../assetManager/partials/TileList.vue        |   3 +-
 .../utilities/zoneEditor/Decorations.vue      | 120 ------------------
 .../utilities/zoneEditor/Objects.vue          |  65 ++++++++++
 src/components/utilities/zoneEditor/Tiles.vue |   2 +-
 .../utilities/zoneEditor/Toolbar.vue          |  13 +-
 .../utilities/zoneEditor/ZoneEditor.vue       |  41 +++---
 src/screens/Game.vue                          |   1 +
 src/screens/Login.vue                         |   4 +-
 src/services/zone.ts                          |   4 +-
 src/stores/assetManager.ts                    |  62 ++++++---
 src/stores/gmPanel.ts                         |   4 +-
 src/stores/zoneEditor.ts                      |  23 ++--
 src/types.ts                                  |  64 +++++++---
 22 files changed, 468 insertions(+), 304 deletions(-)
 create mode 100644 public/assets/icons/zoneEditor/paint.svg
 create mode 100644 src/components/utilities/assetManager/partials/ObjectDetails.vue
 delete mode 100644 src/components/utilities/zoneEditor/Decorations.vue
 create mode 100644 src/components/utilities/zoneEditor/Objects.vue

diff --git a/package-lock.json b/package-lock.json
index a529502..d927a4c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -2137,30 +2137,30 @@
       }
     },
     "node_modules/@volar/language-core": {
-      "version": "2.4.0-alpha.12",
-      "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0-alpha.12.tgz",
-      "integrity": "sha512-Dj9qTifcGGgzFLfMbU5dCo13kHyNuEyvPJhtWDnoVBBmgwW3GMwFmgWnNxBhjf63m5x0gux1okaxX2CLN7qSww==",
+      "version": "2.4.0-alpha.13",
+      "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.0-alpha.13.tgz",
+      "integrity": "sha512-tHeJVIRTJ3dlsdNyRjBlqdKHocWkgORM5eXgf6xcGERoXYe6vBpQpxJgpK1pehA8psXNPqkMN1ryBseA0B+m8A==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@volar/source-map": "2.4.0-alpha.12"
+        "@volar/source-map": "2.4.0-alpha.13"
       }
     },
     "node_modules/@volar/source-map": {
-      "version": "2.4.0-alpha.12",
-      "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0-alpha.12.tgz",
-      "integrity": "sha512-LXATFSj4D7T9sEm7FFj6iBgHjKjrdhAgRPcechVKiNCMQdr3r3GVkkeu8aM+1peaMH3LsCqoDxVZEmh2r7CHiw==",
+      "version": "2.4.0-alpha.13",
+      "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.0-alpha.13.tgz",
+      "integrity": "sha512-NABqcuA9QpHsU3FnA5BENP3PI1FOb6hDxqkV1KAHP7gt4fgfQOqSCWpqj3QAS7RV0PtiKTxiDMIJ7doMBhNm7w==",
       "dev": true,
       "license": "MIT"
     },
     "node_modules/@volar/typescript": {
-      "version": "2.4.0-alpha.12",
-      "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0-alpha.12.tgz",
-      "integrity": "sha512-mLg+OQauMTv/+08a7WBWJo1sev/wc8t2is0zhBZIlFU+j5mG89FM4+4089c2p/zoUFZ400Q/VNg2BPfhpZ8wSA==",
+      "version": "2.4.0-alpha.13",
+      "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.0-alpha.13.tgz",
+      "integrity": "sha512-zW/MOPA9SwkCuuVPqADDYfAEPAh68aJQG3/EAqDYozSuK2YNYHEAC0BWYZESSNZC6jxwwx7w0U82fBkDZ9hHEw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
-        "@volar/language-core": "2.4.0-alpha.12",
+        "@volar/language-core": "2.4.0-alpha.13",
         "path-browserify": "^1.0.1",
         "vscode-uri": "^3.0.8"
       }
@@ -2667,9 +2667,9 @@
       }
     },
     "node_modules/acorn": {
-      "version": "8.12.0",
-      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.0.tgz",
-      "integrity": "sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw==",
+      "version": "8.12.1",
+      "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.12.1.tgz",
+      "integrity": "sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==",
       "dev": true,
       "license": "MIT",
       "bin": {
@@ -2959,9 +2959,9 @@
       }
     },
     "node_modules/caniuse-lite": {
-      "version": "1.0.30001639",
-      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001639.tgz",
-      "integrity": "sha512-eFHflNTBIlFwP2AIKaYuBQN/apnUoKNhBdza8ZnW/h2di4LCZ4xFqYlxUxo+LQ76KFI1PGcC1QDxMbxTZpSCAg==",
+      "version": "1.0.30001640",
+      "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001640.tgz",
+      "integrity": "sha512-lA4VMpW0PSUrFnkmVuEKBUovSWKhj7puyCg8StBChgu298N1AtuF1sKWEvfDuimSEDbhlb/KqPKC3fs1HbuQUA==",
       "dev": true,
       "funding": [
         {
@@ -3474,6 +3474,27 @@
         "xmlhttprequest-ssl": "~2.0.0"
       }
     },
+    "node_modules/engine.io-client/node_modules/ws": {
+      "version": "8.17.1",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
+      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.0.0"
+      },
+      "peerDependencies": {
+        "bufferutil": "^4.0.1",
+        "utf-8-validate": ">=5.0.2"
+      },
+      "peerDependenciesMeta": {
+        "bufferutil": {
+          "optional": true
+        },
+        "utf-8-validate": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/engine.io-parser": {
       "version": "5.2.2",
       "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.2.tgz",
@@ -5192,9 +5213,9 @@
       }
     },
     "node_modules/npm-run-all2": {
-      "version": "6.2.0",
-      "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-6.2.0.tgz",
-      "integrity": "sha512-wA7yVIkthe6qJBfiJ2g6aweaaRlw72itsFGF6HuwCHKwtwAx/4BY1vVpk6bw6lS8RLMsexoasOkd0aYOmsFG7Q==",
+      "version": "6.2.1",
+      "resolved": "https://registry.npmjs.org/npm-run-all2/-/npm-run-all2-6.2.1.tgz",
+      "integrity": "sha512-eX4MWsUYOSm1FhPh9LPAWbqq2quny3u8gEEWIY4HHECi10qOyi1dNaJFCyOOv2uP05ZuTPETwS2p1GZk9oLJsw==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -5213,7 +5234,7 @@
         "run-s": "bin/run-s/index.js"
       },
       "engines": {
-        "node": "^14.18.0 || >=16.0.0",
+        "node": "^14.18.0 || ^16.13.0 || >=18.0.0",
         "npm": ">= 8"
       }
     },
@@ -5636,9 +5657,9 @@
       }
     },
     "node_modules/pkg-types": {
-      "version": "1.1.2",
-      "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.2.tgz",
-      "integrity": "sha512-VEGf1he2DR5yowYRl0XJhWJq5ktm9gYIsH+y8sNJpHlxch7JPDaufgrsl4vYjd9hMUY8QVjoNncKbow9I7exyA==",
+      "version": "1.1.3",
+      "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.1.3.tgz",
+      "integrity": "sha512-+JrgthZG6m3ckicaOB74TwQ+tBWsFl3qVQg7mN8ulwSOElJ7gBhKzj2VkCPnZ4NlF6kEquYU+RIYNVAvzd54UA==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
@@ -6665,9 +6686,9 @@
       }
     },
     "node_modules/update-browserslist-db": {
-      "version": "1.0.16",
-      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.16.tgz",
-      "integrity": "sha512-KVbTxlBYlckhF5wgfyZXTWnMn7MMZjMu9XG8bPlliUOP9ThaF4QnhP8qrjrH7DRzHfSk0oQv1wToW+iA5GajEQ==",
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz",
+      "integrity": "sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ==",
       "dev": true,
       "funding": [
         {
@@ -6723,14 +6744,14 @@
       "license": "MIT"
     },
     "node_modules/vite": {
-      "version": "5.3.2",
-      "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.2.tgz",
-      "integrity": "sha512-6lA7OBHBlXUxiJxbO5aAY2fsHHzDr1q7DvXYnyZycRs2Dz+dXBWuhpWHvmljTRTpQC2uvGmUFFkSHF2vGo90MA==",
+      "version": "5.3.3",
+      "resolved": "https://registry.npmjs.org/vite/-/vite-5.3.3.tgz",
+      "integrity": "sha512-NPQdeCU0Dv2z5fu+ULotpuq5yfCS1BzKUIPhNbP3YBfAMGJXbt2nS+sbTFu+qchaqWTD+H3JK++nRwr6XIcp6A==",
       "dev": true,
       "license": "MIT",
       "dependencies": {
         "esbuild": "^0.21.3",
-        "postcss": "^8.4.38",
+        "postcss": "^8.4.39",
         "rollup": "^4.13.0"
       },
       "bin": {
@@ -7273,9 +7294,10 @@
       "license": "ISC"
     },
     "node_modules/ws": {
-      "version": "8.17.1",
-      "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
-      "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
+      "version": "8.18.0",
+      "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
+      "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
+      "dev": true,
       "license": "MIT",
       "engines": {
         "node": ">=10.0.0"
diff --git a/public/assets/icons/zoneEditor/paint.svg b/public/assets/icons/zoneEditor/paint.svg
new file mode 100644
index 0000000..d393fb0
--- /dev/null
+++ b/public/assets/icons/zoneEditor/paint.svg
@@ -0,0 +1,2 @@
+
+<svg fill="none" height="24" viewBox="0 0 24 24" width="24" xmlns="http://www.w3.org/2000/svg"><g fill="#000"><path clip-rule="evenodd" d="m6 5c0-1.65685 1.34315-3 3-3 1.6569 0 3 1.34315 3 3v.99943c.9228.01637 1.808.52521 2.2889 1.39653l3.3612 6.08924c.7139 1.2934.3069 2.9672-.9737 3.7405l-7.27713 4.3945c-1.30875.7904-2.96071.3017-3.68814-1.0161l-3.36116-6.0893c-.71395-1.2934-.30694-2.9671.97368-3.7405l2.67635-1.61618zm4 0v1.74258l-2 1.20777v-2.95035c0-.55228.44772-1 1-1s1 .44772 1 1z" fill-rule="evenodd"/><path d="m19 16c-.4577 0-.7691.253-.9239.4148-.1627.1699-.278.3663-.3589.5273-.1652.3287-.2937.7288-.3908 1.094-.1902.7157-.3264 1.5619-.3264 1.9639 0 1.1046.8954 2 2 2s2-.8954 2-2c0-.402-.1362-1.2482-.3264-1.9639-.0971-.3652-.2256-.7653-.3908-1.094-.0809-.161-.1962-.3574-.3589-.5273-.1548-.1618-.4662-.4148-.9239-.4148z"/></g></svg>
\ No newline at end of file
diff --git a/src/components/forms/ChipsInput.vue b/src/components/forms/ChipsInput.vue
index d23a2c1..cf50419 100644
--- a/src/components/forms/ChipsInput.vue
+++ b/src/components/forms/ChipsInput.vue
@@ -1,44 +1,34 @@
 <template>
   <div class="chip-container">
-    <div v-for="(chip, i) in chips" :key="i" class="chip">
+    <div v-for="(chip, i) in modelValue" :key="i" class="chip">
       <span>{{ chip }}</span>
       <i class="delete-icon" @click="deleteChip(i)">X</i>
     </div>
-    <input
-      v-model="currentInput"
-      @keypress.enter="saveChip"
-      @keydown.delete="backspaceDelete"
-    >
+    <input v-model="currentInput" @keyup.enter="saveChip" @keydown.delete="backspaceDelete" />
   </div>
 </template>
 
 <script setup>
-import { ref, defineProps } from 'vue'
+import { ref } from 'vue'
 
-const props = defineProps({
-  set: {
-    type: Boolean,
-    default: true
-  }
-})
+const modelValue = defineModel('modelValue', { type: Array, default: () => [] })
 
-const chips = ref([])
 const currentInput = ref('')
 
 const saveChip = () => {
-  if ((props.set && !chips.value.includes(currentInput.value)) || !props.set) {
-    chips.value.push(currentInput.value)
+  if (currentInput.value.trim() && !modelValue.value.includes(currentInput.value)) {
+    modelValue.value = [...modelValue.value, currentInput.value]
+    currentInput.value = ''
   }
-  currentInput.value = ''
 }
 
 const deleteChip = (index) => {
-  chips.value.splice(index, 1)
+  modelValue.value = modelValue.value.filter((_, i) => i !== index)
 }
 
 const backspaceDelete = (event) => {
-  if (event.key === 'Backspace' && currentInput.value === '') {
-    chips.value.pop()
+  if (event.key === 'Backspace' && currentInput.value === '' && modelValue.value.length > 0) {
+    modelValue.value = modelValue.value.slice(0, -1)
   }
 }
 </script>
@@ -78,11 +68,7 @@ const backspaceDelete = (event) => {
     outline: none;
     padding: 4px;
     margin: 4px;
-    color:white;
+    color: white;
   }
-
-  
 }
-
-
-</style>
\ No newline at end of file
+</style>
diff --git a/src/components/utilities/GmPanel.vue b/src/components/utilities/GmPanel.vue
index 17d400c..bd1e4fe 100644
--- a/src/components/utilities/GmPanel.vue
+++ b/src/components/utilities/GmPanel.vue
@@ -1,5 +1,5 @@
 <template>
-  <Modal :isModalOpen="gmPanelStore.isOpen" :modal-width="1000" :modal-height="650">
+  <Modal :isModalOpen="gmPanelStore.isOpen" @modal:close="() => gmPanelStore.toggle()" :modal-width="1000" :modal-height="650">
     <template #modalHeader>
       <h3 class="modal-title">GM Panel</h3>
       <div class="gm-selector">
diff --git a/src/components/utilities/GmTools.vue b/src/components/utilities/GmTools.vue
index 8c6f97c..6100957 100644
--- a/src/components/utilities/GmTools.vue
+++ b/src/components/utilities/GmTools.vue
@@ -16,7 +16,6 @@ import Modal from '@/components/utilities/Modal.vue'
 import { useZoneEditorStore } from '@/stores/zoneEditor'
 import { useGmPanelStore } from '@/stores/gmPanel'
 
-
 const zoneEditorStore = useZoneEditorStore()
 const gmPanelStore = useGmPanelStore()
 </script>
diff --git a/src/components/utilities/assetManager/AssetManager.vue b/src/components/utilities/assetManager/AssetManager.vue
index 13cbc1b..9dccc6d 100644
--- a/src/components/utilities/assetManager/AssetManager.vue
+++ b/src/components/utilities/assetManager/AssetManager.vue
@@ -31,16 +31,18 @@
     <!-- Asset details -->
     <div class="asset-info">
       <TileDetails :tile="selectedTile" v-if="selectedCategory === 'tiles' && assetManagerStore.selectedTile" />
+      <ObjectDetails :object="selectedTile" v-if="selectedCategory === 'objects' && assetManagerStore.selectedObject" />
     </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import { onMounted, ref } from 'vue'
+import { ref } from 'vue'
 import { useSocketStore } from '@/stores/socket'
 import TileList from '@/components/utilities/assetManager/partials/TileList.vue'
 import TileDetails from '@/components/utilities/assetManager/partials/TileDetails.vue'
 import ObjectList from '@/components/utilities/assetManager/partials/ObjectList.vue'
+import ObjectDetails from '@/components/utilities/assetManager/partials/ObjectDetails.vue'
 import { useAssetManagerStore } from '@/stores/assetManager'
 
 const socket = useSocketStore()
diff --git a/src/components/utilities/assetManager/partials/ObjectDetails.vue b/src/components/utilities/assetManager/partials/ObjectDetails.vue
new file mode 100644
index 0000000..317bfc9
--- /dev/null
+++ b/src/components/utilities/assetManager/partials/ObjectDetails.vue
@@ -0,0 +1,81 @@
+<template>
+  <div class="object-manager">
+    <div class="image-container">
+      <img :src="objectImageUrl" :alt="'Object ' + selectedObject" />
+    </div>
+    <div class="modal-form asset-manager">
+      <form class="form-fields" @submit.prevent>
+        <div class="form-field name">
+          <label for="name">Name</label>
+          <input class="input-cyan" type="text" name="name" placeholder="Wall #1" />
+        </div>
+        <div class="form-field name">
+          <label for="name">Origin X</label>
+          <!-- @TODO only allow numbers here -->
+          <input class="input-cyan" type="text" name="name" placeholder="Origin X" />
+        </div>
+        <div class="form-field name">
+          <label for="name">Origin Y</label>
+          <!-- @TODO only allow numbers here -->
+          <input class="input-cyan" type="text" name="name" placeholder="Origin Y" />
+        </div>
+        <div class="submit">
+          <button class="btn-cyan" type="button" @click="removeObject">Save</button>
+          <button class="btn-bordeaux" type="button" @click="removeObject">Remove</button>
+        </div>
+      </form>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { computed, onBeforeUnmount, onMounted } from 'vue'
+import { useAssetManagerStore } from '@/stores/assetManager'
+import { useSocketStore } from '@/stores/socket'
+import config from '@/config'
+
+const socket = useSocketStore()
+const assetManagerStore = useAssetManagerStore()
+const selectedObject = computed(() => assetManagerStore.selectedObject)
+const objectImageUrl = computed(() => `${config.server_endpoint}/assets/objects/${selectedObject.value}.png`)
+const objectDetails = computed(() => assetManagerStore.objectDetails)
+
+function removeObject() {
+  socket.connection.emit('gm:object:remove', { object: selectedObject.value }, (response: boolean) => {
+    if (!response) {
+      console.error('Failed to remove object')
+      return
+    }
+    refreshObjectList()
+  })
+}
+
+function refreshObjectList() {
+  socket.connection.emit('gm:object:list', {}, (response: string[]) => {
+    assetManagerStore.setObjectList(response)
+    assetManagerStore.setSelectedObject('')
+  })
+}
+
+onMounted(() => {
+  if (!selectedObject.value) {
+    return
+  }
+  socket.connection.emit('gm:object:details', { object: selectedObject.value }, (response: any) => {
+    assetManagerStore.setObjectDetails(response)
+  })
+})
+
+onBeforeUnmount(() => {
+  assetManagerStore.setSelectedObject('')
+})
+</script>
+
+<style lang="scss">
+/**
+  * @TODO when scrolling here, the vertical line is cut off
+ */
+.modal-form {
+  padding: 10px; // @TODO why dis no work?  fixme
+}
+</style>
diff --git a/src/components/utilities/assetManager/partials/ObjectList.vue b/src/components/utilities/assetManager/partials/ObjectList.vue
index ae0d37d..f32ab31 100644
--- a/src/components/utilities/assetManager/partials/ObjectList.vue
+++ b/src/components/utilities/assetManager/partials/ObjectList.vue
@@ -1,45 +1,47 @@
 <template>
-  <div class="asset">
+  <div class="asset add-new">
+    <label for="upload-asset" class="file-upload">
+      <input id="upload-asset" ref="objectUploadField" type="file" accept="image/png" multiple @change="handleFileUpload" />
+      Upload object(s)
+    </label>
     <input class="input-cyan search-field" placeholder="Search..." />
   </div>
-
-  <!-- TODO: use the passed :name in props to switch out assets-->
-  <a class="asset" :class="{ active: name === 'tiles' }" v-for="(tile, index) in tiles" :key="index">
+  <a class="asset" :class="{ active: assetManagerStore.selectedObject === object }" v-for="(object, index) in assetManagerStore.objectList" :key="index" @click="assetManagerStore.setSelectedObject(object)">
     <div class="asset-details">
-      <img :src="`${config.server_endpoint}/assets/tiles/${tile}`" />
-      <span class="asset-name">{{ tile }}</span>
+      <img :src="`${config.server_endpoint}/assets/objects/${object}.png`" alt="Object" />
+      <span class="asset-name">{{ object }}</span>
     </div>
   </a>
 </template>
 
 <script setup lang="ts">
-import { useSocketStore } from '@/stores/socket'
 import config from '@/config'
-import { onMounted, ref, defineProps } from 'vue'
-
-const props = defineProps<{ name: string }>()
+import { useSocketStore } from '@/stores/socket'
+import { onMounted, ref } from 'vue'
+import { useAssetManagerStore } from '@/stores/assetManager'
 
 const socket = useSocketStore()
-const tileUploadField = ref(null)
-const tiles = ref()
+const objectUploadField = ref(null)
+const assetManagerStore = useAssetManagerStore()
 
 const handleFileUpload = (e: Event) => {
   const files = (e.target as HTMLInputElement).files
   if (!files) return
-  socket.connection.emit('gm:tile:upload', files, (response: boolean) => {
+  socket.connection.emit('gm:object:upload', files, (response: boolean) => {
     if (!response) {
-      console.error('Failed to upload tile')
+      if (config.development) console.error('Failed to upload object')
       return
     }
-    socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
-      tiles.value = response
+    socket.connection.emit('gm:object:list', {}, (response: string[]) => {
+      assetManagerStore.setObjectList(response)
     })
   })
 }
 
 onMounted(() => {
-  socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
-    tiles.value = response
+  socket.connection.emit('gm:object:list', {}, (response: string[]) => {
+    if (config.development) console.log(response)
+    assetManagerStore.setObjectList(response)
   })
 })
 </script>
@@ -48,9 +50,10 @@ onMounted(() => {
 @import '@/assets/scss/main';
 
 .asset {
+  cursor: pointer;
+
   &.add-new {
     display: flex;
-    align-items: center;
     gap: 10px 20px;
     flex-wrap: wrap;
     .asset-name {
diff --git a/src/components/utilities/assetManager/partials/TileDetails.vue b/src/components/utilities/assetManager/partials/TileDetails.vue
index d30fe03..1e40a73 100644
--- a/src/components/utilities/assetManager/partials/TileDetails.vue
+++ b/src/components/utilities/assetManager/partials/TileDetails.vue
@@ -1,45 +1,94 @@
 <template>
-  <div class="image-container">
-    <img :src="`${config.server_endpoint}/assets/tiles/${assetManagerStore.selectedTile}.png`" alt="Tile" />
-  </div>
-  <div class="modal-form asset-manager">
-    <form class="form-fields" @submit.prevent>
-      <div class="form-field name">
-        <label for="name">Name</label>
-        <input class="input-cyan" type="text" name="name" placeholder="E.g. grass" />
-      </div>
-      <div class="form-field tags">
-        <label for="tags">Tags</label>
-        <ChipsInput />
-      </div>
-      <div class="submit">
-        <button class="btn-cyan" type="submit">Save</button>
-        <button class="btn-bordeaux" type="button" @click="removeTile">Remove</button>
-      </div>
-    </form>
+  <div class="tile-manager">
+    <div class="image-container">
+      <img :src="tileImageUrl" :alt="'Tile ' + selectedTile" />
+    </div>
+    <div class="modal-form asset-manager">
+      <form class="form-fields" @submit.prevent>
+        <div class="form-field tags">
+          <label for="tags">Tags</label>
+          <ChipsInput v-model="tags" @update:modelValue="handleTagsUpdate" />
+        </div>
+        <div class="submit">
+          <button class="btn-bordeaux" type="button" @click="removeTile">Remove</button>
+        </div>
+      </form>
+    </div>
   </div>
 </template>
 
 <script setup lang="ts">
-import config from '@/config'
+import { ref, computed, watch, onBeforeUnmount, onMounted } from 'vue'
 import { useAssetManagerStore } from '@/stores/assetManager'
 import { useSocketStore } from '@/stores/socket'
 import ChipsInput from '@/components/forms/ChipsInput.vue'
+import config from '@/config'
 
 const socket = useSocketStore()
 const assetManagerStore = useAssetManagerStore()
 
-function removeTile() {
-  socket.connection.emit('gm:tile:remove', { tile: assetManagerStore.selectedTile }, (response: boolean) => {
-    if (!response) {
-      return
-    }
-    socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
-      assetManagerStore.setTileList(response)
-      assetManagerStore.setSelectedTile('')
-    })
+const tags = ref<string[]>([])
+
+const selectedTile = computed(() => assetManagerStore.selectedTile)
+
+const tileImageUrl = computed(() => `${config.server_endpoint}/assets/tiles/${selectedTile.value}.png`)
+
+watch(selectedTile, fetchTileTags)
+
+function fetchTileTags(tile: string) {
+  if (config.development) console.log('P241 selectedTile', tile)
+  socket.connection.emit('gm:tile:tags', { tile }, (response: string[]) => {
+    tags.value = response
   })
 }
+
+function handleTagsUpdate(newTags: string[]) {
+  if (config.development) console.log(newTags)
+  saveTags(newTags)
+}
+
+function saveTags(tagsToSave: string[]) {
+  socket.connection.emit(
+    'gm:tile:tags:update',
+    {
+      tile: selectedTile.value,
+      tags: tagsToSave
+    },
+    (response: boolean) => {
+      if (!response) console.error('Failed to save tags')
+    }
+  )
+}
+
+function removeTile() {
+  socket.connection.emit('gm:tile:remove', { tile: selectedTile.value }, (response: boolean) => {
+    if (!response) {
+      console.error('Failed to remove tile')
+      return
+    }
+    refreshTileList()
+  })
+}
+
+function refreshTileList() {
+  socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
+    assetManagerStore.setTileList(response)
+    assetManagerStore.setSelectedTile('')
+  })
+}
+
+onMounted(() => {
+  if (!selectedTile.value) return
+  fetchTileTags(selectedTile.value)
+})
+
+onBeforeUnmount(() => {
+  assetManagerStore.setSelectedTile('')
+})
 </script>
 
-<style lang="scss"></style>
+<style lang="scss">
+.modal-form {
+  padding: 10px; // @TODO why dis no work?  fixme
+}
+</style>
diff --git a/src/components/utilities/assetManager/partials/TileList.vue b/src/components/utilities/assetManager/partials/TileList.vue
index f13849f..5dd1a42 100644
--- a/src/components/utilities/assetManager/partials/TileList.vue
+++ b/src/components/utilities/assetManager/partials/TileList.vue
@@ -29,7 +29,7 @@ const handleFileUpload = (e: Event) => {
   if (!files) return
   socket.connection.emit('gm:tile:upload', files, (response: boolean) => {
     if (!response) {
-      console.error('Failed to upload tile')
+      if (config.development) console.error('Failed to upload tile')
       return
     }
     socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
@@ -40,6 +40,7 @@ const handleFileUpload = (e: Event) => {
 
 onMounted(() => {
   socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
+    if (config.development) console.log(response)
     assetManagerStore.setTileList(response)
   })
 })
diff --git a/src/components/utilities/zoneEditor/Decorations.vue b/src/components/utilities/zoneEditor/Decorations.vue
deleted file mode 100644
index cea0dc0..0000000
--- a/src/components/utilities/zoneEditor/Decorations.vue
+++ /dev/null
@@ -1,120 +0,0 @@
-<template>
-  <Teleport to="body">
-    <Modal v-if="isModalOpen" :isModalOpen="true" :closable="false" :modal-width="745" :modal-height="460">
-      <template #modalHeader>
-        <h3 class="modal-title">Decorations</h3>
-      </template>
-      <template #modalBody>
-        <div class="container decorations">
-          <div class="buttons">
-            <button class="btn-cyan" @click="zoneEditorStore.setDrawMode('tile')">Walls</button>
-            <button class="btn-cyan" @click="zoneEditorStore.setDrawMode('tile')">Decorations</button>
-            <button class="btn-cyan" @click="zoneEditorStore.setDrawMode('tile')">NPC</button>
-          </div>
-          <canvas ref="canvas" :width="decorationWidth" :height="decorationHeight" style="display: none"></canvas>
-          <div class="decorations">
-            <img v-for="(decoration, index) in decorations" :key="index" :src="decoration" alt="Decoration" @click="zoneEditorStore.setSelectedDecoration(index)" :class="{ selected: zoneEditorStore.selectedDecoration && zoneEditorStore.selectedDecoration === index }" />
-          </div>
-        </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'
-import { useZoneEditorStore } from '@/stores/zoneEditor'
-
-const decorationWidth = config.wall_size.x
-const decorationHeight = config.wall_size.y
-const decorations = ref<number[][]>([])
-const selectedDecoration = ref<number | null>(null)
-const canvas = ref<HTMLCanvasElement | null>(null)
-const isModalOpen = ref(false)
-const zoneEditorStore = useZoneEditorStore()
-
-// Hardcoded image path
-const imagePath = '/assets/zone/walls.png'
-
-const loadImage = (src: string): Promise<HTMLImageElement> => {
-  return new Promise((resolve) => {
-    const img = new Image()
-    img.onload = () => resolve(img)
-    img.src = src
-  })
-}
-
-const splitDecorations = (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 decorationsetWidth = img.width
-  const decorationsetHeight = img.height
-  const columns = Math.floor(decorationsetWidth / decorationWidth)
-  const rows = Math.floor(decorationsetHeight / decorationHeight)
-
-  decorations.value = []
-  selectedDecoration.value = null
-
-  for (let row = 0; row < rows; row++) {
-    for (let col = 0; col < columns; col++) {
-      const x = col * decorationWidth
-      const y = row * decorationHeight
-
-      ctx.clearRect(0, 0, decorationWidth, decorationHeight)
-      ctx.drawImage(img, x, y, decorationWidth, decorationHeight, 0, 0, decorationWidth, decorationHeight)
-
-      const decorationDataURL = canvas.value.toDataURL()
-      decorations.value.push(decorationDataURL)
-    }
-  }
-}
-
-const selectDecoration = (index: number) => {
-  selectedDecoration.value = index
-}
-
-onMounted(async () => {
-  isModalOpen.value = true
-  const img = await loadImage(imagePath)
-  await nextTick()
-  splitDecorations(img)
-})
-</script>
-
-<style lang="scss">
-@import '@/assets/scss/main';
-
-.decorations {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 10px;
-}
-
-.decorations img {
-  width: 30px;
-  height: 130px;
-  cursor: pointer;
-  border: 2px solid transparent;
-  transition: border 0.3s ease;
-}
-
-.buttons {
-  display: flex;
-  flex-wrap: wrap;
-  gap: 10px;
-}
-
-.decorations img.selected {
-  border: 2px solid $red;
-}
-</style>
diff --git a/src/components/utilities/zoneEditor/Objects.vue b/src/components/utilities/zoneEditor/Objects.vue
new file mode 100644
index 0000000..bf75a96
--- /dev/null
+++ b/src/components/utilities/zoneEditor/Objects.vue
@@ -0,0 +1,65 @@
+<template>
+  <Teleport to="body">
+    <Modal v-if="isModalOpen" :isModalOpen="true" :closable="false" :modal-width="645" :modal-height="260">
+      <template #modalHeader>
+        <h3 class="modal-title">Objects</h3>
+      </template>
+      <template #modalBody>
+        <div class="container objects">
+          <div class="objects">
+            <img v-for="(object, index) in objects" :key="index" :src="`${config.server_endpoint}/assets/objects/${object}.png`" alt="Object" @click="zoneEditorStore.setSelectedObject(object)" :class="{ selected: zoneEditorStore.selectedObject && zoneEditorStore.selectedObject === object }" />
+          </div>
+        </div>
+      </template>
+    </Modal>
+  </Teleport>
+</template>
+
+<script setup lang="ts">
+import config from '@/config'
+import { ref, onMounted } from 'vue'
+import { useZoneEditorStore } from '@/stores/zoneEditor'
+import { useSocketStore } from '@/stores/socket'
+import Modal from '@/components/utilities/Modal.vue'
+
+const socket = useSocketStore()
+const objects = ref<string[]>([])
+const isModalOpen = ref(false)
+const zoneEditorStore = useZoneEditorStore()
+
+onMounted(async () => {
+  isModalOpen.value = true
+  socket.connection.emit('gm:object:list', {}, (response: string[]) => {
+    objects.value = response
+  })
+})
+</script>
+
+<style lang="scss">
+@import '@/assets/scss/main.scss';
+
+/**
+ @TODO add masonry layout
+ https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/
+ */
+
+.objects {
+  display: grid;
+  width: 100%;
+  grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
+  grid-auto-rows: auto;
+  gap: 10px;
+}
+
+.objects img {
+  width: 100%;
+  height: auto;
+  cursor: pointer;
+  border: 2px solid transparent;
+  transition: border 0.3s ease;
+}
+
+.objects img.selected {
+  border: 2px solid $red;
+}
+</style>
diff --git a/src/components/utilities/zoneEditor/Tiles.vue b/src/components/utilities/zoneEditor/Tiles.vue
index 14d323d..e0bd7d7 100644
--- a/src/components/utilities/zoneEditor/Tiles.vue
+++ b/src/components/utilities/zoneEditor/Tiles.vue
@@ -31,7 +31,7 @@ const zoneEditorStore = useZoneEditorStore()
 onMounted(async () => {
   isModalOpen.value = true
   socket.connection.emit('gm:tile:list', {}, (response: string[]) => {
-    tiles.value = response;
+    tiles.value = response
   })
 })
 </script>
diff --git a/src/components/utilities/zoneEditor/Toolbar.vue b/src/components/utilities/zoneEditor/Toolbar.vue
index 115e6e6..2df96c0 100644
--- a/src/components/utilities/zoneEditor/Toolbar.vue
+++ b/src/components/utilities/zoneEditor/Toolbar.vue
@@ -17,7 +17,7 @@
             </div>
             <div class="options" v-show="selectPencilOpen && zoneEditorStore.tool === 'pencil'">
               <span class="option" @click="setDrawMode('tile')">Tile</span>
-              <span class="option" @click="setDrawMode('decoration')">Decoration</span>
+              <span class="option" @click="setDrawMode('object')">Object</span>
               <span class="option" @click="setDrawMode('teleport')">Teleport</span>
               <span class="option" @click="setDrawMode('blocking tile')">Blocking tile</span>
             </div>
@@ -26,6 +26,12 @@
 
         <div class="divider"></div>
 
+        <button class="tool paint" :class="{ active: zoneEditorStore.tool === 'paint' }" @click="zoneEditorStore.setTool('paint')">
+          <img src="/assets/icons/zoneEditor/paint.svg" alt="Paint bucket" />
+        </button>
+
+        <div class="divider"></div>
+
         <button class="tool eraser" :class="{ active: zoneEditorStore.tool === 'eraser' }" @click="zoneEditorStore.setTool('eraser')">
           <img src="/assets/icons/zoneEditor/eraser.svg" alt="Eraser" />
           <div class="select" v-if="zoneEditorStore.tool === 'eraser'">
@@ -43,6 +49,7 @@
         </button>
 
         <div class="divider"></div>
+
         <button class="tool settings" @click="() => zoneEditorStore.toggleSettingsModal()">
           <img src="/assets/icons/zoneEditor/gear.svg" alt="Zone settings" />
         </button>
@@ -61,9 +68,7 @@
 <script setup lang="ts">
 import { onBeforeUnmount, ref, watch } from 'vue'
 import { useScene } from 'phavuer'
-import { getTile, tileToWorldXY } from '@/services/zone'
-import config from '@/config'
-import { useZoneStore } from '@/stores/zone'
+import { getTile } from '@/services/zone'
 import { useZoneEditorStore } from '@/stores/zoneEditor'
 
 const zoneEditorStore = useZoneEditorStore()
diff --git a/src/components/utilities/zoneEditor/ZoneEditor.vue b/src/components/utilities/zoneEditor/ZoneEditor.vue
index 73ffff5..18900b5 100644
--- a/src/components/utilities/zoneEditor/ZoneEditor.vue
+++ b/src/components/utilities/zoneEditor/ZoneEditor.vue
@@ -4,14 +4,13 @@
   <Controls :layer="tiles" />
   <!--  @TODO: inside asset manager we need to be able to set the originX and originY per individial asset -->
   <Container>
-<!--    <Image :texture="'wall1'" :x="pos.position_x" :y="pos.position_y" :originY="1.13" :originX="1" />-->
-<!--    <Image :texture="'wall1'" :x="pos2.position_x" :y="pos2.position_y" :originY="1.13" :originX="1" />-->
-<!--    <Image :texture="'wall2'" :x="pos3.position_x" :y="pos3.position_y" :originY="1.255" :originX="1" />-->
+    <!--      <Image :texture="'wall1'" :x="pos.position_x" :y="pos.position_y" :originY="1.13" :originX="1" />-->
+    <Image v-for="object in zoneObjects" :key="object.id" :texture="object.object" :x="object.position_x" :y="object.position_y" />
   </Container>
 
   <Toolbar :layer="tiles" @eraser="eraser" @pencil="pencil" @save="save" />
   <Tiles v-if="(zoneEditorStore.tool === 'pencil' || zoneEditorStore.tool === 'eraser') && zoneEditorStore.drawMode === 'tile'" />
-  <Decorations v-if="(zoneEditorStore.tool === 'pencil' || zoneEditorStore.tool === 'eraser') && zoneEditorStore.drawMode === 'decoration'" />
+  <Objects v-if="(zoneEditorStore.tool === 'pencil' || zoneEditorStore.tool === 'eraser') && zoneEditorStore.drawMode === 'object'" />
   <ZoneSettings v-if="zoneEditorStore.isSettingsModalShown" />
 </template>
 
@@ -20,17 +19,17 @@ import config from '@/config'
 import Tileset = Phaser.Tilemaps.Tileset
 import TilemapLayer = Phaser.Tilemaps.TilemapLayer
 import { Container, TilemapLayer as TilemapLayerC, useScene, Image } from 'phavuer'
-import { onBeforeMount, onBeforeUnmount, onMounted, toRaw } from 'vue'
+import { onBeforeMount, onBeforeUnmount, onMounted, ref, toRaw } from 'vue'
 import Controls from '@/components/utilities/Controls.vue'
 import { useSocketStore } from '@/stores/socket'
 import Toolbar from '@/components/utilities/zoneEditor/Toolbar.vue'
 import Tiles from '@/components/utilities/zoneEditor/Tiles.vue'
 import { useZoneEditorStore } from '@/stores/zoneEditor'
 import ZoneSettings from '@/components/utilities/zoneEditor/ZoneSettings.vue'
-import Decorations from '@/components/utilities/zoneEditor/Decorations.vue'
 import { placeTile, tileToWorldXY } from '@/services/zone'
-import GmPanel from '@/components/utilities/GmPanel.vue'
 import { useAssetStore } from '@/stores/assets'
+import Objects from '@/components/utilities/zoneEditor/Objects.vue'
+import { randomUUID } from 'crypto'
 
 const scene = useScene()
 const socket = useSocketStore()
@@ -48,12 +47,20 @@ const zoneData = new Phaser.Tilemaps.MapData({
 })
 
 const zone = new Phaser.Tilemaps.Tilemap(scene, zoneData)
-const tilesetImages: Tileset[] = [];
+const tilesetImages: Tileset[] = []
+
+type ZoneObject = {
+  id: string
+  object: string
+  position_x: number
+  position_y: number
+}
+const zoneObjects = ref<ZoneObject[]>([])
 
 /**
  * Walk through tiles and add them to the zone as tilesetImages
  */
-let tileCount = 1;
+let tileCount = 1
 toRaw(assetStore.assets).forEach((asset) => {
   if (asset.group !== 'tiles') return
   tilesetImages.push(zone.addTilesetImage(asset.key, asset.key, config.tile_size.x, config.tile_size.y, 0, 0, tileCount++) as Tileset)
@@ -65,7 +72,6 @@ const exampleTilesArray = Array.from({ length: zoneEditorStore.width }, () => Ar
 
 placeTile(zone, tiles, 0, 0, 'blank_tile')
 
-
 const pos = tileToWorldXY(tiles, 1, 1)
 const pos2 = tileToWorldXY(tiles, 1, 2)
 const pos3 = tileToWorldXY(tiles, 2, 1)
@@ -108,11 +114,16 @@ function pencil(tile: Phaser.Tilemaps.Tile) {
     // zoneEditorStore.setTiles(tile.x, tile.y, zoneEditorStore.selectedTile)
   }
 
-  if (zoneEditorStore.drawMode === 'wall') {
+  if (zoneEditorStore.drawMode === 'object') {
     // @TODO fix position
-    if (zoneEditorStore.selectedWall === null) return
-    walls.putTileAt(zoneEditorStore.selectedWall, tile.x, tile.y)
-    zoneEditorStore.updateWall(tile.x, tile.y, zoneEditorStore.selectedWall)
+    if (zoneEditorStore.selectedObject === null) return
+    if (config.development) console.log('placing object', tile.x, tile.y, zoneEditorStore.selectedObject)
+    zoneObjects.value.push({
+      id: Math.random().toString(10),
+      object: zoneEditorStore.selectedObject,
+      position_x: tileToWorldXY(tiles, tile.x, tile.y).position_x,
+      position_y: tileToWorldXY(tiles, tile.x, tile.y).position_y
+    })
   }
 }
 
@@ -123,7 +134,7 @@ function save() {
     width: zoneEditorStore.width,
     height: zoneEditorStore.height,
     tiles: zoneEditorStore.tiles,
-    walls: zoneEditorStore.walls
+    objects: zoneEditorStore.objects
   })
 }
 
diff --git a/src/screens/Game.vue b/src/screens/Game.vue
index bcab4f7..cbd98d4 100644
--- a/src/screens/Game.vue
+++ b/src/screens/Game.vue
@@ -85,6 +85,7 @@ const preloadScene = (scene: Phaser.Scene) => {
   })
 
   scene.load.image('blank_tile', '/assets/zone/blank_tile.png')
+  scene.load.image('blank_object', '/assets/zone/blank_tile.png')
   scene.load.image('waypoint', '/assets/waypoint.png')
   scene.textures.addBase64(
     'character',
diff --git a/src/screens/Login.vue b/src/screens/Login.vue
index 83f19c5..e89f290 100644
--- a/src/screens/Login.vue
+++ b/src/screens/Login.vue
@@ -31,7 +31,7 @@ import { onMounted, ref } from 'vue'
 import { login, register } from '@/services/authentication'
 import { useNotificationStore } from '@/stores/notifications'
 import { useSocketStore } from '@/stores/socket'
-import {useAssetStore} from '@/stores/assets'
+import { useAssetStore } from '@/stores/assets'
 
 const bgm = ref('bgm')
 if (bgm.value.paused) {
@@ -50,7 +50,7 @@ onMounted(async () => {
   /**
    * Fetch assets from the server
    */
-  assetStore.fetchAssets();
+  assetStore.fetchAssets()
 
   const response = await login('ethereal', 'kanker123')
 
diff --git a/src/services/zone.ts b/src/services/zone.ts
index 16ffc74..5b359c8 100644
--- a/src/services/zone.ts
+++ b/src/services/zone.ts
@@ -27,9 +27,9 @@ export function tileToWorldXY(layer: Phaser.Tilemaps.TilemapLayer, pos_x: number
 }
 
 export function placeTile(zone: Tilemap, layer: TilemapLayer, x: number, y: number, tileName: string) {
-  const tileImg = zone.getTileset(tileName) as Tileset;
+  const tileImg = zone.getTileset(tileName) as Tileset
   if (!tileImg) return
-  layer.putTileAt(tileImg.firstgid, x, y);
+  layer.putTileAt(tileImg.firstgid, x, y)
 }
 
 export function generateTilemap(scene: Phaser.Scene, width: number, height: number) {}
diff --git a/src/stores/assetManager.ts b/src/stores/assetManager.ts
index 691ff2d..caeb543 100644
--- a/src/stores/assetManager.ts
+++ b/src/stores/assetManager.ts
@@ -1,20 +1,50 @@
+import { ref } from 'vue'
 import { defineStore } from 'pinia'
 
-export const useAssetManagerStore = defineStore('assetManager', {
-  state: () => ({
-    tileList: [] as string[],
-    selectedTile: ''
-  }),
-  actions: {
-    setTileList(tiles: string[]) {
-      this.tileList = tiles
-    },
-    setSelectedTile(tile: string) {
-      this.selectedTile = tile
-    },
-    reset() {
-      this.tileList = []
-      this.selectedTile = ''
-    }
+export const useAssetManagerStore = defineStore('assetManager', () => {
+  const tileList = ref<string[]>([])
+  const objectList = ref<string[]>([])
+  const selectedTile = ref<string | null>(null)
+  const selectedObject = ref<string | null>(null)
+  const objectDetails = ref<Record<string, any>>({})
+
+  function setTileList(tiles: string[]) {
+    tileList.value = tiles
+  }
+
+  function setObjectList(objects: string[]) {
+    objectList.value = objects
+  }
+
+  function setSelectedTile(tile: string) {
+    selectedTile.value = tile
+  }
+
+  function setSelectedObject(object: string) {
+    selectedObject.value = object
+  }
+
+  function setObjectDetails(object: Record<string, any>) {
+    objectDetails.value = object
+  }
+
+  function reset() {
+    tileList.value = []
+    selectedTile.value = null
+    selectedObject.value = null
+    objectDetails.value = {}
+  }
+
+  return {
+    tileList,
+    objectList,
+    setTileList,
+    setObjectList,
+    selectedTile,
+    selectedObject,
+    setSelectedTile,
+    setSelectedObject,
+    objectDetails,
+    setObjectDetails
   }
 })
diff --git a/src/stores/gmPanel.ts b/src/stores/gmPanel.ts
index dfabd82..3b2fed5 100644
--- a/src/stores/gmPanel.ts
+++ b/src/stores/gmPanel.ts
@@ -4,7 +4,7 @@ import config from '@/config'
 
 export const useGmPanelStore = defineStore('gmPanel', {
   state: () => ({
-    isOpen: false,
+    isOpen: false
   }),
   actions: {
     toggle() {
@@ -15,6 +15,6 @@ export const useGmPanelStore = defineStore('gmPanel', {
     },
     close() {
       this.isOpen = false
-    },
+    }
   }
 })
diff --git a/src/stores/zoneEditor.ts b/src/stores/zoneEditor.ts
index 39fe5f7..fdca423 100644
--- a/src/stores/zoneEditor.ts
+++ b/src/stores/zoneEditor.ts
@@ -7,12 +7,11 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
     width: 10,
     height: 10,
     tiles: [] as number[][],
-    decorations: [] as number[][],
+    objects: [] as number[][],
     tool: 'move',
     drawMode: 'tile',
     selectedTile: '',
-    selectedWall: null,
-    selectedDecoration: null,
+    selectedObject: null,
     isSettingsModalShown: false
   }),
   actions: {
@@ -34,11 +33,11 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
     updateTile(x: number, y: number, tile: number) {
       this.tiles[y][x] = tile
     },
-    setDecorations(decorations: number[][]) {
-      this.decorations = decorations
+    setObjects(objects: number[][]) {
+      this.objects = objects
     },
-    updateDecoration(x: number, y: number, decoration: number) {
-      this.decorations[y][x] = decoration
+    updateObject(x: number, y: number, object: number) {
+      this.objects[y][x] = object
     },
     setTool(tool: string) {
       this.tool = tool
@@ -49,11 +48,8 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
     setSelectedTile(tile: string) {
       this.selectedTile = tile
     },
-    setSelectedWall(wall: any) {
-      this.selectedWall = wall
-    },
-    setSelectedDecoration(decoration: any) {
-      this.selectedDecoration = decoration
+    setSelectedObject(object: any) {
+      this.selectedObject = object
     },
     toggleSettingsModal() {
       this.isSettingsModalShown = !this.isSettingsModalShown
@@ -66,8 +62,7 @@ export const useZoneEditorStore = defineStore('zoneEditor', {
       this.tool = 'move'
       this.drawMode = 'tile'
       this.selectedTile = ''
-      this.selectedWall = null
-      this.selectedDecoration = null
+      this.selectedObject = null
       this.isSettingsModalShown = false
     }
   }
diff --git a/src/types.ts b/src/types.ts
index 6c50c91..2a0aef0 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -3,7 +3,33 @@ export type Notification = {
   message: string
 }
 
-// User model
+export type Asset = {
+  key: string
+  value: string
+  group: 'tiles' | 'objects' | 'sound' | 'music' | 'ui' | 'font' | 'other'
+  type: 'base64' | 'link'
+}
+
+export type Object = {
+  id: string
+  name: string
+  origin_x: number
+  origin_y: number
+  createdAt: Date
+  updatedAt: Date
+  ZoneObject: ZoneObject[]
+}
+
+export type Item = {
+  id: string
+  name: string
+  description: string
+  stackable: boolean
+  createdAt: Date
+  updatedAt: Date
+  characters: CharacterItem[]
+}
+
 export type User = {
   id: number
   username: string
@@ -11,7 +37,6 @@ export type User = {
   characters: Character[]
 }
 
-// Character model
 export type Character = {
   id: number
   userId: number
@@ -28,33 +53,47 @@ export type Character = {
   zoneId: number
   zone: Zone
   chats: Chat[]
+  items: CharacterItem[]
+}
+
+export type CharacterItem = {
+  id: number
+  characterId: number
+  character: Character
+  itemId: string
+  item: Item
+  quantity: number
+}
+
+export type TileTag = {
+  tile: string
+  tags: any // Using 'any' for Json type, consider using a more specific type if possible
 }
 
-// Zone model
 export type Zone = {
   id: number
   name: string
   width: number
   height: number
-  tiles: number[][]
-  walls: number[][]
-  decorations: ZoneDecoration[]
+  tiles: any // Using 'any' for Json type, consider using a more specific type if possible
+  walls: any // Using 'any' for Json type, consider using a more specific type if possible
+  zoneObjects: ZoneObject[]
   characters: Character[]
   chats: Chat[]
   createdAt: Date
   updatedAt: Date
 }
 
-export type ZoneDecoration = {
+export type ZoneObject = {
   id: number
   zoneId: number
   zone: Zone
-  type: number
+  objectId: string
+  object: Object
   position_x: number
   position_y: number
 }
 
-// Chat model
 export type Chat = {
   id: number
   characterId: number
@@ -64,10 +103,3 @@ export type Chat = {
   message: string
   createdAt: Date
 }
-
-export type Asset = {
-  key: string
-  value: string
-  group: 'tiles' | 'objects' | 'sound' | 'music' | 'ui' | 'font' | 'other'
-  type: 'base64' | 'link'
-}