From e389534e30c923708c28752c3e1b7636a06f8c9f Mon Sep 17 00:00:00 2001
From: Dennis Postma <dennis@directonline.io>
Date: Fri, 31 Jan 2025 22:33:45 +0100
Subject: [PATCH 1/3] npm run format

---
 src/components/game/gui/Minimap.vue           |  8 ++---
 .../partials/sprite/SpriteDetails.vue         | 29 +++++++++----------
 .../sprite/partials/SpriteImagesInput.vue     |  6 ++--
 .../sprite/partials/SpritePreview.vue         | 12 ++------
 .../controls/useBaseControlsComposable.ts     |  6 ++--
 .../controls/useGameControlsComposable.ts     |  2 +-
 .../useMapEditorControlsComposable.ts         |  2 +-
 src/composables/useControlsComposable.ts      |  7 ++---
 8 files changed, 29 insertions(+), 43 deletions(-)

diff --git a/src/components/game/gui/Minimap.vue b/src/components/game/gui/Minimap.vue
index d3cefb4..35c8ecd 100644
--- a/src/components/game/gui/Minimap.vue
+++ b/src/components/game/gui/Minimap.vue
@@ -5,12 +5,12 @@
     </div>
     <div class="absolute -bottom-3 left-1/2 -translate-x-1/2 flex gap-1">
       <button class="w-6 h-6 relative p-0">
-        <img class="w-3 h-3 center-element" src="/assets/icons/plus-icon.svg" alt="Zoom-in button icon"/>
-        <img class="w-full h-full" src="/assets/ui-elements/button-ui-box-textured.svg" alt=""/>
+        <img class="w-3 h-3 center-element" src="/assets/icons/plus-icon.svg" alt="Zoom-in button icon" />
+        <img class="w-full h-full" src="/assets/ui-elements/button-ui-box-textured.svg" alt="" />
       </button>
       <button class="w-6 h-6 relative p-0">
-        <img class="w-3 h-3 center-element" src="/assets/icons/minus-icon.svg" alt="Zoom-out button icon"/>
-        <img class="w-full h-full" src="/assets/ui-elements/button-ui-box-textured.svg" alt=""/>
+        <img class="w-3 h-3 center-element" src="/assets/icons/minus-icon.svg" alt="Zoom-out button icon" />
+        <img class="w-full h-full" src="/assets/ui-elements/button-ui-box-textured.svg" alt="" />
       </button>
     </div>
   </div>
diff --git a/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue b/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue
index 705af93..bffd251 100644
--- a/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue
+++ b/src/components/gameMaster/assetManager/partials/sprite/SpriteDetails.vue
@@ -48,23 +48,20 @@
               <input v-model.number="action.frameRate" class="input-field" type="number" step="any" name="frame-speed" placeholder="Frame rate" />
             </div>
             <div class="form-field-full">
-              <SpriteActionsInput 
-                v-model="action.sprites" 
-                @tempOffsetChange="(index, offset) => handleTempOffsetChange(action, index, offset)" 
-              />
+              <SpriteActionsInput v-model="action.sprites" @tempOffsetChange="(index, offset) => handleTempOffsetChange(action, index, offset)" />
             </div>
           </form>
         </template>
       </Accordion>
-      <SpritePreview 
-        v-if="selectedAction" 
-        :sprites="selectedAction.sprites" 
-        :frame-rate="selectedAction.frameRate" 
-        :is-modal-open="isModalOpen" 
-        :temp-offset-index="tempOffsetData.index" 
-        :temp-offset="tempOffsetData.offset" 
-        @update:frame-rate="updateFrameRate" 
-        @update:is-modal-open="isModalOpen = $event" 
+      <SpritePreview
+        v-if="selectedAction"
+        :sprites="selectedAction.sprites"
+        :frame-rate="selectedAction.frameRate"
+        :is-modal-open="isModalOpen"
+        :temp-offset-index="tempOffsetData.index"
+        :temp-offset="tempOffsetData.offset"
+        @update:frame-rate="updateFrameRate"
+        @update:is-modal-open="isModalOpen = $event"
       />
     </div>
   </div>
@@ -199,9 +196,9 @@ function updateFrameRate(value: number) {
   }
 }
 
