From e4b9bb4d61e3f7c5d440c92fbc69c9cac7cd487a Mon Sep 17 00:00:00 2001
From: Dennis Postma <dennis@directonline.io>
Date: Sat, 1 Feb 2025 15:10:52 +0100
Subject: [PATCH] Refactored Character.vue as preparation for attack anims.

---
 src/application/enums.ts                      |   5 +
 src/components/game/character/Character.vue   | 152 +++---------------
 .../partials/{Healthbar.vue => HealthBar.vue} |   0
 .../useCharacterSpriteComposable.ts           | 131 +++++++++++++++
 4 files changed, 157 insertions(+), 131 deletions(-)
 create mode 100644 src/application/enums.ts
 rename src/components/game/character/partials/{Healthbar.vue => HealthBar.vue} (100%)
 create mode 100644 src/composables/useCharacterSpriteComposable.ts

diff --git a/src/application/enums.ts b/src/application/enums.ts
new file mode 100644
index 0000000..8dd4620
--- /dev/null
+++ b/src/application/enums.ts
@@ -0,0 +1,5 @@
+export enum Direction {
+  POSITIVE,
+  NEGATIVE,
+  UNCHANGED
+}
diff --git a/src/components/game/character/Character.vue b/src/components/game/character/Character.vue
index 29596bf..f1d2593 100644
--- a/src/components/game/character/Character.vue
+++ b/src/components/game/character/Character.vue
@@ -1,134 +1,43 @@
 <template>
   <ChatBubble :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" />
-  <Healthbar :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" />
+  <HealthBar :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" />
   <CharacterHair :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" />
   <Sprite ref="charSprite" :depth="isometricDepth" :x="currentPositionX" :y="currentPositionY" :origin-y="1" :flipX="isFlippedX" />
 </template>
 
 <script lang="ts" setup>
-import config from '@/application/config'
 import { type MapCharacter } from '@/application/types'
 import CharacterHair from '@/components/game/character/partials/CharacterHair.vue'
 import ChatBubble from '@/components/game/character/partials/ChatBubble.vue'
-import Healthbar from '@/components/game/character/partials/Healthbar.vue'
-import { loadSpriteTextures } from '@/composables/gameComposable'
-import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/mapComposable'
-import { CharacterTypeStorage } from '@/storage/storages'
+import HealthBar from '@/components/game/character/partials/HealthBar.vue'
+import { useCharacterSprite } from '@/composables/useCharacterSpriteComposable'
 import { useGameStore } from '@/stores/gameStore'
 import { useMapStore } from '@/stores/mapStore'
-import { refObj, Sprite, useScene } from 'phavuer'
-import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
-
-enum Direction {
-  POSITIVE,
-  NEGATIVE,
-  UNCHANGED
-}
+import { Sprite, useScene } from 'phavuer'
+import { onMounted, onUnmounted, watch } from 'vue'
+import { Direction } from '@/application/enums'
 
 const props = defineProps<{
   tilemap: Phaser.Tilemaps.Tilemap
   mapCharacter: MapCharacter
 }>()
 
-const charSprite = refObj<Phaser.GameObjects.Sprite>()
-const charSpriteId = ref('')
-
 const gameStore = useGameStore()
 const mapStore = useMapStore()
 const scene = useScene()
 
