forked from noxious/client
Merge remote-tracking branch 'origin/feature/cache'
This commit is contained in:
commit
b9bcfc719f
@ -1,5 +1,5 @@
|
|||||||
VITE_NAME=Noxious
|
VITE_NAME=Noxious
|
||||||
VITE_DEVELOPMENT=true
|
VITE_DEVELOPMENT=true
|
||||||
VITE_SERVER_ENDPOINT=http://localhost:4000
|
VITE_SERVER_ENDPOINT=http://localhost:4000
|
||||||
VITE_TILE_SIZE_X=64
|
VITE_TILE_SIZE_WIDTH=64
|
||||||
VITE_TILE_SIZE_Y=32
|
VITE_TILE_SIZE_HEIGHT=32
|
1632
package-lock.json
generated
1632
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@ -51,7 +51,6 @@
|
|||||||
"typescript": "~5.6.2",
|
"typescript": "~5.6.2",
|
||||||
"vite": "^5.4.9",
|
"vite": "^5.4.9",
|
||||||
"vite-plugin-compression": "^0.5.1",
|
"vite-plugin-compression": "^0.5.1",
|
||||||
"vite-plugin-vue-devtools": "^7.5.2",
|
|
||||||
"vitest": "^2.0.3",
|
"vitest": "^2.0.3",
|
||||||
"vue-tsc": "^1.6.5"
|
"vue-tsc": "^1.6.5"
|
||||||
}
|
}
|
||||||
|
11
src/App.vue
11
src/App.vue
@ -1,4 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<Debug />
|
||||||
<Notifications />
|
<Notifications />
|
||||||
<BackgroundImageLoader />
|
<BackgroundImageLoader />
|
||||||
<GmPanel v-if="gameStore.character?.role === 'gm'" />
|
<GmPanel v-if="gameStore.character?.role === 'gm'" />
|
||||||
@ -9,18 +10,21 @@
|
|||||||
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
import GmPanel from '@/components/gameMaster/GmPanel.vue'
|
||||||
import Characters from '@/components/screens/Characters.vue'
|
import Characters from '@/components/screens/Characters.vue'
|
||||||
import Game from '@/components/screens/Game.vue'
|
import Game from '@/components/screens/Game.vue'
|
||||||
|
import Loading from '@/components/screens/Loading.vue'
|
||||||
import Login from '@/components/screens/Login.vue'
|
import Login from '@/components/screens/Login.vue'
|
||||||
import MapEditor from '@/components/screens/MapEditor.vue'
|
import MapEditor from '@/components/screens/MapEditor.vue'
|
||||||
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
|
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
|
||||||
|
import Debug from '@/components/utilities/Debug.vue'
|
||||||
import Notifications from '@/components/utilities/Notifications.vue'
|
import Notifications from '@/components/utilities/Notifications.vue'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useMapEditorStore } from '@/stores/mapEditorStore'
|
import { useMapEditorStore } from '@/stores/mapEditorStore'
|
||||||
import { computed, watch } from 'vue'
|
import { computed, onMounted, onUnmounted, watch } from 'vue'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const mapEditorStore = useMapEditorStore()
|
const mapEditorStore = useMapEditorStore()
|
||||||
|
|
||||||
const currentScreen = computed(() => {
|
const currentScreen = computed(() => {
|
||||||
|
if (!gameStore.game.isLoaded) return Loading
|
||||||
if (!gameStore.connection) return Login
|
if (!gameStore.connection) return Login
|
||||||
if (!gameStore.token) return Login
|
if (!gameStore.token) return Login
|
||||||
if (!gameStore.character) return Characters
|
if (!gameStore.character) return Characters
|
||||||
@ -32,11 +36,14 @@ const currentScreen = computed(() => {
|
|||||||
watch(
|
watch(
|
||||||
() => mapEditorStore.active,
|
() => mapEditorStore.active,
|
||||||
() => {
|
() => {
|
||||||
gameStore.game.loadedAssets = []
|
gameStore.game.loadedTextures = []
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
// #209: Play sound when a button is pressed
|
// #209: Play sound when a button is pressed
|
||||||
|
/**
|
||||||
|
* @TODO: Not all button-like elements will actually be a button, so we need to find a better way to do this
|
||||||
|
*/
|
||||||
addEventListener('click', (event) => {
|
addEventListener('click', (event) => {
|
||||||
if (!(event.target instanceof HTMLButtonElement)) {
|
if (!(event.target instanceof HTMLButtonElement)) {
|
||||||
return
|
return
|
||||||
|
@ -1,81 +0,0 @@
|
|||||||
import config from '@/application/config'
|
|
||||||
import type { AssetDataT } from '@/application/types'
|
|
||||||
import Dexie from 'dexie'
|
|
||||||
|
|
||||||
export class Assets {
|
|
||||||
private db: Dexie
|
|
||||||
|
|
||||||
constructor() {
|
|
||||||
this.db = new Dexie('assets')
|
|
||||||
this.db.version(1).stores({
|
|
||||||
assets: 'key, group'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
async download(asset: AssetDataT) {
|
|
||||||
try {
|
|
||||||
// Check if the asset already exists, then check if updatedAt is newer
|
|
||||||
const _asset = await this.db.table('assets').get(asset.key)
|
|
||||||
if (_asset && _asset.updatedAt > asset.updatedAt) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Download the asset
|
|
||||||
const response = await fetch(config.server_endpoint + asset.data)
|
|
||||||
const blob = await response.blob()
|
|
||||||
|
|
||||||
// Store the asset in the database
|
|
||||||
await this.db.table('assets').put({
|
|
||||||
key: asset.key,
|
|
||||||
data: blob,
|
|
||||||
group: asset.group,
|
|
||||||
updatedAt: asset.updatedAt,
|
|
||||||
originX: asset.originX,
|
|
||||||
originY: asset.originY,
|
|
||||||
isAnimated: asset.isAnimated,
|
|
||||||
frameRate: asset.frameRate,
|
|
||||||
frameWidth: asset.frameWidth,
|
|
||||||
frameHeight: asset.frameHeight,
|
|
||||||
frameCount: asset.frameCount
|
|
||||||
})
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to add asset ${asset.key}:`, error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async get(key: string) {
|
|
||||||
try {
|
|
||||||
const asset = await this.db.table('assets').get(key)
|
|
||||||
if (asset) {
|
|
||||||
return {
|
|
||||||
...asset,
|
|
||||||
data: URL.createObjectURL(asset.data) // Convert blob to data URL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to retrieve asset ${key}:`, error)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
async getByGroup(group: string) {
|
|
||||||
try {
|
|
||||||
const assets = await this.db.table('assets').where('group').equals(group).toArray()
|
|
||||||
return assets.map((asset) => ({
|
|
||||||
...asset,
|
|
||||||
data: URL.createObjectURL(asset.data) // Convert blob to data URL
|
|
||||||
}))
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to retrieve assets for group ${group}:`, error)
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async delete(key: string) {
|
|
||||||
try {
|
|
||||||
await this.db.table('assets').delete(key)
|
|
||||||
} catch (error) {
|
|
||||||
console.error(`Failed to delete asset ${key}:`, error)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -3,7 +3,7 @@ export default {
|
|||||||
development: import.meta.env.VITE_DEVELOPMENT === 'true',
|
development: import.meta.env.VITE_DEVELOPMENT === 'true',
|
||||||
server_endpoint: import.meta.env.VITE_SERVER_ENDPOINT,
|
server_endpoint: import.meta.env.VITE_SERVER_ENDPOINT,
|
||||||
tile_size: {
|
tile_size: {
|
||||||
x: Number(import.meta.env.VITE_TILE_SIZE_X),
|
width: Number(import.meta.env.VITE_TILE_SIZE_WIDTH),
|
||||||
y: Number(import.meta.env.VITE_TILE_SIZE_Y)
|
height: Number(import.meta.env.VITE_TILE_SIZE_HEIGHT)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,9 +12,9 @@ export type HttpResponse<T> = {
|
|||||||
data?: T
|
data?: T
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AssetDataT = {
|
export type TextureData = {
|
||||||
key: string
|
key: string
|
||||||
data: string
|
data: string // URL or Base64 encoded blob
|
||||||
group: 'tiles' | 'map_objects' | 'sprites' | 'sprite_animations' | 'sound' | 'music' | 'ui' | 'font' | 'other'
|
group: 'tiles' | 'map_objects' | 'sprites' | 'sprite_animations' | 'sound' | 'music' | 'ui' | 'font' | 'other'
|
||||||
updatedAt: Date
|
updatedAt: Date
|
||||||
originX?: number
|
originX?: number
|
||||||
@ -174,10 +174,9 @@ export type Character = {
|
|||||||
positionX: number
|
positionX: number
|
||||||
positionY: number
|
positionY: number
|
||||||
rotation: number
|
rotation: number
|
||||||
characterType: CharacterType | null | string
|
characterType: UUID | null
|
||||||
characterHair: CharacterHair | null
|
characterHair: UUID | null
|
||||||
mapId: UUID
|
map: UUID
|
||||||
map: Map
|
|
||||||
chats: Chat[]
|
chats: Chat[]
|
||||||
items: CharacterItem[]
|
items: CharacterItem[]
|
||||||
equipment: CharacterEquipment[]
|
equipment: CharacterEquipment[]
|
||||||
@ -185,7 +184,7 @@ export type Character = {
|
|||||||
|
|
||||||
export type MapCharacter = {
|
export type MapCharacter = {
|
||||||
character: Character
|
character: Character
|
||||||
isMoving?: boolean
|
isMoving: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export type CharacterItem = {
|
export type CharacterItem = {
|
||||||
@ -256,6 +255,6 @@ export type WeatherState = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type mapLoadData = {
|
export type mapLoadData = {
|
||||||
map: Map
|
mapId: UUID
|
||||||
characters: MapCharacter[]
|
characters: MapCharacter[]
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
<template>
|
<template>
|
||||||
<ChatBubble :mapCharacter="props.mapCharacter" :currentX="currentX" :currentY="currentY" />
|
<ChatBubble :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" />
|
||||||
<Healthbar :mapCharacter="props.mapCharacter" :currentX="currentX" :currentY="currentY" />
|
<Healthbar :mapCharacter="props.mapCharacter" :currentX="currentPositionX" :currentY="currentPositionY" />
|
||||||
<Container ref="charContainer" :depth="isometricDepth" :x="currentX" :y="currentY">
|
<Container ref="charContainer" :depth="isometricDepth" :x="currentPositionX" :y="currentPositionY">
|
||||||
<CharacterHair :mapCharacter="props.mapCharacter" :currentX="currentX" :currentY="currentY" />
|
<!-- <CharacterHair :mapCharacter="props.mapCharacter" :currentX="currentX" :currentY="currentY" />-->
|
||||||
<!-- <CharacterChest :mapCharacter="props.mapCharacter" :currentX="currentX" :currentY="currentY" />-->
|
<!-- <CharacterChest :mapCharacter="props.mapCharacter" :currentX="currentX" :currentY="currentY" />-->
|
||||||
<Sprite ref="charSprite" :origin-y="1" :flipX="isFlippedX" />
|
<Sprite ref="charSprite" :origin-y="1" :flipX="isFlippedX" />
|
||||||
</Container>
|
</Container>
|
||||||
@ -16,6 +16,7 @@ import ChatBubble from '@/components/game/character/partials/ChatBubble.vue'
|
|||||||
import Healthbar from '@/components/game/character/partials/Healthbar.vue'
|
import Healthbar from '@/components/game/character/partials/Healthbar.vue'
|
||||||
import { loadSpriteTextures } from '@/composables/gameComposable'
|
import { loadSpriteTextures } from '@/composables/gameComposable'
|
||||||
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/mapComposable'
|
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/mapComposable'
|
||||||
|
import { CharacterTypeStorage } from '@/storage/storages'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useMapStore } from '@/stores/mapStore'
|
import { useMapStore } from '@/stores/mapStore'
|
||||||
import { Container, refObj, Sprite, useScene } from 'phavuer'
|
import { Container, refObj, Sprite, useScene } from 'phavuer'
|
||||||
@ -36,28 +37,29 @@ const props = defineProps<{
|
|||||||
|
|
||||||
const charContainer = refObj<Phaser.GameObjects.Container>()
|
const charContainer = refObj<Phaser.GameObjects.Container>()
|
||||||
const charSprite = refObj<Phaser.GameObjects.Sprite>()
|
const charSprite = refObj<Phaser.GameObjects.Sprite>()
|
||||||
|
const charSpriteId = ref('')
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const mapStore = useMapStore()
|
const mapStore = useMapStore()
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
|
|
||||||
const currentX = ref(0)
|
const currentPositionX = ref(0)
|
||||||
const currentY = ref(0)
|
const currentPositionY = ref(0)
|
||||||
const isometricDepth = ref(1)
|
const isometricDepth = ref(1)
|
||||||
const isInitialPosition = ref(true)
|
const isInitialPosition = ref(true)
|
||||||
const tween = ref<Phaser.Tweens.Tween | null>(null)
|
const tween = ref<Phaser.Tweens.Tween | null>(null)
|
||||||
|
|
||||||
const updateIsometricDepth = (x: number, y: number) => {
|
const updateIsometricDepth = (positionX: number, positionY: number) => {
|
||||||
isometricDepth.value = calculateIsometricDepth(x, y, 28, 94, true)
|
isometricDepth.value = calculateIsometricDepth(positionX, positionY, 28, 94, true)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatePosition = (x: number, y: number, direction: Direction) => {
|
const updatePosition = (positionX: number, positionY: number, direction: Direction) => {
|
||||||
const targetX = tileToWorldX(props.tilemap, x, y)
|
const newPositionX = tileToWorldX(props.tilemap, positionX, positionY)
|
||||||
const targetY = tileToWorldY(props.tilemap, x, y)
|
const newPositionY = tileToWorldY(props.tilemap, positionX, positionY)
|
||||||
|
|
||||||
if (isInitialPosition.value) {
|
if (isInitialPosition.value) {
|
||||||
currentX.value = targetX
|
currentPositionX.value = newPositionX
|
||||||
currentY.value = targetY
|
currentPositionY.value = newPositionY
|
||||||
isInitialPosition.value = false
|
isInitialPosition.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -66,52 +68,53 @@ const updatePosition = (x: number, y: number, direction: Direction) => {
|
|||||||
tween.value.stop()
|
tween.value.stop()
|
||||||
}
|
}
|
||||||
|
|
||||||
const distance = Math.sqrt(Math.pow(targetX - currentX.value, 2) + Math.pow(targetY - currentY.value, 2))
|
const distance = Math.sqrt(Math.pow(newPositionX - currentPositionX.value, 2) + Math.pow(newPositionY - currentPositionY.value, 2))
|
||||||
|
|
||||||
if (distance >= config.tile_size.x / 1.1) {
|
if (distance >= config.tile_size.width / 1.1) {
|
||||||
currentX.value = targetX
|
currentPositionX.value = newPositionX
|
||||||
currentY.value = targetY
|
currentPositionY.value = newPositionY
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const duration = distance * 5.7
|
const duration = distance * 5.7
|
||||||
|
|
||||||
tween.value = props.tilemap.scene.tweens.add({
|
tween.value = props.tilemap.scene.tweens.add({
|
||||||
targets: { x: currentX.value, y: currentY.value },
|
targets: { x: currentPositionX.value, y: currentPositionY.value },
|
||||||
x: targetX,
|
x: newPositionX,
|
||||||
y: targetY,
|
y: newPositionY,
|
||||||
duration,
|
duration,
|
||||||
ease: 'Linear',
|
ease: 'Linear',
|
||||||
onStart: () => {
|
onStart: () => {
|
||||||
if (direction === Direction.POSITIVE) {
|
if (direction === Direction.POSITIVE) {
|
||||||
updateIsometricDepth(x, y)
|
updateIsometricDepth(positionX, positionY)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onUpdate: (tween) => {
|
onUpdate: (tween) => {
|
||||||
currentX.value = tween.targets[0].x
|
// @ts-ignore
|
||||||
currentY.value = tween.targets[0].y
|
currentPositionX.value = tween.targets[0].x
|
||||||
|
// @ts-ignore
|
||||||
|
currentPositionY.value = tween.targets[0].y
|
||||||
},
|
},
|
||||||
onComplete: () => {
|
onComplete: () => {
|
||||||
if (direction === Direction.NEGATIVE) {
|
if (direction === Direction.NEGATIVE) {
|
||||||
updateIsometricDepth(x, y)
|
updateIsometricDepth(positionX, positionY)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const calcDirection = (oldX: number, oldY: number, newX: number, newY: number): Direction => {
|
const calcDirection = (oldPositionX: number, oldPositionY: number, newPositionX: number, newPositionY: number): Direction => {
|
||||||
if (newY < oldY || newX < oldX) return Direction.NEGATIVE
|
if (newPositionY < oldPositionY || newPositionX < oldPositionX) return Direction.NEGATIVE
|
||||||
if (newX > oldX || newY > oldY) return Direction.POSITIVE
|
if (newPositionX > oldPositionX || newPositionY > oldPositionY) return Direction.POSITIVE
|
||||||
return Direction.UNCHANGED
|
return Direction.UNCHANGED
|
||||||
}
|
}
|
||||||
|
|
||||||
const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0))
|
const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0))
|
||||||
|
|
||||||
const charTexture = computed(() => {
|
const charTexture = computed(() => {
|
||||||
const { rotation, characterType } = props.mapCharacter.character
|
const spriteId = charSpriteId.value ?? 'idle_right_down'
|
||||||
const spriteId = characterType?.sprite ?? 'idle_right_down'
|
|
||||||
const action = props.mapCharacter.isMoving ? 'walk' : 'idle'
|
const action = props.mapCharacter.isMoving ? 'walk' : 'idle'
|
||||||
const direction = [0, 6].includes(rotation) ? 'left_up' : 'right_down'
|
const direction = [0, 6].includes(props.mapCharacter.character.rotation) ? 'left_up' : 'right_down'
|
||||||
|
|
||||||
return `${spriteId}-${action}_${direction}`
|
return `${spriteId}-${action}_${direction}`
|
||||||
})
|
})
|
||||||
@ -119,40 +122,40 @@ const charTexture = computed(() => {
|
|||||||
const updateSprite = () => {
|
const updateSprite = () => {
|
||||||
if (props.mapCharacter.isMoving) {
|
if (props.mapCharacter.isMoving) {
|
||||||
charSprite.value!.anims.play(charTexture.value, true)
|
charSprite.value!.anims.play(charTexture.value, true)
|
||||||
return
|
} else {
|
||||||
}
|
|
||||||
|
|
||||||
charSprite.value!.anims.stop()
|
charSprite.value!.anims.stop()
|
||||||
charSprite.value!.setFrame(0)
|
charSprite.value!.setFrame(0)
|
||||||
charSprite.value!.setTexture(charTexture.value)
|
charSprite.value!.setTexture(charTexture.value)
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => ({
|
() => ({
|
||||||
x: props.mapCharacter.character.positionX,
|
positionX: props.mapCharacter.character.positionX,
|
||||||
y: props.mapCharacter.character.positionY,
|
positionY: props.mapCharacter.character.positionY,
|
||||||
isMoving: props.mapCharacter.isMoving,
|
isMoving: props.mapCharacter.isMoving,
|
||||||
rotation: props.mapCharacter.character.rotation
|
rotation: props.mapCharacter.character.rotation
|
||||||
}),
|
}),
|
||||||
(newValues, oldValues) => {
|
(newValues, oldValues) => {
|
||||||
if (!newValues) return
|
if (!newValues) return
|
||||||
|
|
||||||
if (!oldValues || newValues.x !== oldValues.x || newValues.y !== oldValues.y) {
|
if (!oldValues || newValues.positionX !== oldValues.positionX || newValues.positionY !== oldValues.positionY) {
|
||||||
const direction = !oldValues ? Direction.POSITIVE : calcDirection(oldValues.x, oldValues.y, newValues.x, newValues.y)
|
const direction = !oldValues ? Direction.POSITIVE : calcDirection(oldValues.positionX, oldValues.positionY, newValues.positionX, newValues.positionY)
|
||||||
updatePosition(newValues.x, newValues.y, direction)
|
updatePosition(newValues.positionX, newValues.positionY, direction)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle animation updates
|
// Handle animation updates
|
||||||
if (newValues.isMoving !== oldValues?.isMoving || newValues.rotation !== oldValues?.rotation) {
|
if (newValues.isMoving !== oldValues?.isMoving || newValues.rotation !== oldValues?.rotation) {
|
||||||
updateSprite()
|
updateSprite()
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
{ deep: true }
|
|
||||||
)
|
)
|
||||||
|
|
||||||
watch(() => props.mapCharacter, updateSprite)
|
const characterTypeStorage = new CharacterTypeStorage()
|
||||||
|
characterTypeStorage.getSpriteId(props.mapCharacter.character.characterType!).then((spriteId) => {
|
||||||
loadSpriteTextures(scene, props.mapCharacter.character.characterType?.sprite as string)
|
console.log(spriteId)
|
||||||
|
charSpriteId.value = spriteId
|
||||||
|
loadSpriteTextures(scene, spriteId)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
charSprite.value!.setTexture(charTexture.value)
|
charSprite.value!.setTexture(charTexture.value)
|
||||||
charSprite.value!.setFlipX(isFlippedX.value)
|
charSprite.value!.setFlipX(isFlippedX.value)
|
||||||
@ -160,6 +163,7 @@ loadSpriteTextures(scene, props.mapCharacter.character.characterType?.sprite as
|
|||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
console.error('Error loading texture:', error)
|
console.error('Error loading texture:', error)
|
||||||
})
|
})
|
||||||
|
})
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
charContainer.value!.setName(props.mapCharacter.character!.name)
|
charContainer.value!.setName(props.mapCharacter.character!.name)
|
||||||
@ -168,8 +172,7 @@ onMounted(() => {
|
|||||||
mapStore.setCharacterLoaded(true)
|
mapStore.setCharacterLoaded(true)
|
||||||
|
|
||||||
// #146 : Set camera position to character, need to be improved still
|
// #146 : Set camera position to character, need to be improved still
|
||||||
// scene.cameras.main.startFollow(charContainer.value as Phaser.GameObjects.Container)
|
scene.cameras.main.startFollow(charContainer.value as Phaser.GameObjects.Container)
|
||||||
// scene.cameras.main.stopFollow()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
updatePosition(props.mapCharacter.character.positionX, props.mapCharacter.character.positionY, props.mapCharacter.character.rotation)
|
updatePosition(props.mapCharacter.character.positionX, props.mapCharacter.character.positionY, props.mapCharacter.character.rotation)
|
||||||
|
@ -1,21 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<MapTiles :key="mapStore.map?.id ?? 0" @tileMap:create="tileMap = $event" />
|
<MapTiles :key="mapStore.mapId" @tileMap:create="tileMap = $event" />
|
||||||
<MapObjects v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
|
<!-- <MapObjects v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />-->
|
||||||
<Characters v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
|
<Characters v-if="tileMap" :tilemap="tileMap as Phaser.Tilemaps.Tilemap" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MapCharacter, mapLoadData, UUID } from '@/application/types'
|
import type { MapCharacter, mapLoadData, UUID } from '@/application/types'
|
||||||
import Characters from '@/components/game/map/Characters.vue'
|
import Characters from '@/components/game/map/Characters.vue'
|
||||||
import MapObjects from '@/components/game/map/PlacedMapObjects.vue'
|
|
||||||
import MapTiles from '@/components/game/map/MapTiles.vue'
|
import MapTiles from '@/components/game/map/MapTiles.vue'
|
||||||
import { loadMapTilesIntoScene } from '@/composables/mapComposable'
|
import MapObjects from '@/components/game/map/PlacedMapObjects.vue'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useMapStore } from '@/stores/mapStore'
|
import { useMapStore } from '@/stores/mapStore'
|
||||||
import { useScene } from 'phavuer'
|
import { onUnmounted, shallowRef } from 'vue'
|
||||||
import { onUnmounted, ref, shallowRef } from 'vue'
|
|
||||||
|
|
||||||
const scene = useScene()
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const mapStore = useMapStore()
|
const mapStore = useMapStore()
|
||||||
|
|
||||||
@ -31,8 +28,7 @@ onUnmounted(() => {
|
|||||||
|
|
||||||
// Event listeners
|
// Event listeners
|
||||||
gameStore.connection?.on('map:character:teleport', async (data: mapLoadData) => {
|
gameStore.connection?.on('map:character:teleport', async (data: mapLoadData) => {
|
||||||
await loadMapTilesIntoScene(data.map.id, scene)
|
mapStore.setMapId(data.mapId)
|
||||||
mapStore.setMap(data.map)
|
|
||||||
mapStore.setCharacters(data.characters)
|
mapStore.setCharacters(data.characters)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -1,69 +1,75 @@
|
|||||||
<template>
|
<template>
|
||||||
<Controls :layer="tileLayer" :depth="0" />
|
<Controls v-if="tileLayer" :layer="tileLayer" :depth="0" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
|
import type { UUID } from '@/application/types'
|
||||||
import { unduplicateArray } from '@/application/utilities'
|
import { unduplicateArray } from '@/application/utilities'
|
||||||
import Controls from '@/components/utilities/Controls.vue'
|
import Controls from '@/components/utilities/Controls.vue'
|
||||||
import { FlattenMapArray, setLayerTiles } from '@/composables/mapComposable'
|
import { FlattenMapArray, loadMapTilesIntoScene, setLayerTiles } from '@/composables/mapComposable'
|
||||||
|
import { MapStorage } from '@/storage/storages'
|
||||||
import { useMapStore } from '@/stores/mapStore'
|
import { useMapStore } from '@/stores/mapStore'
|
||||||
import { useScene } from 'phavuer'
|
import { useScene } from 'phavuer'
|
||||||
import { onBeforeUnmount } from 'vue'
|
import { onBeforeUnmount, onMounted, shallowRef } from 'vue'
|
||||||
|
|
||||||
|
import Tileset = Phaser.Tilemaps.Tileset
|
||||||
|
|
||||||
const emit = defineEmits(['tileMap:create'])
|
const emit = defineEmits(['tileMap:create'])
|
||||||
|
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
const mapStore = useMapStore()
|
const mapStore = useMapStore()
|
||||||
const tileMap = createTileMap()
|
const mapStorage = new MapStorage()
|
||||||
const tileLayer = createTileLayer()
|
|
||||||
|
|
||||||
/**
|
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
||||||
* A Tilemap is a container for Tilemap data.
|
const tileLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
||||||
* This isn't a display object, rather, it holds data about the map and allows you to add tilesets and tilemap layers to it.
|
|
||||||
* A map can have one or more tilemap layers, which are the display objects that actually render the tiles.
|
function createTileMap(mapData: any) {
|
||||||
*/
|
const mapConfig = new Phaser.Tilemaps.MapData({
|
||||||
function createTileMap() {
|
width: mapData?.width,
|
||||||
const mapData = new Phaser.Tilemaps.MapData({
|
height: mapData?.height,
|
||||||
width: mapStore.map?.width,
|
tileWidth: config.tile_size.width,
|
||||||
height: mapStore.map?.height,
|
tileHeight: config.tile_size.height,
|
||||||
tileWidth: config.tile_size.x,
|
|
||||||
tileHeight: config.tile_size.y,
|
|
||||||
orientation: Phaser.Tilemaps.Orientation.ISOMETRIC,
|
orientation: Phaser.Tilemaps.Orientation.ISOMETRIC,
|
||||||
format: Phaser.Tilemaps.Formats.ARRAY_2D
|
format: Phaser.Tilemaps.Formats.ARRAY_2D
|
||||||
})
|
})
|
||||||
|
|
||||||
const newTileMap = new Phaser.Tilemaps.Tilemap(scene, mapData)
|
const newTileMap = new Phaser.Tilemaps.Tilemap(scene, mapConfig)
|
||||||
emit('tileMap:create', newTileMap)
|
emit('tileMap:create', newTileMap)
|
||||||
|
|
||||||
return newTileMap
|
return newTileMap
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
function createTileLayer(currentTileMap: Phaser.Tilemaps.Tilemap, mapData: any) {
|
||||||
* A Tileset is a combination of a single image containing the tiles and a container for data about each tile.
|
const tilesArray = unduplicateArray(FlattenMapArray(mapData?.tiles ?? []))
|
||||||
*/
|
|
||||||
function createTileLayer() {
|
|
||||||
const tilesArray = unduplicateArray(FlattenMapArray(mapStore.map?.tiles ?? []))
|
|
||||||
|
|
||||||
const tilesetImages = Array.from(tilesArray).map((tile: any, index: number) => {
|
const tilesetImages = tilesArray.map((tile: any, index: number) => {
|
||||||
return tileMap.addTilesetImage(tile, tile, config.tile_size.x, config.tile_size.y, 1, 2, index + 1, { x: 0, y: -config.tile_size.y })
|
return currentTileMap.addTilesetImage(tile, tile, config.tile_size.width, config.tile_size.height, 1, 2, index + 1, { x: 0, y: -config.tile_size.height })
|
||||||
}) as any
|
})
|
||||||
|
|
||||||
// Add blank tile
|
// Add blank tile
|
||||||
tilesetImages.push(tileMap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.x, config.tile_size.y, 1, 2, 0, { x: 0, y: -config.tile_size.y }))
|
tilesetImages.push(currentTileMap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.width, config.tile_size.height, 1, 2, 0, { x: 0, y: -config.tile_size.height }))
|
||||||
const layer = tileMap.createBlankLayer('tiles', tilesetImages, 0, config.tile_size.y) as Phaser.Tilemaps.TilemapLayer
|
|
||||||
|
const layer = currentTileMap.createBlankLayer('tiles', tilesetImages as Tileset[], 0, config.tile_size.height) as Phaser.Tilemaps.TilemapLayer
|
||||||
|
|
||||||
layer.setDepth(0)
|
layer.setDepth(0)
|
||||||
layer.setCullPadding(2, 2)
|
layer.setCullPadding(2, 2)
|
||||||
|
|
||||||
return layer
|
return layer
|
||||||
}
|
}
|
||||||
|
|
||||||
setLayerTiles(tileMap, tileLayer, mapStore.map?.tiles)
|
onMounted(() => {
|
||||||
|
loadMapTilesIntoScene(mapStore.mapId as UUID, scene)
|
||||||
|
.then(() => mapStorage.get(mapStore.mapId))
|
||||||
|
.then((mapData) => {
|
||||||
|
tileMap.value = createTileMap(mapData)
|
||||||
|
tileLayer.value = createTileLayer(tileMap.value, mapData)
|
||||||
|
setLayerTiles(tileMap.value, tileLayer.value, mapData?.tiles)
|
||||||
|
})
|
||||||
|
.catch((error) => console.error('Failed to initialize map:', error))
|
||||||
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
tileMap.destroyLayer('tiles')
|
if (!tileMap.value) return
|
||||||
tileMap.removeAllLayers()
|
tileMap.value.destroyLayer('tiles')
|
||||||
tileMap.destroy()
|
tileMap.value.removeAllLayers()
|
||||||
|
tileMap.value.destroy()
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<Image v-if="gameStore.getLoadedAsset(props.placedMapObject.mapObject.id)" v-bind="imageProps" />
|
<Image v-if="gameStore.isAssetLoaded(props.placedMapObject.mapObject)" v-bind="imageProps" />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { AssetDataT, PlacedMapObject } from '@/application/types'
|
import type { PlacedMapObject, TextureData } from '@/application/types'
|
||||||
import { loadTexture } from '@/composables/gameComposable'
|
import { loadTexture } from '@/composables/gameComposable'
|
||||||
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/mapComposable'
|
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/mapComposable'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
@ -30,12 +30,12 @@ const imageProps = computed(() => ({
|
|||||||
|
|
||||||
loadTexture(scene, {
|
loadTexture(scene, {
|
||||||
key: props.placedMapObject.mapObject.id,
|
key: props.placedMapObject.mapObject.id,
|
||||||
data: '/assets/map_objects/' + props.placedMapObject.mapObject.id + '.png',
|
data: '/textures/map_objects/' + props.placedMapObject.mapObject.id + '.png',
|
||||||
group: 'map_objects',
|
group: 'map_objects',
|
||||||
updatedAt: props.placedMapObject.mapObject.updatedAt,
|
updatedAt: props.placedMapObject.mapObject.updatedAt,
|
||||||
frameWidth: props.placedMapObject.mapObject.frameWidth,
|
frameWidth: props.placedMapObject.mapObject.frameWidth,
|
||||||
frameHeight: props.placedMapObject.mapObject.frameHeight
|
frameHeight: props.placedMapObject.mapObject.frameHeight
|
||||||
} as AssetDataT).catch((error) => {
|
} as TextureData).catch((error) => {
|
||||||
console.error('Error loading texture:', error)
|
console.error('Error loading texture:', error)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-full overflow-auto">
|
<div class="h-full overflow-auto">
|
||||||
<div class="relative p-2.5 flex flex-col items-center justify-center h-72 rounded-md default-border bg-gray">
|
<div class="relative p-2.5 flex flex-col items-center justify-center h-72 rounded-md default-border bg-gray">
|
||||||
<img class="max-h-56" :src="`${config.server_endpoint}/assets/map_objects/${selectedMapObject?.id}.png`" :alt="'Object ' + selectedMapObject?.id" />
|
<img class="max-h-56" :src="`${config.server_endpoint}/textures/map_objects/${selectedMapObject?.id}.png`" :alt="'Object ' + selectedMapObject?.id" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 block">
|
<div class="mt-5 block">
|
||||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveObject">
|
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveObject">
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<a v-for="{ data: mapObject } in list" :key="mapObject.id" class="relative p-2.5 cursor-pointer block rounded hover:bg-cyan group" :class="{ 'bg-cyan': assetManagerStore.selectedMapObject?.id === mapObject.id }" @click="assetManagerStore.setSelectedMapObject(mapObject as MapObject)">
|
<a v-for="{ data: mapObject } in list" :key="mapObject.id" class="relative p-2.5 cursor-pointer block rounded hover:bg-cyan group" :class="{ 'bg-cyan': assetManagerStore.selectedMapObject?.id === mapObject.id }" @click="assetManagerStore.setSelectedMapObject(mapObject as MapObject)">
|
||||||
<div class="flex items-center gap-2.5">
|
<div class="flex items-center gap-2.5">
|
||||||
<div class="h-7 w-16 max-w-16 flex justify-center">
|
<div class="h-7 w-16 max-w-16 flex justify-center">
|
||||||
<img class="h-7" :src="`${config.server_endpoint}/assets/map_objects/${mapObject.id}.png`" alt="Object" />
|
<img class="h-7" :src="`${config.server_endpoint}/textures/map_objects/${mapObject.id}.png`" alt="Object" />
|
||||||
</div>
|
</div>
|
||||||
<span :class="{ 'text-white': assetManagerStore.selectedMapObject?.id === mapObject.id }">{{ mapObject.name }}</span>
|
<span :class="{ 'text-white': assetManagerStore.selectedMapObject?.id === mapObject.id }">{{ mapObject.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="h-full overflow-auto">
|
<div class="h-full overflow-auto">
|
||||||
<div class="relative p-2.5 flex flex-col items-center justify-center h-72 rounded-md default-border bg-gray">
|
<div class="relative p-2.5 flex flex-col items-center justify-center h-72 rounded-md default-border bg-gray">
|
||||||
<img class="max-h-72" :src="`${config.server_endpoint}/assets/tiles/${selectedTile?.id}.png`" :alt="'Tile ' + selectedTile?.id" />
|
<img class="max-h-72" :src="`${config.server_endpoint}/textures/tiles/${selectedTile?.id}.png`" :alt="'Tile ' + selectedTile?.id" />
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-5 block">
|
<div class="mt-5 block">
|
||||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveTile">
|
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveTile">
|
||||||
|
@ -13,7 +13,7 @@
|
|||||||
<a v-for="{ data: tile } in list" :key="tile.id" class="relative p-2.5 cursor-pointer block rounded hover:bg-cyan group" :class="{ 'bg-cyan': assetManagerStore.selectedTile?.id === tile.id }" @click="assetManagerStore.setSelectedTile(tile)">
|
<a v-for="{ data: tile } in list" :key="tile.id" class="relative p-2.5 cursor-pointer block rounded hover:bg-cyan group" :class="{ 'bg-cyan': assetManagerStore.selectedTile?.id === tile.id }" @click="assetManagerStore.setSelectedTile(tile)">
|
||||||
<div class="flex items-center gap-2.5">
|
<div class="flex items-center gap-2.5">
|
||||||
<div class="h-7 w-16 max-w-16 flex justify-center">
|
<div class="h-7 w-16 max-w-16 flex justify-center">
|
||||||
<img class="h-7" :src="`${config.server_endpoint}/assets/tiles/${tile.id}.png`" alt="Tile" />
|
<img class="h-7" :src="`${config.server_endpoint}/textures/tiles/${tile.id}.png`" alt="Tile" />
|
||||||
</div>
|
</div>
|
||||||
<span class="group-hover:text-white" :class="{ 'text-white': assetManagerStore.selectedTile?.id === tile.id }">{{ tile.name }}</span>
|
<span class="group-hover:text-white" :class="{ 'text-white': assetManagerStore.selectedTile?.id === tile.id }">{{ tile.name }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
@ -16,11 +16,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { type Map } from '@/application/types'
|
import { type Map } from '@/application/types'
|
||||||
import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue'
|
import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue'
|
||||||
import MapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
|
|
||||||
import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
|
import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
|
||||||
|
import MapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
|
||||||
import MapList from '@/components/gameMaster/mapEditor/partials/MapList.vue'
|
import MapList from '@/components/gameMaster/mapEditor/partials/MapList.vue'
|
||||||
import MapSettings from '@/components/gameMaster/mapEditor/partials/MapSettings.vue'
|
|
||||||
import ObjectList from '@/components/gameMaster/mapEditor/partials/MapObjectList.vue'
|
import ObjectList from '@/components/gameMaster/mapEditor/partials/MapObjectList.vue'
|
||||||
|
import MapSettings from '@/components/gameMaster/mapEditor/partials/MapSettings.vue'
|
||||||
import TeleportModal from '@/components/gameMaster/mapEditor/partials/TeleportModal.vue'
|
import TeleportModal from '@/components/gameMaster/mapEditor/partials/TeleportModal.vue'
|
||||||
import TileList from '@/components/gameMaster/mapEditor/partials/TileList.vue'
|
import TileList from '@/components/gameMaster/mapEditor/partials/TileList.vue'
|
||||||
// Components
|
// Components
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
import type { AssetDataT } from '@/application/types'
|
import type { TextureData } from '@/application/types'
|
||||||
import Controls from '@/components/utilities/Controls.vue'
|
import Controls from '@/components/utilities/Controls.vue'
|
||||||
import { createTileArray, getTile, placeTile, setLayerTiles } from '@/composables/mapComposable'
|
import { createTileArray, getTile, placeTile, setLayerTiles } from '@/composables/mapComposable'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
@ -29,8 +29,8 @@ function createTileMap() {
|
|||||||
const mapData = new Phaser.Tilemaps.MapData({
|
const mapData = new Phaser.Tilemaps.MapData({
|
||||||
width: mapEditorStore.map?.width,
|
width: mapEditorStore.map?.width,
|
||||||
height: mapEditorStore.map?.height,
|
height: mapEditorStore.map?.height,
|
||||||
tileWidth: config.tile_size.x,
|
tileWidth: config.tile_size.width,
|
||||||
tileHeight: config.tile_size.y,
|
tileHeight: config.tile_size.height,
|
||||||
orientation: Phaser.Tilemaps.Orientation.ISOMETRIC,
|
orientation: Phaser.Tilemaps.Orientation.ISOMETRIC,
|
||||||
format: Phaser.Tilemaps.Formats.ARRAY_2D
|
format: Phaser.Tilemaps.Formats.ARRAY_2D
|
||||||
})
|
})
|
||||||
@ -47,13 +47,13 @@ function createTileMap() {
|
|||||||
function createTileLayer() {
|
function createTileLayer() {
|
||||||
const tilesArray = gameStore.getLoadedAssetsByGroup('tiles')
|
const tilesArray = gameStore.getLoadedAssetsByGroup('tiles')
|
||||||
|
|
||||||
const tilesetImages = Array.from(tilesArray).map((tile: AssetDataT, index: number) => {
|
const tilesetImages = Array.from(tilesArray).map((tile: TextureData, index: number) => {
|
||||||
return tileMap.addTilesetImage(tile.key, tile.key, config.tile_size.x, config.tile_size.y, 1, 2, index + 1, { x: 0, y: -config.tile_size.y })
|
return tileMap.addTilesetImage(tile.key, tile.key, config.tile_size.width, config.tile_size.height, 1, 2, index + 1, { x: 0, y: -config.tile_size.height })
|
||||||
}) as any
|
}) as any
|
||||||
|
|
||||||
// Add blank tile
|
// Add blank tile
|
||||||
tilesetImages.push(tileMap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.x, config.tile_size.y, 1, 2, 0, { x: 0, y: -config.tile_size.y }))
|
tilesetImages.push(tileMap.addTilesetImage('blank_tile', 'blank_tile', config.tile_size.width, config.tile_size.height, 1, 2, 0, { x: 0, y: -config.tile_size.height }))
|
||||||
const layer = tileMap.createBlankLayer('tiles', tilesetImages, 0, config.tile_size.y) as Phaser.Tilemaps.TilemapLayer
|
const layer = tileMap.createBlankLayer('tiles', tilesetImages, 0, config.tile_size.height) as Phaser.Tilemaps.TilemapLayer
|
||||||
|
|
||||||
layer.setDepth(0)
|
layer.setDepth(0)
|
||||||
layer.setCullPadding(2, 2)
|
layer.setCullPadding(2, 2)
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { AssetDataT, PlacedMapObject } from '@/application/types'
|
import type { PlacedMapObject, TextureData } from '@/application/types'
|
||||||
import { loadTexture } from '@/composables/gameComposable'
|
import { loadTexture } from '@/composables/gameComposable'
|
||||||
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/mapComposable'
|
import { calculateIsometricDepth, tileToWorldX, tileToWorldY } from '@/composables/mapComposable'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
@ -34,12 +34,12 @@ const imageProps = computed(() => ({
|
|||||||
|
|
||||||
loadTexture(scene, {
|
loadTexture(scene, {
|
||||||
key: props.placedMapObject.mapObject.id,
|
key: props.placedMapObject.mapObject.id,
|
||||||
data: '/assets/map_objects/' + props.placedMapObject.mapObject.id + '.png',
|
data: '/textures/map_objects/' + props.placedMapObject.mapObject.id + '.png',
|
||||||
group: 'map_objects',
|
group: 'map_objects',
|
||||||
updatedAt: props.placedMapObject.mapObject.updatedAt,
|
updatedAt: props.placedMapObject.mapObject.updatedAt,
|
||||||
frameWidth: props.placedMapObject.mapObject.frameWidth,
|
frameWidth: props.placedMapObject.mapObject.frameWidth,
|
||||||
frameHeight: props.placedMapObject.mapObject.frameHeight
|
frameHeight: props.placedMapObject.mapObject.frameHeight
|
||||||
} as AssetDataT).catch((error) => {
|
} as TextureData).catch((error) => {
|
||||||
console.error('Error loading texture:', error)
|
console.error('Error loading texture:', error)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -6,12 +6,12 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { MapObject, PlacedMapObject as PlacedMapObjectT } from '@/application/types'
|
import type { MapObject, PlacedMapObject as PlacedMapObjectT } from '@/application/types'
|
||||||
import { uuidv4 } from '@/application/utilities'
|
import { uuidv4 } from '@/application/utilities'
|
||||||
|
import PlacedMapObject from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObject.vue'
|
||||||
|
import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/partials/SelectedPlacedMapObject.vue'
|
||||||
import { getTile } from '@/composables/mapComposable'
|
import { getTile } from '@/composables/mapComposable'
|
||||||
import { useMapEditorStore } from '@/stores/mapEditorStore'
|
import { useMapEditorStore } from '@/stores/mapEditorStore'
|
||||||
import { useScene } from 'phavuer'
|
import { useScene } from 'phavuer'
|
||||||
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
import { onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
import PlacedMapObject from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObject.vue'
|
|
||||||
import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/partials/SelectedPlacedMapObject.vue'
|
|
||||||
|
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
const mapEditorStore = useMapEditorStore()
|
const mapEditorStore = useMapEditorStore()
|
||||||
@ -240,7 +240,7 @@ watch(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
// { deep: true }
|
// { deep: true }
|
||||||
)
|
)
|
||||||
</script>
|
</script>
|
||||||
|
@ -23,7 +23,7 @@
|
|||||||
<div v-for="(mapObject, index) in filteredMapObjects" :key="index" class="max-w-1/4 inline-block">
|
<div v-for="(mapObject, index) in filteredMapObjects" :key="index" class="max-w-1/4 inline-block">
|
||||||
<img
|
<img
|
||||||
class="border-2 border-solid max-w-full"
|
class="border-2 border-solid max-w-full"
|
||||||
:src="`${config.server_endpoint}/assets/map_objects/${mapObject.id}.png`"
|
:src="`${config.server_endpoint}/textures/map_objects/${mapObject.id}.png`"
|
||||||
alt="Object"
|
alt="Object"
|
||||||
@click="mapEditorStore.setSelectedMapObject(mapObject)"
|
@click="mapEditorStore.setSelectedMapObject(mapObject)"
|
||||||
:class="{
|
:class="{
|
||||||
|
@ -24,7 +24,7 @@
|
|||||||
<div v-for="group in groupedTiles" :key="group.parent.id" class="flex flex-col items-center justify-center relative">
|
<div v-for="group in groupedTiles" :key="group.parent.id" class="flex flex-col items-center justify-center relative">
|
||||||
<img
|
<img
|
||||||
class="max-w-full max-h-full border-2 border-solid cursor-pointer transition-all duration-300"
|
class="max-w-full max-h-full border-2 border-solid cursor-pointer transition-all duration-300"
|
||||||
:src="`${config.server_endpoint}/assets/tiles/${group.parent.id}.png`"
|
:src="`${config.server_endpoint}/textures/tiles/${group.parent.id}.png`"
|
||||||
:alt="group.parent.name"
|
:alt="group.parent.name"
|
||||||
@click="openGroup(group)"
|
@click="openGroup(group)"
|
||||||
@load="() => processTile(group.parent)"
|
@load="() => processTile(group.parent)"
|
||||||
@ -50,7 +50,7 @@
|
|||||||
<div class="flex flex-col items-center justify-center">
|
<div class="flex flex-col items-center justify-center">
|
||||||
<img
|
<img
|
||||||
class="max-w-full max-h-full border-2 border-solid cursor-pointer transition-all duration-300"
|
class="max-w-full max-h-full border-2 border-solid cursor-pointer transition-all duration-300"
|
||||||
:src="`${config.server_endpoint}/assets/tiles/${selectedGroup.parent.id}.png`"
|
:src="`${config.server_endpoint}/textures/tiles/${selectedGroup.parent.id}.png`"
|
||||||
:alt="selectedGroup.parent.name"
|
:alt="selectedGroup.parent.name"
|
||||||
@click="selectTile(selectedGroup.parent.id)"
|
@click="selectTile(selectedGroup.parent.id)"
|
||||||
:class="{
|
:class="{
|
||||||
@ -63,7 +63,7 @@
|
|||||||
<div v-for="childTile in selectedGroup.children" :key="childTile.id" class="flex flex-col items-center justify-center">
|
<div v-for="childTile in selectedGroup.children" :key="childTile.id" class="flex flex-col items-center justify-center">
|
||||||
<img
|
<img
|
||||||
class="max-w-full max-h-full border-2 border-solid cursor-pointer transition-all duration-300"
|
class="max-w-full max-h-full border-2 border-solid cursor-pointer transition-all duration-300"
|
||||||
:src="`${config.server_endpoint}/assets/tiles/${childTile.id}.png`"
|
:src="`${config.server_endpoint}/textures/tiles/${childTile.id}.png`"
|
||||||
:alt="childTile.name"
|
:alt="childTile.name"
|
||||||
@click="selectTile(childTile.id)"
|
@click="selectTile(childTile.id)"
|
||||||
:class="{
|
:class="{
|
||||||
@ -169,7 +169,7 @@ function processTile(tile: Tile) {
|
|||||||
tileColorData.value.set(tile.id, getDominantColor(imageData))
|
tileColorData.value.set(tile.id, getDominantColor(imageData))
|
||||||
tileEdgeData.value.set(tile.id, getEdgeComplexity(imageData))
|
tileEdgeData.value.set(tile.id, getEdgeComplexity(imageData))
|
||||||
}
|
}
|
||||||
img.src = `${config.server_endpoint}/assets/tiles/${tile.id}.png`
|
img.src = `${config.server_endpoint}/textures/tiles/${tile.id}.png`
|
||||||
}
|
}
|
||||||
|
|
||||||
function getDominantColor(imageData: ImageData) {
|
function getDominantColor(imageData: ImageData) {
|
||||||
|
@ -34,27 +34,23 @@
|
|||||||
<button class="ml-6 w-4 h-8 p-0">
|
<button class="ml-6 w-4 h-8 p-0">
|
||||||
<img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 m-auto" alt="Arrow left" />
|
<img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 m-auto" alt="Arrow left" />
|
||||||
</button>
|
</button>
|
||||||
<img class="w-24 object-contain mb-3.5" alt="Player avatar" :src="config.server_endpoint + '/avatar/s/' + characters.find((c) => c.id === selectedCharacterId)?.characterType?.id + '/' + (selectedHairId ?? 'default')" />
|
<img class="w-24 object-contain mb-3.5" alt="Player avatar" :src="config.server_endpoint + '/avatar/s/' + characters.find((c) => c.id === selectedCharacterId)?.characterType + '/' + (selectedHairId ?? 'default')" />
|
||||||
<button class="mr-6 w-4 h-8 p-0">
|
<button class="mr-6 w-4 h-8 p-0">
|
||||||
<img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 -scale-x-100" alt="Arrow right" />
|
<img src="/assets/icons/triangle-icon.svg" class="w-3 h-3.5 -scale-x-100" alt="Arrow right" />
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
<!-- <div class="flex justify-between w-[190px]">-->
|
|
||||||
<!-- <!– TODO: replace with color swatches –>-->
|
|
||||||
<!-- <button v-for="n in 9" class="w-4 h-4 rounded-sm bg-white"></button>-->
|
|
||||||
<!-- </div>-->
|
|
||||||
</div>
|
</div>
|
||||||
<!-- TODO: update gender on (selected) character -->
|
<!-- TODO: update gender on (selected) character -->
|
||||||
<div class="flex justify-between w-[190px]">
|
<!-- <div class="flex justify-between w-[190px]">-->
|
||||||
<button class="btn-empty flex gap-2" :class="{ selected: characters.find((c) => c.id == selectedCharacterId)?.characterType?.gender === 'MALE' }">
|
<!-- <button class="btn-empty flex gap-2" :class="{ selected: characters.find((c) => c.id == selectedCharacterId)?.characterType?.gender === 'MALE' }">-->
|
||||||
<img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" />
|
<!-- <img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" />-->
|
||||||
<span class="text-white">Male</span>
|
<!-- <span class="text-white">Male</span>-->
|
||||||
</button>
|
<!-- </button>-->
|
||||||
<button class="btn-empty flex gap-2" :class="{ selected: characters.find((c) => c.id == selectedCharacterId)?.characterType?.gender === 'FEMALE' }">
|
<!-- <button class="btn-empty flex gap-2" :class="{ selected: characters.find((c) => c.id == selectedCharacterId)?.characterType?.gender === 'FEMALE' }">-->
|
||||||
<img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" />
|
<!-- <img src="/assets/icons/male-icon.svg" class="w-4 h-4 m-auto" alt="Male symbol" />-->
|
||||||
<span class="text-white">Female</span>
|
<!-- <span class="text-white">Female</span>-->
|
||||||
</button>
|
<!-- </button>-->
|
||||||
</div>
|
<!-- </div>-->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -74,7 +70,7 @@
|
|||||||
v-for="hair in characterHairs"
|
v-for="hair in characterHairs"
|
||||||
class="relative flex justify-center items-center bg-gray default-border w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-gray-300 focus-visible:bg-gray-500 has-[:checked]:bg-cyan has-[:checked]:border-transparent"
|
class="relative flex justify-center items-center bg-gray default-border w-[18px] h-[18px] p-2 rounded-sm hover:bg-gray-500 hover:border-gray-400 focus-visible:outline-none focus-visible:border-gray-300 focus-visible:bg-gray-500 has-[:checked]:bg-cyan has-[:checked]:border-transparent"
|
||||||
>
|
>
|
||||||
<img class="h-4 object-contain" :src="config.server_endpoint + '/assets/sprites/' + hair.sprite.id + '/front.png'" alt="Hair sprite" />
|
<img class="h-4 object-contain" :src="config.server_endpoint + '/textures/sprites/' + hair.sprite + '/front.png'" alt="Hair sprite" />
|
||||||
<input type="radio" name="hair" :value="hair.id" v-model="selectedHairId" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
|
<input type="radio" name="hair" :value="hair.id" v-model="selectedHairId" class="h-full w-full absolute left-0 top-0 m-0 z-10 hover:cursor-pointer focus-visible:outline-offset-0 focus-visible:outline-white" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -128,8 +124,9 @@
|
|||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
import { type CharacterHair, type Character as CharacterT, type Map } from '@/application/types'
|
import { type CharacterHair, type Character as CharacterT, type Map } from '@/application/types'
|
||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
|
import { CharacterHairStorage } from '@/storage/storages'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { onBeforeUnmount, ref, watch } from 'vue'
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const isLoading = ref<boolean>(true)
|
const isLoading = ref<boolean>(true)
|
||||||
@ -148,12 +145,6 @@ setTimeout(() => {
|
|||||||
gameStore.connection?.on('character:list', (data: any) => {
|
gameStore.connection?.on('character:list', (data: any) => {
|
||||||
characters.value = data
|
characters.value = data
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
|
|
||||||
// Fetch hairs
|
|
||||||
// @TODO: This is hacky, we should have a better way to do this
|
|
||||||
gameStore.connection?.emit('character:hair:list', {}, (data: CharacterHair[]) => {
|
|
||||||
characterHairs.value = data
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
// Select character logics
|
// Select character logics
|
||||||
@ -184,7 +175,12 @@ function createCharacter() {
|
|||||||
// Watch changes for selected character and update hairs
|
// Watch changes for selected character and update hairs
|
||||||
watch(selectedCharacterId, (characterId) => {
|
watch(selectedCharacterId, (characterId) => {
|
||||||
if (!characterId) return
|
if (!characterId) return
|
||||||
selectedHairId.value = characters.value.find((c) => c.id == characterId)?.characterHairId ?? null
|
// selectedHairId.value = characters.value.find((c) => c.id == characterId)?.characterHairId ?? null
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
const characterHairStorage = new CharacterHairStorage()
|
||||||
|
characterHairs.value = await characterHairStorage.getAll()
|
||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
|
@ -33,6 +33,7 @@ import Menu from '@/components/gui/Menu.vue'
|
|||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import AwaitLoaderPlugin from 'phaser3-rex-plugins/plugins/awaitloader-plugin'
|
import AwaitLoaderPlugin from 'phaser3-rex-plugins/plugins/awaitloader-plugin'
|
||||||
import { Game, Scene } from 'phavuer'
|
import { Game, Scene } from 'phavuer'
|
||||||
|
import { onBeforeUnmount } from 'vue'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
|
||||||
@ -80,4 +81,6 @@ function preloadScene(scene: Phaser.Scene) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function createScene(scene: Phaser.Scene) {}
|
function createScene(scene: Phaser.Scene) {}
|
||||||
|
|
||||||
|
onBeforeUnmount(() => {})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,25 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex flex-col justify-center items-center h-dvh relative">
|
<div class="flex flex-col justify-center items-center h-dvh relative col">
|
||||||
<button @click="continueBtnClick" class="w-32 h-12 rounded-full bg-gray-500 flex items-center justify-between px-4 hover:bg-gray-600 transition-colors">
|
<svg width="40" height="40" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
<span class="text-white text-lg flex-1 text-center">Play</span>
|
<circle cx="4" cy="12" r="3" fill="white">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<animate id="spinner_qFRN" begin="0;spinner_OcgL.end+0.25s" attributeName="cy" calcMode="spline" dur="0.6s" values="12;6;12" keySplines=".33,.66,.66,1;.33,0,.66,.33" />
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 5l7 7-7 7" />
|
</circle>
|
||||||
|
<circle cx="12" cy="12" r="3" fill="white">
|
||||||
|
<animate begin="spinner_qFRN.begin+0.1s" attributeName="cy" calcMode="spline" dur="0.6s" values="12;6;12" keySplines=".33,.66,.66,1;.33,0,.66,.33" />
|
||||||
|
</circle>
|
||||||
|
<circle cx="20" cy="12" r="3" fill="white">
|
||||||
|
<animate id="spinner_OcgL" begin="spinner_qFRN.begin+0.2s" attributeName="cy" calcMode="spline" dur="0.6s" values="12;6;12" keySplines=".33,.66,.66,1;.33,0,.66,.33" />
|
||||||
|
</circle>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts" async>
|
<script setup lang="ts" async>
|
||||||
|
import config from '@/application/config'
|
||||||
|
import type { HttpResponse, MapObject } from '@/application/types'
|
||||||
|
import type { BaseStorage } from '@/storage/baseStorage'
|
||||||
|
import { CharacterHairStorage, CharacterTypeStorage, MapObjectStorage, MapStorage, SpriteStorage, TileStorage } from '@/storage/storages'
|
||||||
|
// import type { Map } from '@/application/types'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
|
||||||
function continueBtnClick() {
|
const totalItems = ref(0)
|
||||||
// Play music
|
const currentItem = ref(0)
|
||||||
const audio = new Audio('/assets/music/login.mp3')
|
|
||||||
audio.play()
|
|
||||||
|
|
||||||
// Set isLoaded to true
|
async function downloadAndStore<T extends { id: string }>(endpoint: string, storage: BaseStorage<T>) {
|
||||||
gameStore.game.isLoaded = true
|
const request = await fetch(`${config.server_endpoint}/cache/${endpoint}`)
|
||||||
|
const response = (await request.json()) as HttpResponse<T[]>
|
||||||
|
|
||||||
|
if (!response.success) {
|
||||||
|
console.error(`Failed to download ${endpoint}:`, response.message)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const items = response.data ?? []
|
||||||
|
for (const item of items) {
|
||||||
|
await storage.add(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const tileStorage = new TileStorage()
|
||||||
|
const mapStorage = new MapStorage()
|
||||||
|
const mapObjectStorage = new MapObjectStorage()
|
||||||
|
|
||||||
|
Promise.all([
|
||||||
|
downloadAndStore('tiles', tileStorage),
|
||||||
|
downloadAndStore('maps', mapStorage),
|
||||||
|
downloadAndStore('map_objects', mapObjectStorage),
|
||||||
|
downloadAndStore('sprites', new SpriteStorage()),
|
||||||
|
downloadAndStore('character_types', new CharacterTypeStorage()),
|
||||||
|
downloadAndStore('character_hair', new CharacterHairStorage())
|
||||||
|
]).then(() => {
|
||||||
|
gameStore.game.isLoaded = true
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
import 'phaser'
|
import 'phaser'
|
||||||
import type { AssetDataT } from '@/application/types'
|
import type { TextureData } from '@/application/types'
|
||||||
import MapEditor from '@/components/gameMaster/mapEditor/MapEditor.vue'
|
import MapEditor from '@/components/gameMaster/mapEditor/MapEditor.vue'
|
||||||
import { loadTexture } from '@/composables/gameComposable'
|
import { loadTexture } from '@/composables/gameComposable'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
@ -72,7 +72,7 @@ const preloadScene = async (scene: Phaser.Scene) => {
|
|||||||
* Then load them into the scene.
|
* Then load them into the scene.
|
||||||
*/
|
*/
|
||||||
scene.load.rexAwait(async function (successCallback: any) {
|
scene.load.rexAwait(async function (successCallback: any) {
|
||||||
const tiles: { data: AssetDataT[] } = await fetch(config.server_endpoint + '/assets/list_tiles').then((response) => response.json())
|
const tiles: { data: TextureData[] } = await fetch(config.server_endpoint + '/assets/list_tiles').then((response) => response.json())
|
||||||
|
|
||||||
for await (const tile of tiles?.data ?? []) {
|
for await (const tile of tiles?.data ?? []) {
|
||||||
await loadTexture(scene, tile)
|
await loadTexture(scene, tile)
|
||||||
|
46
src/components/utilities/Debug.vue
Normal file
46
src/components/utilities/Debug.vue
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
<template></template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { CharacterHairStorage, CharacterTypeStorage, MapObjectStorage, MapStorage, SpriteStorage, TileStorage } from '@/storage/storages'
|
||||||
|
import { TextureStorage } from '@/storage/textureStorage'
|
||||||
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
|
const mapStorage = new MapStorage()
|
||||||
|
const tileStorage = new TileStorage()
|
||||||
|
const mapObjectStorage = new MapObjectStorage()
|
||||||
|
const spriteStorage = new SpriteStorage()
|
||||||
|
const characterTypeStorage = new CharacterTypeStorage()
|
||||||
|
const characterHairStorage = new CharacterHairStorage()
|
||||||
|
const textureStorage = new TextureStorage()
|
||||||
|
|
||||||
|
let currentString = ''
|
||||||
|
|
||||||
|
async function handleKeyPress(event: KeyboardEvent) {
|
||||||
|
// Ignore if typing in input/textarea
|
||||||
|
if (document.activeElement?.tagName.toUpperCase() === 'INPUT' || document.activeElement?.tagName.toUpperCase() === 'TEXTAREA') {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentString += event.key
|
||||||
|
|
||||||
|
// Do something when string matches
|
||||||
|
if (currentString.includes('reset')) {
|
||||||
|
await mapStorage.destroy()
|
||||||
|
await tileStorage.destroy()
|
||||||
|
await mapObjectStorage.destroy()
|
||||||
|
await spriteStorage.destroy()
|
||||||
|
await characterTypeStorage.destroy()
|
||||||
|
await characterHairStorage.destroy()
|
||||||
|
await textureStorage.destroy()
|
||||||
|
currentString = '' // Reset
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reset string after a certain amount of time
|
||||||
|
setTimeout(() => {
|
||||||
|
currentString = ''
|
||||||
|
}, 60000)
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => window.addEventListener('keydown', handleKeyPress))
|
||||||
|
onUnmounted(() => window.removeEventListener('keydown', handleKeyPress))
|
||||||
|
</script>
|
0
src/composables/characterComposable.ts
Normal file
0
src/composables/characterComposable.ts
Normal file
@ -1,94 +1,104 @@
|
|||||||
import { Assets } from '@/application/assets'
|
import type { TextureData } from '@/application/types'
|
||||||
import config from '@/application/config'
|
import { SpriteStorage } from '@/storage/storages'
|
||||||
import type { AssetDataT, HttpResponse, Sprite, SpriteAction } from '@/application/types'
|
import { TextureStorage } from '@/storage/textureStorage'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
|
||||||
const textureLoadingPromises = new Map<string, Promise<boolean>>()
|
const textureLoadingPromises = new Map<string, Promise<boolean>>()
|
||||||
|
|
||||||
export async function loadTexture(scene: Phaser.Scene, assetData: AssetDataT): Promise<boolean> {
|
export async function loadTexture(scene: Phaser.Scene, textureData: TextureData): Promise<boolean> {
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const assetStorage = new Assets()
|
const textureStorage = new TextureStorage()
|
||||||
|
|
||||||
// Check if the texture is already loaded in Phaser
|
// Check if the texture is already loaded in Phaser
|
||||||
if (gameStore.game.loadedAssets.find((asset) => asset.key === assetData.key)) {
|
if (gameStore.game.loadedTextures.find((texture) => texture === textureData.key)) {
|
||||||
return Promise.resolve(true)
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// If there's already a loading promise for this texture, return it
|
// If there's already a loading promise for this texture, return it
|
||||||
if (textureLoadingPromises.has(assetData.key)) {
|
if (textureLoadingPromises.has(textureData.key)) {
|
||||||
return await textureLoadingPromises.get(assetData.key)!
|
return await textureLoadingPromises.get(textureData.key)!
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create new loading promise
|
// Create new loading promise
|
||||||
const loadingPromise = (async () => {
|
const loadingPromise = (async () => {
|
||||||
// Check if the asset is already cached
|
// Check if the asset is already cached
|
||||||
let asset = await assetStorage.get(assetData.key)
|
let texture = await textureStorage.get(textureData.key)
|
||||||
|
|
||||||
// If asset is not found, download it
|
// If asset is not found, download it
|
||||||
if (!asset) {
|
if (!texture) {
|
||||||
await assetStorage.download(assetData)
|
console.log('Downloading texture:', textureData.key)
|
||||||
asset = await assetStorage.get(assetData.key)
|
await textureStorage.download(textureData)
|
||||||
|
texture = await textureStorage.get(textureData.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
// If asset is found, add it to the scene
|
// If asset is found, add it to the scene
|
||||||
if (asset) {
|
if (texture) {
|
||||||
return new Promise<boolean>((resolve) => {
|
return new Promise<boolean>((resolve) => {
|
||||||
// Remove existing texture if it exists
|
// Remove existing texture if it exists
|
||||||
if (scene.textures.exists(asset.key)) {
|
if (scene.textures.exists(texture.key)) {
|
||||||
scene.textures.remove(asset.key)
|
scene.textures.remove(texture.key)
|
||||||
}
|
}
|
||||||
|
|
||||||
scene.textures.addBase64(asset.key, asset.data)
|
scene.textures.addBase64(texture.key, texture.data)
|
||||||
scene.textures.once(`addtexture-${asset.key}`, () => {
|
scene.textures.once(`addtexture-${texture.key}`, () => {
|
||||||
gameStore.game.loadedAssets.push(assetData)
|
gameStore.game.loadedTextures.push(textureData.key)
|
||||||
textureLoadingPromises.delete(assetData.key) // Clean up the promise
|
textureLoadingPromises.delete(textureData.key) // Clean up the promise
|
||||||
resolve(true)
|
resolve(true)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
textureLoadingPromises.delete(assetData.key) // Clean up the promise
|
textureLoadingPromises.delete(textureData.key) // Clean up the promise
|
||||||
return Promise.resolve(false)
|
return false
|
||||||
})()
|
})()
|
||||||
|
|
||||||
// Store the loading promise
|
// Store the loading promise
|
||||||
textureLoadingPromises.set(assetData.key, loadingPromise)
|
textureLoadingPromises.set(textureData.key, loadingPromise)
|
||||||
return loadingPromise
|
return loadingPromise
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function loadSpriteTextures(scene: Phaser.Scene, sprite_id: string) {
|
export async function loadSpriteTextures(scene: Phaser.Scene, sprite_id: string) {
|
||||||
if (!sprite_id) return
|
if (!sprite_id) return
|
||||||
|
|
||||||
|
console.log(sprite_id)
|
||||||
// @TODO: Fix this
|
// @TODO: Fix this
|
||||||
const sprite_actions: HttpResponse<any[]> = await fetch(config.server_endpoint + '/assets/list_sprite_actions/' + sprite_id).then((response) => response.json())
|
const spriteStorage = new SpriteStorage()
|
||||||
|
const sprite = await spriteStorage.get(sprite_id)
|
||||||
|
|
||||||
for await (const sprite_action of sprite_actions.data ?? []) {
|
if (!sprite) {
|
||||||
|
console.error('Failed to load sprite:', sprite_id)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for await (const sprite_action of sprite.spriteActions) {
|
||||||
|
console.log('Loading sprite action:', sprite.id + '-' + sprite_action.action)
|
||||||
|
const key = sprite.id + '-' + sprite_action.action
|
||||||
await loadTexture(scene, {
|
await loadTexture(scene, {
|
||||||
key: sprite_action.key,
|
key,
|
||||||
data: sprite_action.data,
|
data: '/textures/sprites/' + sprite.id + '/' + sprite_action.action + '.png',
|
||||||
group: sprite_action.isAnimated ? 'sprite_animations' : 'sprites',
|
group: sprite_action.isAnimated ? 'sprite_animations' : 'sprites',
|
||||||
updatedAt: sprite_action.updatedAt,
|
updatedAt: sprite_action.updatedAt,
|
||||||
originX: sprite_action.originX ?? 0,
|
originX: sprite_action.originX,
|
||||||
originY: sprite_action.originY ?? 0,
|
originY: sprite_action.originY,
|
||||||
isAnimated: sprite_action.isAnimated,
|
isAnimated: sprite_action.isAnimated,
|
||||||
frameWidth: sprite_action.frameWidth,
|
frameWidth: sprite_action.frameWidth,
|
||||||
frameHeight: sprite_action.frameHeight,
|
frameHeight: sprite_action.frameHeight,
|
||||||
frameRate: sprite_action.frameRate
|
frameRate: sprite_action.frameRate
|
||||||
} as AssetDataT)
|
} as TextureData)
|
||||||
|
|
||||||
// If the sprite is not animated, skip
|
// If the sprite is not animated, skip
|
||||||
if (!sprite_action.isAnimated) continue
|
if (!sprite_action.isAnimated) continue
|
||||||
|
|
||||||
// Check if animation already exists
|
// Check if animation already exists
|
||||||
if (scene.anims.get(sprite_action.key)) continue
|
if (scene.anims.get(key)) continue
|
||||||
|
|
||||||
// Add the animation to the scene
|
// Add the animation to the scene
|
||||||
const anim = scene.textures.get(sprite_action.key)
|
const anim = scene.textures.get(key)
|
||||||
scene.textures.addSpriteSheet(sprite_action.key, anim, { frameWidth: sprite_action.frameWidth ?? 0, frameHeight: sprite_action.frameHeight ?? 0 })
|
scene.textures.addSpriteSheet(key, anim, { frameWidth: sprite_action.frameWidth ?? 0, frameHeight: sprite_action.frameHeight ?? 0 })
|
||||||
scene.anims.create({
|
scene.anims.create({
|
||||||
key: sprite_action.key,
|
key: key,
|
||||||
frameRate: sprite_action.frameRate,
|
frameRate: sprite_action.frameRate,
|
||||||
frames: scene.anims.generateFrameNumbers(sprite_action.key, { start: 0, end: sprite_action.frameCount! - 1 }),
|
frames: scene.anims.generateFrameNumbers(key, { start: 0, end: sprite_action.frameCount! - 1 }),
|
||||||
repeat: -1
|
repeat: -1
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -1,56 +1,58 @@
|
|||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
import type { AssetDataT, HttpResponse, UUID } from '@/application/types'
|
import type { HttpResponse, TextureData, UUID } from '@/application/types'
|
||||||
|
import { unduplicateArray } from '@/application/utilities'
|
||||||
import { loadTexture } from '@/composables/gameComposable'
|
import { loadTexture } from '@/composables/gameComposable'
|
||||||
|
import { MapStorage } from '@/storage/storages'
|
||||||
|
|
||||||
import Tilemap = Phaser.Tilemaps.Tilemap
|
import Tilemap = Phaser.Tilemaps.Tilemap
|
||||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
||||||
import Tileset = Phaser.Tilemaps.Tileset
|
import Tileset = Phaser.Tilemaps.Tileset
|
||||||
import Tile = Phaser.Tilemaps.Tile
|
import Tile = Phaser.Tilemaps.Tile
|
||||||
|
|
||||||
export function getTile(layer: TilemapLayer | Tilemap, x: number, y: number): Tile | undefined {
|
export function getTile(layer: TilemapLayer | Tilemap, positionX: number, positionY: number): Tile | undefined {
|
||||||
const tile = layer.getTileAtWorldXY(x, y)
|
const tile = layer.getTileAtWorldXY(positionX, positionY)
|
||||||
if (!tile) return undefined
|
if (!tile) return undefined
|
||||||
return tile
|
return tile
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tileToWorldXY(layer: TilemapLayer | Tilemap, pos_x: number, pos_y: number) {
|
export function tileToWorldXY(layer: TilemapLayer | Tilemap, positionX: number, positionY: number) {
|
||||||
const worldPoint = layer.tileToWorldXY(pos_x, pos_y)
|
const worldPoint = layer.tileToWorldXY(positionX, positionY)
|
||||||
if (!worldPoint) return { positionX: 0, positionY: 0 }
|
if (!worldPoint) return { positionX: 0, positionY: 0 }
|
||||||
|
|
||||||
const positionX = worldPoint.x + config.tile_size.y
|
const worldPositionX = worldPoint.x + config.tile_size.height
|
||||||
const positionY = worldPoint.y
|
const worldPositionY = worldPoint.y
|
||||||
|
|
||||||
return { positionX, positionY }
|
return { worldPositionX, worldPositionY }
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tileToWorldX(layer: TilemapLayer | Tilemap, pos_x: number, pos_y: number): number {
|
export function tileToWorldX(layer: TilemapLayer | Tilemap, positionX: number, positionY: number): number {
|
||||||
const worldPoint = layer.tileToWorldXY(pos_x, pos_y)
|
const worldPoint = layer.tileToWorldXY(positionX, positionY)
|
||||||
if (!worldPoint) return 0
|
if (!worldPoint) return 0
|
||||||
|
|
||||||
return worldPoint.x + config.tile_size.x / 2
|
return worldPoint.x + config.tile_size.width / 2
|
||||||
}
|
}
|
||||||
|
|
||||||
export function tileToWorldY(layer: TilemapLayer | Tilemap, pos_x: number, pos_y: number): number {
|
export function tileToWorldY(layer: TilemapLayer | Tilemap, positionX: number, positionY: number): number {
|
||||||
const worldPoint = layer.tileToWorldXY(pos_x, pos_y)
|
const worldPoint = layer.tileToWorldXY(positionX, positionY)
|
||||||
if (!worldPoint) return 0
|
if (!worldPoint) return 0
|
||||||
|
|
||||||
return worldPoint.y + config.tile_size.y * 1.5
|
return worldPoint.y + config.tile_size.height * 1.5
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Can also be used to replace tiles
|
* Can also be used to replace tiles
|
||||||
* @param map
|
* @param map
|
||||||
* @param layer
|
* @param layer
|
||||||
* @param x
|
* @param positionX
|
||||||
* @param y
|
* @param positionY
|
||||||
* @param tileName
|
* @param tileName
|
||||||
*/
|
*/
|
||||||
export function placeTile(map: Tilemap, layer: TilemapLayer, x: number, y: number, tileName: string) {
|
export function placeTile(map: Tilemap, layer: TilemapLayer, positionX: number, positionY: number, tileName: string) {
|
||||||
let tileImg = map.getTileset(tileName) as Tileset
|
let tileImg = map.getTileset(tileName) as Tileset
|
||||||
if (!tileImg) {
|
if (!tileImg) {
|
||||||
tileImg = map.getTileset('blank_tile') as Tileset
|
tileImg = map.getTileset('blank_tile') as Tileset
|
||||||
}
|
}
|
||||||
layer.putTileAt(tileImg.firstgid, x, y)
|
layer.putTileAt(tileImg.firstgid, positionX, positionY)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function setLayerTiles(map: Tilemap, layer: TilemapLayer, tiles: string[][]) {
|
export function setLayerTiles(map: Tilemap, layer: TilemapLayer, tiles: string[][]) {
|
||||||
@ -67,12 +69,12 @@ export function createTileArray(width: number, height: number, tile: string = 'b
|
|||||||
return Array.from({ length: height }, () => Array.from({ length: width }, () => tile))
|
return Array.from({ length: height }, () => Array.from({ length: width }, () => tile))
|
||||||
}
|
}
|
||||||
|
|
||||||
export const calculateIsometricDepth = (x: number, y: number, width: number = 0, height: number = 0, isCharacter: boolean = false) => {
|
export const calculateIsometricDepth = (positionX: number, positionY: number, width: number = 0, height: number = 0, isCharacter: boolean = false) => {
|
||||||
const baseDepth = x + y
|
const baseDepth = positionX + positionY
|
||||||
if (isCharacter) {
|
if (isCharacter) {
|
||||||
return baseDepth // @TODO: Fix collision, this is a hack
|
return baseDepth // @TODO: Fix collision, this is a hack
|
||||||
}
|
}
|
||||||
return baseDepth + (width + height) / (2 * config.tile_size.x)
|
return baseDepth + (width + height) / (2 * config.tile_size.width)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FlattenMapArray(tiles: string[][]) {
|
export function FlattenMapArray(tiles: string[][]) {
|
||||||
@ -86,11 +88,20 @@ export function FlattenMapArray(tiles: string[][]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function loadMapTilesIntoScene(map_id: UUID, scene: Phaser.Scene) {
|
export async function loadMapTilesIntoScene(map_id: UUID, scene: Phaser.Scene) {
|
||||||
// Fetch the list of tiles from the server
|
const mapStorage = new MapStorage()
|
||||||
const tileArray: HttpResponse<AssetDataT[]> = await fetch(config.server_endpoint + '/assets/list_tiles/' + map_id).then((response) => response.json())
|
const map = await mapStorage.get(map_id)
|
||||||
|
if (!map) return
|
||||||
|
|
||||||
|
const tileArray = unduplicateArray(FlattenMapArray(map.tiles))
|
||||||
|
|
||||||
// Load each tile into the scene
|
// Load each tile into the scene
|
||||||
for (const tile of tileArray.data ?? []) {
|
for (const tile of tileArray) {
|
||||||
await loadTexture(scene, tile)
|
const textureData = {
|
||||||
|
key: tile,
|
||||||
|
data: '/textures/tiles/' + tile + '.png',
|
||||||
|
group: 'tiles',
|
||||||
|
updatedAt: map.updatedAt // @TODO: Fix this
|
||||||
|
} as TextureData
|
||||||
|
await loadTexture(scene, textureData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,15 +10,17 @@ export function useGamePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilema
|
|||||||
|
|
||||||
function updateWaypoint(worldX: number, worldY: number) {
|
function updateWaypoint(worldX: number, worldY: number) {
|
||||||
const pointerTile = getTile(layer, worldX, worldY)
|
const pointerTile = getTile(layer, worldX, worldY)
|
||||||
if (pointerTile) {
|
if (!pointerTile) {
|
||||||
|
waypoint.value.visible = false
|
||||||
|
return
|
||||||
|
}
|
||||||
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
||||||
|
if (!worldPoint.worldPositionX || !worldPoint.worldPositionX) return
|
||||||
|
|
||||||
waypoint.value = {
|
waypoint.value = {
|
||||||
visible: true,
|
visible: true,
|
||||||
x: worldPoint.positionX,
|
x: worldPoint.worldPositionX,
|
||||||
y: worldPoint.positionY + config.tile_size.y + 15
|
y: worldPoint.worldPositionY + config.tile_size.height + 15
|
||||||
}
|
|
||||||
} else {
|
|
||||||
waypoint.value.visible = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,35 +30,36 @@ export function useGamePointerHandlers(scene: Phaser.Scene, layer: Phaser.Tilema
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
||||||
const { worldX, worldY } = pointer
|
updateWaypoint(pointer.worldX, pointer.worldY)
|
||||||
updateWaypoint(worldX, worldY)
|
|
||||||
|
if (!gameStore.game.isPlayerDraggingCamera) return
|
||||||
|
|
||||||
if (gameStore.game.isPlayerDraggingCamera) {
|
|
||||||
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
|
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
|
||||||
|
|
||||||
if (distance > dragThreshold) {
|
// If the distance is less than the drag threshold, return
|
||||||
const { x, y, prevPosition } = pointer
|
// We do this to prevent the camera from scrolling too quickly
|
||||||
const { scrollX, scrollY, zoom } = camera
|
if (distance <= dragThreshold) return
|
||||||
camera.setScroll(scrollX - (x - prevPosition.x) / zoom, scrollY - (y - prevPosition.y) / zoom)
|
|
||||||
}
|
camera.setScroll(camera.scrollX - (pointer.x - pointer.prevPosition.x) / camera.zoom, camera.scrollY - (pointer.y - pointer.prevPosition.y) / camera.zoom)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
||||||
|
gameStore.setPlayerDraggingCamera(false)
|
||||||
|
|
||||||
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
|
const distance = Phaser.Math.Distance.Between(pointerStartPosition.value.x, pointerStartPosition.value.y, pointer.x, pointer.y)
|
||||||
|
|
||||||
if (distance <= dragThreshold) {
|
// If the distance is greater than the drag threshold, return
|
||||||
|
// We do this to prevent the camera from scrolling too quickly
|
||||||
|
if (distance > dragThreshold) return
|
||||||
|
|
||||||
const pointerTile = getTile(layer, pointer.worldX, pointer.worldY)
|
const pointerTile = getTile(layer, pointer.worldX, pointer.worldY)
|
||||||
if (pointerTile) {
|
if (!pointerTile) return
|
||||||
|
|
||||||
gameStore.connection?.emit('map:character:move', {
|
gameStore.connection?.emit('map:character:move', {
|
||||||
positionX: pointerTile.x,
|
positionX: pointerTile.x,
|
||||||
positionY: pointerTile.y
|
positionY: pointerTile.y
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
gameStore.setPlayerDraggingCamera(false)
|
|
||||||
}
|
|
||||||
|
|
||||||
const setupPointerHandlers = () => {
|
const setupPointerHandlers = () => {
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
scene.input.on(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
||||||
|
@ -15,10 +15,12 @@ export function useMapEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.T
|
|||||||
|
|
||||||
if (pointerTile) {
|
if (pointerTile) {
|
||||||
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
const worldPoint = tileToWorldXY(layer, pointerTile.x, pointerTile.y)
|
||||||
|
if (!worldPoint.positionX || !worldPoint.positionY) return
|
||||||
|
|
||||||
waypoint.value = {
|
waypoint.value = {
|
||||||
visible: true,
|
visible: true,
|
||||||
x: worldPoint.positionX,
|
x: worldPoint.positionX,
|
||||||
y: worldPoint.positionY + config.tile_size.y + 15
|
y: worldPoint.positionY + config.tile_size.height + 15
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
waypoint.value.visible = false
|
waypoint.value.visible = false
|
||||||
@ -26,11 +28,8 @@ export function useMapEditorPointerHandlers(scene: Phaser.Scene, layer: Phaser.T
|
|||||||
}
|
}
|
||||||
|
|
||||||
function dragMap(pointer: Phaser.Input.Pointer) {
|
function dragMap(pointer: Phaser.Input.Pointer) {
|
||||||
if (gameStore.game.isPlayerDraggingCamera) {
|
if (!gameStore.game.isPlayerDraggingCamera) return
|
||||||
const { x, y, prevPosition } = pointer
|
camera.setScroll(camera.scrollX - (pointer.x - pointer.prevPosition.x) / camera.zoom, scrollY - (pointer.y - pointer.prevPosition.y) / camera.zoom)
|
||||||
const { scrollX, scrollY, zoom } = camera
|
|
||||||
camera.setScroll(scrollX - (x - prevPosition.x) / zoom, scrollY - (y - prevPosition.y) / zoom)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
||||||
|
0
src/composables/spriteComposable.ts
Normal file
0
src/composables/spriteComposable.ts
Normal file
0
src/composables/useCharacter.ts
Normal file
0
src/composables/useCharacter.ts
Normal file
60
src/storage/baseStorage.ts
Normal file
60
src/storage/baseStorage.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import Dexie from 'dexie'
|
||||||
|
|
||||||
|
export class BaseStorage<T extends { id: string }> {
|
||||||
|
protected dexie: Dexie
|
||||||
|
protected tableName: string
|
||||||
|
|
||||||
|
constructor(tableName: string, schema: string) {
|
||||||
|
this.tableName = tableName
|
||||||
|
this.dexie = new Dexie(tableName)
|
||||||
|
this.dexie.version(1).stores({
|
||||||
|
[tableName]: schema
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async add(item: T, overwrite = false) {
|
||||||
|
try {
|
||||||
|
const existing = await this.get(item.id)
|
||||||
|
if (existing && !overwrite) return
|
||||||
|
|
||||||
|
await this.dexie.table(this.tableName).put({ ...item })
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to add ${this.tableName} ${item.id}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(id: string): Promise<T | null> {
|
||||||
|
try {
|
||||||
|
const item = await this.dexie.table(this.tableName).get(id)
|
||||||
|
return item || null
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to retrieve ${this.tableName} ${id}:`, error)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getAll(): Promise<T[]> {
|
||||||
|
try {
|
||||||
|
return await this.dexie.table(this.tableName).toArray()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to retrieve all ${this.tableName}:`, error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reset() {
|
||||||
|
try {
|
||||||
|
await this.dexie.table(this.tableName).clear()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to clear ${this.tableName}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy() {
|
||||||
|
try {
|
||||||
|
await this.dexie.delete()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to destroy ${this.tableName}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
43
src/storage/storages.ts
Normal file
43
src/storage/storages.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import type { Map, MapObject, Tile } from '@/application/types'
|
||||||
|
import { BaseStorage } from '@/storage/baseStorage'
|
||||||
|
|
||||||
|
export class MapStorage extends BaseStorage<Map> {
|
||||||
|
constructor() {
|
||||||
|
super('maps', 'id, name, createdAt, updatedAt')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TileStorage extends BaseStorage<Tile> {
|
||||||
|
constructor() {
|
||||||
|
super('tiles', 'id, name, createdAt, updatedAt')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MapObjectStorage extends BaseStorage<MapObject> {
|
||||||
|
constructor() {
|
||||||
|
super('mapObjects', 'id, name, createdAt, updatedAt')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class SpriteStorage extends BaseStorage<any> {
|
||||||
|
constructor() {
|
||||||
|
super('sprites', 'id, name, createdAt, updatedAt')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CharacterTypeStorage extends BaseStorage<any> {
|
||||||
|
constructor() {
|
||||||
|
super('characterTypes', 'id, name, createdAt, updatedAt')
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSpriteId(characterTypeId: string) {
|
||||||
|
const characterType = await this.get(characterTypeId)
|
||||||
|
return characterType?.sprite
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CharacterHairStorage extends BaseStorage<any> {
|
||||||
|
constructor() {
|
||||||
|
super('characterHairs', 'id, name, createdAt, updatedAt')
|
||||||
|
}
|
||||||
|
}
|
97
src/storage/textureStorage.ts
Normal file
97
src/storage/textureStorage.ts
Normal file
@ -0,0 +1,97 @@
|
|||||||
|
import config from '@/application/config'
|
||||||
|
import type { TextureData } from '@/application/types'
|
||||||
|
import Dexie from 'dexie'
|
||||||
|
|
||||||
|
export class TextureStorage {
|
||||||
|
private dexie: Dexie
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.dexie = new Dexie('textures')
|
||||||
|
this.dexie.version(1).stores({
|
||||||
|
textures: 'key, group'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async download(texture: TextureData) {
|
||||||
|
try {
|
||||||
|
// Check if the texture already exists, then check if updatedAt is newer
|
||||||
|
const _texture = await this.dexie.table('textures').get(texture.key)
|
||||||
|
if (_texture && _texture.updatedAt > texture.updatedAt) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download the texture
|
||||||
|
const response = await fetch(config.server_endpoint + texture.data)
|
||||||
|
const blob = await response.blob()
|
||||||
|
|
||||||
|
// Store the texture in the database
|
||||||
|
await this.dexie.table('textures').put({
|
||||||
|
key: texture.key,
|
||||||
|
data: blob,
|
||||||
|
group: texture.group,
|
||||||
|
updatedAt: texture.updatedAt,
|
||||||
|
originX: texture.originX,
|
||||||
|
originY: texture.originY,
|
||||||
|
isAnimated: texture.isAnimated,
|
||||||
|
frameRate: texture.frameRate,
|
||||||
|
frameWidth: texture.frameWidth,
|
||||||
|
frameHeight: texture.frameHeight,
|
||||||
|
frameCount: texture.frameCount
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to add texture ${texture.key}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async get(key: string) {
|
||||||
|
try {
|
||||||
|
const texture = await this.dexie.table('textures').get(key)
|
||||||
|
if (texture) {
|
||||||
|
return {
|
||||||
|
...texture,
|
||||||
|
data: URL.createObjectURL(texture.data) // Convert blob to data URL
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to retrieve texture ${key}:`, error)
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async getByGroup(group: string) {
|
||||||
|
try {
|
||||||
|
const textures = await this.dexie.table('textures').where('group').equals(group).toArray()
|
||||||
|
return textures.map((texture) => ({
|
||||||
|
...texture,
|
||||||
|
data: URL.createObjectURL(texture.data) // Convert blob to data URL
|
||||||
|
}))
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to retrieve textures for group ${group}:`, error)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(key: string) {
|
||||||
|
try {
|
||||||
|
await this.dexie.table('textures').delete(key)
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to delete texture ${key}:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async reset() {
|
||||||
|
try {
|
||||||
|
await this.dexie.table('textures').clear()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to clear textures:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async destroy() {
|
||||||
|
try {
|
||||||
|
await this.dexie.delete()
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`Failed to destroy textures:`, error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,5 +1,5 @@
|
|||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
import type { AssetDataT, Character, Notification, User, WorldSettings } from '@/application/types'
|
import type { Character, Notification, TextureData, User, WorldSettings } from '@/application/types'
|
||||||
import { getDomain } from '@/application/utilities'
|
import { getDomain } from '@/application/utilities'
|
||||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
@ -9,7 +9,7 @@ export const useGameStore = defineStore('game', {
|
|||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
notifications: [] as Notification[],
|
notifications: [] as Notification[],
|
||||||
token: '' as string | null,
|
token: '',
|
||||||
connection: null as Socket | null,
|
connection: null as Socket | null,
|
||||||
user: null as User | null,
|
user: null as User | null,
|
||||||
character: null as Character | null,
|
character: null as Character | null,
|
||||||
@ -17,12 +17,12 @@ export const useGameStore = defineStore('game', {
|
|||||||
date: new Date(),
|
date: new Date(),
|
||||||
isRainEnabled: false,
|
isRainEnabled: false,
|
||||||
isFogEnabled: false,
|
isFogEnabled: false,
|
||||||
fogDensity: 0.5
|
fogDensity: 0
|
||||||
} as WorldSettings,
|
} as WorldSettings,
|
||||||
game: {
|
game: {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
isLoaded: false, // isLoaded is currently being used to determine if the player has interacted with the game
|
isLoaded: false, // isLoaded is currently being used to determine if the player has interacted with the game
|
||||||
loadedAssets: [] as AssetDataT[],
|
loadedTextures: [] as string[],
|
||||||
isPlayerDraggingCamera: false,
|
isPlayerDraggingCamera: false,
|
||||||
isCameraFollowingCharacter: false
|
isCameraFollowingCharacter: false
|
||||||
},
|
},
|
||||||
@ -35,16 +35,12 @@ export const useGameStore = defineStore('game', {
|
|||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
getLoadedAssets: (state) => {
|
getLoadedAssets: (state) => {
|
||||||
return state.game.loadedAssets
|
return state.game.loadedTextures
|
||||||
},
|
},
|
||||||
getLoadedAsset: (state) => {
|
isAssetLoaded: (state) => {
|
||||||
return (key: string | undefined) => {
|
return (key: string) => {
|
||||||
if (!key) return null
|
return state.game.loadedTextures.includes(key)
|
||||||
return state.game.loadedAssets.find((asset) => asset.key === key)
|
|
||||||
}
|
}
|
||||||
},
|
|
||||||
getLoadedAssetsByGroup: (state) => {
|
|
||||||
return (group: string) => state.game.loadedAssets.filter((asset) => asset.group === group)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
@ -112,12 +108,12 @@ export const useGameStore = defineStore('game', {
|
|||||||
})
|
})
|
||||||
|
|
||||||
this.connection = null
|
this.connection = null
|
||||||
this.token = null
|
this.token = ''
|
||||||
this.user = null
|
this.user = null
|
||||||
this.character = null
|
this.character = null
|
||||||
|
|
||||||
this.game.isLoaded = false
|
this.game.isLoaded = false
|
||||||
this.game.loadedAssets = []
|
this.game.loadedTextures = []
|
||||||
this.game.isPlayerDraggingCamera = false
|
this.game.isPlayerDraggingCamera = false
|
||||||
this.game.isCameraFollowingCharacter = false
|
this.game.isCameraFollowingCharacter = false
|
||||||
|
|
||||||
@ -128,7 +124,7 @@ export const useGameStore = defineStore('game', {
|
|||||||
this.world.date = new Date()
|
this.world.date = new Date()
|
||||||
this.world.isRainEnabled = false
|
this.world.isRainEnabled = false
|
||||||
this.world.isFogEnabled = false
|
this.world.isFogEnabled = false
|
||||||
this.world.fogDensity = 0.5
|
this.world.fogDensity = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -4,7 +4,7 @@ import { defineStore } from 'pinia'
|
|||||||
export const useMapStore = defineStore('map', {
|
export const useMapStore = defineStore('map', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
map: null as Map | null,
|
mapId: '',
|
||||||
characters: [] as MapCharacter[],
|
characters: [] as MapCharacter[],
|
||||||
characterLoaded: false
|
characterLoaded: false
|
||||||
}
|
}
|
||||||
@ -15,14 +15,11 @@ export const useMapStore = defineStore('map', {
|
|||||||
},
|
},
|
||||||
getCharacterCount: (state) => {
|
getCharacterCount: (state) => {
|
||||||
return state.characters.length
|
return state.characters.length
|
||||||
},
|
|
||||||
isMapSet: (state) => {
|
|
||||||
return state.map !== null
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
setMap(map: Map | null) {
|
setMapId(mapId: UUID) {
|
||||||
this.map = map
|
this.mapId = mapId
|
||||||
},
|
},
|
||||||
setCharacters(characters: MapCharacter[]) {
|
setCharacters(characters: MapCharacter[]) {
|
||||||
this.characters = characters
|
this.characters = characters
|
||||||
@ -50,7 +47,7 @@ export const useMapStore = defineStore('map', {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
reset() {
|
reset() {
|
||||||
this.map = null
|
this.mapId = ''
|
||||||
this.characters = []
|
this.characters = []
|
||||||
this.characterLoaded = false
|
this.characterLoaded = false
|
||||||
}
|
}
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url';
|
import { fileURLToPath, URL } from 'node:url';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
|
||||||
import viteCompression from 'vite-plugin-compression'
|
import viteCompression from 'vite-plugin-compression'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
// VueDevTools(),
|
|
||||||
viteCompression()
|
viteCompression()
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
|
@ -1,14 +1,12 @@
|
|||||||
import { fileURLToPath, URL } from 'node:url'
|
import { fileURLToPath, URL } from 'node:url'
|
||||||
import { defineConfig } from 'vite'
|
import { defineConfig } from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue';
|
import vue from '@vitejs/plugin-vue';
|
||||||
import VueDevTools from 'vite-plugin-vue-devtools'
|
|
||||||
import viteCompression from 'vite-plugin-compression';
|
import viteCompression from 'vite-plugin-compression';
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
// VueDevTools(),
|
|
||||||
viteCompression()
|
viteCompression()
|
||||||
],
|
],
|
||||||
resolve: {
|
resolve: {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user