-const tempOffsetData = ref<{ index: number | undefined; offset: { x: number; y: number } | undefined }>({ 
-  index: undefined, 
-  offset: undefined 
+const tempOffsetData = ref<{ index: number | undefined; offset: { x: number; y: number } | undefined }>({
+  index: undefined,
+  offset: undefined
 })
 
 function handleTempOffsetChange(action: SpriteAction, index: number, offset: { x: number; y: number }) {
diff --git a/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue b/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue
index 96b6061..00d047c 100644
--- a/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue
+++ b/src/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue
@@ -2,9 +2,7 @@
   <div class="flex flex-wrap gap-3">
     <div v-for="(image, index) in modelValue" :key="index" class="h-20 w-20 p-4 bg-gray-300 bg-opacity-50 rounded text-center relative group cursor-move" draggable="true" @dragstart="dragStart($event, index)" @dragover.prevent @dragenter.prevent @drop="drop($event, index)">
       <img :src="image.url" class="max-w-full max-h-full object-contain pointer-events-none" alt="Uploaded image" @load="updateImageDimensions($event, index)" />
-      <div v-if="image.dimensions" class="absolute bottom-1 right-1 bg-black/50 text-white text-xs px-1 py-0.5 rounded transition-opacity font-default">
-        {{ image.dimensions.width }}x{{ image.dimensions.height }}
-      </div>
+      <div v-if="image.dimensions" class="absolute bottom-1 right-1 bg-black/50 text-white text-xs px-1 py-0.5 rounded transition-opacity font-default">{{ image.dimensions.width }}x{{ image.dimensions.height }}</div>
       <div class="absolute top-1 left-1 flex-row space-y-1">
         <button @click.stop="deleteImage(index)" class="bg-red-500 text-white rounded-full w-6 h-6 flex items-center justify-center cursor-pointer opacity-0 group-hover:opacity-100 transition-opacity" aria-label="Delete image">
           <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
@@ -78,7 +76,7 @@ const props = withDefaults(defineProps<Props>(), {
 const emit = defineEmits<{
   (e: 'update:modelValue', value: SpriteImage[]): void
   (e: 'close'): void
-  (e: 'tempOffsetChange', index: number, offset: { x: number, y: number }): void
+  (e: 'tempOffsetChange', index: number, offset: { x: number; y: number }): void
 }>()
 
 const fileInput = ref<HTMLInputElement | null>(null)
diff --git a/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue b/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue
index c44e6ea..61bcc4e 100644
--- a/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue
+++ b/src/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue
@@ -39,15 +39,7 @@
           </div>
           <div class="flex flex-col">
             <label class="block mb-2 text-white">Frame: {{ currentFrame + 1 }} of {{ sprites.length }}</label>
-            <input 
-              type="range" 
-              v-model.number="currentFrame" 
-              :min="0" 
-              :max="sprites.length - 1" 
-              step="1" 
-              class="w-full accent-cyan-500" 
-              @input="stopAnimation"
-            />
+            <input type="range" v-model.number="currentFrame" :min="0" :max="sprites.length - 1" step="1" class="w-full accent-cyan-500" @input="stopAnimation" />
           </div>
           <div class="flex flex-col">
             <label class="block mb-2 text-white">Zoom: {{ zoomLevel }}%</label>
@@ -69,7 +61,7 @@ const props = defineProps<{
   frameRate: number
   isModalOpen?: boolean
   tempOffsetIndex?: number
-  tempOffset?: { x: number, y: number }
+  tempOffset?: { x: number; y: number }
 }>()
 
 const emit = defineEmits<{
diff --git a/src/composables/controls/useBaseControlsComposable.ts b/src/composables/controls/useBaseControlsComposable.ts
index 5651152..dce4312 100644
--- a/src/composables/controls/useBaseControlsComposable.ts
+++ b/src/composables/controls/useBaseControlsComposable.ts
@@ -32,8 +32,8 @@ export function useBaseControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
 
     if (Math.abs(deltaX) <= dragThreshold && Math.abs(deltaY) <= dragThreshold) return
 
-    const scrollX = camera.scrollX - (deltaX / camera.zoom)
-    const scrollY = camera.scrollY - (deltaY / camera.zoom)
+    const scrollX = camera.scrollX - deltaX / camera.zoom
+    const scrollY = camera.scrollY - deltaY / camera.zoom
 
     camera.setScroll(scrollX, scrollY)
     pointerStartPosition.value = { x: pointer.x, y: pointer.y }
@@ -66,4 +66,4 @@ export function useBaseControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
     handleZoom,
     pointerStartPosition
   }
-}
\ No newline at end of file
+}
diff --git a/src/composables/controls/useGameControlsComposable.ts b/src/composables/controls/useGameControlsComposable.ts
index 79e26d3..a79297d 100644
--- a/src/composables/controls/useGameControlsComposable.ts
+++ b/src/composables/controls/useGameControlsComposable.ts
@@ -1,7 +1,7 @@
 import { getTile } from '@/composables/mapComposable'
 import { useGameStore } from '@/stores/gameStore'
-import { useBaseControlsComposable } from './useBaseControlsComposable'
 import type { Ref } from 'vue'
+import { useBaseControlsComposable } from './useBaseControlsComposable'
 
 export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
   const gameStore = useGameStore()
diff --git a/src/composables/controls/useMapEditorControlsComposable.ts b/src/composables/controls/useMapEditorControlsComposable.ts
index 8c3afa4..c0db832 100644
--- a/src/composables/controls/useMapEditorControlsComposable.ts
+++ b/src/composables/controls/useMapEditorControlsComposable.ts
@@ -1,6 +1,6 @@
 import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
-import { useBaseControlsComposable } from './useBaseControlsComposable'
 import { computed, type Ref } from 'vue'
+import { useBaseControlsComposable } from './useBaseControlsComposable'
 
 export function useMapEditorControlsComposable(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
   const mapEditor = useMapEditorComposable()
diff --git a/src/composables/useControlsComposable.ts b/src/composables/useControlsComposable.ts
index 92cab72..2ce48fb 100644
--- a/src/composables/useControlsComposable.ts
+++ b/src/composables/useControlsComposable.ts
@@ -1,15 +1,14 @@
-import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
-import { computed, watch, type Ref } from 'vue'
 import { useGameControlsComposable } from '@/composables/controls/useGameControlsComposable'
 import { useMapEditorControlsComposable } from '@/composables/controls/useMapEditorControlsComposable'
-import { useGameStore } from '@/stores/gameStore'
+import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
+import { computed, type Ref } from 'vue'
 
 export function useControlsComposable(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>) {
   const camera = scene.cameras.main
-  const mapEditor = useMapEditorComposable()
   const gameHandlers = useGameControlsComposable(scene, layer, waypoint, camera)
   const mapEditorHandlers = useMapEditorControlsComposable(scene, layer, waypoint, camera)
 
+  const mapEditor = useMapEditorComposable()
   const currentHandlers = computed(() => (mapEditor.active.value ? mapEditorHandlers : gameHandlers))
 
   const setupControls = () => currentHandlers.value.setupControls()

From a9de031673a9c623a8cd2d67d11dc3b82d437873 Mon Sep 17 00:00:00 2001
From: Dennis Postma <dennis@directonline.io>
Date: Sat, 1 Feb 2025 01:31:28 +0100
Subject: [PATCH 2/3] poc

---
 src/components/game/map/Map.vue               |  6 +++++
 .../controls/useGameControlsComposable.ts     | 25 +++++++++++++++++++
 2 files changed, 31 insertions(+)

diff --git a/src/components/game/map/Map.vue b/src/components/game/map/Map.vue
index a9a6df7..fb95343 100644
--- a/src/components/game/map/Map.vue
+++ b/src/components/game/map/Map.vue
@@ -34,6 +34,12 @@ gameStore.connection?.on('map:character:leave', (characterId: UUID) => {
 
 gameStore.connection?.on('map:character:move', (data: { characterId: UUID; positionX: number; positionY: number; rotation: number; isMoving: boolean }) => {
   mapStore.updateCharacterPosition(data)
+  // @TODO: Replace with universal class, composable or store
+  if (data.characterId === gameStore.character?.id) {
+    gameStore.character!.positionX = data.positionX
+    gameStore.character!.positionY = data.positionY
+    gameStore.character!.rotation = data.rotation
+  }
 })
 
 onUnmounted(() => {
diff --git a/src/composables/controls/useGameControlsComposable.ts b/src/composables/controls/useGameControlsComposable.ts
index a79297d..fa52197 100644
--- a/src/composables/controls/useGameControlsComposable.ts
+++ b/src/composables/controls/useGameControlsComposable.ts
@@ -28,11 +28,35 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
     })
   }
 
+  function handleArrowKeys(event: KeyboardEvent) {
+    if (event.key === 'ArrowLeft') {
+      console.log('ArrowLeft')
+      gameStore.connection?.emit('map:character:move', { positionX: gameStore.character!.positionX - 1, positionY: 0 })
+      return
+    }
+    if (event.key === 'ArrowRight') {
+      console.log('ArrowRight')
+      gameStore.connection?.emit('map:character:move', { positionX: gameStore.character!.positionX + 1, positionY: 0 })
+      return
+    }
+    if (event.key === 'ArrowUp') {
+      console.log('ArrowUp')
+      gameStore.connection?.emit('map:character:move', { positionX: 0, positionY: gameStore.character!.positionY - 1 })
+      return
+    }
+    if (event.key === 'ArrowDown') {
+      console.log('ArrowDown')
+      gameStore.connection?.emit('map:character:move', { positionX: 0, positionY: gameStore.character!.positionY + 1 })
+      return
+    }
+  }
+
   const setupControls = () => {
     scene.input.on(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
     scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
     scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
     scene.input.on(Phaser.Input.Events.POINTER_WHEEL, baseHandlers.handleZoom)
+    scene.input.keyboard!.on(Phaser.Input.Keyboard.Events.KEY_DOWN, handleArrowKeys)
   }
 
   const cleanupControls = () => {
@@ -40,6 +64,7 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
     scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
     scene.input.off(Phaser.Input.Events.POINTER_UP, handlePointerUp)
     scene.input.off(Phaser.Input.Events.POINTER_WHEEL, baseHandlers.handleZoom)
+    scene.input.keyboard!.off(Phaser.Input.Keyboard.Events.KEY_DOWN, handleArrowKeys)
   }
 
   return { setupControls, cleanupControls }

From 9d9556267929ea0cb32e9edab3cd9dda9eb22ecb Mon Sep 17 00:00:00 2001
From: Dennis Postma <dennis@directonline.io>
Date: Sat, 1 Feb 2025 01:43:01 +0100
Subject: [PATCH 3/3] Implemented logic to walk with arrow keys

---
 .../mapEditor/partials/TileList.vue           | 22 +++---
 src/components/screens/MapEditor.vue          |  2 +-
 .../controls/useGameControlsComposable.ts     | 74 ++++++++++++++-----
 3 files changed, 67 insertions(+), 31 deletions(-)

diff --git a/src/components/gameMaster/mapEditor/partials/TileList.vue b/src/components/gameMaster/mapEditor/partials/TileList.vue
index 718c163..248a290 100644
--- a/src/components/gameMaster/mapEditor/partials/TileList.vue
+++ b/src/components/gameMaster/mapEditor/partials/TileList.vue
@@ -31,14 +31,14 @@
                       @click="openGroup(group)"
                       @load="() => processTile(group.parent)"
                       :class="{
-                    'border-cyan shadow-lg': isActiveTile(group.parent),
-                    'border-transparent hover:border-gray-300': !isActiveTile(group.parent)
-                  }"
+                        'border-cyan shadow-lg': isActiveTile(group.parent),
+                        'border-transparent hover:border-gray-300': !isActiveTile(group.parent)
+                      }"
                     />
                     <span class="text-xs mt-1">{{ getTileCategory(group.parent) }}</span>
                     <span v-if="group.children.length > 0" class="absolute top-0 right-0 bg-cyan text-white rounded-full w-5 h-5 flex items-center justify-center text-xs">
-                  {{ group.children.length + 1 }}
-                </span>
+                      {{ group.children.length + 1 }}
+                    </span>
                   </div>
                 </div>
               </div>
@@ -56,9 +56,9 @@
                     :alt="selectedGroup.parent.name"
                     @click="selectTile(selectedGroup.parent.id)"
                     :class="{
-                  'border-cyan shadow-lg': isActiveTile(selectedGroup.parent),
-                  'border-transparent hover:border-gray-300': !isActiveTile(selectedGroup.parent)
-                }"
+                      'border-cyan shadow-lg': isActiveTile(selectedGroup.parent),
+                      'border-transparent hover:border-gray-300': !isActiveTile(selectedGroup.parent)
+                    }"
                   />
                   <span class="text-xs mt-1">{{ getTileCategory(selectedGroup.parent) }}</span>
                 </div>
@@ -69,9 +69,9 @@
                     :alt="childTile.name"
                     @click="selectTile(childTile.id)"
                     :class="{
-                  'border-cyan shadow-lg': isActiveTile(childTile),
-                  'border-transparent hover:border-gray-300': !isActiveTile(childTile)
-                }"
+                      'border-cyan shadow-lg': isActiveTile(childTile),
+                      'border-transparent hover:border-gray-300': !isActiveTile(childTile)
+                    }"
                   />
                   <span class="text-xs mt-1">{{ getTileCategory(childTile) }}</span>
                 </div>
diff --git a/src/components/screens/MapEditor.vue b/src/components/screens/MapEditor.vue
index 1599663..1c1eaac 100644
--- a/src/components/screens/MapEditor.vue
+++ b/src/components/screens/MapEditor.vue
@@ -19,7 +19,7 @@
           />
           <MapList ref="mapModal" @open-create-map="mapSettingsModal?.open" />
           <TileList ref="tileList" />
-          <ObjectList ref="objectList"/>
+          <ObjectList ref="objectList" />
           <MapSettings ref="mapSettingsModal" />
           <TeleportModal ref="teleportModal" />
         </div>
diff --git a/src/composables/controls/useGameControlsComposable.ts b/src/composables/controls/useGameControlsComposable.ts
index fa52197..6fff96f 100644
--- a/src/composables/controls/useGameControlsComposable.ts
+++ b/src/composables/controls/useGameControlsComposable.ts
@@ -28,26 +28,60 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
     })
   }
 
