Compare commits
71 Commits
feature/#3
...
feature/#3
Author | SHA1 | Date | |
---|---|---|---|
4042808d4e | |||
a6d6d894a9 | |||
0c61fe77de | |||
bfb2bcb939 | |||
af5a97f66d | |||
79fa54b1bb | |||
dbb4cae154 | |||
9a8220e4e0 | |||
bc0db8b32b | |||
ad611ef593 | |||
d819a84a37 | |||
15dc331a43 | |||
920baaebde | |||
b569888682 | |||
94eab073e6 | |||
d843b954ab | |||
337446497b | |||
d8805dd775 | |||
4c040c21d6 | |||
d0af83ec60 | |||
2de34d2034 | |||
132121c082 | |||
201f628bfa | |||
af99d66595 | |||
56f30093f6 | |||
8f26a40a0e | |||
110fd4e608 | |||
c1edf31ca0 | |||
90c0ed3141 | |||
bcf0d2832d | |||
8bf67ab168 | |||
f83e2bf8c8 | |||
8b0bf6534e | |||
5e243e5201 | |||
c82db9813e | |||
579749f4e0 | |||
ddc26a021b | |||
2d6b1ff1e0 | |||
16720777c9 | |||
41e7832cbe | |||
e6412d8a65 | |||
faa8e5def9 | |||
beed1d6903 | |||
2ebcc24390 | |||
2e3ff803f6 | |||
dd1cc795de | |||
59243e0e17 | |||
87ffc98cce | |||
0c450b24ed | |||
9459639497 | |||
5f2c7a09b1 | |||
13e8c1b4dd | |||
b27a2e8779 | |||
b3c9e3ca3d | |||
31a91c3f9f | |||
5d4de60f90 | |||
4070bcf048 | |||
04203cb9c1 | |||
592d1df9bf | |||
9413fdbb2f | |||
34caac562c | |||
52dafb8643 | |||
390187f353 | |||
cbd111a05b | |||
5ef11f3157 | |||
c56c2796c4 | |||
c228af7bb6 | |||
f45a51c230 | |||
790a62c600 | |||
6de0bb200d | |||
ca307d4de3 |
16
nginx.conf
16
nginx.conf
@ -1,16 +0,0 @@
|
|||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name localhost;
|
|
||||||
root /usr/share/nginx/html;
|
|
||||||
index index.html;
|
|
||||||
|
|
||||||
# Redirect example
|
|
||||||
location /discord {
|
|
||||||
return 301 https://discord.gg/JTev3nzeDa;
|
|
||||||
}
|
|
||||||
|
|
||||||
# Serve static files
|
|
||||||
location / {
|
|
||||||
try_files $uri $uri/ /index.html;
|
|
||||||
}
|
|
||||||
}
|
|
1207
package-lock.json
generated
1207
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
20
package.json
20
package.json
@ -16,14 +16,18 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@vueuse/core": "^10.5.0",
|
"@vueuse/core": "^10.5.0",
|
||||||
"@vueuse/integrations": "^10.5.0",
|
"@vueuse/integrations": "^10.5.0",
|
||||||
"axios": "^1.7.7",
|
"axios": "^1.7.9",
|
||||||
"dexie": "^4.0.8",
|
"dexie": "^4.0.11",
|
||||||
"phaser": "^3.86.0",
|
"phaser": "^3.88.2",
|
||||||
"pinia": "^2.1.6",
|
"phavuer": "^0.16.5",
|
||||||
"socket.io-client": "^4.8.0",
|
"phaser3-rex-plugins": "^1.80.13",
|
||||||
|
"pinia": "^2.3.1",
|
||||||
|
"sharp": "^0.33.5",
|
||||||
|
"socket.io-client": "^4.8.1",
|
||||||
"universal-cookie": "^6.1.3",
|
"universal-cookie": "^6.1.3",
|
||||||
"vue": "^3.5.12",
|
"vite-plugin-image-optimizer": "^1.1.8",
|
||||||
"zod": "^3.22.2"
|
"vue": "^3.5.13",
|
||||||
|
"zod": "^3.24.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
|
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
|
||||||
@ -36,8 +40,6 @@
|
|||||||
"autoprefixer": "^10.4.19",
|
"autoprefixer": "^10.4.19",
|
||||||
"jsdom": "^24.1.1",
|
"jsdom": "^24.1.1",
|
||||||
"npm-run-all2": "^6.2.3",
|
"npm-run-all2": "^6.2.3",
|
||||||
"phaser3-rex-plugins": "^1.80.8",
|
|
||||||
"phavuer": "^0.16.1",
|
|
||||||
"postcss": "^8.4.47",
|
"postcss": "^8.4.47",
|
||||||
"prettier": "^3.3.3",
|
"prettier": "^3.3.3",
|
||||||
"sass": "^1.79.4",
|
"sass": "^1.79.4",
|
||||||
|
Binary file not shown.
@ -1,7 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<Debug />
|
<Debug />
|
||||||
<Notifications />
|
<Notifications />
|
||||||
<BackgroundImageLoader />
|
|
||||||
<GmPanel v-if="gameStore.character?.role === 'gm'" />
|
<GmPanel v-if="gameStore.character?.role === 'gm'" />
|
||||||
<component :is="currentScreen" />
|
<component :is="currentScreen" />
|
||||||
</template>
|
</template>
|
||||||
@ -13,11 +12,11 @@ import Game from '@/components/screens/Game.vue'
|
|||||||
import Loading from '@/components/screens/Loading.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 Debug from '@/components/utilities/Debug.vue'
|
import Debug from '@/components/utilities/Debug.vue'
|
||||||
import Notifications from '@/components/utilities/Notifications.vue'
|
import Notifications from '@/components/utilities/Notifications.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { useSoundComposable } from '@/composables/useSoundComposable'
|
import { useSoundComposable } from '@/composables/useSoundComposable'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { computed, watch } from 'vue'
|
import { computed, watch } from 'vue'
|
||||||
|
|
||||||
@ -28,8 +27,8 @@ const { playSound } = useSoundComposable()
|
|||||||
|
|
||||||
const currentScreen = computed(() => {
|
const currentScreen = computed(() => {
|
||||||
if (!gameStore.game.isLoaded) return Loading
|
if (!gameStore.game.isLoaded) return Loading
|
||||||
if (!gameStore.connection) return Login
|
if (!socketManager.connection) return Login
|
||||||
if (!gameStore.token) return Login
|
if (!socketManager.token) return Login
|
||||||
if (!gameStore.character) return Characters
|
if (!gameStore.character) return Characters
|
||||||
if (mapEditor.active.value) return MapEditor
|
if (mapEditor.active.value) return MapEditor
|
||||||
return Game
|
return Game
|
||||||
|
@ -3,3 +3,61 @@ export enum Direction {
|
|||||||
NEGATIVE,
|
NEGATIVE,
|
||||||
UNCHANGED
|
UNCHANGED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export enum SocketEvent {
|
||||||
|
CONNECT_ERROR = 'connect_error',
|
||||||
|
RECONNECT_FAILED = 'reconnect_failed',
|
||||||
|
CLOSE = '52',
|
||||||
|
DATA = '51',
|
||||||
|
CHARACTER_CONNECT = '50',
|
||||||
|
CHARACTER_CREATE = '49',
|
||||||
|
CHARACTER_DELETE = '48',
|
||||||
|
CHARACTER_LIST = '47',
|
||||||
|
GM_CHARACTERHAIR_CREATE = '46',
|
||||||
|
GM_CHARACTERHAIR_REMOVE = '45',
|
||||||
|
GM_CHARACTERHAIR_LIST = '44',
|
||||||
|
GM_CHARACTERHAIR_UPDATE = '43',
|
||||||
|
GM_CHARACTERTYPE_CREATE = '42',
|
||||||
|
GM_CHARACTERTYPE_REMOVE = '41',
|
||||||
|
GM_CHARACTERTYPE_LIST = '40',
|
||||||
|
GM_CHARACTERTYPE_UPDATE = '39',
|
||||||
|
GM_ITEM_CREATE = '38',
|
||||||
|
GM_ITEM_REMOVE = '37',
|
||||||
|
GM_ITEM_LIST = '36',
|
||||||
|
GM_ITEM_UPDATE = '35',
|
||||||
|
GM_MAPOBJECT_LIST = '34',
|
||||||
|
GM_MAPOBJECT_REMOVE = '33',
|
||||||
|
GM_MAPOBJECT_UPDATE = '32',
|
||||||
|
GM_MAPOBJECT_UPLOAD = '31',
|
||||||
|
GM_SPRITE_COPY = '30',
|
||||||
|
GM_SPRITE_CREATE = '29',
|
||||||
|
GM_SPRITE_DELETE = '28',
|
||||||
|
GM_SPRITE_LIST = '27',
|
||||||
|
GM_SPRITE_UPDATE = '26',
|
||||||
|
GM_TILE_DELETE = '25',
|
||||||
|
GM_TILE_LIST = '24',
|
||||||
|
GM_TILE_UPDATE = '23',
|
||||||
|
GM_TILE_UPLOAD = '22',
|
||||||
|
GM_MAP_CREATE = '21',
|
||||||
|
GM_MAP_DELETE = '20',
|
||||||
|
GM_MAP_REQUEST = '19',
|
||||||
|
GM_MAP_UPDATE = '18',
|
||||||
|
MAP_CHARACTER_MOVEERROR = '17',
|
||||||
|
DISCONNECT = 'disconnect',
|
||||||
|
USER_DISCONNECT = '15',
|
||||||
|
LOGIN = '14',
|
||||||
|
LOGGED_IN = '13',
|
||||||
|
NOTIFICATION = '12',
|
||||||
|
DATE = '11',
|
||||||
|
FAILED = '10',
|
||||||
|
COMPLETED = '9',
|
||||||
|
CONNECTION = 'connection',
|
||||||
|
WEATHER = '7',
|
||||||
|
CHARACTER_DISCONNECT = '6',
|
||||||
|
MAP_CHARACTER_ATTACK = '5',
|
||||||
|
MAP_CHARACTER_TELEPORT = '4',
|
||||||
|
MAP_CHARACTER_JOIN = '3',
|
||||||
|
MAP_CHARACTER_LEAVE = '2',
|
||||||
|
MAP_CHARACTER_MOVE = '1',
|
||||||
|
CHAT_MESSAGE = '0'
|
||||||
|
}
|
||||||
|
@ -36,7 +36,8 @@ export type Tile = {
|
|||||||
export type MapObject = {
|
export type MapObject = {
|
||||||
id: string
|
id: string
|
||||||
name: string
|
name: string
|
||||||
tags: any | null
|
tags: string[]
|
||||||
|
pivotPoints: { x: number; y: number }[]
|
||||||
originX: number
|
originX: number
|
||||||
originY: number
|
originY: number
|
||||||
frameRate: number
|
frameRate: number
|
||||||
@ -100,7 +101,7 @@ export enum MapEventTileType {
|
|||||||
|
|
||||||
export type MapEventTile = {
|
export type MapEventTile = {
|
||||||
id: string
|
id: string
|
||||||
mapid: string
|
map: string
|
||||||
type: MapEventTileType
|
type: MapEventTileType
|
||||||
positionX: number
|
positionX: number
|
||||||
positionY: number
|
positionY: number
|
||||||
|
@ -21,7 +21,17 @@ export async function downloadCache<T extends { id: string; updatedAt: Date }>(e
|
|||||||
}
|
}
|
||||||
|
|
||||||
const items = response.data ?? []
|
const items = response.data ?? []
|
||||||
|
const serverItemIds = new Set(items.map((item) => item.id))
|
||||||
|
|
||||||
|
// Remove items that don't exist on server
|
||||||
|
const existingItems = await storage.getAll()
|
||||||
|
for (const existingItem of existingItems) {
|
||||||
|
if (!serverItemIds.has(existingItem.id)) {
|
||||||
|
await storage.delete(existingItem.id)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update or add new items
|
||||||
for (const item of items) {
|
for (const item of items) {
|
||||||
let overwrite = false
|
let overwrite = false
|
||||||
const existingItem = await storage.getById(item.id)
|
const existingItem = await storage.getById(item.id)
|
||||||
|
@ -28,7 +28,7 @@ const gameStore = useGameStore()
|
|||||||
const mapStore = useMapStore()
|
const mapStore = useMapStore()
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
|
|
||||||
const { characterContainer, characterSprite, currentPositionX, currentPositionY, isometricDepth, isFlippedX, updatePosition, playAnimation, calcDirection, updateSprite, initializeSprite, cleanup } = useCharacterSpriteComposable(scene, props.tileMap, props.mapCharacter)
|
const { characterContainer, characterSprite, currentPositionX, currentPositionY, isometricDepth, isFlippedX, updatePosition, playAnimation, updateSprite, initializeSprite, cleanup } = useCharacterSpriteComposable(scene, props.tileMap, props.mapCharacter)
|
||||||
const { playSound, stopSound } = useSoundComposable()
|
const { playSound, stopSound } = useSoundComposable()
|
||||||
|
|
||||||
const handlePositionUpdate = (newValues: any, oldValues: any) => {
|
const handlePositionUpdate = (newValues: any, oldValues: any) => {
|
||||||
@ -92,7 +92,6 @@ watch(
|
|||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await initializeSprite()
|
await initializeSprite()
|
||||||
if (props.mapCharacter.character.id === gameStore.character!.id) {
|
if (props.mapCharacter.character.id === gameStore.character!.id) {
|
||||||
mapStore.setCharacterLoaded(true)
|
|
||||||
scene.cameras.main.startFollow(characterContainer.value as Phaser.GameObjects.Container)
|
scene.cameras.main.startFollow(characterContainer.value as Phaser.GameObjects.Container)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -1,51 +0,0 @@
|
|||||||
<template>
|
|
||||||
<Image v-bind="imageProps" v-if="gameStore.getLoadedAsset(texture)" />
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { MapCharacter, Sprite as SpriteT } from '@/application/types'
|
|
||||||
import { loadSpriteTextures } from '@/services/textureService'
|
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
|
||||||
import { Image, useScene } from 'phavuer'
|
|
||||||
import { computed } from 'vue'
|
|
||||||
|
|
||||||
const props = defineProps<{
|
|
||||||
mapCharacter: MapCharacter
|
|
||||||
currentX: number
|
|
||||||
currentY: number
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
|
||||||
const scene = useScene()
|
|
||||||
|
|
||||||
const texture = computed(() => {
|
|
||||||
const { rotation, characterHair } = props.mapCharacter.character
|
|
||||||
const spriteId = characterHair?.sprite?.id
|
|
||||||
const direction = [0, 6].includes(rotation) ? 'back' : 'front'
|
|
||||||
|
|
||||||
return `${spriteId}-${direction}`
|
|
||||||
})
|
|
||||||
|
|
||||||
const isFlippedX = computed(() => [6, 4].includes(props.mapCharacter.character.rotation ?? 0))
|
|
||||||
|
|
||||||
const imageProps = computed(() => {
|
|
||||||
// Get the current sprite action based on direction
|
|
||||||
const direction = [0, 6].includes(props.mapCharacter.character.rotation ?? 0) ? 'back' : 'front'
|
|
||||||
const spriteAction = props.mapCharacter.character.characterHair?.sprite?.spriteActions?.find((spriteAction) => spriteAction.action === direction)
|
|
||||||
|
|
||||||
return {
|
|
||||||
depth: 1,
|
|
||||||
originX: Number(spriteAction?.originX) ?? 0,
|
|
||||||
originY: Number(spriteAction?.originY) ?? 0,
|
|
||||||
flipX: isFlippedX.value,
|
|
||||||
texture: texture.value
|
|
||||||
// y: props.mapCharacter.isMoving ? Math.floor(Date.now() / 250) % 2 : 0
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
loadSpriteTextures(scene, props.mapCharacter.character.characterHair?.sprite as SpriteT)
|
|
||||||
.then(() => {})
|
|
||||||
.catch((error) => {
|
|
||||||
console.error('Error loading texture:', error)
|
|
||||||
})
|
|
||||||
</script>
|
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Container ref="characterChatContainer" :depth="999">
|
<Container ref="characterChatContainer">
|
||||||
<RoundRectangle @create="createChatBubble" :origin-x="0.5" :origin-y="7.5" :fillColor="0xffffff" :width="194" :height="21" :radius="20" />
|
<RoundRectangle @create="createChatBubble" :origin-x="0.5" :origin-y="7.5" :fillColor="0xffffff" :width="194" :height="21" :radius="20" />
|
||||||
<Text @create="createChatText" :style="{ fontSize: 13, fontFamily: 'Arial', color: '#000' }" />
|
<Text @create="createChatText" :style="{ fontSize: 13, fontFamily: 'Arial', color: '#000' }" />
|
||||||
</Container>
|
</Container>
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
<div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col absolute left-1/2 -translate-x-1/2 bottom-5">
|
<div class="w-full md:min-w-[350px] max-w-[750px] flex flex-col absolute left-1/2 -translate-x-1/2 bottom-5">
|
||||||
<div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray rounded-md border-2 border-solid border-gray-500 text-gray-300" v-show="gameStore.uiSettings.isChatOpen">
|
<div ref="chatWindow" class="w-full overflow-auto h-32 mb-5 bg-gray rounded-md border-2 border-solid border-gray-500 text-gray-300" v-show="gameStore.uiSettings.isChatOpen">
|
||||||
<div v-for="message in chats" class="flex-col py-2 items-center p-3">
|
<div v-for="message in chats" class="flex-col py-2 items-center p-3">
|
||||||
<span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm" :class="{ 'text-cyan-300': gameStore.character?.role == 'gm' }">{{ message.character.name }}</span>
|
<span class="text-ellipsis overflow-hidden whitespace-nowrap text-sm" :class="{ 'text-cyan-300': gameStore.character?.role == 'gm' }">{{ message.character }}</span>
|
||||||
<p class="text-gray-50 m-0">{{ message.message }}</p>
|
<p class="text-gray-50 m-0">{{ message.message }}</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -21,7 +21,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Chat } from '@/application/types'
|
import { SocketEvent } from '@/application/enums'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useMapStore } from '@/stores/mapStore'
|
import { useMapStore } from '@/stores/mapStore'
|
||||||
import { onClickOutside, useFocus } from '@vueuse/core'
|
import { onClickOutside, useFocus } from '@vueuse/core'
|
||||||
@ -30,10 +31,9 @@ import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
|||||||
|
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const mapStore = useMapStore()
|
|
||||||
|
|
||||||
const message = ref('')
|
const message = ref('')
|
||||||
const chats = ref([] as Chat[])
|
const chats = ref<{ character: string; message: string }[]>([])
|
||||||
const chatWindow = ref<HTMLElement | null>(null)
|
const chatWindow = ref<HTMLElement | null>(null)
|
||||||
const chatInput = ref<HTMLElement | null>(null)
|
const chatInput = ref<HTMLElement | null>(null)
|
||||||
|
|
||||||
@ -55,7 +55,7 @@ function unfocusChat(event: Event, targetElement: HTMLElement) {
|
|||||||
|
|
||||||
const sendMessage = () => {
|
const sendMessage = () => {
|
||||||
if (!message.value.trim()) return
|
if (!message.value.trim()) return
|
||||||
gameStore.connection?.emit('chat:message', { message: message.value }, (response: boolean) => {})
|
socketManager.emit(SocketEvent.CHAT_MESSAGE, { message: message.value }, (response: boolean) => {})
|
||||||
message.value = ''
|
message.value = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,21 +79,30 @@ const scrollToBottom = () => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.on('chat:message', (data: Chat) => {
|
socketManager.on(SocketEvent.CHAT_MESSAGE, (data: { character: string; message: string }) => {
|
||||||
chats.value.push(data)
|
if (!data.character || !data.message) return
|
||||||
|
|
||||||
|
chats.value.push({ character: data.character, message: data.message })
|
||||||
scrollToBottom()
|
scrollToBottom()
|
||||||
|
|
||||||
if (!mapStore.characterLoaded) return
|
const characterContainer = scene.children.getByName(data.character) as Phaser.GameObjects.Container
|
||||||
|
if (!characterContainer) {
|
||||||
|
console.log('No character container found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const characterContainer = scene.children.getByName(data.character.name) as Phaser.GameObjects.Container
|
const characterChatContainer = characterContainer.getByName(data.character + '_chatContainer') as Phaser.GameObjects.Container
|
||||||
if (!characterContainer) return
|
if (!characterChatContainer) {
|
||||||
|
console.log('No character chat container found')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
const characterChatContainer = characterContainer.getByName(data.character.name + '_chatContainer') as Phaser.GameObjects.Container
|
const chatBubble = characterChatContainer.getByName(data.character + '_chatBubble') as Phaser.GameObjects.Container
|
||||||
if (!characterChatContainer) return
|
const chatText = characterChatContainer.getByName(data.character + '_chatText') as Phaser.GameObjects.Text
|
||||||
|
if (!chatText || !chatBubble) {
|
||||||
const chatBubble = characterChatContainer.getByName(data.character.name + '_chatBubble') as Phaser.GameObjects.Container
|
console.log('No chat text or bubble found')
|
||||||
const chatText = characterChatContainer.getByName(data.character.name + '_chatText') as Phaser.GameObjects.Text
|
return
|
||||||
if (!chatText || !chatBubble) return
|
}
|
||||||
|
|
||||||
function calculateTextWidth(text: string, font: string, fontSize: number): number {
|
function calculateTextWidth(text: string, font: string, fontSize: number): number {
|
||||||
// Create a canvas element
|
// Create a canvas element
|
||||||
@ -144,7 +153,7 @@ onMounted(() => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
gameStore.connection?.off('chat:message')
|
socketManager.off(SocketEvent.CHAT_MESSAGE)
|
||||||
removeEventListener('keydown', focusChat)
|
removeEventListener('keydown', focusChat)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute top-0 right-4 hidden lg:block">
|
<div class="absolute top-0 right-4 hidden lg:block" v-if="gameStore.world.date && typeof gameStore.world.date === 'object'">
|
||||||
<p class="text-white text-lg">{{ gameStore.world.date.toLocaleString() }}</p>
|
<p class="text-white text-lg">
|
||||||
|
{{ useDateFormat(gameStore.world.date, 'YYYY/MM/DD HH:mm') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
|
import { useDateFormat } from '@vueuse/core'
|
||||||
import { onUnmounted } from 'vue'
|
import { onUnmounted } from 'vue'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
|
||||||
// Listen for new date from socket
|
|
||||||
gameStore.connection?.on('date', (data: Date) => {
|
|
||||||
gameStore.world.date = new Date(data)
|
|
||||||
})
|
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
gameStore.connection?.off('date')
|
socketManager.off(SocketEvent.DATE)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { MapCharacter, UUID } from '@/application/types'
|
import type { MapCharacter, UUID } from '@/application/types'
|
||||||
import Character from '@/components/game/character/Character.vue'
|
import Character from '@/components/game/character/Character.vue'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useMapStore } from '@/stores/mapStore'
|
import { useMapStore } from '@/stores/mapStore'
|
||||||
import { onUnmounted } from 'vue'
|
import { onUnmounted } from 'vue'
|
||||||
@ -16,31 +18,32 @@ const props = defineProps<{
|
|||||||
tileMap: Phaser.Tilemaps.Tilemap
|
tileMap: Phaser.Tilemaps.Tilemap
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
gameStore.connection?.on('map:character:join', async (data: MapCharacter) => {
|
socketManager.on(SocketEvent.MAP_CHARACTER_JOIN, (data: MapCharacter) => {
|
||||||
mapStore.addCharacter(data)
|
mapStore.addCharacter(data)
|
||||||
})
|
})
|
||||||
|
|
||||||
gameStore.connection?.on('map:character:leave', (characterId: UUID) => {
|
socketManager.on(SocketEvent.MAP_CHARACTER_LEAVE, (characterId: UUID) => {
|
||||||
mapStore.removeCharacter(characterId)
|
mapStore.removeCharacter(characterId)
|
||||||
})
|
})
|
||||||
|
|
||||||
gameStore.connection?.on('map:character:move', (data: { characterId: UUID; positionX: number; positionY: number; rotation: number; isMoving: boolean }) => {
|
socketManager.on(SocketEvent.MAP_CHARACTER_MOVE, ([characterId, posX, posY, rot, isMoving]: [UUID, number, number, number, boolean]) => {
|
||||||
mapStore.updateCharacterPosition(data)
|
mapStore.updateCharacterPosition([characterId, posX, posY, rot, isMoving])
|
||||||
// @TODO: Replace with universal class, composable or store
|
|
||||||
if (data.characterId === gameStore.character?.id) {
|
if (characterId === gameStore.character?.id) {
|
||||||
gameStore.character!.positionX = data.positionX
|
gameStore.character!.positionX = posX
|
||||||
gameStore.character!.positionY = data.positionY
|
gameStore.character!.positionY = posY
|
||||||
gameStore.character!.rotation = data.rotation
|
gameStore.character!.rotation = rot
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
gameStore.connection?.on('map:character:attack', (characterId: UUID) => {
|
socketManager.on(SocketEvent.MAP_CHARACTER_ATTACK, (characterId: UUID) => {
|
||||||
mapStore.updateCharacterProperty(characterId, 'isAttacking', true)
|
mapStore.updateCharacterProperty(characterId, 'isAttacking', true)
|
||||||
})
|
})
|
||||||
|
|
||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
gameStore.connection?.off('map:character:join')
|
socketManager.off(SocketEvent.MAP_CHARACTER_ATTACK)
|
||||||
gameStore.connection?.off('map:character:leave')
|
socketManager.off(SocketEvent.MAP_CHARACTER_MOVE)
|
||||||
gameStore.connection?.off('map:character:move')
|
socketManager.off(SocketEvent.MAP_CHARACTER_JOIN)
|
||||||
|
socketManager.off(SocketEvent.MAP_CHARACTER_LEAVE)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -5,11 +5,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { mapLoadData } from '@/application/types'
|
import type { mapLoadData } from '@/application/types'
|
||||||
import { unduplicateArray } from '@/application/utilities'
|
import { unduplicateArray } from '@/application/utilities'
|
||||||
import Characters from '@/components/game/map/Characters.vue'
|
import Characters from '@/components/game/map/Characters.vue'
|
||||||
import MapTiles from '@/components/game/map/MapTiles.vue'
|
import MapTiles from '@/components/game/map/MapTiles.vue'
|
||||||
import PlacedMapObjects from '@/components/game/map/PlacedMapObjects.vue'
|
import PlacedMapObjects from '@/components/game/map/PlacedMapObjects.vue'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { createTileLayer, createTileMap, loadTileTexturesFromMapTileArray } from '@/services/mapService'
|
import { createTileLayer, createTileMap, loadTileTexturesFromMapTileArray } from '@/services/mapService'
|
||||||
import { MapStorage } from '@/storage/storages'
|
import { MapStorage } from '@/storage/storages'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
@ -28,7 +30,7 @@ const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
|||||||
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
||||||
|
|
||||||
// Event listeners
|
// Event listeners
|
||||||
gameStore.connection?.on('map:character:teleport', async (data: mapLoadData) => {
|
socketManager.on(SocketEvent.MAP_CHARACTER_TELEPORT, (data: mapLoadData) => {
|
||||||
mapStore.setMapId(data.mapId)
|
mapStore.setMapId(data.mapId)
|
||||||
mapStore.setCharacters(data.characters)
|
mapStore.setCharacters(data.characters)
|
||||||
})
|
})
|
||||||
@ -64,6 +66,6 @@ onUnmounted(() => {
|
|||||||
tileMap.value.destroy()
|
tileMap.value.destroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.off('map:character:teleport')
|
socketManager.off(SocketEvent.MAP_CHARACTER_TELEPORT)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -59,9 +59,9 @@ function calculateObjectPlacement(mapObj: PlacedMapObject): { x: number; y: numb
|
|||||||
}
|
}
|
||||||
|
|
||||||
const imageProps = computed(() => ({
|
const imageProps = computed(() => ({
|
||||||
alpha: mapEditor.movingPlacedObject.value?.id == props.placedMapObject.id ? 0.5 : 1,
|
alpha: mapEditor.movingPlacedObject.value?.id == props.placedMapObject.id || mapEditor.selectedMapObject.value?.id == props.placedMapObject.id ? 0.5 : 1,
|
||||||
tint: mapEditor.selectedPlacedObject.value?.id == props.placedMapObject.id ? 0x00ff00 : 0xffffff,
|
tint: mapEditor.selectedPlacedObject.value?.id == props.placedMapObject.id ? 0x00ff00 : 0xffffff,
|
||||||
depth: calculateIsometricDepth(props.placedMapObject.positionX, props.placedMapObject.positionY, mapObject.value!.frameWidth, mapObject.value!.frameHeight),
|
depth: calculateIsometricDepth(props.placedMapObject.positionX, props.placedMapObject.positionY),
|
||||||
...calculateObjectPlacement(props.placedMapObject),
|
...calculateObjectPlacement(props.placedMapObject),
|
||||||
flipX: props.placedMapObject.isRotated,
|
flipX: props.placedMapObject.isRotated,
|
||||||
texture: mapObject.value!.id,
|
texture: mapObject.value!.id,
|
||||||
|
@ -20,9 +20,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import AssetManager from '@/components/gameMaster/assetManager/AssetManager.vue'
|
import AssetManager from '@/components/gameMaster/assetManager/AssetManager.vue'
|
||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
|
||||||
|
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
|
@ -34,7 +34,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { CharacterGender, CharacterHair, Sprite } from '@/application/types'
|
import type { CharacterGender, CharacterHair, Sprite } from '@/application/types'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
@ -65,7 +67,7 @@ if (selectedCharacterHair.value) {
|
|||||||
function removeCharacterHair() {
|
function removeCharacterHair() {
|
||||||
if (!selectedCharacterHair.value) return
|
if (!selectedCharacterHair.value) return
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:characterHair:remove', { id: selectedCharacterHair.value.id }, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_REMOVE, { id: selectedCharacterHair.value.id }, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to remove character hair')
|
console.error('Failed to remove character hair')
|
||||||
return
|
return
|
||||||
@ -75,7 +77,7 @@ function removeCharacterHair() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshCharacterHairList(unsetSelectedCharacterHair = true) {
|
function refreshCharacterHairList(unsetSelectedCharacterHair = true) {
|
||||||
gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||||
assetManagerStore.setCharacterHairList(response)
|
assetManagerStore.setCharacterHairList(response)
|
||||||
|
|
||||||
if (unsetSelectedCharacterHair) {
|
if (unsetSelectedCharacterHair) {
|
||||||
@ -93,7 +95,7 @@ function saveCharacterHair() {
|
|||||||
spriteId: characterSpriteId.value
|
spriteId: characterSpriteId.value
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:characterHair:update', characterHairData, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_UPDATE, characterHairData, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to save character type')
|
console.error('Failed to save character type')
|
||||||
return
|
return
|
||||||
@ -113,7 +115,7 @@ watch(selectedCharacterHair, (characterHair: CharacterHair | null) => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!selectedCharacterHair.value) return
|
if (!selectedCharacterHair.value) return
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||||
assetManagerStore.setSpriteList(response)
|
assetManagerStore.setSpriteList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -32,7 +32,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { CharacterHair } from '@/application/types'
|
import type { CharacterHair } from '@/application/types'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useVirtualList } from '@vueuse/core'
|
import { useVirtualList } from '@vueuse/core'
|
||||||
@ -52,13 +54,13 @@ const handleSearch = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createNewCharacterHair = () => {
|
const createNewCharacterHair = () => {
|
||||||
gameStore.connection?.emit('gm:characterHair:create', {}, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_CREATE, {}, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to create new character type')
|
console.error('Failed to create new character type')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||||
assetManagerStore.setCharacterHairList(response)
|
assetManagerStore.setCharacterHairList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -92,7 +94,7 @@ function toTop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||||
assetManagerStore.setCharacterHairList(response)
|
assetManagerStore.setCharacterHairList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -40,7 +40,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { CharacterGender, CharacterRace, CharacterType, Sprite } from '@/application/types'
|
import type { CharacterGender, CharacterRace, CharacterType, Sprite } from '@/application/types'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
@ -74,7 +76,7 @@ if (selectedCharacterType.value) {
|
|||||||
function removeCharacterType() {
|
function removeCharacterType() {
|
||||||
if (!selectedCharacterType.value) return
|
if (!selectedCharacterType.value) return
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:characterType:remove', { id: selectedCharacterType.value.id }, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_REMOVE, { id: selectedCharacterType.value.id }, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to remove character type')
|
console.error('Failed to remove character type')
|
||||||
return
|
return
|
||||||
@ -84,7 +86,7 @@ function removeCharacterType() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshCharacterTypeList(unsetSelectedCharacterType = true) {
|
function refreshCharacterTypeList(unsetSelectedCharacterType = true) {
|
||||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||||
assetManagerStore.setCharacterTypeList(response)
|
assetManagerStore.setCharacterTypeList(response)
|
||||||
|
|
||||||
if (unsetSelectedCharacterType) {
|
if (unsetSelectedCharacterType) {
|
||||||
@ -103,7 +105,7 @@ function saveCharacterType() {
|
|||||||
spriteId: characterSpriteId.value
|
spriteId: characterSpriteId.value
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:characterType:update', characterTypeData, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_UPDATE, characterTypeData, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to save character type')
|
console.error('Failed to save character type')
|
||||||
return
|
return
|
||||||
@ -124,7 +126,7 @@ watch(selectedCharacterType, (characterType: CharacterType | null) => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!selectedCharacterType.value) return
|
if (!selectedCharacterType.value) return
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||||
assetManagerStore.setSpriteList(response)
|
assetManagerStore.setSpriteList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -32,7 +32,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { CharacterType } from '@/application/types'
|
import type { CharacterType } from '@/application/types'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useVirtualList } from '@vueuse/core'
|
import { useVirtualList } from '@vueuse/core'
|
||||||
@ -52,13 +54,13 @@ const handleSearch = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createNewCharacterType = () => {
|
const createNewCharacterType = () => {
|
||||||
gameStore.connection?.emit('gm:characterType:create', {}, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_CREATE, {}, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to create new character type')
|
console.error('Failed to create new character type')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||||
assetManagerStore.setCharacterTypeList(response)
|
assetManagerStore.setCharacterTypeList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -92,7 +94,7 @@ function toTop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||||
assetManagerStore.setCharacterTypeList(response)
|
assetManagerStore.setCharacterTypeList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -44,7 +44,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { Item, ItemRarity, ItemType, Sprite } from '@/application/types'
|
import type { Item, ItemRarity, ItemType, Sprite } from '@/application/types'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
@ -80,7 +82,7 @@ if (selectedItem.value) {
|
|||||||
function removeItem() {
|
function removeItem() {
|
||||||
if (!selectedItem.value) return
|
if (!selectedItem.value) return
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:item:remove', { id: selectedItem.value.id }, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_ITEM_REMOVE, { id: selectedItem.value.id }, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to remove item')
|
console.error('Failed to remove item')
|
||||||
return
|
return
|
||||||
@ -90,7 +92,7 @@ function removeItem() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshItemList(unsetSelectedItem = true) {
|
function refreshItemList(unsetSelectedItem = true) {
|
||||||
gameStore.connection?.emit('gm:item:list', {}, (response: Item[]) => {
|
socketManager.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||||
assetManagerStore.setItemList(response)
|
assetManagerStore.setItemList(response)
|
||||||
|
|
||||||
if (unsetSelectedItem) {
|
if (unsetSelectedItem) {
|
||||||
@ -110,7 +112,7 @@ function saveItem() {
|
|||||||
spriteId: itemSpriteId.value
|
spriteId: itemSpriteId.value
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:item:update', itemData, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_ITEM_UPDATE, itemData, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to save item')
|
console.error('Failed to save item')
|
||||||
return
|
return
|
||||||
@ -132,7 +134,7 @@ watch(selectedItem, (item: Item | null) => {
|
|||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (!selectedItem.value) return
|
if (!selectedItem.value) return
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||||
assetManagerStore.setSpriteList(response)
|
assetManagerStore.setSpriteList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -29,7 +29,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { Item } from '@/application/types'
|
import type { Item } from '@/application/types'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useVirtualList } from '@vueuse/core'
|
import { useVirtualList } from '@vueuse/core'
|
||||||
@ -48,13 +50,13 @@ const handleSearch = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const createNewItem = () => {
|
const createNewItem = () => {
|
||||||
gameStore.connection?.emit('gm:item:create', {}, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_ITEM_CREATE, {}, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to create new item')
|
console.error('Failed to create new item')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:item:list', {}, (response: Item[]) => {
|
socketManager.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||||
assetManagerStore.setItemList(response)
|
assetManagerStore.setItemList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -88,7 +90,7 @@ function toTop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
gameStore.connection?.emit('gm:item:list', {}, (response: Item[]) => {
|
socketManager.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||||
assetManagerStore.setItemList(response)
|
assetManagerStore.setItemList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,7 +1,20 @@
|
|||||||
<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}/textures/map_objects/${selectedMapObject?.id}.png`" :alt="'Object ' + selectedMapObject?.id" />
|
<div class="relative">
|
||||||
|
<img class="max-h-56" :src="`${config.server_endpoint}/textures/map_objects/${selectedMapObject?.id}.png`" :alt="'Object ' + selectedMapObject?.id" @click="addPivotPoint" ref="imageRef" />
|
||||||
|
<svg class="absolute bottom-1 left-0 w-full h-full pointer-events-none">
|
||||||
|
<line v-for="(_, index) in mapObjectPivotPoints.slice(0, -1)" :key="index" :x1="mapObjectPivotPoints[index].x" :y1="mapObjectPivotPoints[index].y" :x2="mapObjectPivotPoints[index + 1].x" :y2="mapObjectPivotPoints[index + 1].y" stroke="white" stroke-width="2" />
|
||||||
|
</svg>
|
||||||
|
<div
|
||||||
|
v-for="(point, index) in mapObjectPivotPoints"
|
||||||
|
:key="index"
|
||||||
|
class="absolute w-2 h-2 bg-white rounded-full cursor-move -translate-x-1.5 -translate-y-1.5 ring-2 ring-black"
|
||||||
|
:style="{ left: point.x + 'px', top: point.y + 'px' }"
|
||||||
|
@mousedown="startDragging(index, $event)"
|
||||||
|
@contextmenu.prevent="removePivotPoint(index)"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</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">
|
||||||
@ -44,8 +57,10 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { MapObject } from '@/application/types'
|
import type { MapObject } from '@/application/types'
|
||||||
import ChipsInput from '@/components/forms/ChipsInput.vue'
|
import ChipsInput from '@/components/forms/ChipsInput.vue'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
@ -57,11 +72,15 @@ const selectedMapObject = computed(() => assetManagerStore.selectedMapObject)
|
|||||||
|
|
||||||
const mapObjectName = ref('')
|
const mapObjectName = ref('')
|
||||||
const mapObjectTags = ref<string[]>([])
|
const mapObjectTags = ref<string[]>([])
|
||||||
|
const mapObjectPivotPoints = ref<Array<{ x: number; y: number }>>([])
|
||||||
const mapObjectOriginX = ref(0)
|
const mapObjectOriginX = ref(0)
|
||||||
const mapObjectOriginY = ref(0)
|
const mapObjectOriginY = ref(0)
|
||||||
const mapObjectFrameRate = ref(0)
|
const mapObjectFrameRate = ref(0)
|
||||||
const mapObjectFrameWidth = ref(0)
|
const mapObjectFrameWidth = ref(0)
|
||||||
const mapObjectFrameHeight = ref(0)
|
const mapObjectFrameHeight = ref(0)
|
||||||
|
const imageRef = ref<HTMLImageElement | null>(null)
|
||||||
|
const isDragging = ref(false)
|
||||||
|
const draggedPointIndex = ref(-1)
|
||||||
|
|
||||||
if (!selectedMapObject.value) {
|
if (!selectedMapObject.value) {
|
||||||
console.error('No map mapObject selected')
|
console.error('No map mapObject selected')
|
||||||
@ -70,6 +89,7 @@ if (!selectedMapObject.value) {
|
|||||||
if (selectedMapObject.value) {
|
if (selectedMapObject.value) {
|
||||||
mapObjectName.value = selectedMapObject.value.name
|
mapObjectName.value = selectedMapObject.value.name
|
||||||
mapObjectTags.value = selectedMapObject.value.tags
|
mapObjectTags.value = selectedMapObject.value.tags
|
||||||
|
mapObjectPivotPoints.value = selectedMapObject.value.pivotPoints
|
||||||
mapObjectOriginX.value = selectedMapObject.value.originX
|
mapObjectOriginX.value = selectedMapObject.value.originX
|
||||||
mapObjectOriginY.value = selectedMapObject.value.originY
|
mapObjectOriginY.value = selectedMapObject.value.originY
|
||||||
mapObjectFrameRate.value = selectedMapObject.value.frameRate
|
mapObjectFrameRate.value = selectedMapObject.value.frameRate
|
||||||
@ -78,7 +98,7 @@ if (selectedMapObject.value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function removeObject() {
|
function removeObject() {
|
||||||
gameStore.connection?.emit('gm:mapObject:remove', { mapObject: selectedMapObject.value?.id }, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_MAPOBJECT_REMOVE, { mapObject: selectedMapObject.value?.id }, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to remove mapObject')
|
console.error('Failed to remove mapObject')
|
||||||
return
|
return
|
||||||
@ -88,7 +108,7 @@ function removeObject() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshObjectList(unsetSelectedMapObject = true) {
|
function refreshObjectList(unsetSelectedMapObject = true) {
|
||||||
gameStore.connection?.emit('gm:mapObject:list', {}, (response: MapObject[]) => {
|
socketManager.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||||
assetManagerStore.setMapObjectList(response)
|
assetManagerStore.setMapObjectList(response)
|
||||||
|
|
||||||
if (unsetSelectedMapObject) {
|
if (unsetSelectedMapObject) {
|
||||||
@ -103,12 +123,13 @@ function saveObject() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit(
|
socketManager.emit(
|
||||||
'gm:mapObject:update',
|
SocketEvent.GM_MAPOBJECT_UPDATE,
|
||||||
{
|
{
|
||||||
id: selectedMapObject.value.id,
|
id: selectedMapObject.value.id,
|
||||||
name: mapObjectName.value,
|
name: mapObjectName.value,
|
||||||
tags: mapObjectTags.value,
|
tags: mapObjectTags.value,
|
||||||
|
pivotPoints: mapObjectPivotPoints.value,
|
||||||
originX: mapObjectOriginX.value,
|
originX: mapObjectOriginX.value,
|
||||||
originY: mapObjectOriginY.value,
|
originY: mapObjectOriginY.value,
|
||||||
frameRate: mapObjectFrameRate.value,
|
frameRate: mapObjectFrameRate.value,
|
||||||
@ -129,6 +150,7 @@ watch(selectedMapObject, (mapObject: MapObject | null) => {
|
|||||||
if (!mapObject) return
|
if (!mapObject) return
|
||||||
mapObjectName.value = mapObject.name
|
mapObjectName.value = mapObject.name
|
||||||
mapObjectTags.value = mapObject.tags
|
mapObjectTags.value = mapObject.tags
|
||||||
|
mapObjectPivotPoints.value = mapObject.pivotPoints
|
||||||
mapObjectOriginX.value = mapObject.originX
|
mapObjectOriginX.value = mapObject.originX
|
||||||
mapObjectOriginY.value = mapObject.originY
|
mapObjectOriginY.value = mapObject.originY
|
||||||
mapObjectFrameRate.value = mapObject.frameRate
|
mapObjectFrameRate.value = mapObject.frameRate
|
||||||
@ -140,7 +162,51 @@ onMounted(() => {
|
|||||||
if (!selectedMapObject.value) return
|
if (!selectedMapObject.value) return
|
||||||
})
|
})
|
||||||
|
|
||||||
|
function addPivotPoint(event: MouseEvent) {
|
||||||
|
if (!imageRef.value) return
|
||||||
|
// Max 2
|
||||||
|
if (mapObjectPivotPoints.value.length >= 2) return
|
||||||
|
const rect = imageRef.value.getBoundingClientRect()
|
||||||
|
const x = event.clientX - rect.left
|
||||||
|
const y = event.clientY - rect.top
|
||||||
|
mapObjectPivotPoints.value.push({ x, y })
|
||||||
|
}
|
||||||
|
|
||||||
|
function startDragging(index: number, event: MouseEvent) {
|
||||||
|
isDragging.value = true
|
||||||
|
draggedPointIndex.value = index
|
||||||
|
|
||||||
|
const moveHandler = (e: MouseEvent) => {
|
||||||
|
if (!isDragging.value || !imageRef.value) return
|
||||||
|
const rect = imageRef.value.getBoundingClientRect()
|
||||||
|
mapObjectPivotPoints.value[draggedPointIndex.value] = {
|
||||||
|
x: e.clientX - rect.left,
|
||||||
|
y: e.clientY - rect.top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const upHandler = () => {
|
||||||
|
isDragging.value = false
|
||||||
|
draggedPointIndex.value = -1
|
||||||
|
window.removeEventListener('mousemove', moveHandler)
|
||||||
|
window.removeEventListener('mouseup', upHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('mousemove', moveHandler)
|
||||||
|
window.addEventListener('mouseup', upHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
function removePivotPoint(index: number) {
|
||||||
|
mapObjectPivotPoints.value.splice(index, 1)
|
||||||
|
}
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
assetManagerStore.setSelectedMapObject(null)
|
assetManagerStore.setSelectedMapObject(null)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.pointer-events-none {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@ -29,7 +29,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { MapObject } from '@/application/types'
|
import type { MapObject } from '@/application/types'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useVirtualList } from '@vueuse/core'
|
import { useVirtualList } from '@vueuse/core'
|
||||||
@ -47,13 +49,13 @@ const elementToScroll = ref()
|
|||||||
const handleFileUpload = (e: Event) => {
|
const handleFileUpload = (e: Event) => {
|
||||||
const files = (e.target as HTMLInputElement).files
|
const files = (e.target as HTMLInputElement).files
|
||||||
if (!files) return
|
if (!files) return
|
||||||
gameStore.connection?.emit('gm:mapObject:upload', files, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_MAPOBJECT_UPLOAD, files, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
if (config.environment === 'development') console.error('Failed to upload map object')
|
if (config.environment === 'development') console.error('Failed to upload map object')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:mapObject:list', {}, (response: MapObject[]) => {
|
socketManager.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||||
assetManagerStore.setMapObjectList(response)
|
assetManagerStore.setMapObjectList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -92,7 +94,7 @@ function toTop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
gameStore.connection?.emit('gm:mapObject:list', {}, (response: MapObject[]) => {
|
socketManager.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||||
assetManagerStore.setMapObjectList(response)
|
assetManagerStore.setMapObjectList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -68,11 +68,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { Sprite, SpriteAction, UUID } from '@/application/types'
|
import type { Sprite, SpriteAction, UUID } from '@/application/types'
|
||||||
import { uuidv4 } from '@/application/utilities'
|
import { uuidv4 } from '@/application/utilities'
|
||||||
import SpriteActionsInput from '@/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue'
|
import SpriteActionsInput from '@/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue'
|
||||||
import SpritePreview from '@/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue'
|
import SpritePreview from '@/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue'
|
||||||
import Accordion from '@/components/utilities/Accordion.vue'
|
import Accordion from '@/components/utilities/Accordion.vue'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
@ -97,7 +99,7 @@ if (selectedSprite.value) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function deleteSprite() {
|
function deleteSprite() {
|
||||||
gameStore.connection?.emit('gm:sprite:delete', { id: selectedSprite.value?.id }, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_SPRITE_DELETE, { id: selectedSprite.value?.id }, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to delete sprite')
|
console.error('Failed to delete sprite')
|
||||||
return
|
return
|
||||||
@ -107,7 +109,7 @@ function deleteSprite() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function copySprite() {
|
function copySprite() {
|
||||||
gameStore.connection?.emit('gm:sprite:copy', { id: selectedSprite.value?.id }, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_SPRITE_COPY, { id: selectedSprite.value?.id }, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to copy sprite')
|
console.error('Failed to copy sprite')
|
||||||
return
|
return
|
||||||
@ -117,7 +119,7 @@ function copySprite() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshSpriteList(unsetSelectedSprite = true) {
|
function refreshSpriteList(unsetSelectedSprite = true) {
|
||||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||||
assetManagerStore.setSpriteList(response)
|
assetManagerStore.setSpriteList(response)
|
||||||
|
|
||||||
if (unsetSelectedSprite) {
|
if (unsetSelectedSprite) {
|
||||||
@ -149,7 +151,7 @@ function saveSprite() {
|
|||||||
}) ?? []
|
}) ?? []
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:sprite:update', updatedSprite, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_SPRITE_UPDATE, updatedSprite, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to save sprite')
|
console.error('Failed to save sprite')
|
||||||
return
|
return
|
||||||
|
@ -25,7 +25,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { Sprite } from '@/application/types'
|
import type { Sprite } from '@/application/types'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useVirtualList } from '@vueuse/core'
|
import { useVirtualList } from '@vueuse/core'
|
||||||
@ -40,13 +42,13 @@ const hasScrolled = ref(false)
|
|||||||
const elementToScroll = ref()
|
const elementToScroll = ref()
|
||||||
|
|
||||||
function newButtonClickHandler() {
|
function newButtonClickHandler() {
|
||||||
gameStore.connection?.emit('gm:sprite:create', {}, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_SPRITE_CREATE, {}, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
if (config.environment === 'development') console.error('Failed to create new sprite')
|
if (config.environment === 'development') console.error('Failed to create new sprite')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||||
assetManagerStore.setSpriteList(response)
|
assetManagerStore.setSpriteList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -85,7 +87,7 @@ function toTop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||||
assetManagerStore.setSpriteList(response)
|
assetManagerStore.setSpriteList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -34,7 +34,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col justify-center gap-8 flex-1">
|
<div class="flex flex-col justify-center gap-8 flex-1">
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
<label class="block mb-2 text-white">Frame Rate: {{ frameRate }} FPS</label>
|
<label class="block mb-2 text-white">Frame Rate: {{ frameRate }} FPS (Duration: {{ totalDuration }}s)</label>
|
||||||
<input type="range" v-model.number="localFrameRate" min="0" max="60" step="1" class="w-full accent-cyan-500" @input="updateFrameRate" />
|
<input type="range" v-model.number="localFrameRate" min="0" max="60" step="1" class="w-full accent-cyan-500" @input="updateFrameRate" />
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-col">
|
<div class="flex flex-col">
|
||||||
@ -76,6 +76,11 @@ const localFrameRate = ref(props.frameRate)
|
|||||||
const zoomLevel = ref(100)
|
const zoomLevel = ref(100)
|
||||||
let animationInterval: number | null = null
|
let animationInterval: number | null = null
|
||||||
|
|
||||||
|
const totalDuration = computed(() => {
|
||||||
|
if (props.frameRate <= 0) return 0
|
||||||
|
return (props.sprites.length / props.frameRate).toFixed(2)
|
||||||
|
})
|
||||||
|
|
||||||
const spritesWithTempOffset = computed(() => {
|
const spritesWithTempOffset = computed(() => {
|
||||||
return props.sprites.map((sprite, index) => {
|
return props.sprites.map((sprite, index) => {
|
||||||
if (index === props.tempOffsetIndex && props.tempOffset) {
|
if (index === props.tempOffsetIndex && props.tempOffset) {
|
||||||
|
@ -24,8 +24,10 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { Tile } from '@/application/types'
|
import type { Tile } from '@/application/types'
|
||||||
import ChipsInput from '@/components/forms/ChipsInput.vue'
|
import ChipsInput from '@/components/forms/ChipsInput.vue'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { TileStorage } from '@/storage/storages'
|
import { TileStorage } from '@/storage/storages'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
@ -56,7 +58,7 @@ watch(selectedTile, (tile: Tile | null) => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
async function deleteTile() {
|
async function deleteTile() {
|
||||||
gameStore.connection?.emit('gm:tile:delete', { id: selectedTile.value?.id }, async (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_TILE_DELETE, { id: selectedTile.value?.id }, async (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
console.error('Failed to delete tile')
|
console.error('Failed to delete tile')
|
||||||
return
|
return
|
||||||
@ -67,7 +69,7 @@ async function deleteTile() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function refreshTileList(unsetSelectedTile = true) {
|
function refreshTileList(unsetSelectedTile = true) {
|
||||||
gameStore.connection?.emit('gm:tile:list', {}, (response: Tile[]) => {
|
socketManager.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||||
assetManagerStore.setTileList(response)
|
assetManagerStore.setTileList(response)
|
||||||
|
|
||||||
if (unsetSelectedTile) {
|
if (unsetSelectedTile) {
|
||||||
@ -82,7 +84,7 @@ function saveTile() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit(
|
socketManager.emit(
|
||||||
'gm:tile:update',
|
'gm:tile:update',
|
||||||
{
|
{
|
||||||
id: selectedTile.value.id,
|
id: selectedTile.value.id,
|
||||||
|
@ -29,7 +29,9 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { Tile } from '@/application/types'
|
import type { Tile } from '@/application/types'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useVirtualList } from '@vueuse/core'
|
import { useVirtualList } from '@vueuse/core'
|
||||||
@ -47,13 +49,13 @@ const elementToScroll = ref()
|
|||||||
const handleFileUpload = (e: Event) => {
|
const handleFileUpload = (e: Event) => {
|
||||||
const files = (e.target as HTMLInputElement).files
|
const files = (e.target as HTMLInputElement).files
|
||||||
if (!files) return
|
if (!files) return
|
||||||
gameStore.connection?.emit('gm:tile:upload', files, (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_TILE_UPLOAD, files, (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
if (config.environment === 'development') console.error('Failed to upload tile')
|
if (config.environment === 'development') console.error('Failed to upload tile')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:tile:list', {}, (response: Tile[]) => {
|
socketManager.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||||
assetManagerStore.setTileList(response)
|
assetManagerStore.setTileList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
@ -92,7 +94,7 @@ function toTop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
gameStore.connection?.emit('gm:tile:list', {}, (response: Tile[]) => {
|
socketManager.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||||
assetManagerStore.setTileList(response)
|
assetManagerStore.setTileList(response)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -1,30 +1,141 @@
|
|||||||
<template>
|
<template>
|
||||||
<MapTiles ref="mapTiles" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
<MapTiles ref="mapTiles" @createCommand="addCommand" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
||||||
<PlacedMapObjects ref="mapObjects" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
<PlacedMapObjects ref="mapObjects" @update="updateMapObjects" @updateAndCommit="updateAndCommit" @pauseObjectTracking="pause" @resumeObjectTracking="resume" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
||||||
<MapEventTiles ref="eventTiles" v-if="tileMap" :tileMap />
|
<MapEventTiles ref="eventTiles" @createCommand="addCommand" v-if="tileMap" :tileMap />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import type { MapEventTile, Map as MapT, PlacedMapObject as PlacedMapObjectT } from '@/application/types'
|
||||||
import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue'
|
import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue'
|
||||||
import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
|
import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
|
||||||
import PlacedMapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
|
import PlacedMapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { createTileLayer, createTileMap } from '@/services/mapService'
|
import { cloneArray, createTileArray, createTileLayer, createTileMap, placeTiles } from '@/services/mapService'
|
||||||
import { TileStorage } from '@/storage/storages'
|
import { TileStorage } from '@/storage/storages'
|
||||||
|
import { useManualRefHistory, useRefHistory } from '@vueuse/core'
|
||||||
import { useScene } from 'phavuer'
|
import { useScene } from 'phavuer'
|
||||||
import { onBeforeUnmount, onMounted, onUnmounted, shallowRef, useTemplateRef } from 'vue'
|
import { onBeforeUnmount, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue'
|
||||||
|
|
||||||
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
||||||
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
||||||
|
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
|
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
|
|
||||||
const mapTiles = useTemplateRef('mapTiles')
|
const mapTiles = useTemplateRef('mapTiles')
|
||||||
const mapObjects = useTemplateRef('mapObjects')
|
const mapObjects = useTemplateRef('mapObjects')
|
||||||
const eventTiles = useTemplateRef('eventTiles')
|
const eventTiles = useTemplateRef('eventTiles')
|
||||||
|
|
||||||
|
//Record of commands
|
||||||
|
let commandStack: (EditorCommand | number)[] = []
|
||||||
|
let commandIndex = ref(0)
|
||||||
|
|
||||||
|
let originTiles: string[][] = []
|
||||||
|
let originEventTiles: MapEventTile[] = []
|
||||||
|
let originObjects = ref<PlacedMapObjectT[]>(mapEditor.currentMap.value?.placedMapObjects ?? [])
|
||||||
|
|
||||||
|
const { undo, redo, commit, pause, resume, canUndo, canRedo } = useRefHistory(originObjects, { deep: true, capacity: 9 })
|
||||||
|
|
||||||
|
//Command Pattern basic interface, extended to store what elements have been changed by each edit
|
||||||
|
export interface EditorCommand {
|
||||||
|
apply: (elements: any[]) => any[]
|
||||||
|
type: 'tile' | 'map_object' | 'event_tile'
|
||||||
|
operation: 'draw' | 'erase' | 'clear'
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCommands(tiles: any[], ...commands: EditorCommand[]): any[] {
|
||||||
|
let tileVersion = cloneArray(tiles)
|
||||||
|
for (let command of commands) {
|
||||||
|
tileVersion = command.apply(tileVersion)
|
||||||
|
}
|
||||||
|
return tileVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(
|
||||||
|
() => mapEditor.shouldClearTiles.value,
|
||||||
|
(shouldClear) => {
|
||||||
|
if (shouldClear && mapEditor.currentMap.value) {
|
||||||
|
mapTiles.value!.clearTiles()
|
||||||
|
eventTiles.value!.clearTiles()
|
||||||
|
mapEditor.currentMap.value.placedMapObjects = []
|
||||||
|
updateAndCommit(mapEditor.currentMap.value)
|
||||||
|
mapEditor.resetClearTilesFlag()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
function update(commands: (EditorCommand | number)[]) {
|
||||||
|
if (!mapEditor.currentMap.value) return
|
||||||
|
|
||||||
|
if (commandStack.length >= 9) {
|
||||||
|
if (typeof commandStack[0] !== 'number') {
|
||||||
|
const base = commandStack.shift() as EditorCommand
|
||||||
|
if (base.operation !== 'clear') {
|
||||||
|
switch (base.type) {
|
||||||
|
case 'tile':
|
||||||
|
originTiles = base.apply(originTiles) as string[][]
|
||||||
|
break
|
||||||
|
case 'event_tile':
|
||||||
|
originEventTiles = base.apply(originEventTiles) as MapEventTile[]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
commandStack.shift()
|
||||||
|
}
|
||||||
|
} else if (typeof commandStack[0] === 'number') {
|
||||||
|
commandStack.shift()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let tileCommands = commands.filter((item) => typeof item !== 'number' && item.type === 'tile') as EditorCommand[]
|
||||||
|
let eventTileCommands = commands.filter((item) => typeof item !== 'number' && item.type === 'event_tile') as EditorCommand[]
|
||||||
|
|
||||||
|
let modifiedTiles = applyCommands(originTiles, ...tileCommands)
|
||||||
|
placeTiles(tileMap.value!, tileMapLayer.value!, modifiedTiles)
|
||||||
|
|
||||||
|
let eventTiles = applyCommands(originEventTiles, ...eventTileCommands)
|
||||||
|
|
||||||
|
mapEditor.currentMap.value.tiles = modifiedTiles
|
||||||
|
mapEditor.currentMap.value.mapEventTiles = eventTiles
|
||||||
|
mapEditor.currentMap.value.placedMapObjects = originObjects.value
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateMapObjects(map: MapT) {
|
||||||
|
originObjects.value = map.placedMapObjects
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateAndCommit(map?: MapT) {
|
||||||
|
commandStack = commandStack.slice(0, commandIndex.value)
|
||||||
|
if (map) updateMapObjects(map)
|
||||||
|
commit()
|
||||||
|
commandStack.push(0)
|
||||||
|
commandIndex.value = commandStack.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCommand(command: EditorCommand) {
|
||||||
|
commandStack = commandStack.slice(0, commandIndex.value)
|
||||||
|
commandStack.push(command)
|
||||||
|
commandIndex.value = commandStack.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function undoEdit() {
|
||||||
|
if (commandIndex.value > 0) {
|
||||||
|
if (typeof commandStack[--commandIndex.value] === 'number' && canUndo) {
|
||||||
|
undo()
|
||||||
|
}
|
||||||
|
update(commandStack.slice(0, commandIndex.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function redoEdit() {
|
||||||
|
if (commandIndex.value <= 9 && commandIndex.value < commandStack.length) {
|
||||||
|
if (typeof commandStack[commandIndex.value++] === 'number' && canRedo) {
|
||||||
|
redo()
|
||||||
|
}
|
||||||
|
update(commandStack.slice(0, commandIndex.value))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
||||||
if (!mapTiles.value || !mapObjects.value || !eventTiles.value) return
|
if (!mapTiles.value || !mapObjects.value || !eventTiles.value) return
|
||||||
|
|
||||||
@ -54,12 +165,12 @@ function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
|||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
//CTRL+Y
|
//CTRL+Y
|
||||||
if (event.key === 'y' && event.ctrlKey) {
|
if (event.key === 'y' && event.ctrlKey) {
|
||||||
mapTiles.value!.redo()
|
redoEdit()
|
||||||
}
|
}
|
||||||
|
|
||||||
//CTRL+Z
|
//CTRL+Z
|
||||||
if (event.key === 'z' && event.ctrlKey) {
|
if (event.key === 'z' && event.ctrlKey) {
|
||||||
mapTiles.value!.undo()
|
undoEdit()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,8 +181,22 @@ function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
||||||
if (mapEditor.drawMode.value === 'tile') {
|
switch (mapEditor.drawMode.value) {
|
||||||
mapTiles.value?.finalizeCommand()
|
case 'tile':
|
||||||
|
mapTiles.value!.finalizeCommand()
|
||||||
|
break
|
||||||
|
case 'map_object':
|
||||||
|
if (mapEditor.tool.value === 'pencil' || mapEditor.tool.value === 'eraser') {
|
||||||
|
resume()
|
||||||
|
updateAndCommit()
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'teleport':
|
||||||
|
eventTiles.value!.finalizeCommand()
|
||||||
|
break
|
||||||
|
case 'blocking tile':
|
||||||
|
eventTiles.value!.finalizeCommand()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +204,10 @@ onMounted(async () => {
|
|||||||
let mapValue = mapEditor.currentMap.value
|
let mapValue = mapEditor.currentMap.value
|
||||||
if (!mapValue) return
|
if (!mapValue) return
|
||||||
|
|
||||||
|
//Clone
|
||||||
|
originTiles = cloneArray(mapValue.tiles)
|
||||||
|
originEventTiles = cloneArray(mapValue.mapEventTiles)
|
||||||
|
|
||||||
const tileStorage = new TileStorage()
|
const tileStorage = new TileStorage()
|
||||||
const allTiles = await tileStorage.getAll()
|
const allTiles = await tileStorage.getAll()
|
||||||
const allTileIds = allTiles.map((tile) => tile.id)
|
const allTileIds = allTiles.map((tile) => tile.id)
|
||||||
|
@ -5,20 +5,69 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { MapEventTileType, type MapEventTile, type Map as MapT, type UUID } from '@/application/types'
|
import { MapEventTileType, type MapEventTile, type Map as MapT, type UUID } from '@/application/types'
|
||||||
import { uuidv4 } from '@/application/utilities'
|
import { uuidv4 } from '@/application/utilities'
|
||||||
|
import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { getTile, tileToWorldX, tileToWorldY } from '@/services/mapService'
|
import { cloneArray, getTile, tileToWorldX, tileToWorldY } from '@/services/mapService'
|
||||||
import { Image } from 'phavuer'
|
import { Image } from 'phavuer'
|
||||||
import { shallowRef } from 'vue'
|
|
||||||
|
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
|
|
||||||
defineExpose({ handlePointer })
|
defineExpose({ handlePointer, finalizeCommand, clearTiles })
|
||||||
|
|
||||||
|
const emit = defineEmits(['createCommand'])
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tileMap: Phaser.Tilemaps.Tilemap
|
tileMap: Phaser.Tilemaps.Tilemap
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tileLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
// *** COMMAND STATE ***
|
||||||
|
|
||||||
|
let currentCommand: EventTileCommand | null = null
|
||||||
|
|
||||||
|
class EventTileCommand implements EditorCommand {
|
||||||
|
public operation: 'draw' | 'erase' | 'clear' = 'draw'
|
||||||
|
public type: 'event_tile' = 'event_tile'
|
||||||
|
public affectedTiles: MapEventTile[] = []
|
||||||
|
|
||||||
|
apply(elements: MapEventTile[]) {
|
||||||
|
let tileVersion = cloneArray(elements) as MapEventTile[]
|
||||||
|
if (this.operation === 'draw') {
|
||||||
|
tileVersion = tileVersion.concat(this.affectedTiles)
|
||||||
|
} else if (this.operation === 'erase') {
|
||||||
|
tileVersion = tileVersion.filter((v) => !this.affectedTiles.includes(v))
|
||||||
|
} else if (this.operation === 'clear') {
|
||||||
|
tileVersion = []
|
||||||
|
}
|
||||||
|
return tileVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(operation: 'draw' | 'erase' | 'clear') {
|
||||||
|
this.operation = operation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCommandUpdate(tile?: MapEventTile | null, operation: 'draw' | 'erase' | 'clear' = 'draw') {
|
||||||
|
if (!tile) return
|
||||||
|
|
||||||
|
if (!currentCommand) {
|
||||||
|
currentCommand = new EventTileCommand(operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
//If position is already in, do not proceed
|
||||||
|
for (const priorTile of currentCommand.affectedTiles) {
|
||||||
|
if (priorTile.positionX === tile.positionX && priorTile.positionY == tile.positionY) return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCommand.affectedTiles.push(tile)
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalizeCommand() {
|
||||||
|
if (!currentCommand) return
|
||||||
|
emit('createCommand', currentCommand)
|
||||||
|
currentCommand = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// *** HANDLERS ***
|
||||||
|
|
||||||
function getImageProps(tile: MapEventTile) {
|
function getImageProps(tile: MapEventTile) {
|
||||||
return {
|
return {
|
||||||
@ -39,19 +88,17 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
if (existingEventTile) return
|
if (existingEventTile) return
|
||||||
|
|
||||||
// If teleport, check if there is a selected map
|
// If teleport, check if there is a selected map
|
||||||
if (mapEditor.drawMode.value === 'teleport' && !mapEditor.teleportSettings.value.toMapId) return
|
if (mapEditor.drawMode.value === 'teleport' && !mapEditor.teleportSettings.value.toMap) return
|
||||||
|
|
||||||
const newEventTile = {
|
const newEventTile = {
|
||||||
id: uuidv4() as UUID,
|
id: uuidv4() as UUID,
|
||||||
mapId: map.id,
|
|
||||||
map: map.id,
|
|
||||||
type: mapEditor.drawMode.value === 'blocking tile' ? MapEventTileType.BLOCK : MapEventTileType.TELEPORT,
|
type: mapEditor.drawMode.value === 'blocking tile' ? MapEventTileType.BLOCK : MapEventTileType.TELEPORT,
|
||||||
positionX: tile.x,
|
positionX: tile.x,
|
||||||
positionY: tile.y,
|
positionY: tile.y,
|
||||||
teleport:
|
teleport:
|
||||||
mapEditor.drawMode.value === 'teleport'
|
mapEditor.drawMode.value === 'teleport'
|
||||||
? {
|
? {
|
||||||
toMap: mapEditor.teleportSettings.value.toMapId,
|
toMap: mapEditor.teleportSettings.value.toMap,
|
||||||
toPositionX: mapEditor.teleportSettings.value.toPositionX,
|
toPositionX: mapEditor.teleportSettings.value.toPositionX,
|
||||||
toPositionY: mapEditor.teleportSettings.value.toPositionY,
|
toPositionY: mapEditor.teleportSettings.value.toPositionY,
|
||||||
toRotation: mapEditor.teleportSettings.value.toRotation
|
toRotation: mapEditor.teleportSettings.value.toRotation
|
||||||
@ -59,7 +106,9 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
map.mapEventTiles.push(newEventTile)
|
createCommandUpdate(newEventTile as MapEventTile, 'draw')
|
||||||
|
|
||||||
|
map.mapEventTiles.push(newEventTile as MapEventTile)
|
||||||
}
|
}
|
||||||
|
|
||||||
function erase(pointer: Phaser.Input.Pointer, map: MapT) {
|
function erase(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||||
@ -77,6 +126,8 @@ function erase(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
else return
|
else return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createCommandUpdate(existingEventTile, 'erase')
|
||||||
|
|
||||||
// Remove existing event tile
|
// Remove existing event tile
|
||||||
map.mapEventTiles = map.mapEventTiles.filter((eventTile) => eventTile.id !== existingEventTile.id)
|
map.mapEventTiles = map.mapEventTiles.filter((eventTile) => eventTile.id !== existingEventTile.id)
|
||||||
}
|
}
|
||||||
@ -96,4 +147,10 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearTiles() {
|
||||||
|
if (mapEditor.currentMap.value?.mapEventTiles.length === 0) return
|
||||||
|
createCommandUpdate(null, 'clear')
|
||||||
|
finalizeCommand()
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -3,59 +3,74 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
|
||||||
import Controls from '@/components/utilities/Controls.vue'
|
import Controls from '@/components/utilities/Controls.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { createTileArray, getTile, placeTile, placeTiles } from '@/services/mapService'
|
import { cloneArray, createTileArray, getTile, placeTile, placeTiles } from '@/services/mapService'
|
||||||
import { onMounted, ref, watch } from 'vue'
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
|
|
||||||
defineExpose({ handlePointer, finalizeCommand, undo, redo })
|
defineExpose({ handlePointer, finalizeCommand, clearTiles })
|
||||||
|
|
||||||
|
const emit = defineEmits(['createCommand'])
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tileMap: Phaser.Tilemaps.Tilemap
|
tileMap: Phaser.Tilemaps.Tilemap
|
||||||
tileMapLayer: Phaser.Tilemaps.TilemapLayer
|
tileMapLayer: Phaser.Tilemaps.TilemapLayer
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
class EditorCommand {
|
// *** COMMAND STATE ***
|
||||||
public operation: 'draw' | 'erase' = 'draw'
|
|
||||||
public tileName: string = 'blank_tile'
|
|
||||||
public affectedTiles: number[][]
|
|
||||||
|
|
||||||
constructor(operation: 'draw' | 'erase', tileName: string) {
|
let currentCommand: TileCommand | null = null
|
||||||
|
|
||||||
|
class TileCommand implements EditorCommand {
|
||||||
|
public operation: 'draw' | 'erase' | 'clear' = 'draw'
|
||||||
|
public type: 'tile' = 'tile'
|
||||||
|
public tileName: string = 'blank_tile'
|
||||||
|
public affectedTiles: number[][] = []
|
||||||
|
|
||||||
|
apply(elements: string[][]) {
|
||||||
|
let tileVersion
|
||||||
|
if (this.operation === 'clear') {
|
||||||
|
tileVersion = createTileArray(props.tileMapLayer.width, props.tileMapLayer.height, 'blank_tile')
|
||||||
|
} else {
|
||||||
|
tileVersion = cloneArray(elements) as string[][]
|
||||||
|
for (const position of this.affectedTiles) {
|
||||||
|
tileVersion[position[1]][position[0]] = this.tileName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return tileVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(operation: 'draw' | 'erase' | 'clear', tileName: string) {
|
||||||
this.operation = operation
|
this.operation = operation
|
||||||
this.tileName = tileName
|
this.tileName = tileName
|
||||||
this.affectedTiles = []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Record of commands
|
function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase' | 'clear') {
|
||||||
let commandStack: EditorCommand[] = []
|
if (!currentCommand) {
|
||||||
let currentCommand: EditorCommand | null = null
|
currentCommand = new TileCommand(operation, tileName)
|
||||||
let commandIndex = ref(0)
|
|
||||||
let originTiles: string[][] = []
|
|
||||||
|
|
||||||
function pencil(pointer: Phaser.Input.Pointer) {
|
|
||||||
let map = mapEditor.currentMap.value
|
|
||||||
if (!map) return
|
|
||||||
|
|
||||||
// Check if there is a selected tile
|
|
||||||
if (!mapEditor.selectedTile.value) return
|
|
||||||
|
|
||||||
// Check if there is a tile
|
|
||||||
const tile = getTile(props.tileMapLayer, pointer.worldX, pointer.worldY)
|
|
||||||
if (!tile) return
|
|
||||||
|
|
||||||
// Place tile
|
|
||||||
placeTile(props.tileMap, props.tileMapLayer, tile.x, tile.y, mapEditor.selectedTile.value)
|
|
||||||
|
|
||||||
createCommandUpdate(tile.x, tile.y, mapEditor.selectedTile.value, 'draw')
|
|
||||||
|
|
||||||
// Adjust mapEditorStore.map.tiles
|
|
||||||
map.tiles[tile.y][tile.x] = mapEditor.selectedTile.value
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function eraser(pointer: Phaser.Input.Pointer) {
|
//If position is already in, do not proceed
|
||||||
|
for (const vec of currentCommand.affectedTiles) {
|
||||||
|
if (vec[0] === x && vec[1] === y) return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCommand.affectedTiles.push([x, y])
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalizeCommand() {
|
||||||
|
if (!currentCommand) return
|
||||||
|
emit('createCommand', currentCommand)
|
||||||
|
currentCommand = null
|
||||||
|
}
|
||||||
|
|
||||||
|
// *** HANDLERS ***
|
||||||
|
|
||||||
|
function draw(pointer: Phaser.Input.Pointer, tileName: string) {
|
||||||
let map = mapEditor.currentMap.value
|
let map = mapEditor.currentMap.value
|
||||||
if (!map) return
|
if (!map) return
|
||||||
|
|
||||||
@ -64,12 +79,12 @@ function eraser(pointer: Phaser.Input.Pointer) {
|
|||||||
if (!tile) return
|
if (!tile) return
|
||||||
|
|
||||||
// Place tile
|
// Place tile
|
||||||
placeTile(props.tileMap, props.tileMapLayer, tile.x, tile.y, 'blank_tile')
|
placeTile(props.tileMap, props.tileMapLayer, tile.x, tile.y, tileName)
|
||||||
|
|
||||||
createCommandUpdate(tile.x, tile.y, 'blank_tile', 'erase')
|
createCommandUpdate(tile.x, tile.y, tileName, tileName === 'blank_tile' ? 'erase' : 'draw')
|
||||||
|
|
||||||
// Adjust mapEditorStore.map.tiles
|
// Adjust mapEditorStore.map.tiles
|
||||||
map.tiles[tile.y][tile.x] = 'blank_tile'
|
map.tiles[tile.y][tile.x] = tileName
|
||||||
}
|
}
|
||||||
|
|
||||||
function paint(pointer: Phaser.Input.Pointer) {
|
function paint(pointer: Phaser.Input.Pointer) {
|
||||||
@ -113,10 +128,10 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
|||||||
// Check if draw mode is tile
|
// Check if draw mode is tile
|
||||||
switch (mapEditor.tool.value) {
|
switch (mapEditor.tool.value) {
|
||||||
case 'pencil':
|
case 'pencil':
|
||||||
pencil(pointer)
|
draw(pointer, mapEditor.selectedTile.value!)
|
||||||
break
|
break
|
||||||
case 'eraser':
|
case 'eraser':
|
||||||
eraser(pointer)
|
draw(pointer, 'blank_tile')
|
||||||
break
|
break
|
||||||
case 'paint':
|
case 'paint':
|
||||||
paint(pointer)
|
paint(pointer)
|
||||||
@ -124,90 +139,19 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase') {
|
// *** LIFECYCLE ***
|
||||||
if (!currentCommand) {
|
|
||||||
currentCommand = new EditorCommand(operation, tileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
//If position is already in, do not proceed
|
function clearTiles() {
|
||||||
for (const vec of currentCommand.affectedTiles) {
|
const tileArray = createTileArray(props.tileMap.width, props.tileMap.height, 'blank_tile')
|
||||||
if (vec[0] === x && vec[1] === y) return
|
placeTiles(props.tileMap, props.tileMapLayer, tileArray)
|
||||||
|
createCommandUpdate(0, 0, 'blank_tile', 'clear')
|
||||||
|
finalizeCommand()
|
||||||
}
|
}
|
||||||
|
|
||||||
currentCommand.affectedTiles.push([x, y])
|
|
||||||
}
|
|
||||||
|
|
||||||
function finalizeCommand() {
|
|
||||||
if (!currentCommand) return
|
|
||||||
//Cut the stack so the current edit is the last
|
|
||||||
commandStack = commandStack.slice(0, commandIndex.value)
|
|
||||||
commandStack.push(currentCommand)
|
|
||||||
if (commandStack.length >= 9) {
|
|
||||||
originTiles = applyCommands(originTiles, commandStack.shift()!)
|
|
||||||
}
|
|
||||||
|
|
||||||
commandIndex.value = commandStack.length
|
|
||||||
currentCommand = null
|
|
||||||
}
|
|
||||||
|
|
||||||
function undo() {
|
|
||||||
if (commandIndex.value > 0) {
|
|
||||||
commandIndex.value--
|
|
||||||
updateMapTiles()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function redo() {
|
|
||||||
if (commandIndex.value <= 9 && commandIndex.value <= commandStack.length) {
|
|
||||||
commandIndex.value++
|
|
||||||
updateMapTiles()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function applyCommands(tiles: string[][], ...commands: EditorCommand[]): string[][] {
|
|
||||||
let tileVersion = cloneArray(tiles)
|
|
||||||
for (let command of commands) {
|
|
||||||
for (const position of command.affectedTiles) {
|
|
||||||
tileVersion[position[1]][position[0]] = command.tileName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tileVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateMapTiles() {
|
|
||||||
if (!mapEditor.currentMap.value) return
|
|
||||||
|
|
||||||
let indexedCommands = commandStack.slice(0, commandIndex.value)
|
|
||||||
let modifiedTiles = applyCommands(originTiles, ...indexedCommands)
|
|
||||||
|
|
||||||
placeTiles(props.tileMap, props.tileMapLayer, modifiedTiles)
|
|
||||||
mapEditor.currentMap.value.tiles = modifiedTiles
|
|
||||||
}
|
|
||||||
|
|
||||||
//Recursive Array Clone
|
|
||||||
function cloneArray(arr: any[]): any[] {
|
|
||||||
return arr.map((item) => (item instanceof Array ? cloneArray(item) : item))
|
|
||||||
}
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => mapEditor.shouldClearTiles.value,
|
|
||||||
(shouldClear) => {
|
|
||||||
if (shouldClear && mapEditor.currentMap.value) {
|
|
||||||
const blankTiles = createTileArray(props.tileMapLayer.width, props.tileMapLayer.height, 'blank_tile')
|
|
||||||
placeTiles(props.tileMap, props.tileMapLayer, blankTiles)
|
|
||||||
mapEditor.currentMap.value.tiles = blankTiles
|
|
||||||
mapEditor.resetClearTilesFlag()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
if (!mapEditor.currentMap.value) return
|
if (!mapEditor.currentMap.value) return
|
||||||
const mapState = mapEditor.currentMap.value
|
const mapState = mapEditor.currentMap.value
|
||||||
|
|
||||||
//Clone
|
|
||||||
originTiles = cloneArray(mapState.tiles)
|
|
||||||
|
|
||||||
placeTiles(props.tileMap, props.tileMapLayer, mapState.tiles)
|
placeTiles(props.tileMap, props.tileMapLayer, mapState.tiles)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
<template>
|
<template>
|
||||||
|
<PlacedMapObject
|
||||||
|
v-if="mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'map_object' && mapEditor.isPlacedMapObjectPreviewEnabled.value && mapEditor.selectedMapObject.value && previewPlacedMapObject"
|
||||||
|
:tileMap
|
||||||
|
:tileMapLayer
|
||||||
|
:key="previewPlacedMapObject?.id"
|
||||||
|
:placedMapObject="previewPlacedMapObject as PlacedMapObjectT"
|
||||||
|
/>
|
||||||
<SelectedPlacedMapObjectComponent v-if="mapEditor.selectedPlacedObject.value" :key="mapEditor.selectedPlacedObject.value.id" :map :placedMapObject="mapEditor.selectedPlacedObject.value" @move="moveMapObject" @rotate="rotatePlacedMapObject" @delete="deletePlacedMapObject" />
|
<SelectedPlacedMapObjectComponent v-if="mapEditor.selectedPlacedObject.value" :key="mapEditor.selectedPlacedObject.value.id" :map :placedMapObject="mapEditor.selectedPlacedObject.value" @move="moveMapObject" @rotate="rotatePlacedMapObject" @delete="deletePlacedMapObject" />
|
||||||
<PlacedMapObject v-for="placedMapObject in mapEditor.currentMap.value?.placedMapObjects" :tileMap :tileMapLayer :placedMapObject @pointerdown="clickPlacedMapObject(placedMapObject)" />
|
<PlacedMapObject v-for="placedMapObject in mapEditor.currentMap.value?.placedMapObjects" :tileMap :tileMapLayer :placedMapObject @pointerdown="clickPlacedMapObject(placedMapObject)" />
|
||||||
</template>
|
</template>
|
||||||
@ -11,7 +18,7 @@ import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/
|
|||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { getTile } from '@/services/mapService'
|
import { getTile } from '@/services/mapService'
|
||||||
import { useScene } from 'phavuer'
|
import { useScene } from 'phavuer'
|
||||||
import { computed } from 'vue'
|
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||||
|
|
||||||
import Tilemap = Phaser.Tilemaps.Tilemap
|
import Tilemap = Phaser.Tilemaps.Tilemap
|
||||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
||||||
@ -20,6 +27,8 @@ const scene = useScene()
|
|||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
const map = computed(() => mapEditor.currentMap.value!)
|
const map = computed(() => mapEditor.currentMap.value!)
|
||||||
|
|
||||||
|
const emit = defineEmits<{ (e: 'update', map: MapT): void; (e: 'updateAndCommit', map: MapT): void; (e: 'pauseObjectTracking'): void; (e: 'resumeObjectTracking'): void }>()
|
||||||
|
|
||||||
defineExpose({ handlePointer })
|
defineExpose({ handlePointer })
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
@ -27,36 +36,59 @@ const props = defineProps<{
|
|||||||
tileMapLayer: TilemapLayer
|
tileMapLayer: TilemapLayer
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const previewPosition = ref({ x: 0, y: 0 })
|
||||||
|
const previewPlacedMapObject = computed(() => ({
|
||||||
|
id: mapEditor.selectedMapObject.value!.id,
|
||||||
|
mapObject: mapEditor.selectedMapObject.value!.id,
|
||||||
|
isRotated: false,
|
||||||
|
positionX: previewPosition.value.x,
|
||||||
|
positionY: previewPosition.value.y
|
||||||
|
}))
|
||||||
|
|
||||||
|
function updatePreviewPosition(pointer: Phaser.Input.Pointer) {
|
||||||
|
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
||||||
|
if (!tile || (previewPosition.value.x === tile.x && previewPosition.value.y === tile.y)) return
|
||||||
|
|
||||||
|
previewPosition.value = { x: tile.x, y: tile.y }
|
||||||
|
}
|
||||||
|
|
||||||
function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||||
|
emit('pauseObjectTracking')
|
||||||
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
||||||
if (!tile) return
|
if (!tile) return
|
||||||
|
|
||||||
// Check if object already exists on position
|
// Check if object already exists on position
|
||||||
const existingPlacedMapObject = findObjectByPointer(pointer, map)
|
const existingPlacedMapObject = findObjectByPointer(pointer, mapEditor.currentMap.value!)
|
||||||
if (existingPlacedMapObject) return
|
if (existingPlacedMapObject) return
|
||||||
|
|
||||||
if (!mapEditor.selectedMapObject.value) return
|
if (!mapEditor.selectedMapObject.value) return
|
||||||
|
|
||||||
const newPlacedMapObject: PlacedMapObjectT = {
|
const newPlacedMapObject: PlacedMapObjectT = {
|
||||||
id: uuidv4(),
|
id: uuidv4(),
|
||||||
mapObject: mapEditor.selectedMapObject.value,
|
mapObject: mapEditor.selectedMapObject.value.id,
|
||||||
isRotated: false,
|
isRotated: false,
|
||||||
positionX: tile.x,
|
positionX: tile.x,
|
||||||
positionY: tile.y
|
positionY: tile.y
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add new object to mapObjects
|
// Add new object to mapObjects
|
||||||
map.placedMapObjects.push(newPlacedMapObject)
|
|
||||||
mapEditor.selectedPlacedObject.value = newPlacedMapObject
|
mapEditor.selectedPlacedObject.value = newPlacedMapObject
|
||||||
|
map.placedMapObjects.push(newPlacedMapObject)
|
||||||
|
|
||||||
|
emit('update', map)
|
||||||
}
|
}
|
||||||
|
|
||||||
function eraser(pointer: Phaser.Input.Pointer, map: MapT) {
|
function eraser(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||||
|
emit('pauseObjectTracking')
|
||||||
|
|
||||||
// Check if object already exists on position
|
// Check if object already exists on position
|
||||||
const existingPlacedMapObject = findObjectByPointer(pointer, map)
|
const existingPlacedMapObject = findObjectByPointer(pointer, map)
|
||||||
if (!existingPlacedMapObject) return
|
if (!existingPlacedMapObject) return
|
||||||
|
|
||||||
// Remove existing object
|
// Remove existing object
|
||||||
map.placedMapObjects = map.placedMapObjects.filter((placedMapObject) => placedMapObject.id !== existingPlacedMapObject.id)
|
map.placedMapObjects = map.placedMapObjects.filter((placedMapObject) => placedMapObject.id !== existingPlacedMapObject.id)
|
||||||
|
|
||||||
|
emit('update', map)
|
||||||
}
|
}
|
||||||
|
|
||||||
function findObjectByPointer(pointer: Phaser.Input.Pointer, map: MapT): PlacedMapObjectT | undefined {
|
function findObjectByPointer(pointer: Phaser.Input.Pointer, map: MapT): PlacedMapObjectT | undefined {
|
||||||
@ -78,6 +110,8 @@ function objectPicker(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
function moveMapObject(id: string, map: MapT) {
|
function moveMapObject(id: string, map: MapT) {
|
||||||
mapEditor.movingPlacedObject.value = map.placedMapObjects.find((object) => object.id === id) as PlacedMapObjectT
|
mapEditor.movingPlacedObject.value = map.placedMapObjects.find((object) => object.id === id) as PlacedMapObjectT
|
||||||
|
|
||||||
|
emit('pauseObjectTracking')
|
||||||
|
|
||||||
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
||||||
if (!mapEditor.movingPlacedObject.value) return
|
if (!mapEditor.movingPlacedObject.value) return
|
||||||
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
||||||
@ -88,24 +122,43 @@ function moveMapObject(id: string, map: MapT) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||||
|
|
||||||
function handlePointerUp() {
|
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
|
||||||
mapEditor.movingPlacedObject.value = null
|
|
||||||
}
|
|
||||||
|
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||||
|
|
||||||
|
function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
||||||
|
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||||
|
|
||||||
|
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
||||||
|
if (!tile) return
|
||||||
|
|
||||||
|
map.placedMapObjects.map((placed) => {
|
||||||
|
if (placed.id === id) {
|
||||||
|
placed.positionX = tile.x
|
||||||
|
placed.positionY = tile.y
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
mapEditor.movingPlacedObject.value = null
|
||||||
|
|
||||||
|
scene.input.off(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||||
|
emit('resumeObjectTracking')
|
||||||
|
emit('updateAndCommit', map)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function rotatePlacedMapObject(id: string, map: MapT) {
|
function rotatePlacedMapObject(id: string, map: MapT) {
|
||||||
const matchingObject = map.placedMapObjects.find((placedMapObject) => placedMapObject.id === id)
|
map.placedMapObjects.map((placed) => {
|
||||||
matchingObject!.isRotated = !matchingObject!.isRotated
|
if (placed.id === id) {
|
||||||
|
placed.isRotated = !placed.isRotated
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
emit('updateAndCommit', map)
|
||||||
}
|
}
|
||||||
|
|
||||||
function deletePlacedMapObject(id: string, map: MapT) {
|
function deletePlacedMapObject(id: string, map: MapT) {
|
||||||
let mapE = mapEditor.currentMap.value!
|
map.placedMapObjects = map.placedMapObjects.filter((object) => object.id !== id)
|
||||||
mapE.placedMapObjects = map.placedMapObjects.filter((object) => object.id !== id)
|
|
||||||
mapEditor.selectedPlacedObject.value = null
|
mapEditor.selectedPlacedObject.value = null
|
||||||
|
emit('updateAndCommit', map)
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickPlacedMapObject(placedMapObject: PlacedMapObjectT) {
|
function clickPlacedMapObject(placedMapObject: PlacedMapObjectT) {
|
||||||
@ -137,4 +190,12 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
|||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
scene.input.on(Phaser.Input.Events.POINTER_MOVE, updatePreviewPosition)
|
||||||
|
})
|
||||||
|
|
||||||
|
onUnmounted(() => {
|
||||||
|
scene.input.off(Phaser.Input.Events.POINTER_MOVE, updatePreviewPosition)
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -35,9 +35,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { Map } from '@/application/types'
|
import type { Map } from '@/application/types'
|
||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { MapStorage } from '@/storage/storages'
|
import { MapStorage } from '@/storage/storages'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { ref, useTemplateRef } from 'vue'
|
import { ref, useTemplateRef } from 'vue'
|
||||||
@ -56,7 +58,7 @@ const pvp = ref(false)
|
|||||||
defineExpose({ open: () => modalRef.value?.open() })
|
defineExpose({ open: () => modalRef.value?.open() })
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
gameStore.connection?.emit('gm:map:create', { name: name.value, width: width.value, height: height.value }, async (response: Map | false) => {
|
socketManager.emit(SocketEvent.GM_MAP_CREATE, { name: name.value, width: width.value, height: height.value }, async (response: Map | false) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -29,13 +29,14 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Map, UUID } from '@/application/types'
|
import { SocketEvent } from '@/application/enums'
|
||||||
|
import type { Map } from '@/application/types'
|
||||||
import CreateMap from '@/components/gameMaster/mapEditor/partials/CreateMap.vue'
|
import CreateMap from '@/components/gameMaster/mapEditor/partials/CreateMap.vue'
|
||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { MapStorage } from '@/storage/storages'
|
import { MapStorage } from '@/storage/storages'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useMapEditorStore } from '@/stores/mapEditorStore'
|
|
||||||
import { onMounted, ref, useTemplateRef } from 'vue'
|
import { onMounted, ref, useTemplateRef } from 'vue'
|
||||||
|
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
@ -60,15 +61,15 @@ async function fetchMaps() {
|
|||||||
mapList.value = await mapStorage.getAll()
|
mapList.value = await mapStorage.getAll()
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadMap(id: UUID) {
|
function loadMap(id: string) {
|
||||||
gameStore.connection?.emit('gm:map:request', { mapId: id }, (response: Map) => {
|
socketManager.emit(SocketEvent.GM_MAP_REQUEST, { mapId: id }, (response: Map) => {
|
||||||
mapEditor.loadMap(response)
|
mapEditor.loadMap(response)
|
||||||
})
|
})
|
||||||
modalRef.value?.close()
|
modalRef.value?.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
async function deleteMap(id: UUID) {
|
async function deleteMap(id: string) {
|
||||||
gameStore.connection?.emit('gm:map:delete', { mapId: id }, async (response: boolean) => {
|
socketManager.emit(SocketEvent.GM_MAP_DELETE, { mapId: id }, async (response: boolean) => {
|
||||||
if (!response) {
|
if (!response) {
|
||||||
gameStore.addNotification({
|
gameStore.addNotification({
|
||||||
title: 'Error',
|
title: 'Error',
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute border-0 border-l-2 border-solid border-gray-500 w-1/4 min-w-80 flex flex-col top-0 right-0 z-10 h-dvh bg-gray-800">
|
<div class="absolute border-0 border-l-2 border-solid border-gray-500 w-1/4 min-w-80 flex flex-col top-0 right-0 z-10 h-dvh bg-gray-800" v-if="mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'map_object'">
|
||||||
<div class="flex flex-col gap-2.5 p-2.5">
|
<div class="flex flex-col gap-2.5 p-2.5">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="flex-grow">
|
||||||
<div class="relative flex">
|
<div class="relative flex">
|
||||||
<img src="/assets/icons/mapEditor/search.svg" class="w-4 h-4 py-0.5 absolute top-1/2 -translate-y-1/2 left-2.5" alt="Search icon" />
|
<img src="/assets/icons/mapEditor/search.svg" class="w-4 h-4 py-0.5 absolute top-1/2 -translate-y-1/2 left-2.5" alt="Search icon" />
|
||||||
<label class="mb-1.5 font-titles hidden" for="search">Search</label>
|
<label class="mb-1.5 font-titles hidden" for="search">Search</label>
|
||||||
<input @mousedown.stop class="!pl-7 input-field w-full" type="text" name="search" placeholder="Search" v-model="searchQuery" />
|
<input @mousedown.stop class="!pl-7 input-field w-full" type="text" name="search" placeholder="Search" v-model="searchQuery" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<img src="/assets/icons/mapEditor/dropdown-chevron.svg" class="w-12 h-12 ml-2 cursor-pointer hover:opacity-80 -rotate-90" alt="Close" @click="mapEditor.setTool('move')" />
|
||||||
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<select class="input-field w-full" name="lists">
|
<select class="input-field w-full" name="lists" v-model="mapEditor.drawMode.value" @change="(event: any) => mapEditor.setDrawMode(event.target.value)">
|
||||||
<option value="tile">Tiles</option>
|
<option value="tile">Tiles</option>
|
||||||
<option value="map_object">Objects</option>
|
<option value="map_object">Objects</option>
|
||||||
</select>
|
</select>
|
||||||
|
@ -41,9 +41,11 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { MapObject, Map as MapT, PlacedMapObject } from '@/application/types'
|
import type { MapObject, Map as MapT, PlacedMapObject } from '@/application/types'
|
||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { MapObjectStorage } from '@/storage/storages'
|
import { MapObjectStorage } from '@/storage/storages'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { onMounted, ref } from 'vue'
|
import { onMounted, ref } from 'vue'
|
||||||
@ -80,8 +82,8 @@ const handleDelete = () => {
|
|||||||
async function handleUpdate() {
|
async function handleUpdate() {
|
||||||
if (!mapObject.value) return
|
if (!mapObject.value) return
|
||||||
|
|
||||||
gameStore.connection?.emit(
|
socketManager.emit(
|
||||||
'gm:mapObject:update',
|
SocketEvent.GM_MAPOBJECT_UPDATE,
|
||||||
{
|
{
|
||||||
id: props.placedMapObject.mapObject as string,
|
id: props.placedMapObject.mapObject as string,
|
||||||
name: mapObjectName.value,
|
name: mapObjectName.value,
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal ref="modalRef" @modal:close="() => mapEditorStore.setTool('move')" :modal-width="300" :modal-height="350" :is-resizable="false" bg-style="none">
|
<Modal v-if="showTeleportModal" ref="modalRef" @modal:close="() => mapEditor.setTool('move')" :modal-width="300" :modal-height="350" :is-resizable="false" bg-style="none">
|
||||||
<template #modalHeader>
|
<template #modalHeader>
|
||||||
<h3 class="m-0 font-medium shrink-0 text-white">Teleport settings</h3>
|
<h3 class="m-0 font-medium shrink-0 text-white">Teleport settings</h3>
|
||||||
</template>
|
</template>
|
||||||
@ -28,7 +28,7 @@
|
|||||||
<label for="toMap">Map to teleport to</label>
|
<label for="toMap">Map to teleport to</label>
|
||||||
<select v-model="toMap" class="input-field" name="toMap" id="toMap">
|
<select v-model="toMap" class="input-field" name="toMap" id="toMap">
|
||||||
<option :value="null">Select map</option>
|
<option :value="null">Select map</option>
|
||||||
<option v-for="map in mapList" :key="map.id" :value="map">{{ map.name }}</option>
|
<option v-for="map in mapList" :key="map.id" :value="map.id">{{ map.name }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -41,48 +41,48 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import type { Map } from '@/application/types'
|
import type { Map } from '@/application/types'
|
||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { useMapEditorStore } from '@/stores/mapEditorStore'
|
import { MapStorage } from '@/storage/storages'
|
||||||
import { computed, onMounted, ref, useTemplateRef, watch } from 'vue'
|
import { computed, onMounted, ref, useTemplateRef, watch } from 'vue'
|
||||||
|
|
||||||
const showTeleportModal = computed(() => mapEditorStore.tool === 'pencil' && mapEditorStore.drawMode === 'teleport')
|
const showTeleportModal = computed(() => mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'teleport')
|
||||||
const mapEditorStore = useMapEditorStore()
|
const mapStorage = new MapStorage()
|
||||||
const gameStore = useGameStore()
|
const mapEditor = useMapEditorComposable()
|
||||||
const mapList = ref<Map[]>([])
|
|
||||||
const modalRef = useTemplateRef('modalRef')
|
const modalRef = useTemplateRef('modalRef')
|
||||||
|
const mapList = ref<Map[]>([])
|
||||||
|
|
||||||
defineExpose({
|
defineExpose({
|
||||||
open: () => modalRef.value?.open()
|
open: () => modalRef.value?.open()
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(fetchMaps)
|
|
||||||
|
|
||||||
function fetchMaps() {
|
|
||||||
gameStore.connection?.emit('gm:map:list', {}, (response: Map[]) => {
|
|
||||||
mapList.value = response
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
const { toPositionX, toPositionY, toRotation, toMap } = useRefTeleportSettings()
|
const { toPositionX, toPositionY, toRotation, toMap } = useRefTeleportSettings()
|
||||||
|
|
||||||
function useRefTeleportSettings() {
|
function useRefTeleportSettings() {
|
||||||
const settings = mapEditorStore.teleportSettings
|
const settings = mapEditor.teleportSettings.value
|
||||||
return {
|
return {
|
||||||
toPositionX: ref(settings.toPositionX),
|
toPositionX: ref(settings.toPositionX),
|
||||||
toPositionY: ref(settings.toPositionY),
|
toPositionY: ref(settings.toPositionY),
|
||||||
toRotation: ref(settings.toRotation),
|
toRotation: ref(settings.toRotation),
|
||||||
toMap: ref(settings.toMapId)
|
toMap: ref(settings.toMap)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watch([toPositionX, toPositionY, toRotation, toMap], updateTeleportSettings)
|
watch([toPositionX, toPositionY, toRotation, toMap], updateTeleportSettings)
|
||||||
|
|
||||||
function updateTeleportSettings() {
|
function updateTeleportSettings() {
|
||||||
mapEditorStore.setTeleportSettings({
|
mapEditor.setTeleportSettings({
|
||||||
toPositionX: toPositionX.value,
|
toPositionX: toPositionX.value,
|
||||||
toPositionY: toPositionY.value,
|
toPositionY: toPositionY.value,
|
||||||
toRotation: toRotation.value,
|
toRotation: toRotation.value,
|
||||||
toMapId: toMap.value
|
toMap: toMap.value
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function fetchMaps() {
|
||||||
|
mapList.value = await mapStorage.getAll()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await fetchMaps()
|
||||||
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="absolute border-0 border-l-2 border-solid border-gray-500 w-1/4 min-w-80 flex flex-col top-0 right-0 z-10 h-dvh bg-gray-800">
|
<div class="absolute border-0 border-l-2 border-solid border-gray-500 w-1/4 min-w-80 flex flex-col top-0 right-0 z-10 h-dvh bg-gray-800" v-if="(mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'tile') || mapEditor.tool.value === 'paint'">
|
||||||
<div class="flex flex-col gap-2.5 p-2.5">
|
<div class="flex flex-col gap-2.5 p-2.5">
|
||||||
|
<div class="flex justify-between items-center">
|
||||||
|
<div class="flex-grow">
|
||||||
<div class="relative flex">
|
<div class="relative flex">
|
||||||
<img src="/assets/icons/mapEditor/search.svg" class="w-4 h-4 py-0.5 absolute top-1/2 -translate-y-1/2 left-2.5" alt="Search icon" />
|
<img src="/assets/icons/mapEditor/search.svg" class="w-4 h-4 py-0.5 absolute top-1/2 -translate-y-1/2 left-2.5" alt="Search icon" />
|
||||||
<label class="mb-1.5 font-titles hidden" for="search">Search</label>
|
<label class="mb-1.5 font-titles hidden" for="search">Search</label>
|
||||||
<input @mousedown.stop class="!pl-7 input-field w-full" type="text" name="search" placeholder="Search" v-model="searchQuery" />
|
<input @mousedown.stop class="!pl-7 input-field w-full" type="text" name="search" placeholder="Search" v-model="searchQuery" />
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
<img src="/assets/icons/mapEditor/dropdown-chevron.svg" class="w-12 h-12 ml-2 cursor-pointer hover:opacity-80 -rotate-90" alt="Close" @click="mapEditor.setTool('move')" />
|
||||||
|
</div>
|
||||||
<div class="flex">
|
<div class="flex">
|
||||||
<select class="input-field w-full" name="lists">
|
<select class="input-field w-full" name="lists" v-model="mapEditor.drawMode.value" @change="(event: any) => mapEditor.setDrawMode(event.target.value)">
|
||||||
<option value="tile">Tiles</option>
|
<option value="tile">Tiles</option>
|
||||||
<option value="map_object">Objects</option>
|
<option value="map_object">Objects</option>
|
||||||
</select>
|
</select>
|
||||||
@ -37,8 +42,9 @@
|
|||||||
</div>
|
</div>
|
||||||
<div v-else class="h-full overflow-auto">
|
<div v-else class="h-full overflow-auto">
|
||||||
<div class="p-4">
|
<div class="p-4">
|
||||||
<button @click="closeGroup" class="btn-cyan mb-4">Back to All Tiles</button>
|
<div class="text-center mb-8">
|
||||||
<h4 class="text-lg mb-4">{{ selectedGroup.parent.name }} Group</h4>
|
<button @click="closeGroup" class="hover:text-white">Back to all tiles</button>
|
||||||
|
</div>
|
||||||
<div class="grid grid-cols-4 gap-2 justify-items-center">
|
<div class="grid grid-cols-4 gap-2 justify-items-center">
|
||||||
<div class="flex flex-col items-center justify-center">
|
<div class="flex flex-col items-center justify-center">
|
||||||
<img
|
<img
|
||||||
|
@ -68,7 +68,7 @@
|
|||||||
|
|
||||||
<div class="w-px bg-cyan"></div>
|
<div class="w-px bg-cyan"></div>
|
||||||
|
|
||||||
<button class="flex justify-center items-center min-w-10 p-0 relative" @click="handleClick('settings')"><img class="invert w-5 h-5" src="/assets/icons/mapEditor/gear.svg" alt="Map settings" /> <span class="h-5 ml-2.5">(Z)</span></button>
|
<button class="flex justify-center items-center min-w-10 p-0 relative" @click="isMapEditorSettingsModalOpen = !isMapEditorSettingsModalOpen"><img class="invert w-5 h-5" src="/assets/icons/mapEditor/gear.svg" alt="Map settings" /> <span class="h-5 ml-2.5">(Z)</span></button>
|
||||||
|
|
||||||
<div class="w-px bg-cyan"></div>
|
<div class="w-px bg-cyan"></div>
|
||||||
|
|
||||||
@ -79,7 +79,7 @@
|
|||||||
<button class="btn-cyan px-3.5" @click="() => emit('open-maps')">Load</button>
|
<button class="btn-cyan px-3.5" @click="() => emit('open-maps')">Load</button>
|
||||||
<button class="btn-cyan px-3.5" @click="() => emit('save')" v-if="mapEditor.currentMap.value">Save</button>
|
<button class="btn-cyan px-3.5" @click="() => emit('save')" v-if="mapEditor.currentMap.value">Save</button>
|
||||||
<button class="btn-cyan px-3.5" @click="() => emit('clear')" v-if="mapEditor.currentMap.value">Clear</button>
|
<button class="btn-cyan px-3.5" @click="() => emit('clear')" v-if="mapEditor.currentMap.value">Clear</button>
|
||||||
<button class="btn-cyan px-3.5" @click="() => emit('close-editor')">Exit</button>
|
<button class="btn-cyan px-3.5" @click="() => mapEditor.toggleActive()">Exit</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -89,9 +89,13 @@
|
|||||||
</template>
|
</template>
|
||||||
<template #modalBody>
|
<template #modalBody>
|
||||||
<div class="m-4 flex items-center space-x-2">
|
<div class="m-4 flex items-center space-x-2">
|
||||||
<input id="continuous-drawing" @change="handleCheck" v-model="checkboxValue" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
|
<input id="continuous-drawing" @change="toggleContinuousDrawing" v-model="isContinuousDrawingEnabled" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
|
||||||
<label for="continuous-drawing" class="text-sm font-medium text-gray-200 cursor-pointer"> Continuous Drawing </label>
|
<label for="continuous-drawing" class="text-sm font-medium text-gray-200 cursor-pointer"> Continuous Drawing </label>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="m-4 flex items-center space-x-2">
|
||||||
|
<input id="show-placed-map-object-preview" @change="mapEditor.togglePlacedMapObjectPreview()" v-model="isShowPlacedMapObjectPreviewEnabled" type="checkbox" class="h-4 w-4 rounded border-gray-300 text-blue-600 focus:ring-blue-500" />
|
||||||
|
<label for="show-placed-map-object-preview" class="text-sm font-medium text-gray-200 cursor-pointer"> Show placed map object preview </label>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</Modal>
|
</Modal>
|
||||||
</template>
|
</template>
|
||||||
@ -100,19 +104,20 @@
|
|||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { onClickOutside } from '@vueuse/core'
|
import { onClickOutside } from '@vueuse/core'
|
||||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||||
|
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
|
|
||||||
const emit = defineEmits(['save', 'clear', 'open-maps', 'open-settings', 'close-editor'])
|
const emit = defineEmits(['save', 'clear', 'open-maps', 'open-settings', 'open-teleport'])
|
||||||
|
|
||||||
// States
|
// States
|
||||||
const toolbar = ref(null)
|
const toolbar = ref(null)
|
||||||
const isMapEditorSettingsModalOpen = ref(false)
|
const isMapEditorSettingsModalOpen = ref(false)
|
||||||
const selectPencilOpen = ref(false)
|
const selectPencilOpen = ref(false)
|
||||||
const selectEraserOpen = ref(false)
|
const selectEraserOpen = ref(false)
|
||||||
const checkboxValue = ref<Boolean>(false)
|
const isContinuousDrawingEnabled = ref<Boolean>(false)
|
||||||
const listOpen = ref(false)
|
const isShowPlacedMapObjectPreviewEnabled = ref<Boolean>(mapEditor.isPlacedMapObjectPreviewEnabled.value)
|
||||||
|
const listOpen = computed(() => (mapEditor.tool.value === 'pencil' && (mapEditor.drawMode.value === 'tile' || mapEditor.drawMode.value === 'map_object')) || mapEditor.tool.value === 'paint')
|
||||||
|
|
||||||
// drawMode
|
// drawMode
|
||||||
function setDrawMode(value: string) {
|
function setDrawMode(value: string) {
|
||||||
@ -132,8 +137,8 @@ function setEraserMode() {
|
|||||||
selectEraserOpen.value = false
|
selectEraserOpen.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleCheck() {
|
function toggleContinuousDrawing() {
|
||||||
mapEditor.setInputMode(checkboxValue.value ? 'hold' : 'tap')
|
mapEditor.setInputMode(isContinuousDrawingEnabled.value ? 'hold' : 'tap')
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleModeClick(mode: string, type: 'pencil' | 'eraser') {
|
function handleModeClick(mode: string, type: 'pencil' | 'eraser') {
|
||||||
@ -142,20 +147,11 @@ function handleModeClick(mode: string, type: 'pencil' | 'eraser') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handleClick(tool: string) {
|
function handleClick(tool: string) {
|
||||||
if (tool === 'mapEditorSettings') {
|
|
||||||
isMapEditorSettingsModalOpen.value = true
|
|
||||||
listOpen.value = false
|
|
||||||
} else if (tool === 'settings') {
|
|
||||||
listOpen.value = false
|
|
||||||
} else if (tool === 'move') {
|
|
||||||
listOpen.value = false
|
|
||||||
mapEditor.setTool(tool)
|
mapEditor.setTool(tool)
|
||||||
} else {
|
if (tool === 'paint') mapEditor.setDrawMode('tile')
|
||||||
mapEditor.setTool(tool)
|
|
||||||
}
|
|
||||||
|
|
||||||
selectPencilOpen.value = tool === 'pencil' ? !selectPencilOpen.value : false
|
selectPencilOpen.value = tool === 'pencil' ? !selectPencilOpen.value : false
|
||||||
selectEraserOpen.value = tool === 'eraser' ? !selectEraserOpen.value : false
|
selectEraserOpen.value = tool === 'eraser' ? !selectEraserOpen.value : false
|
||||||
|
if (mapEditor.drawMode.value === 'teleport') emit('open-teleport')
|
||||||
}
|
}
|
||||||
|
|
||||||
function cycleToolMode(tool: 'pencil' | 'eraser') {
|
function cycleToolMode(tool: 'pencil' | 'eraser') {
|
||||||
@ -171,7 +167,7 @@ function initKeyShortcuts(event: KeyboardEvent) {
|
|||||||
// Check if map is set
|
// Check if map is set
|
||||||
if (!mapEditor.currentMap.value) return
|
if (!mapEditor.currentMap.value) return
|
||||||
|
|
||||||
// prevent if focused on composables
|
// prevent if focused on inputs
|
||||||
if (document.activeElement?.tagName === 'INPUT') return
|
if (document.activeElement?.tagName === 'INPUT') return
|
||||||
|
|
||||||
if (event.ctrlKey) return
|
if (event.ctrlKey) return
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { login } from '@/services/authenticationService'
|
import { login } from '@/services/authenticationService'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
@ -39,15 +40,6 @@ const password = ref('')
|
|||||||
const formError = ref('')
|
const formError = ref('')
|
||||||
const showPassword = ref(false)
|
const showPassword = ref(false)
|
||||||
|
|
||||||
// automatic login because of development
|
|
||||||
onMounted(async () => {
|
|
||||||
const token = useCookies().get('token')
|
|
||||||
if (token) {
|
|
||||||
gameStore.setToken(token)
|
|
||||||
gameStore.initConnection()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
// check if username and password are valid
|
// check if username and password are valid
|
||||||
if (username.value === '' || password.value === '') {
|
if (username.value === '' || password.value === '') {
|
||||||
@ -62,7 +54,7 @@ async function submit() {
|
|||||||
formError.value = response.error
|
formError.value = response.error
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gameStore.setToken(response.token)
|
socketManager.setToken(response.token)
|
||||||
gameStore.initConnection()
|
gameStore.initConnection()
|
||||||
return true // Indicate success
|
return true // Indicate success
|
||||||
}
|
}
|
||||||
|
@ -34,15 +34,6 @@ const password = ref('')
|
|||||||
const newPasswordError = ref('')
|
const newPasswordError = ref('')
|
||||||
const showPassword = ref(false)
|
const showPassword = ref(false)
|
||||||
|
|
||||||
// automatic login because of development
|
|
||||||
onMounted(async () => {
|
|
||||||
const token = useCookies().get('token')
|
|
||||||
if (token) {
|
|
||||||
gameStore.setToken(token)
|
|
||||||
gameStore.initConnection()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function newPasswordFunc() {
|
async function newPasswordFunc() {
|
||||||
// check if username and password are valid
|
// check if username and password are valid
|
||||||
if (password.value === '') {
|
if (password.value === '') {
|
||||||
|
@ -26,6 +26,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { login, register } from '@/services/authenticationService'
|
import { login, register } from '@/services/authenticationService'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
@ -40,15 +41,6 @@ const email = ref('')
|
|||||||
const formError = ref('')
|
const formError = ref('')
|
||||||
const showPassword = ref(false)
|
const showPassword = ref(false)
|
||||||
|
|
||||||
// automatic login because of development
|
|
||||||
onMounted(async () => {
|
|
||||||
const token = useCookies().get('token')
|
|
||||||
if (token) {
|
|
||||||
gameStore.setToken(token)
|
|
||||||
gameStore.initConnection()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
async function submit() {
|
async function submit() {
|
||||||
// check if username and password are valid
|
// check if username and password are valid
|
||||||
if (username.value === '' || email.value === '' || password.value === '') {
|
if (username.value === '' || email.value === '' || password.value === '') {
|
||||||
@ -76,7 +68,7 @@ async function submit() {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.setToken(loginResponse.token)
|
socketManager.setToken(loginResponse.token)
|
||||||
gameStore.initConnection()
|
gameStore.initConnection()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -122,9 +122,11 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
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 { useSoundComposable } from '@/composables/useSoundComposable'
|
import { useSoundComposable } from '@/composables/useSoundComposable'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { CharacterHairStorage } from '@/storage/storages'
|
import { CharacterHairStorage } from '@/storage/storages'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||||
@ -141,10 +143,10 @@ const selectedHairId = ref<string | null>(null)
|
|||||||
|
|
||||||
// Fetch characters
|
// Fetch characters
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
gameStore.connection?.emit('character:list')
|
socketManager.emit(SocketEvent.CHARACTER_LIST)
|
||||||
}, 750)
|
}, 750)
|
||||||
|
|
||||||
gameStore.connection?.on('character:list', (data: any) => {
|
socketManager.on(SocketEvent.CHARACTER_LIST, (data: any) => {
|
||||||
characters.value = data
|
characters.value = data
|
||||||
isLoading.value = false
|
isLoading.value = false
|
||||||
})
|
})
|
||||||
@ -153,8 +155,8 @@ gameStore.connection?.on('character:list', (data: any) => {
|
|||||||
function loginWithCharacter() {
|
function loginWithCharacter() {
|
||||||
if (!selectedCharacterId.value) return
|
if (!selectedCharacterId.value) return
|
||||||
|
|
||||||
gameStore.connection?.emit(
|
socketManager.emit(
|
||||||
'character:connect',
|
SocketEvent.CHARACTER_CONNECT,
|
||||||
{
|
{
|
||||||
characterId: selectedCharacterId.value,
|
characterId: selectedCharacterId.value,
|
||||||
characterHairId: selectedHairId.value
|
characterHairId: selectedHairId.value
|
||||||
@ -167,7 +169,7 @@ function loginWithCharacter() {
|
|||||||
|
|
||||||
// Create character logics
|
// Create character logics
|
||||||
function createCharacter() {
|
function createCharacter() {
|
||||||
gameStore.connection?.emit('character:create', { name: newCharacterName.value }, (success: boolean) => {
|
socketManager.emit(SocketEvent.CHARACTER_CREATE, { name: newCharacterName.value }, (success: boolean) => {
|
||||||
if (success) return
|
if (success) return
|
||||||
isCreateNewCharacterModalOpen.value = false
|
isCreateNewCharacterModalOpen.value = false
|
||||||
})
|
})
|
||||||
@ -176,7 +178,7 @@ 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)?.characterHair ?? null
|
||||||
})
|
})
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
@ -186,8 +188,8 @@ onMounted(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
gameStore.connection?.off('character:list')
|
socketManager.off(SocketEvent.CHARACTER_LIST)
|
||||||
gameStore.connection?.off('character:connect')
|
socketManager.off(SocketEvent.CHARACTER_CONNECT)
|
||||||
gameStore.connection?.off('character:create:success')
|
socketManager.off(SocketEvent.CHARACTER_CREATE)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -44,13 +44,4 @@ function switchToLogin() {
|
|||||||
currentForm.value = 'login'
|
currentForm.value = 'login'
|
||||||
doesUrlHaveToken.value = false
|
doesUrlHaveToken.value = false
|
||||||
}
|
}
|
||||||
|
|
||||||
// automatic login because of development
|
|
||||||
onMounted(async () => {
|
|
||||||
const token = useCookies().get('token')
|
|
||||||
if (token) {
|
|
||||||
gameStore.setToken(token)
|
|
||||||
gameStore.initConnection()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
</script>
|
</script>
|
||||||
|
@ -5,10 +5,10 @@
|
|||||||
<div v-if="!isLoaded" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-white text-3xl font-ui">Loading...</div>
|
<div v-if="!isLoaded" class="absolute top-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-white text-3xl font-ui">Loading...</div>
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<Map v-if="mapEditor.currentMap.value" :key="mapEditor.currentMap.value?.id" />
|
<Map v-if="mapEditor.currentMap.value" :key="mapEditor.currentMap.value?.id" />
|
||||||
<Toolbar ref="toolbar" @save="save" @clear="clear" @open-maps="mapModal?.open" @open-settings="mapSettingsModal?.open" @close-editor="mapEditor.toggleActive" />
|
<Toolbar ref="toolbar" @save="save" @clear="clear" @open-maps="mapModal?.open" @open-settings="mapSettingsModal?.open" @open-teleport="teleportModal?.open" />
|
||||||
<MapList ref="mapModal" @open-create-map="mapSettingsModal?.open" />
|
<MapList ref="mapModal" @open-create-map="mapSettingsModal?.open" />
|
||||||
<TileList v-if="mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'tile'" />
|
<TileList />
|
||||||
<MapObjectList v-if="mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'map_object'" />
|
<MapObjectList />
|
||||||
<MapSettings ref="mapSettingsModal" />
|
<MapSettings ref="mapSettingsModal" />
|
||||||
<TeleportModal ref="teleportModal" />
|
<TeleportModal ref="teleportModal" />
|
||||||
</div>
|
</div>
|
||||||
@ -19,6 +19,8 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import config from '@/application/config'
|
import config from '@/application/config'
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import 'phaser'
|
import 'phaser'
|
||||||
import type { Map as MapT } from '@/application/types'
|
import type { Map as MapT } from '@/application/types'
|
||||||
import Map from '@/components/gameMaster/mapEditor/Map.vue'
|
import Map from '@/components/gameMaster/mapEditor/Map.vue'
|
||||||
@ -41,6 +43,7 @@ const gameStore = useGameStore()
|
|||||||
|
|
||||||
const mapModal = useTemplateRef('mapModal')
|
const mapModal = useTemplateRef('mapModal')
|
||||||
const mapSettingsModal = useTemplateRef('mapSettingsModal')
|
const mapSettingsModal = useTemplateRef('mapSettingsModal')
|
||||||
|
const teleportModal = useTemplateRef('teleportModal')
|
||||||
|
|
||||||
const isLoaded = ref(false)
|
const isLoaded = ref(false)
|
||||||
|
|
||||||
@ -86,18 +89,11 @@ function save() {
|
|||||||
if (!currentMap) return
|
if (!currentMap) return
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
mapId: currentMap.id,
|
...currentMap,
|
||||||
name: currentMap.name,
|
mapId: currentMap.id
|
||||||
width: currentMap.width,
|
|
||||||
height: currentMap.height,
|
|
||||||
tiles: currentMap.tiles,
|
|
||||||
pvp: currentMap.pvp,
|
|
||||||
mapEffects: currentMap.mapEffects,
|
|
||||||
mapEventTiles: currentMap.mapEventTiles,
|
|
||||||
placedMapObjects: currentMap.placedMapObjects.map(({ id, mapObject, isRotated, positionX, positionY }) => ({ id, mapObject, isRotated, positionX, positionY })) ?? []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:map:update', data, (response: MapT) => {
|
socketManager.emit(SocketEvent.GM_MAP_UPDATE, data, (response: MapT) => {
|
||||||
mapStorage.update(response.id, response)
|
mapStorage.update(response.id, response)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -106,7 +102,6 @@ function clear() {
|
|||||||
if (!mapEditor.currentMap.value) return
|
if (!mapEditor.currentMap.value) return
|
||||||
|
|
||||||
// Clear placed objects, event tiles and tiles
|
// Clear placed objects, event tiles and tiles
|
||||||
mapEditor.clearMap()
|
|
||||||
mapEditor.triggerClearTiles()
|
mapEditor.triggerClearTiles()
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,23 +0,0 @@
|
|||||||
<template>
|
|
||||||
<div style="display: none">
|
|
||||||
<img v-for="(url, index) in imageUrls" :key="index" :src="url" alt="" @load="handleImageLoad(index)" @error="handleImageError(index)" />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
// Internal array of images to preload
|
|
||||||
const imageUrls = ref<string[]>(['/assets/ui-elements/button-ui-box-textured.svg', '/assets/ui-elements/button-ui-frame-empty.svg', '/assets/ui-elements/button-ui-box-textured-small.svg'])
|
|
||||||
|
|
||||||
const loadedImages = ref<Set<number>>(new Set())
|
|
||||||
|
|
||||||
const handleImageLoad = (index: number) => {
|
|
||||||
loadedImages.value.add(index)
|
|
||||||
console.log(`Image ${index} loaded:`, imageUrls.value[index])
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleImageError = (index: number) => {
|
|
||||||
console.log(`Image ${index} failed to load:`, imageUrls.value[index])
|
|
||||||
}
|
|
||||||
</script>
|
|
@ -1,18 +1,14 @@
|
|||||||
<template></template>
|
<template></template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import {
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
CharacterHairStorage,
|
import { login } from '@/services/authenticationService'
|
||||||
CharacterTypeStorage,
|
import { CharacterHairStorage, CharacterTypeStorage, MapObjectStorage, MapStorage, SoundStorage, SpriteStorage, TileStorage } from '@/storage/storages'
|
||||||
MapObjectStorage,
|
|
||||||
MapStorage,
|
|
||||||
SoundStorage,
|
|
||||||
SpriteStorage,
|
|
||||||
TileStorage
|
|
||||||
} from '@/storage/storages'
|
|
||||||
import { TextureStorage } from '@/storage/textureStorage'
|
import { TextureStorage } from '@/storage/textureStorage'
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { onMounted, onUnmounted } from 'vue'
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
|
const gameStore = useGameStore()
|
||||||
const mapStorage = new MapStorage()
|
const mapStorage = new MapStorage()
|
||||||
const tileStorage = new TileStorage()
|
const tileStorage = new TileStorage()
|
||||||
const mapObjectStorage = new MapObjectStorage()
|
const mapObjectStorage = new MapObjectStorage()
|
||||||
@ -45,6 +41,17 @@ async function handleKeyPress(event: KeyboardEvent) {
|
|||||||
currentString = '' // Reset
|
currentString = '' // Reset
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (currentString.includes('11')) {
|
||||||
|
if (socketManager.token) return
|
||||||
|
const response = await login('root', 'password')
|
||||||
|
|
||||||
|
if (response.success === undefined) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
socketManager.setToken(response.token)
|
||||||
|
gameStore.initConnection()
|
||||||
|
}
|
||||||
|
|
||||||
// Reset string after a certain amount of time
|
// Reset string after a certain amount of time
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
currentString = ''
|
currentString = ''
|
||||||
|
@ -10,7 +10,9 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
import Modal from '@/components/utilities/Modal.vue'
|
import Modal from '@/components/utilities/Modal.vue'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { onBeforeMount, onBeforeUnmount, onMounted, onUnmounted, watch } from 'vue'
|
import { onBeforeMount, onBeforeUnmount, onMounted, onUnmounted, watch } from 'vue'
|
||||||
|
|
||||||
@ -26,7 +28,7 @@ type Notification = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function setupNotificationListener(connection: any) {
|
function setupNotificationListener(connection: any) {
|
||||||
connection.on('notification', (data: Notification) => {
|
connection.on(SocketEvent.NOTIFICATION, (data: Notification) => {
|
||||||
gameStore.addNotification({
|
gameStore.addNotification({
|
||||||
title: data.title,
|
title: data.title,
|
||||||
message: data.message
|
message: data.message
|
||||||
@ -52,7 +54,7 @@ onMounted(() => {
|
|||||||
onUnmounted(() => {
|
onUnmounted(() => {
|
||||||
const connection = gameStore.connection
|
const connection = gameStore.connection
|
||||||
if (connection) {
|
if (connection) {
|
||||||
connection.off('notification')
|
connection.off(SocketEvent.NOTIFICATION)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { getTile } from '@/services/mapService'
|
import { getTile } from '@/services/mapService'
|
||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import type { Ref } from 'vue'
|
import type { Ref } from 'vue'
|
||||||
@ -6,7 +8,77 @@ import { useBaseControlsComposable } from './useBaseControlsComposable'
|
|||||||
export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
|
export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Tilemaps.TilemapLayer, waypoint: Ref<{ visible: boolean; x: number; y: number }>, camera: Phaser.Cameras.Scene2D.Camera) {
|
||||||
const gameStore = useGameStore()
|
const gameStore = useGameStore()
|
||||||
const baseHandlers = useBaseControlsComposable(scene, layer, waypoint, camera)
|
const baseHandlers = useBaseControlsComposable(scene, layer, waypoint, camera)
|
||||||
|
const pressedKeys = new Set<string>()
|
||||||
|
|
||||||
|
let moveTimeout: NodeJS.Timeout | null = null
|
||||||
|
let currentPosition = {
|
||||||
|
x: 0,
|
||||||
|
y: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
// Movement constants
|
||||||
|
const MOVEMENT_DELAY = 110 // Milliseconds between moves
|
||||||
|
const ARROW_KEYS = ['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'] as const
|
||||||
|
|
||||||
|
function updateCurrentPosition() {
|
||||||
|
if (!gameStore.character) return
|
||||||
|
currentPosition = {
|
||||||
|
x: gameStore.character.positionX,
|
||||||
|
y: gameStore.character.positionY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateNewPosition() {
|
||||||
|
let newX = currentPosition.x
|
||||||
|
let newY = currentPosition.y
|
||||||
|
|
||||||
|
if (pressedKeys.has('ArrowLeft')) newX--
|
||||||
|
if (pressedKeys.has('ArrowRight')) newX++
|
||||||
|
if (pressedKeys.has('ArrowUp')) newY--
|
||||||
|
if (pressedKeys.has('ArrowDown')) newY++
|
||||||
|
|
||||||
|
return { newX, newY }
|
||||||
|
}
|
||||||
|
|
||||||
|
function emitMovement(x: number, y: number) {
|
||||||
|
if (x === currentPosition.x && y === currentPosition.y) return
|
||||||
|
|
||||||
|
socketManager.emit(SocketEvent.MAP_CHARACTER_MOVE, [x, y])
|
||||||
|
socketManager.on(SocketEvent.MAP_CHARACTER_MOVE, ([characterId, posX, posY, rot, isMoving]: [string, number, number, number, boolean]) => {
|
||||||
|
if (characterId !== gameStore.character?.id) return
|
||||||
|
currentPosition = { x: posX, y: posY }
|
||||||
|
})
|
||||||
|
|
||||||
|
currentPosition = { x, y }
|
||||||
|
}
|
||||||
|
|
||||||
|
function startMovementLoop() {
|
||||||
|
if (moveTimeout) return
|
||||||
|
|
||||||
|
const move = () => {
|
||||||
|
if (pressedKeys.size === 0) {
|
||||||
|
stopMovementLoop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
updateCurrentPosition()
|
||||||
|
const { newX, newY } = calculateNewPosition()
|
||||||
|
emitMovement(newX, newY)
|
||||||
|
|
||||||
|
moveTimeout = setTimeout(move, MOVEMENT_DELAY)
|
||||||
|
}
|
||||||
|
|
||||||
|
move()
|
||||||
|
}
|
||||||
|
|
||||||
|
function stopMovementLoop() {
|
||||||
|
if (moveTimeout) {
|
||||||
|
clearTimeout(moveTimeout)
|
||||||
|
moveTimeout = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pointer Handlers
|
||||||
function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
||||||
baseHandlers.startDragging(pointer)
|
baseHandlers.startDragging(pointer)
|
||||||
}
|
}
|
||||||
@ -22,77 +94,37 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
|
|||||||
const pointerTile = getTile(layer, pointer.worldX, pointer.worldY)
|
const pointerTile = getTile(layer, pointer.worldX, pointer.worldY)
|
||||||
if (!pointerTile) return
|
if (!pointerTile) return
|
||||||
|
|
||||||
gameStore.connection?.emit('map:character:move', {
|
emitMovement(pointerTile.x, pointerTile.y)
|
||||||
positionX: pointerTile.x,
|
|
||||||
positionY: pointerTile.y
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const pressedKeys = new Set<string>()
|
// Keyboard Handlers
|
||||||
let moveInterval: number | null = null
|
|
||||||
|
|
||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
if (!gameStore.character) return
|
if (!gameStore.character) return
|
||||||
|
|
||||||
// console.log(event.key)
|
if (ARROW_KEYS.includes(event.key as (typeof ARROW_KEYS)[number])) {
|
||||||
|
if (event.repeat) return
|
||||||
|
|
||||||
if (['ArrowLeft', 'ArrowRight', 'ArrowUp', 'ArrowDown'].includes(event.key)) {
|
|
||||||
pressedKeys.add(event.key)
|
pressedKeys.add(event.key)
|
||||||
|
updateCurrentPosition()
|
||||||
// Start movement loop if not already running
|
startMovementLoop()
|
||||||
if (!moveInterval) {
|
|
||||||
moveInterval = window.setInterval(moveCharacter, 100) // Adjust timing as needed
|
|
||||||
moveCharacter() // Move immediately on first press
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attack on CTRL
|
|
||||||
if (event.key === 'Control') {
|
if (event.key === 'Control') {
|
||||||
gameStore.connection?.emit('map:character:attack')
|
socketManager.emit(SocketEvent.MAP_CHARACTER_ATTACK)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function handleKeyUp(event: KeyboardEvent) {
|
function handleKeyUp(event: KeyboardEvent) {
|
||||||
pressedKeys.delete(event.key)
|
pressedKeys.delete(event.key)
|
||||||
|
|
||||||
// If no movement keys are pressed, clear the interval
|
if (pressedKeys.size === 0) {
|
||||||
if (pressedKeys.size === 0 && moveInterval) {
|
stopMovementLoop()
|
||||||
clearInterval(moveInterval)
|
|
||||||
moveInterval = null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function moveCharacter() {
|
|
||||||
if (!gameStore.character) return
|
|
||||||
const { positionX, positionY } = gameStore.character
|
|
||||||
|
|
||||||
if (pressedKeys.has('ArrowLeft')) {
|
|
||||||
gameStore.connection?.emit('map:character:move', {
|
|
||||||
positionX: positionX - 1,
|
|
||||||
positionY: positionY
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (pressedKeys.has('ArrowRight')) {
|
|
||||||
gameStore.connection?.emit('map:character:move', {
|
|
||||||
positionX: positionX + 1,
|
|
||||||
positionY: positionY
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (pressedKeys.has('ArrowUp')) {
|
|
||||||
gameStore.connection?.emit('map:character:move', {
|
|
||||||
positionX: positionX,
|
|
||||||
positionY: positionY - 1
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if (pressedKeys.has('ArrowDown')) {
|
|
||||||
gameStore.connection?.emit('map:character:move', {
|
|
||||||
positionX: positionX,
|
|
||||||
positionY: positionY + 1
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const setupControls = () => {
|
const setupControls = () => {
|
||||||
|
updateCurrentPosition() // Initialize position
|
||||||
|
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
scene.input.on(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||||
@ -102,6 +134,9 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
|
|||||||
}
|
}
|
||||||
|
|
||||||
const cleanupControls = () => {
|
const cleanupControls = () => {
|
||||||
|
stopMovementLoop()
|
||||||
|
pressedKeys.clear()
|
||||||
|
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
scene.input.off(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
scene.input.off(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||||
|
@ -18,7 +18,7 @@ export function useCharacterSpriteComposable(scene: Phaser.Scene, tilemap: Phase
|
|||||||
const tween = ref<Phaser.Tweens.Tween | null>(null)
|
const tween = ref<Phaser.Tweens.Tween | null>(null)
|
||||||
|
|
||||||
const updateIsometricDepth = (positionX: number, positionY: number) => {
|
const updateIsometricDepth = (positionX: number, positionY: number) => {
|
||||||
isometricDepth.value = calculateIsometricDepth(positionX, positionY, 30, 95, true)
|
isometricDepth.value = calculateIsometricDepth(positionX, positionY)
|
||||||
}
|
}
|
||||||
|
|
||||||
const updatePosition = (positionX: number, positionY: number) => {
|
const updatePosition = (positionX: number, positionY: number) => {
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import type { Map, MapObject, PlacedMapObject, UUID } from '@/application/types'
|
import type { Map, MapObject, PlacedMapObject, UUID } from '@/application/types'
|
||||||
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
export type TeleportSettings = {
|
export type TeleportSettings = {
|
||||||
toMapId: string
|
toMap: string
|
||||||
toPositionX: number
|
toPositionX: number
|
||||||
toPositionY: number
|
toPositionY: number
|
||||||
toRotation: number
|
toRotation: number
|
||||||
@ -14,12 +15,13 @@ const tool = ref('move')
|
|||||||
const drawMode = ref('tile')
|
const drawMode = ref('tile')
|
||||||
const inputMode = ref('tap')
|
const inputMode = ref('tap')
|
||||||
const selectedTile = ref('')
|
const selectedTile = ref('')
|
||||||
|
const isPlacedMapObjectPreviewEnabled = ref(true)
|
||||||
const selectedMapObject = ref<MapObject | null>(null)
|
const selectedMapObject = ref<MapObject | null>(null)
|
||||||
const movingPlacedObject = ref<PlacedMapObject | null>(null)
|
const movingPlacedObject = ref<PlacedMapObject | null>(null)
|
||||||
const selectedPlacedObject = ref<PlacedMapObject | null>(null)
|
const selectedPlacedObject = ref<PlacedMapObject | null>(null)
|
||||||
const shouldClearTiles = ref(false)
|
const shouldClearTiles = ref(false)
|
||||||
const teleportSettings = ref<TeleportSettings>({
|
const teleportSettings = ref<TeleportSettings>({
|
||||||
toMapId: '1000',
|
toMap: '1000',
|
||||||
toPositionX: 0,
|
toPositionX: 0,
|
||||||
toPositionY: 0,
|
toPositionY: 0,
|
||||||
toRotation: 0
|
toRotation: 0
|
||||||
@ -42,15 +44,15 @@ export function useMapEditorComposable() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const clearMap = () => {
|
|
||||||
if (!currentMap.value) return
|
|
||||||
currentMap.value.placedMapObjects = []
|
|
||||||
currentMap.value.mapEventTiles = []
|
|
||||||
}
|
|
||||||
|
|
||||||
const toggleActive = () => {
|
const toggleActive = () => {
|
||||||
if (active.value) reset()
|
if (active.value) reset()
|
||||||
active.value = !active.value
|
active.value = !active.value
|
||||||
|
const gameStore = useGameStore()
|
||||||
|
gameStore.uiSettings.isGmPanelOpen = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const togglePlacedMapObjectPreview = () => {
|
||||||
|
isPlacedMapObjectPreviewEnabled.value = !isPlacedMapObjectPreviewEnabled.value
|
||||||
}
|
}
|
||||||
|
|
||||||
const setTool = (newTool: string) => {
|
const setTool = (newTool: string) => {
|
||||||
@ -94,7 +96,9 @@ export function useMapEditorComposable() {
|
|||||||
drawMode.value = 'tile'
|
drawMode.value = 'tile'
|
||||||
inputMode.value = 'tap'
|
inputMode.value = 'tap'
|
||||||
selectedTile.value = ''
|
selectedTile.value = ''
|
||||||
|
isPlacedMapObjectPreviewEnabled.value = false
|
||||||
selectedMapObject.value = null
|
selectedMapObject.value = null
|
||||||
|
selectedPlacedObject.value = null
|
||||||
shouldClearTiles.value = false
|
shouldClearTiles.value = false
|
||||||
refreshMapObject.value = 0
|
refreshMapObject.value = 0
|
||||||
}
|
}
|
||||||
@ -107,6 +111,7 @@ export function useMapEditorComposable() {
|
|||||||
drawMode,
|
drawMode,
|
||||||
inputMode,
|
inputMode,
|
||||||
selectedTile,
|
selectedTile,
|
||||||
|
isPlacedMapObjectPreviewEnabled,
|
||||||
selectedMapObject,
|
selectedMapObject,
|
||||||
movingPlacedObject,
|
movingPlacedObject,
|
||||||
selectedPlacedObject,
|
selectedPlacedObject,
|
||||||
@ -117,12 +122,12 @@ export function useMapEditorComposable() {
|
|||||||
// Methods
|
// Methods
|
||||||
loadMap,
|
loadMap,
|
||||||
updateProperty,
|
updateProperty,
|
||||||
clearMap,
|
|
||||||
toggleActive,
|
toggleActive,
|
||||||
setTool,
|
setTool,
|
||||||
setDrawMode,
|
setDrawMode,
|
||||||
setInputMode,
|
setInputMode,
|
||||||
setSelectedTile,
|
setSelectedTile,
|
||||||
|
togglePlacedMapObjectPreview,
|
||||||
setSelectedMapObject,
|
setSelectedMapObject,
|
||||||
setTeleportSettings,
|
setTeleportSettings,
|
||||||
triggerClearTiles,
|
triggerClearTiles,
|
||||||
|
76
src/managers/SocketManager.ts
Normal file
76
src/managers/SocketManager.ts
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
import config from '@/application/config'
|
||||||
|
import { SocketEvent } from '@/application/enums'
|
||||||
|
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||||
|
import { io, Socket } from 'socket.io-client'
|
||||||
|
import { ref, shallowRef } from 'vue'
|
||||||
|
|
||||||
|
class SocketManager {
|
||||||
|
private static instance: SocketManager
|
||||||
|
private _connection = shallowRef<Socket | null>(null)
|
||||||
|
private _token = ref('')
|
||||||
|
|
||||||
|
private constructor() {}
|
||||||
|
|
||||||
|
public static getInstance(): SocketManager {
|
||||||
|
if (!SocketManager.instance) {
|
||||||
|
SocketManager.instance = new SocketManager()
|
||||||
|
}
|
||||||
|
return SocketManager.instance
|
||||||
|
}
|
||||||
|
|
||||||
|
public get connection() {
|
||||||
|
return this._connection.value
|
||||||
|
}
|
||||||
|
|
||||||
|
public get token() {
|
||||||
|
return this._token.value
|
||||||
|
}
|
||||||
|
|
||||||
|
public setToken(token: string) {
|
||||||
|
this._token.value = token
|
||||||
|
}
|
||||||
|
|
||||||
|
public initConnection(): Socket {
|
||||||
|
if (this._connection.value) return this._connection.value
|
||||||
|
|
||||||
|
const socket = io(config.server_endpoint, {
|
||||||
|
secure: config.environment === 'production',
|
||||||
|
withCredentials: true,
|
||||||
|
transports: ['websocket'],
|
||||||
|
reconnectionAttempts: 5
|
||||||
|
})
|
||||||
|
|
||||||
|
this._connection.value = socket
|
||||||
|
return socket
|
||||||
|
}
|
||||||
|
|
||||||
|
public disconnect(): void {
|
||||||
|
if (!this._connection.value) return
|
||||||
|
|
||||||
|
this._connection.value.off(SocketEvent.CONNECT_ERROR)
|
||||||
|
this._connection.value.off(SocketEvent.RECONNECT_FAILED)
|
||||||
|
this._connection.value.off(SocketEvent.DATE)
|
||||||
|
this._connection.value.disconnect()
|
||||||
|
|
||||||
|
useCookies().remove('token', {
|
||||||
|
domain: config.domain
|
||||||
|
})
|
||||||
|
|
||||||
|
this._connection.value = null
|
||||||
|
this._token.value = ''
|
||||||
|
}
|
||||||
|
|
||||||
|
public emit(event: string, ...args: any[]): void {
|
||||||
|
this._connection.value?.emit(event, ...args)
|
||||||
|
}
|
||||||
|
|
||||||
|
public on(event: string, callback: (...args: any[]) => void): void {
|
||||||
|
this._connection.value?.on(event, callback)
|
||||||
|
}
|
||||||
|
|
||||||
|
public off(event: string, callback?: (...args: any[]) => void): void {
|
||||||
|
this._connection.value?.off(event, callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const socketManager = SocketManager.getInstance()
|
@ -62,12 +62,8 @@ 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 = (positionX: number, positionY: number, width: number = 0, height: number = 0, isCharacter: boolean = false) => {
|
export const calculateIsometricDepth = (positionX: number, positionY: number, pivotPoints: { x: number; y: number }[] = []) => {
|
||||||
const baseDepth = positionX + positionY
|
return Math.max(positionX + positionY)
|
||||||
if (isCharacter) {
|
|
||||||
return baseDepth
|
|
||||||
}
|
|
||||||
return baseDepth + (width + height) / (2 * config.tile_size.width)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadTileTextures(tiles: TileT[], scene: Phaser.Scene) {
|
async function loadTileTextures(tiles: TileT[], scene: Phaser.Scene) {
|
||||||
@ -151,3 +147,8 @@ export function createTileLayer(tileMap: Phaser.Tilemaps.Tilemap, tilesArray: st
|
|||||||
|
|
||||||
return layer
|
return layer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Recursive Array Clone
|
||||||
|
export function cloneArray(arr: any[]): any[] {
|
||||||
|
return arr.map((item) => (item instanceof Array ? cloneArray(item) : item))
|
||||||
|
}
|
||||||
|
@ -1,15 +1,12 @@
|
|||||||
import config from '@/application/config'
|
import { SocketEvent } from '@/application/enums'
|
||||||
import type { Character, Notification, User, WorldSettings } from '@/application/types'
|
import type { Character, Notification, User, WorldSettings } from '@/application/types'
|
||||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
import { socketManager } from '@/managers/SocketManager'
|
||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
import { io, Socket } from 'socket.io-client'
|
|
||||||
|
|
||||||
export const useGameStore = defineStore('game', {
|
export const useGameStore = defineStore('game', {
|
||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
notifications: [] as Notification[],
|
notifications: [] as Notification[],
|
||||||
token: '',
|
|
||||||
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,
|
||||||
world: {
|
world: {
|
||||||
@ -50,9 +47,6 @@ export const useGameStore = defineStore('game', {
|
|||||||
removeNotification(id: string) {
|
removeNotification(id: string) {
|
||||||
this.notifications = this.notifications.filter((notification: Notification) => notification.id !== id)
|
this.notifications = this.notifications.filter((notification: Notification) => notification.id !== id)
|
||||||
},
|
},
|
||||||
setToken(token: string) {
|
|
||||||
this.token = token
|
|
||||||
},
|
|
||||||
setUser(user: User | null) {
|
setUser(user: User | null) {
|
||||||
this.user = user
|
this.user = user
|
||||||
},
|
},
|
||||||
@ -72,40 +66,34 @@ export const useGameStore = defineStore('game', {
|
|||||||
this.uiSettings.isCharacterProfileOpen = !this.uiSettings.isCharacterProfileOpen
|
this.uiSettings.isCharacterProfileOpen = !this.uiSettings.isCharacterProfileOpen
|
||||||
},
|
},
|
||||||
initConnection() {
|
initConnection() {
|
||||||
this.connection = io(config.server_endpoint, {
|
const socket = socketManager.initConnection()
|
||||||
secure: config.environment === 'production',
|
|
||||||
withCredentials: true,
|
|
||||||
transports: ['websocket'],
|
|
||||||
reconnectionAttempts: 5
|
|
||||||
})
|
|
||||||
|
|
||||||
// #99 - If we can't connect, disconnect
|
// Handle connect error
|
||||||
this.connection.on('connect_error', () => {
|
socket.on(SocketEvent.CONNECT_ERROR, () => {
|
||||||
this.disconnectSocket()
|
this.disconnectSocket()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Let the server know the user is logged in
|
// Handle failed reconnection
|
||||||
this.connection.emit('login')
|
socket.on(SocketEvent.RECONNECT_FAILED, () => {
|
||||||
|
this.disconnectSocket()
|
||||||
|
})
|
||||||
|
|
||||||
// set user
|
// Emit login event
|
||||||
this.connection.on('logged_in', (user: User) => {
|
socketManager.emit(SocketEvent.LOGIN)
|
||||||
|
|
||||||
|
// Handle logged in event
|
||||||
|
socketManager.on(SocketEvent.LOGGED_IN, (user: User) => {
|
||||||
this.setUser(user)
|
this.setUser(user)
|
||||||
})
|
})
|
||||||
|
|
||||||
// When we can't reconnect, disconnect
|
// Handle date updates
|
||||||
this.connection.on('reconnect_failed', () => {
|
socketManager.on(SocketEvent.DATE, (data: Date) => {
|
||||||
this.disconnectSocket()
|
this.world.date = new Date(data)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
disconnectSocket() {
|
disconnectSocket() {
|
||||||
this.connection?.disconnect()
|
socketManager.disconnect()
|
||||||
|
|
||||||
useCookies().remove('token', {
|
|
||||||
domain: config.domain
|
|
||||||
})
|
|
||||||
|
|
||||||
this.connection = null
|
|
||||||
this.token = ''
|
|
||||||
this.user = null
|
this.user = null
|
||||||
this.character = null
|
this.character = null
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import type { MapObject, Map as MapT } from '@/application/types'
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
export type TeleportSettings = {
|
export type TeleportSettings = {
|
||||||
toMapId: string
|
toMap: string
|
||||||
toPositionX: number
|
toPositionX: number
|
||||||
toPositionY: number
|
toPositionY: number
|
||||||
toRotation: number
|
toRotation: number
|
||||||
@ -18,7 +18,7 @@ export const useMapEditorStore = defineStore('mapEditor', {
|
|||||||
selectedMapObject: null as MapObject | null,
|
selectedMapObject: null as MapObject | null,
|
||||||
shouldClearTiles: false,
|
shouldClearTiles: false,
|
||||||
teleportSettings: {
|
teleportSettings: {
|
||||||
toMapId: '',
|
toMap: '',
|
||||||
toPositionX: 0,
|
toPositionX: 0,
|
||||||
toPositionY: 0,
|
toPositionY: 0,
|
||||||
toRotation: 0
|
toRotation: 0
|
||||||
|
@ -5,8 +5,7 @@ export const useMapStore = defineStore('map', {
|
|||||||
state: () => {
|
state: () => {
|
||||||
return {
|
return {
|
||||||
mapId: '',
|
mapId: '',
|
||||||
characters: [] as MapCharacter[],
|
characters: [] as MapCharacter[]
|
||||||
characterLoaded: false
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
@ -36,22 +35,18 @@ export const useMapStore = defineStore('map', {
|
|||||||
removeCharacter(characterId: UUID) {
|
removeCharacter(characterId: UUID) {
|
||||||
this.characters = this.characters.filter((char) => char.character.id !== characterId)
|
this.characters = this.characters.filter((char) => char.character.id !== characterId)
|
||||||
},
|
},
|
||||||
setCharacterLoaded(loaded: boolean) {
|
updateCharacterPosition([characterId, posX, posY, rot, isMoving]: [UUID, number, number, number, boolean]) {
|
||||||
this.characterLoaded = loaded
|
const character = this.characters.find((char) => char.character.id === characterId)
|
||||||
},
|
|
||||||
updateCharacterPosition(data: { characterId: UUID; positionX: number; positionY: number; rotation: number; isMoving: boolean }) {
|
|
||||||
const character = this.characters.find((char) => char.character.id === data.characterId)
|
|
||||||
if (character) {
|
if (character) {
|
||||||
character.character.positionX = data.positionX
|
character.character.positionX = posX
|
||||||
character.character.positionY = data.positionY
|
character.character.positionY = posY
|
||||||
character.character.rotation = data.rotation
|
character.character.rotation = rot
|
||||||
character.isMoving = data.isMoving
|
character.isMoving = isMoving
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
reset() {
|
reset() {
|
||||||
this.mapId = ''
|
this.mapId = ''
|
||||||
this.characters = []
|
this.characters = []
|
||||||
this.characterLoaded = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -2,13 +2,37 @@ 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 viteCompression from 'vite-plugin-compression';
|
import viteCompression from 'vite-plugin-compression';
|
||||||
|
import {ViteImageOptimizer} from "vite-plugin-image-optimizer";
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
viteCompression()
|
viteCompression({
|
||||||
|
algorithm: 'gzip',
|
||||||
|
ext: '.gz',
|
||||||
|
threshold: 10240 // Only compress files larger than 10KB
|
||||||
|
}),
|
||||||
|
ViteImageOptimizer()
|
||||||
],
|
],
|
||||||
|
build: {
|
||||||
|
minify: 'terser', // Better minification
|
||||||
|
terserOptions: {
|
||||||
|
compress: {
|
||||||
|
drop_console: true, // Remove console.log in production
|
||||||
|
drop_debugger: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
'vendor': ['vue'], // Split vendor chunks
|
||||||
|
// Add other large dependencies here
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
chunkSizeWarningLimit: 1000, // Increase chunk size warning limit if needed
|
||||||
|
},
|
||||||
resolve: {
|
resolve: {
|
||||||
alias: {
|
alias: {
|
||||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||||
|
Reference in New Issue
Block a user