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' -}