-  function handleArrowKeys(event: KeyboardEvent) {
-    if (event.key === 'ArrowLeft') {
-      console.log('ArrowLeft')
-      gameStore.connection?.emit('map:character:move', { positionX: gameStore.character!.positionX - 1, positionY: 0 })
-      return
+  const pressedKeys = new Set<string>()
+  let moveInterval: number | null = null
+
+  function handleKeyDown(event: KeyboardEvent) {
+    if (!gameStore.character) return
+
+    if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) {
+      pressedKeys.add(event.key)
+
+      // Start movement loop if not already running
+      if (!moveInterval) {
+        moveInterval = window.setInterval(moveCharacter, 250) // Adjust timing as needed
+        moveCharacter() // Move immediately on first press
+      }
     }
-    if (event.key === 'ArrowRight') {
-      console.log('ArrowRight')
-      gameStore.connection?.emit('map:character:move', { positionX: gameStore.character!.positionX + 1, positionY: 0 })
-      return
+  }
+
+  function handleKeyUp(event: KeyboardEvent) {
+    pressedKeys.delete(event.key)
+
+    // If no movement keys are pressed, clear the interval
+    if (pressedKeys.size === 0 && moveInterval) {
+      clearInterval(moveInterval)
+      moveInterval = null
     }
-    if (event.key === 'ArrowUp') {
-      console.log('ArrowUp')
-      gameStore.connection?.emit('map:character:move', { positionX: 0, positionY: gameStore.character!.positionY - 1 })
-      return
+  }
+
+  function moveCharacter() {
+    if (!gameStore.character) return
+    const { positionX, positionY } = gameStore.character
+
+    if (pressedKeys.has('ArrowLeft')) {
+      gameStore.connection?.emit('map:character:move', {
+        positionX: positionX - 1,
+        positionY: positionY
+      })
     }
-    if (event.key === 'ArrowDown') {
-      console.log('ArrowDown')
-      gameStore.connection?.emit('map:character:move', { positionX: 0, positionY: gameStore.character!.positionY + 1 })
-      return
+    if (pressedKeys.has('ArrowRight')) {
+      gameStore.connection?.emit('map:character:move', {
+        positionX: positionX + 1,
+        positionY: positionY
+      })
+    }
+    if (pressedKeys.has('ArrowUp')) {
+      gameStore.connection?.emit('map:character:move', {
+        positionX: positionX,
+        positionY: positionY - 1
+      })
+    }
+    if (pressedKeys.has('ArrowDown')) {
+      gameStore.connection?.emit('map:character:move', {
+        positionX: positionX,
+        positionY: positionY + 1
+      })
     }
   }
 
@@ -56,7 +90,8 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
     scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
     scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
     scene.input.on(Phaser.Input.Events.POINTER_WHEEL, baseHandlers.handleZoom)
-    scene.input.keyboard!.on(Phaser.Input.Keyboard.Events.KEY_DOWN, handleArrowKeys)
+    scene.input.keyboard!.on('keydown', handleKeyDown)
+    scene.input.keyboard!.on('keyup', handleKeyUp)
   }
 
   const cleanupControls = () => {
@@ -64,7 +99,8 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
     scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
     scene.input.off(Phaser.Input.Events.POINTER_UP, handlePointerUp)
     scene.input.off(Phaser.Input.Events.POINTER_WHEEL, baseHandlers.handleZoom)
-    scene.input.keyboard!.off(Phaser.Input.Keyboard.Events.KEY_DOWN, handleArrowKeys)
+    scene.input.keyboard!.off('keydown', handleKeyDown)
+    scene.input.keyboard!.off('keyup', handleKeyUp)
   }
 
   return { setupControls, cleanupControls }