From 58bff2010fb55b3b1d719559dc2f75bea52c3340 Mon Sep 17 00:00:00 2001
From: Dennis Postma <dennis@directonline.io>
Date: Mon, 10 Jun 2024 17:46:25 +0200
Subject: [PATCH] Zone editor GUI (WIP), added a few helper functions

---
 .../assets/icons/zoneEditor/eraser-tool.svg   |  1 +
 public/assets/icons/zoneEditor/tiles.svg      | 48 +++++++++++
 src/assets/scss/_variables.scss               |  1 +
 src/components/Game.vue                       | 20 +++--
 src/components/World.vue                      |  6 +-
 src/components/sprites/Character.vue          | 18 ++---
 src/components/utilities/Controls.vue         | 38 +++------
 .../GmUtilityWindow.vue => GmTools.vue}       |  0
 .../utilities/zoneEditor/ZoneEditor.vue       |  5 +-
 .../zoneEditor/ZoneEditorToolbar.vue          | 81 +++++++++++++++++++
 src/services/zone.ts                          | 10 +++
 src/stores/zone.ts                            |  9 ++-
 12 files changed, 190 insertions(+), 47 deletions(-)
 create mode 100644 public/assets/icons/zoneEditor/eraser-tool.svg
 create mode 100644 public/assets/icons/zoneEditor/tiles.svg
 rename src/components/utilities/{gmTools/GmUtilityWindow.vue => GmTools.vue} (100%)
 create mode 100644 src/components/utilities/zoneEditor/ZoneEditorToolbar.vue

diff --git a/public/assets/icons/zoneEditor/eraser-tool.svg b/public/assets/icons/zoneEditor/eraser-tool.svg
new file mode 100644
index 0000000..c0e1bf9
--- /dev/null
+++ b/public/assets/icons/zoneEditor/eraser-tool.svg
@@ -0,0 +1 @@
+<svg fill="none" height="25" viewBox="0 0 24 25" width="24" xmlns="http://www.w3.org/2000/svg"><g fill="#000"><path d="m11.671 20.8824h-2.342c-.668 0-1.296-.26-1.768-.732l-3-3c-.472-.472-.732-1.1-.732-1.768s.26-1.295.732-1.768l11.585-11.58497c.188-.188.52-.188.707 0l7 7c.195.195.195.512 0 .707l-10.414 10.41397c-.472.472-1.1.732-1.768.732zm4.829-17.79297-11.232 11.23297c-.283.283-.439.66-.439 1.061s.156.777.439 1.061l3 3c.284.283.66.439 1.061.439h2.343c.401 0 .777-.156 1.061-.439l10.06-10.06197z"/><path d="m15.5 17.8824c-.128 0-.256-.049-.354-.146l-7-7c-.195-.195-.195-.512 0-.707.195-.19497.512-.19497.707 0l7 7c.195.195.195.512 0 .707-.097.097-.225.146-.353.146z"/><path d="m5.5 20.8824h-5c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h5c.276 0 .5.224.5.5s-.224.5-.5.5z"/><path d="m14.5 22.8824h-9c-.276 0-.5-.224-.5-.5s.224-.5.5-.5h9c.276 0 .5.224.5.5s-.224.5-.5.5z"/></g></svg>
\ No newline at end of file
diff --git a/public/assets/icons/zoneEditor/tiles.svg b/public/assets/icons/zoneEditor/tiles.svg
new file mode 100644
index 0000000..26cad8a
--- /dev/null
+++ b/public/assets/icons/zoneEditor/tiles.svg
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="iso-8859-1"?>
+<!-- Generator: Adobe Illustrator 19.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0)  -->
+<svg version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+	 viewBox="0 0 512 512" style="enable-background:new 0 0 512 512;" xml:space="preserve">
+<g>
+	<g>
+		<path d="M506.816,268.181l-145.984-87.595l18.219-11.584c3.072-1.963,4.928-5.355,4.928-9.003s-1.856-7.04-4.928-9.003
+			L261.718,76.331c-3.499-2.219-7.957-2.219-11.456,0l-117.333,74.667C129.856,152.96,128,156.352,128,160s1.856,7.04,4.928,9.003
+			l18.219,11.605L5.184,268.181c-3.2,1.92-5.184,5.376-5.184,9.109c-0.021,3.733,1.92,7.211,5.12,9.152l245.333,149.333
+			c1.707,1.045,3.627,1.557,5.547,1.557c1.92,0,3.84-0.512,5.547-1.557L506.88,286.443c3.2-1.963,5.12-5.419,5.12-9.152
+			C511.979,273.557,510.016,270.101,506.816,268.181z M256,97.984L353.472,160L256,222.016L158.528,160L256,97.984z
+			 M171.264,193.408l78.997,50.261c1.749,1.109,3.755,1.664,5.739,1.664c1.984,0,3.989-0.555,5.717-1.664l78.997-50.283
+			l14.059,8.448L256,264.704l-98.795-62.869L171.264,193.408z M31.296,277.355l105.472-63.253l99.371,63.232l-100.181,63.744
+			L31.296,277.355z M256,414.165l-99.733-60.715L256,289.984l99.733,63.467L256,414.165z M376.043,341.099l-100.181-63.765
+			l99.371-63.232l105.472,63.275L376.043,341.099z"/>
+	</g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
diff --git a/src/assets/scss/_variables.scss b/src/assets/scss/_variables.scss
index b66a71c..ad7825c 100644
--- a/src/assets/scss/_variables.scss
+++ b/src/assets/scss/_variables.scss
@@ -11,4 +11,5 @@ $light-gray: #d3d3d3;
 $blue-gray: #778899;
 $cyan: #368f8b;
 $dark-cyan: #376362;