-const currentPositionX = ref(0)
-const currentPositionY = ref(0)
-const isometricDepth = ref(1)
-const isInitialPosition = ref(true)
-const tween = ref<Phaser.Tweens.Tween | null>(null)
-
-const updateIsometricDepth = (positionX: number, positionY: number) => {
-  isometricDepth.value = calculateIsometricDepth(positionX, positionY, 28, 94, true)
-}
-
-const updatePosition = (positionX: number, positionY: number, direction: Direction) => {
-  const newPositionX = tileToWorldX(props.tilemap, positionX, positionY)
-  const newPositionY = tileToWorldY(props.tilemap, positionX, positionY)
-
-  if (isInitialPosition.value) {
-    currentPositionX.value = newPositionX
-    currentPositionY.value = newPositionY
-    isInitialPosition.value = false
-    return
-  }
-
-  if (tween.value?.isPlaying()) {
-    tween.value.stop()
-  }
-
-  const distance = Math.sqrt(Math.pow(newPositionX - currentPositionX.value, 2) + Math.pow(newPositionY - currentPositionY.value, 2))
-
-  if (distance >= config.tile_size.width / 1.1) {
-    currentPositionX.value = newPositionX
-    currentPositionY.value = newPositionY
-    return
-  }
-
-  const duration = distance * 5.7
-
-  tween.value = props.tilemap.scene.tweens.add({
-    targets: { x: currentPositionX.value, y: currentPositionY.value },
-    x: newPositionX,
-    y: newPositionY,
-    duration,
-    ease: 'Linear',
-    onStart: () => {
-      if (direction === Direction.POSITIVE) {
-        updateIsometricDepth(positionX, positionY)
-      }
-    },
-    onUpdate: (tween) => {
-      // @ts-ignore
-      currentPositionX.value = tween.targets[0].x
-      // @ts-ignore
-      currentPositionY.value = tween.targets[0].y
-    },
-    onComplete: () => {
-      if (direction === Direction.NEGATIVE) {
-        updateIsometricDepth(positionX, positionY)
-      }
-    }
-  })
-}
-
-const calcDirection = (oldPositionX: number, oldPositionY: number, newPositionX: number, newPositionY: number): Direction => {
-  if (newPositionY < oldPositionY || newPositionX < oldPositionX) return Direction.NEGATIVE
-  if (newPositionX > oldPositionX || newPositionY > oldPositionY) return Direction.POSITIVE
-  return Direction.UNCHANGED
-}
-
-const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0))
-
-const currentDirection = computed(() => {
-  return [0, 6].includes(props.mapCharacter.character.rotation ?? 0) ? 'left_up' : 'right_down'
-})
-
-const currentAction = computed(() => {
-  return props.mapCharacter.isMoving ? 'walk' : 'idle'
-})
-
-const charTexture = computed(() => {
-  const spriteId = charSpriteId.value ?? 'idle_right_down'
-  return `${spriteId}-${currentAction.value}_${currentDirection.value}`
-})
-
-const updateSprite = () => {
-  if (!charSprite.value) return
-
-  if (props.mapCharacter.isMoving) {
-    charSprite.value.anims.play(charTexture.value, true)
-  } else {
-    charSprite.value.anims.stop()
-    charSprite.value.setFrame(0)
-    charSprite.value.setTexture(charTexture.value)
-  }
-}
+const {
+  charSprite,
+  currentPositionX,
+  currentPositionY,
+  isometricDepth,
+  isFlippedX,
+  updatePosition,
+  calcDirection,
+  updateSprite,
+  initializeSprite,
+  cleanup
+} = useCharacterSprite(scene, props.tilemap, props.mapCharacter)
 
 const handlePositionUpdate = (newValues: any, oldValues: any) => {
   if (!newValues) return
@@ -155,34 +64,15 @@ watch(
 )
 
 onMounted(async () => {
-  let character = props.mapCharacter.character
+  await initializeSprite()
 
-  const characterTypeStorage = new CharacterTypeStorage()
-
-  const spriteId = await characterTypeStorage.getSpriteId(character.characterType!)
-  if (!spriteId) return
-
-  charSpriteId.value = spriteId
-
-  await loadSpriteTextures(scene, spriteId)
-
-  if (charSprite.value) {
-    charSprite.value.setTexture(charTexture.value)
-    charSprite.value.setFlipX(isFlippedX.value)
-    charSprite.value.setName(props.mapCharacter.character.name)
-  }
-
-  if (character.id === gameStore.character!.id) {
+  if (props.mapCharacter.character.id === gameStore.character!.id) {
     mapStore.setCharacterLoaded(true)
-
-    // #146 : Set camera position to character, need to be improved still
     scene.cameras.main.startFollow(charSprite.value as Phaser.GameObjects.Sprite)
   }
-
-  updatePosition(character.positionX, character.positionY, character.rotation)
 })
 
 onUnmounted(() => {
-  tween.value?.stop()
+  cleanup()
 })
 </script>
diff --git a/src/components/game/character/partials/Healthbar.vue b/src/components/game/character/partials/HealthBar.vue
similarity index 100%
rename from src/components/game/character/partials/Healthbar.vue
rename to src/components/game/character/partials/HealthBar.vue
diff --git a/src/composables/useCharacterSpriteComposable.ts b/src/composables/useCharacterSpriteComposable.ts
new file mode 100644
index 0000000..500d768
--- /dev/null
+++ b/src/composables/useCharacterSpriteComposable.ts
@@ -0,0 +1,131 @@
+import { type MapCharacter } from '@/application/types'
+import { loadSpriteTextures } from '@/composables/gameComposable'
+import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/mapComposable'
+import { CharacterTypeStorage } from '@/storage/storages'
+import { refObj } from 'phavuer'
+import { computed, ref } from 'vue'
+import { Direction } from '@/application/enums'
+
+export function useCharacterSprite(scene: Phaser.Scene, tilemap: Phaser.Tilemaps.Tilemap, mapCharacter: MapCharacter) {
+  const charSprite = refObj<Phaser.GameObjects.Sprite>()
+  const charSpriteId = ref('')
+  const currentPositionX = ref(0)
+  const currentPositionY = ref(0)
+  const isometricDepth = ref(1)
+  const isInitialPosition = ref(true)
+  const tween = ref<Phaser.Tweens.Tween | null>(null)
+
+  const updateIsometricDepth = (positionX: number, positionY: number) => {
+    isometricDepth.value = calculateIsometricDepth(positionX, positionY, 28, 94, true)
+  }
+
+  const updatePosition = (positionX: number, positionY: number, direction: Direction) => {
+    const newPositionX = tileToWorldX(tilemap, positionX, positionY)
+    const newPositionY = tileToWorldY(tilemap, positionX, positionY)
+
+    if (isInitialPosition.value) {
+      currentPositionX.value = newPositionX
+      currentPositionY.value = newPositionY
+      isInitialPosition.value = false
+      return
+    }
+
+    if (tween.value?.isPlaying()) {
+      tween.value.stop()
+    }
+
+    const distance = Math.sqrt(Math.pow(newPositionX - currentPositionX.value, 2) + Math.pow(newPositionY - currentPositionY.value, 2))
+    const baseSpeed = 150 // pixels per second
+    const duration = (distance / baseSpeed) * 1000 // Convert to milliseconds
+
+    tween.value = tilemap.scene.tweens.add({
+      targets: charSprite.value,
+      x: newPositionX,
+      y: newPositionY,
+      duration,
+      ease: 'Linear',
+      onStart: () => {
+        if (direction === Direction.POSITIVE) {
+          updateIsometricDepth(positionX, positionY)
+        }
+      },
+      onUpdate: () => {
+        currentPositionX.value = charSprite.value?.x ?? currentPositionX.value
+        currentPositionY.value = charSprite.value?.y ?? currentPositionY.value
+      },
+      onComplete: () => {
+        if (direction === Direction.NEGATIVE) {
+          updateIsometricDepth(positionX, positionY)
+        }
+      }
+    })
+  }
+
+  const calcDirection = (oldPositionX: number, oldPositionY: number, newPositionX: number, newPositionY: number): Direction => {
+    if (newPositionY < oldPositionY || newPositionX < oldPositionX) return Direction.NEGATIVE
+    if (newPositionX > oldPositionX || newPositionY > oldPositionY) return Direction.POSITIVE
+    return Direction.UNCHANGED
+  }
+
+  const isFlippedX = computed(() => [6, 4].includes(mapCharacter.character.rotation ?? 0))
+
+  const currentDirection = computed(() => {
+    return [0, 6].includes(mapCharacter.character.rotation ?? 0) ? 'left_up' : 'right_down'
+  })
+
+  const currentAction = computed(() => {
+    return mapCharacter.isMoving ? 'walk' : 'idle'
+  })
+
+  const charTexture = computed(() => {
+    const spriteId = charSpriteId.value ?? 'idle_right_down'
+    return `${spriteId}-${currentAction.value}_${currentDirection.value}`
+  })
+
+  const updateSprite = () => {
+    if (!charSprite.value) return
+
+    if (mapCharacter.isMoving) {
+      charSprite.value.anims.play(charTexture.value, true)
+    } else {
+      charSprite.value.anims.stop()
+      charSprite.value.setFrame(0)
+      charSprite.value.setTexture(charTexture.value)
+    }
+  }
+
+  const initializeSprite = async () => {
+    const characterTypeStorage = new CharacterTypeStorage()
+    const spriteId = await characterTypeStorage.getSpriteId(mapCharacter.character.characterType!)
+    if (!spriteId) return
+
+    charSpriteId.value = spriteId
+    await loadSpriteTextures(scene, spriteId)
+
+    if (charSprite.value) {
+      charSprite.value.setTexture(charTexture.value)
+      charSprite.value.setFlipX(isFlippedX.value)
+      charSprite.value.setName(mapCharacter.character.name)
+    }
+
+    updatePosition(mapCharacter.character.positionX, mapCharacter.character.positionY, mapCharacter.character.rotation)
+  }
+
+  const cleanup = () => {
+    tween.value?.stop()
+  }
+
+  return {
+    charSprite,
+    charSpriteId,
+    currentPositionX,
+    currentPositionY,
+    isometricDepth,
+    isFlippedX,
+    updatePosition,
+    calcDirection,
+    updateSprite,
+    initializeSprite,
+    cleanup
+  }
+}
\ No newline at end of file