+$light-cyan: #00b3b3;
 $green: #09ad19;
\ No newline at end of file
diff --git a/src/components/Game.vue b/src/components/Game.vue
index cb09aa3..c2ca608 100644
--- a/src/components/Game.vue
+++ b/src/components/Game.vue
@@ -1,7 +1,9 @@
 <template>
   <div class="game-container">
-    <GmUtilityWindow />
-    <div class="top-ui"><Hud /></div>
+    <GmTools />
+    <div class="top-ui" v-if="!zone.getEditorIsOpen">
+      <Hud />
+    </div>
 
     <Game :config="gameConfig" class="game" @create="createGame">
       <Scene name="main" @preload="preloadScene" @create="createScene" @play="playScene">
@@ -10,8 +12,13 @@
     </Game>
 
     <div class="bottom-ui">
-      <Chat />
-      <Menubar />
+      <div v-if="!zone.getEditorIsOpen">
+        <Chat />
+        <Menubar />
+      </div>
+      <div v-else>
+        <ZoneEditorToolbar />
+      </div>
     </div>
   </div>
 </template>
@@ -28,9 +35,12 @@ import Menubar from '@/components/game/Menu.vue'
 import { onUnmounted } from 'vue'
 import ZoneEditor from '@/components/utilities/zoneEditor/ZoneEditor.vue'
 import Modal from '@/components/utilities/Modal.vue'
-import GmUtilityWindow from '@/components/utilities/gmTools/GmUtilityWindow.vue'
+import GmTools from '@/components/utilities/GmTools.vue'
+import { useZoneStore } from '@/stores/zone'
+import ZoneEditorToolbar from '@/components/utilities/zoneEditor/ZoneEditorToolbar.vue'
 
 const socket = useSocketStore()
+const zone = useZoneStore()
 
 onUnmounted(() => {
   socket.disconnectSocket()
diff --git a/src/components/World.vue b/src/components/World.vue
index 8d01b5e..bbffa75 100644
--- a/src/components/World.vue
+++ b/src/components/World.vue
@@ -1,9 +1,9 @@
 <template>
   <TilemapLayerC v-if="zoneStore.isLoaded" :tilemap="tileMap" :tileset="zoneStore.getTiles" ref="tilemapLayer" :layerIndex="0" :cull-padding-x="10" :cull-padding-y="10" />
   <Controls :layer="layer" />
-  <Container v-if="zoneStore.isLoaded && !zoneEditorStore.isLoaded">
-    <Character :layer="layer" v-for="character in zoneStore.getCharacters" :key="character.id" :character="character" />
-  </Container>
+<!--  <Container v-if="zoneStore.isLoaded && !zoneEditorStore.isLoaded">-->
+<!--    <Character :layer="layer" v-for="character in zoneStore.getCharacters" :key="character.id" :character="character" />-->
+<!--  </Container>-->
 </template>
 
 <script setup lang="ts">
diff --git a/src/components/sprites/Character.vue b/src/components/sprites/Character.vue
index def2b10..a5a4ea5 100644
--- a/src/components/sprites/Character.vue
+++ b/src/components/sprites/Character.vue
@@ -1,8 +1,8 @@
 <template>
   <Container>
     <Rectangle
-      :x="tileToWorldXY(layer, props.character?.position_x, props.character?.position_y).position_x"
-      :y="tileToWorldXY(layer, props.character?.position_x, props.character?.position_y).position_y"
+      :x="tileToWorldX(layer, props.character?.position_x, props.character?.position_y)"
+      :y="tileToWorldY(layer, props.character?.position_x, props.character?.position_y)"
       :origin-x="0.5"
       :origin-y="10.5"
       :fillColor="0xFFFFFF"
@@ -10,8 +10,8 @@
       :height="8"
     >
       <Rectangle
-        :x="tileToWorldXY(layer, props.character?.position_x, props.character?.position_y).position_x"
-        :y="tileToWorldXY(layer, props.character?.position_x, props.character?.position_y).position_y"
+        :x="tileToWorldX(layer, props.character?.position_x, props.character?.position_y)"
+        :y="tileToWorldY(layer, props.character?.position_x, props.character?.position_y)"
         :origin-x="0.5"
         :origin-y="20.5"
         :fillColor="0x09ad19"
@@ -22,8 +22,8 @@
     <Text
       @create="createText"
       :text="props.character?.name"
-      :x="tileToWorldXY(layer, props.character?.position_x, props.character?.position_y).position_x"
-      :y="tileToWorldXY(layer, props.character?.position_x, props.character?.position_y).position_y"
+      :x="tileToWorldX(layer, props.character?.position_x, props.character?.position_y)"
+      :y="tileToWorldY(layer, props.character?.position_x, props.character?.position_y)"
       :origin-x="0.5"
       :origin-y="4.5"
       :style="{
@@ -34,8 +34,8 @@
     />
     <Sprite
       ref="sprite"
-      :x="tileToWorldXY(layer, props.character?.position_x, props.character?.position_y).position_x"
-      :y="tileToWorldXY(layer, props.character?.position_x, props.character?.position_y).position_y"
+      :x="tileToWorldX(layer, props.character?.position_x, props.character?.position_y)"
+      :y="tileToWorldY(layer, props.character?.position_x, props.character?.position_y)"
       play="walk" />
   </Container>
 </template>
@@ -45,7 +45,7 @@ import { Container, Rectangle, Sprite, Text, useScene } from 'phavuer'
 import { onMounted, ref } from 'vue'
 import { useSocketStore } from '@/stores/socket'
 import { type Character as CharacterT } from '@/types'
-import { getTile, tileToWorldXY } from '@/services/zone'
+import { getTile, tileToWorldX, tileToWorldXY, tileToWorldY } from '@/services/zone'
 
 const socket = useSocketStore()
 
diff --git a/src/components/utilities/Controls.vue b/src/components/utilities/Controls.vue
index b8221af..875a4c9 100644
--- a/src/components/utilities/Controls.vue
+++ b/src/components/utilities/Controls.vue
@@ -1,13 +1,14 @@
 <template>
   <Image texture="waypoint" :x="waypoint.x" :y="waypoint.y" :visible="waypoint.visible" />
 </template>
+
 <script setup lang="ts">
 import { Image, useScene } from 'phavuer'
 import { ref } from 'vue'
 import config from '@/config'
+import { getTile, tileToWorldXY } from '@/services/zone'
 
 const scene = useScene()
-const pointer_tile = ref(undefined)
 const props = defineProps({
   layer: Phaser.Tilemaps.TilemapLayer
 })
@@ -21,34 +22,19 @@ function onPointerMove(pointer: Phaser.Input.Pointer) {
   const px = scene.cameras.main.worldView.x + pointer.x
   const py = scene.cameras.main.worldView.y + pointer.y
 
-  pointer_tile.value = getTile(px, py, props.layer)
-  if (pointer_tile.value) {
-    waypoint.value.visible = true
-
-    // Convert tile coordinates to world coordinates
-    const worldPoint = props.layer.tileToWorldXY(pointer_tile.value.x, pointer_tile.value.y)
-    waypoint.value.x = worldPoint.x + config.tile_size.y
-    waypoint.value.y = worldPoint.y + config.tile_size.y + 15
-  } else {
+  const pointer_tile = getTile(px, py, props.layer) as Phaser.Tilemaps.Tile
+  if (!pointer_tile) {
     waypoint.value.visible = false
+    return
   }
+
+  waypoint.value.visible = true
+
+  // Convert tile coordinates to world coordinates
+  const worldPoint = tileToWorldXY(props.layer, pointer_tile.x, pointer_tile.y)
+  waypoint.value.x = worldPoint.position_x
+  waypoint.value.y = worldPoint.position_y + config.tile_size.y + 15
 }
 
 scene.input.on(Phaser.Input.Events.POINTER_MOVE, onPointerMove)
-
-function getTile(x: number, y: number, layer: Phaser.Tilemaps.TilemapLayer): Phaser.Tilemaps.Tile | undefined {
-  const tile: Phaser.Tilemaps.Tile = layer.getTileAtWorldXY(x, y)
-  if (!tile) return undefined;
-  return tile
-}
-
-function getTileAt(iX: number, iY: number, layer: Phaser.Tilemaps.TilemapLayer): Phaser.Tilemaps.Tile | undefined {
-  const tile: Phaser.Tilemaps.Tile = layer.getTileAt(iX, iY)
-  if (tile) return tile
-  else return undefined
-}
-
-function getDepth(tile: Phaser.Tilemaps.Tile): number {
-  return 32
-}
 </script>
diff --git a/src/components/utilities/gmTools/GmUtilityWindow.vue b/src/components/utilities/GmTools.vue
similarity index 100%
rename from src/components/utilities/gmTools/GmUtilityWindow.vue
rename to src/components/utilities/GmTools.vue
diff --git a/src/components/utilities/zoneEditor/ZoneEditor.vue b/src/components/utilities/zoneEditor/ZoneEditor.vue
index 3a9b539..ce6c7e4 100644
--- a/src/components/utilities/zoneEditor/ZoneEditor.vue
+++ b/src/components/utilities/zoneEditor/ZoneEditor.vue
@@ -16,9 +16,10 @@
 
 <script setup lang="ts">
 import { ref, onMounted } from 'vue';
+import config from '@/config'
 
-const tileWidth = 64;
-const tileHeight = 32;
+const tileWidth = config.tile_size.x;
+const tileHeight = config.tile_size.y;
 const tiles = ref([]);
 const selectedTile = ref(null);
 const tileCanvas = ref(null);
diff --git a/src/components/utilities/zoneEditor/ZoneEditorToolbar.vue b/src/components/utilities/zoneEditor/ZoneEditorToolbar.vue
new file mode 100644
index 0000000..6187f16
--- /dev/null
+++ b/src/components/utilities/zoneEditor/ZoneEditorToolbar.vue
@@ -0,0 +1,81 @@
+<template>
+  <div class="toolbar">
+    <div class="tools">
+      <button :class="{ active: activeTool === 'tiles' }" @click="activeTool = 'tiles'">
+        <img src="/assets/icons/zoneEditor/tiles.svg" alt="Eraser tool" />
+      </button>
+      <div class="divider"></div>
+      <button :class="{ active: activeTool === 'eraser' }" @click="activeTool = 'eraser'">
+        <img src="/assets/icons/zoneEditor/eraser-tool.svg" alt="Eraser tool" />
+      </button>
+    </div>
+    <div class="buttons">
+      <button class="btn-cyan">Save</button>
+      <button class="btn-cyan">Load</button>
+      <button class="btn-cyan">Clear</button>
+      <button class="btn-cyan">Exit</button>
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { ref } from 'vue'
+
+const activeTool = ref('tiles');
+</script>
+
+<style scoped lang="scss">
+@import '@/assets/scss/main';
+
+.toolbar {
+  position: absolute;
+  border-radius: 5px;
+  opacity: 0.8;
+  display: flex;
+  background: $dark-gray;
+  border: 2px solid $cyan;
+  color: $light-gray;
+  padding: 5px;
+  min-width: 100%;
+  height: 40px;
+
+  .tools {
+    display: flex;
+    gap: 10px;
+
+    .divider {
+      width: 1px;
+      background: $cyan;
+    }
+
+    // vertical center
+    button {
+      display: flex;
+      justify-content: center;
+      align-items: center;
+
+      &.active {
+        border-bottom: 3px solid $light-cyan;
+        border-radius: 5px;
+      }
+    }
+
+    img {
+      filter: invert(1);
+      width: 35px;
+      height: 35px;
+    }
+  }
+
+  .buttons {
+    display: flex;
+    gap: 10px;
+    margin-left: auto;
+
+    button {
+      padding-left: 10px;
+      padding-right: 10px;
+    }
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/services/zone.ts b/src/services/zone.ts
index 793ed13..3eb7203 100644
--- a/src/services/zone.ts
+++ b/src/services/zone.ts
@@ -6,6 +6,16 @@ export function getTile(x: number, y: number, layer: Phaser.Tilemaps.TilemapLaye
   return tile
 }
 
+export function tileToWorldX(layer: Phaser.Tilemaps.TilemapLayer, pos_x: number, pos_y: number) {
+  const worldPoint = layer.tileToWorldXY(pos_x, pos_y)
+  return worldPoint.x + config.tile_size.y
+}
+
+export function tileToWorldY(layer: Phaser.Tilemaps.TilemapLayer, pos_x: number, pos_y: number) {
+  const worldPoint = layer.tileToWorldXY(pos_x, pos_y)
+  return worldPoint.y
+}
+
 export function tileToWorldXY(layer: Phaser.Tilemaps.TilemapLayer, pos_x: number, pos_y: number) {
   const worldPoint = layer.tileToWorldXY(pos_x, pos_y)
   const position_x = worldPoint.x + config.tile_size.y
diff --git a/src/stores/zone.ts b/src/stores/zone.ts
index c3de41d..9e9b61f 100644
--- a/src/stores/zone.ts
+++ b/src/stores/zone.ts
@@ -5,12 +5,14 @@ export const useZoneStore = defineStore('zone', {
   state: () => ({
     loaded: false,
     tiles: undefined,
-    characters: [] as Character[]
+    characters: [] as Character[],
+    editorIsOpen: true
   }),
   getters: {
     isLoaded: (state) => state.loaded,
     getTiles: (state) => state.tiles,
-    getCharacters: (state) => state.characters
+    getCharacters: (state) => state.characters,
+    getEditorIsOpen: (state) => state.editorIsOpen
   },
   actions: {
     loadTiles(tiles: any) {
@@ -33,6 +35,9 @@ export const useZoneStore = defineStore('zone', {
       } else {
         console.error(`Character with id ${character.id} not found`)
       }
+    },
+    setEditorIsOpen(isOpen: boolean) {
+      this.editorIsOpen = isOpen
     }
   }
 })