forked from noxious/client
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": {
|
||||
"@vueuse/core": "^10.5.0",
|
||||
"@vueuse/integrations": "^10.5.0",
|
||||
"axios": "^1.7.7",
|
||||
"dexie": "^4.0.8",
|
||||
"phaser": "^3.86.0",
|
||||
"pinia": "^2.1.6",
|
||||
"socket.io-client": "^4.8.0",
|
||||
"axios": "^1.7.9",
|
||||
"dexie": "^4.0.11",
|
||||
"phaser": "^3.88.2",
|
||||
"phavuer": "^0.16.5",
|
||||
"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",
|
||||
"vue": "^3.5.12",
|
||||
"zod": "^3.22.2"
|
||||
"vite-plugin-image-optimizer": "^1.1.8",
|
||||
"vue": "^3.5.13",
|
||||
"zod": "^3.24.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
|
||||
@ -36,8 +40,6 @@
|
||||
"autoprefixer": "^10.4.19",
|
||||
"jsdom": "^24.1.1",
|
||||
"npm-run-all2": "^6.2.3",
|
||||
"phaser3-rex-plugins": "^1.80.8",
|
||||
"phavuer": "^0.16.1",
|
||||
"postcss": "^8.4.47",
|
||||
"prettier": "^3.3.3",
|
||||
"sass": "^1.79.4",
|
||||
|
Binary file not shown.
@ -1,7 +1,6 @@
|
||||
<template>
|
||||
<Debug />
|
||||
<Notifications />
|
||||
<BackgroundImageLoader />
|
||||
<GmPanel v-if="gameStore.character?.role === 'gm'" />
|
||||
<component :is="currentScreen" />
|
||||
</template>
|
||||
@ -13,11 +12,11 @@ import Game from '@/components/screens/Game.vue'
|
||||
import Loading from '@/components/screens/Loading.vue'
|
||||
import Login from '@/components/screens/Login.vue'
|
||||
import MapEditor from '@/components/screens/MapEditor.vue'
|
||||
import BackgroundImageLoader from '@/components/utilities/BackgroundImageLoader.vue'
|
||||
import Debug from '@/components/utilities/Debug.vue'
|
||||
import Notifications from '@/components/utilities/Notifications.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { useSoundComposable } from '@/composables/useSoundComposable'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, watch } from 'vue'
|
||||
|
||||
@ -28,8 +27,8 @@ const { playSound } = useSoundComposable()
|
||||
|
||||
const currentScreen = computed(() => {
|
||||
if (!gameStore.game.isLoaded) return Loading
|
||||
if (!gameStore.connection) return Login
|
||||
if (!gameStore.token) return Login
|
||||
if (!socketManager.connection) return Login
|
||||
if (!socketManager.token) return Login
|
||||
if (!gameStore.character) return Characters
|
||||
if (mapEditor.active.value) return MapEditor
|
||||
return Game
|
||||
|
@ -3,3 +3,61 @@ export enum Direction {
|
||||
NEGATIVE,
|
||||
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 = {
|
||||
id: string
|
||||
name: string
|
||||
tags: any | null
|
||||
tags: string[]
|
||||
pivotPoints: { x: number; y: number }[]
|
||||
originX: number
|
||||
originY: number
|
||||
frameRate: number
|
||||
@ -100,7 +101,7 @@ export enum MapEventTileType {
|
||||
|
||||
export type MapEventTile = {
|
||||
id: string
|
||||
mapid: string
|
||||
map: string
|
||||
type: MapEventTileType
|
||||
positionX: number
|
||||
positionY: number
|
||||
|
@ -21,7 +21,17 @@ export async function downloadCache<T extends { id: string; updatedAt: Date }>(e
|
||||
}
|
||||
|
||||
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) {
|
||||
let overwrite = false
|
||||
const existingItem = await storage.getById(item.id)
|
||||
|
@ -28,7 +28,7 @@ const gameStore = useGameStore()
|
||||
const mapStore = useMapStore()
|
||||
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 handlePositionUpdate = (newValues: any, oldValues: any) => {
|
||||
@ -92,7 +92,6 @@ watch(
|
||||
onMounted(async () => {
|
||||
await initializeSprite()
|
||||
if (props.mapCharacter.character.id === gameStore.character!.id) {
|
||||
mapStore.setCharacterLoaded(true)
|
||||
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>
|
||||
<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" />
|
||||
<Text @create="createChatText" :style="{ fontSize: 13, fontFamily: 'Arial', color: '#000' }" />
|
||||
</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 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">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -21,7 +21,8 @@
|
||||
</template>
|
||||
|
||||
<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 { useMapStore } from '@/stores/mapStore'
|
||||
import { onClickOutside, useFocus } from '@vueuse/core'
|
||||
@ -30,10 +31,9 @@ import { nextTick, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
const scene = useScene()
|
||||
const gameStore = useGameStore()
|
||||
const mapStore = useMapStore()
|
||||
|
||||
const message = ref('')
|
||||
const chats = ref([] as Chat[])
|
||||
const chats = ref<{ character: string; message: string }[]>([])
|
||||
const chatWindow = ref<HTMLElement | null>(null)
|
||||
const chatInput = ref<HTMLElement | null>(null)
|
||||
|
||||
@ -55,7 +55,7 @@ function unfocusChat(event: Event, targetElement: HTMLElement) {
|
||||
|
||||
const sendMessage = () => {
|
||||
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 = ''
|
||||
}
|
||||
|
||||
@ -79,21 +79,30 @@ const scrollToBottom = () => {
|
||||
})
|
||||
}
|
||||
|
||||
gameStore.connection?.on('chat:message', (data: Chat) => {
|
||||
chats.value.push(data)
|
||||
socketManager.on(SocketEvent.CHAT_MESSAGE, (data: { character: string; message: string }) => {
|
||||
if (!data.character || !data.message) return
|
||||
|
||||
chats.value.push({ character: data.character, message: data.message })
|
||||
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
|
||||
if (!characterContainer) return
|
||||
const characterChatContainer = characterContainer.getByName(data.character + '_chatContainer') as Phaser.GameObjects.Container
|
||||
if (!characterChatContainer) {
|
||||
console.log('No character chat container found')
|
||||
return
|
||||
}
|
||||
|
||||
const characterChatContainer = characterContainer.getByName(data.character.name + '_chatContainer') as Phaser.GameObjects.Container
|
||||
if (!characterChatContainer) return
|
||||
|
||||
const chatBubble = characterChatContainer.getByName(data.character.name + '_chatBubble') as Phaser.GameObjects.Container
|
||||
const chatText = characterChatContainer.getByName(data.character.name + '_chatText') as Phaser.GameObjects.Text
|
||||
if (!chatText || !chatBubble) return
|
||||
const chatBubble = characterChatContainer.getByName(data.character + '_chatBubble') as Phaser.GameObjects.Container
|
||||
const chatText = characterChatContainer.getByName(data.character + '_chatText') as Phaser.GameObjects.Text
|
||||
if (!chatText || !chatBubble) {
|
||||
console.log('No chat text or bubble found')
|
||||
return
|
||||
}
|
||||
|
||||
function calculateTextWidth(text: string, font: string, fontSize: number): number {
|
||||
// Create a canvas element
|
||||
@ -144,7 +153,7 @@ onMounted(() => {
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
gameStore.connection?.off('chat:message')
|
||||
socketManager.off(SocketEvent.CHAT_MESSAGE)
|
||||
removeEventListener('keydown', focusChat)
|
||||
})
|
||||
</script>
|
||||
|
@ -1,21 +1,21 @@
|
||||
<template>
|
||||
<div class="absolute top-0 right-4 hidden lg:block">
|
||||
<p class="text-white text-lg">{{ gameStore.world.date.toLocaleString() }}</p>
|
||||
<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">
|
||||
{{ useDateFormat(gameStore.world.date, 'YYYY/MM/DD HH:mm') }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useDateFormat } from '@vueuse/core'
|
||||
import { onUnmounted } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
|
||||
// Listen for new date from socket
|
||||
gameStore.connection?.on('date', (data: Date) => {
|
||||
gameStore.world.date = new Date(data)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
gameStore.connection?.off('date')
|
||||
socketManager.off(SocketEvent.DATE)
|
||||
})
|
||||
</script>
|
||||
|
@ -3,8 +3,10 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { MapCharacter, UUID } from '@/application/types'
|
||||
import Character from '@/components/game/character/Character.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useMapStore } from '@/stores/mapStore'
|
||||
import { onUnmounted } from 'vue'
|
||||
@ -16,31 +18,32 @@ const props = defineProps<{
|
||||
tileMap: Phaser.Tilemaps.Tilemap
|
||||
}>()
|
||||
|
||||
gameStore.connection?.on('map:character:join', async (data: MapCharacter) => {
|
||||
socketManager.on(SocketEvent.MAP_CHARACTER_JOIN, (data: MapCharacter) => {
|
||||
mapStore.addCharacter(data)
|
||||
})
|
||||
|
||||
gameStore.connection?.on('map:character:leave', (characterId: UUID) => {
|
||||
socketManager.on(SocketEvent.MAP_CHARACTER_LEAVE, (characterId: UUID) => {
|
||||
mapStore.removeCharacter(characterId)
|
||||
})
|
||||
|
||||
gameStore.connection?.on('map:character:move', (data: { characterId: UUID; positionX: number; positionY: number; rotation: number; isMoving: boolean }) => {
|
||||
mapStore.updateCharacterPosition(data)
|
||||
// @TODO: Replace with universal class, composable or store
|
||||
if (data.characterId === gameStore.character?.id) {
|
||||
gameStore.character!.positionX = data.positionX
|
||||
gameStore.character!.positionY = data.positionY
|
||||
gameStore.character!.rotation = data.rotation
|
||||
socketManager.on(SocketEvent.MAP_CHARACTER_MOVE, ([characterId, posX, posY, rot, isMoving]: [UUID, number, number, number, boolean]) => {
|
||||
mapStore.updateCharacterPosition([characterId, posX, posY, rot, isMoving])
|
||||
|
||||
if (characterId === gameStore.character?.id) {
|
||||
gameStore.character!.positionX = posX
|
||||
gameStore.character!.positionY = posY
|
||||
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)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
gameStore.connection?.off('map:character:join')
|
||||
gameStore.connection?.off('map:character:leave')
|
||||
gameStore.connection?.off('map:character:move')
|
||||
socketManager.off(SocketEvent.MAP_CHARACTER_ATTACK)
|
||||
socketManager.off(SocketEvent.MAP_CHARACTER_MOVE)
|
||||
socketManager.off(SocketEvent.MAP_CHARACTER_JOIN)
|
||||
socketManager.off(SocketEvent.MAP_CHARACTER_LEAVE)
|
||||
})
|
||||
</script>
|
||||
|
@ -5,11 +5,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { mapLoadData } from '@/application/types'
|
||||
import { unduplicateArray } from '@/application/utilities'
|
||||
import Characters from '@/components/game/map/Characters.vue'
|
||||
import MapTiles from '@/components/game/map/MapTiles.vue'
|
||||
import PlacedMapObjects from '@/components/game/map/PlacedMapObjects.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { createTileLayer, createTileMap, loadTileTexturesFromMapTileArray } from '@/services/mapService'
|
||||
import { MapStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
@ -28,7 +30,7 @@ const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
||||
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
||||
|
||||
// Event listeners
|
||||
gameStore.connection?.on('map:character:teleport', async (data: mapLoadData) => {
|
||||
socketManager.on(SocketEvent.MAP_CHARACTER_TELEPORT, (data: mapLoadData) => {
|
||||
mapStore.setMapId(data.mapId)
|
||||
mapStore.setCharacters(data.characters)
|
||||
})
|
||||
@ -64,6 +66,6 @@ onUnmounted(() => {
|
||||
tileMap.value.destroy()
|
||||
}
|
||||
|
||||
gameStore.connection?.off('map:character:teleport')
|
||||
socketManager.off(SocketEvent.MAP_CHARACTER_TELEPORT)
|
||||
})
|
||||
</script>
|
||||
|
@ -59,9 +59,9 @@ function calculateObjectPlacement(mapObj: PlacedMapObject): { x: number; y: numb
|
||||
}
|
||||
|
||||
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,
|
||||
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),
|
||||
flipX: props.placedMapObject.isRotated,
|
||||
texture: mapObject.value!.id,
|
||||
|
@ -20,9 +20,9 @@
|
||||
<script setup lang="ts">
|
||||
import AssetManager from '@/components/gameMaster/assetManager/AssetManager.vue'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { ref } from 'vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
|
||||
const mapEditor = useMapEditorComposable()
|
||||
const gameStore = useGameStore()
|
||||
|
@ -34,7 +34,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { CharacterGender, CharacterHair, Sprite } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
@ -65,7 +67,7 @@ if (selectedCharacterHair.value) {
|
||||
function removeCharacterHair() {
|
||||
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) {
|
||||
console.error('Failed to remove character hair')
|
||||
return
|
||||
@ -75,7 +77,7 @@ function removeCharacterHair() {
|
||||
}
|
||||
|
||||
function refreshCharacterHairList(unsetSelectedCharacterHair = true) {
|
||||
gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||
assetManagerStore.setCharacterHairList(response)
|
||||
|
||||
if (unsetSelectedCharacterHair) {
|
||||
@ -93,7 +95,7 @@ function saveCharacterHair() {
|
||||
spriteId: characterSpriteId.value
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:characterHair:update', characterHairData, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_UPDATE, characterHairData, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save character type')
|
||||
return
|
||||
@ -113,7 +115,7 @@ watch(selectedCharacterHair, (characterHair: CharacterHair | null) => {
|
||||
onMounted(() => {
|
||||
if (!selectedCharacterHair.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
@ -32,7 +32,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { CharacterHair } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -52,13 +54,13 @@ const handleSearch = () => {
|
||||
}
|
||||
|
||||
const createNewCharacterHair = () => {
|
||||
gameStore.connection?.emit('gm:characterHair:create', {}, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_CREATE, {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to create new character type')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||
assetManagerStore.setCharacterHairList(response)
|
||||
})
|
||||
})
|
||||
@ -92,7 +94,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit('gm:characterHair:list', {}, (response: CharacterHair[]) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERHAIR_LIST, {}, (response: CharacterHair[]) => {
|
||||
assetManagerStore.setCharacterHairList(response)
|
||||
})
|
||||
})
|
||||
|
@ -40,7 +40,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { CharacterGender, CharacterRace, CharacterType, Sprite } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
@ -74,7 +76,7 @@ if (selectedCharacterType.value) {
|
||||
function removeCharacterType() {
|
||||
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) {
|
||||
console.error('Failed to remove character type')
|
||||
return
|
||||
@ -84,7 +86,7 @@ function removeCharacterType() {
|
||||
}
|
||||
|
||||
function refreshCharacterTypeList(unsetSelectedCharacterType = true) {
|
||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
|
||||
if (unsetSelectedCharacterType) {
|
||||
@ -103,7 +105,7 @@ function saveCharacterType() {
|
||||
spriteId: characterSpriteId.value
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:characterType:update', characterTypeData, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_UPDATE, characterTypeData, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save character type')
|
||||
return
|
||||
@ -124,7 +126,7 @@ watch(selectedCharacterType, (characterType: CharacterType | null) => {
|
||||
onMounted(() => {
|
||||
if (!selectedCharacterType.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
@ -32,7 +32,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { CharacterType } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -52,13 +54,13 @@ const handleSearch = () => {
|
||||
}
|
||||
|
||||
const createNewCharacterType = () => {
|
||||
gameStore.connection?.emit('gm:characterType:create', {}, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_CREATE, {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to create new character type')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
})
|
||||
})
|
||||
@ -92,7 +94,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit('gm:characterType:list', {}, (response: CharacterType[]) => {
|
||||
socketManager.emit(SocketEvent.GM_CHARACTERTYPE_LIST, {}, (response: CharacterType[]) => {
|
||||
assetManagerStore.setCharacterTypeList(response)
|
||||
})
|
||||
})
|
||||
|
@ -44,7 +44,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Item, ItemRarity, ItemType, Sprite } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
@ -80,7 +82,7 @@ if (selectedItem.value) {
|
||||
function removeItem() {
|
||||
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) {
|
||||
console.error('Failed to remove item')
|
||||
return
|
||||
@ -90,7 +92,7 @@ function removeItem() {
|
||||
}
|
||||
|
||||
function refreshItemList(unsetSelectedItem = true) {
|
||||
gameStore.connection?.emit('gm:item:list', {}, (response: Item[]) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||
assetManagerStore.setItemList(response)
|
||||
|
||||
if (unsetSelectedItem) {
|
||||
@ -110,7 +112,7 @@ function saveItem() {
|
||||
spriteId: itemSpriteId.value
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:item:update', itemData, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_UPDATE, itemData, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to save item')
|
||||
return
|
||||
@ -132,7 +134,7 @@ watch(selectedItem, (item: Item | null) => {
|
||||
onMounted(() => {
|
||||
if (!selectedItem.value) return
|
||||
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
@ -29,7 +29,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Item } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -48,13 +50,13 @@ const handleSearch = () => {
|
||||
}
|
||||
|
||||
const createNewItem = () => {
|
||||
gameStore.connection?.emit('gm:item:create', {}, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_CREATE, {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
console.error('Failed to create new item')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:item:list', {}, (response: Item[]) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||
assetManagerStore.setItemList(response)
|
||||
})
|
||||
})
|
||||
@ -88,7 +90,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit('gm:item:list', {}, (response: Item[]) => {
|
||||
socketManager.emit(SocketEvent.GM_ITEM_LIST, {}, (response: Item[]) => {
|
||||
assetManagerStore.setItemList(response)
|
||||
})
|
||||
})
|
||||
|
@ -1,7 +1,20 @@
|
||||
<template>
|
||||
<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">
|
||||
<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 class="mt-5 block">
|
||||
<form class="flex gap-2.5 flex-wrap" @submit.prevent="saveObject">
|
||||
@ -44,8 +57,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { MapObject } from '@/application/types'
|
||||
import ChipsInput from '@/components/forms/ChipsInput.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
@ -57,11 +72,15 @@ const selectedMapObject = computed(() => assetManagerStore.selectedMapObject)
|
||||
|
||||
const mapObjectName = ref('')
|
||||
const mapObjectTags = ref<string[]>([])
|
||||
const mapObjectPivotPoints = ref<Array<{ x: number; y: number }>>([])
|
||||
const mapObjectOriginX = ref(0)
|
||||
const mapObjectOriginY = ref(0)
|
||||
const mapObjectFrameRate = ref(0)
|
||||
const mapObjectFrameWidth = ref(0)
|
||||
const mapObjectFrameHeight = ref(0)
|
||||
const imageRef = ref<HTMLImageElement | null>(null)
|
||||
const isDragging = ref(false)
|
||||
const draggedPointIndex = ref(-1)
|
||||
|
||||
if (!selectedMapObject.value) {
|
||||
console.error('No map mapObject selected')
|
||||
@ -70,6 +89,7 @@ if (!selectedMapObject.value) {
|
||||
if (selectedMapObject.value) {
|
||||
mapObjectName.value = selectedMapObject.value.name
|
||||
mapObjectTags.value = selectedMapObject.value.tags
|
||||
mapObjectPivotPoints.value = selectedMapObject.value.pivotPoints
|
||||
mapObjectOriginX.value = selectedMapObject.value.originX
|
||||
mapObjectOriginY.value = selectedMapObject.value.originY
|
||||
mapObjectFrameRate.value = selectedMapObject.value.frameRate
|
||||
@ -78,7 +98,7 @@ if (selectedMapObject.value) {
|
||||
}
|
||||
|
||||
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) {
|
||||
console.error('Failed to remove mapObject')
|
||||
return
|
||||
@ -88,7 +108,7 @@ function removeObject() {
|
||||
}
|
||||
|
||||
function refreshObjectList(unsetSelectedMapObject = true) {
|
||||
gameStore.connection?.emit('gm:mapObject:list', {}, (response: MapObject[]) => {
|
||||
socketManager.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||
assetManagerStore.setMapObjectList(response)
|
||||
|
||||
if (unsetSelectedMapObject) {
|
||||
@ -103,12 +123,13 @@ function saveObject() {
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(
|
||||
'gm:mapObject:update',
|
||||
socketManager.emit(
|
||||
SocketEvent.GM_MAPOBJECT_UPDATE,
|
||||
{
|
||||
id: selectedMapObject.value.id,
|
||||
name: mapObjectName.value,
|
||||
tags: mapObjectTags.value,
|
||||
pivotPoints: mapObjectPivotPoints.value,
|
||||
originX: mapObjectOriginX.value,
|
||||
originY: mapObjectOriginY.value,
|
||||
frameRate: mapObjectFrameRate.value,
|
||||
@ -129,6 +150,7 @@ watch(selectedMapObject, (mapObject: MapObject | null) => {
|
||||
if (!mapObject) return
|
||||
mapObjectName.value = mapObject.name
|
||||
mapObjectTags.value = mapObject.tags
|
||||
mapObjectPivotPoints.value = mapObject.pivotPoints
|
||||
mapObjectOriginX.value = mapObject.originX
|
||||
mapObjectOriginY.value = mapObject.originY
|
||||
mapObjectFrameRate.value = mapObject.frameRate
|
||||
@ -140,7 +162,51 @@ onMounted(() => {
|
||||
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(() => {
|
||||
assetManagerStore.setSelectedMapObject(null)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.pointer-events-none {
|
||||
pointer-events: none;
|
||||
}
|
||||
</style>
|
||||
|
@ -29,7 +29,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { MapObject } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -47,13 +49,13 @@ const elementToScroll = ref()
|
||||
const handleFileUpload = (e: Event) => {
|
||||
const files = (e.target as HTMLInputElement).files
|
||||
if (!files) return
|
||||
gameStore.connection?.emit('gm:mapObject:upload', files, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_MAPOBJECT_UPLOAD, files, (response: boolean) => {
|
||||
if (!response) {
|
||||
if (config.environment === 'development') console.error('Failed to upload map object')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:mapObject:list', {}, (response: MapObject[]) => {
|
||||
socketManager.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||
assetManagerStore.setMapObjectList(response)
|
||||
})
|
||||
})
|
||||
@ -92,7 +94,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit('gm:mapObject:list', {}, (response: MapObject[]) => {
|
||||
socketManager.emit(SocketEvent.GM_MAPOBJECT_LIST, {}, (response: MapObject[]) => {
|
||||
assetManagerStore.setMapObjectList(response)
|
||||
})
|
||||
})
|
||||
|
@ -68,11 +68,13 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Sprite, SpriteAction, UUID } from '@/application/types'
|
||||
import { uuidv4 } from '@/application/utilities'
|
||||
import SpriteActionsInput from '@/components/gameMaster/assetManager/partials/sprite/partials/SpriteImagesInput.vue'
|
||||
import SpritePreview from '@/components/gameMaster/assetManager/partials/sprite/partials/SpritePreview.vue'
|
||||
import Accordion from '@/components/utilities/Accordion.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
@ -97,7 +99,7 @@ if (selectedSprite.value) {
|
||||
}
|
||||
|
||||
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) {
|
||||
console.error('Failed to delete sprite')
|
||||
return
|
||||
@ -107,7 +109,7 @@ function deleteSprite() {
|
||||
}
|
||||
|
||||
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) {
|
||||
console.error('Failed to copy sprite')
|
||||
return
|
||||
@ -117,7 +119,7 @@ function copySprite() {
|
||||
}
|
||||
|
||||
function refreshSpriteList(unsetSelectedSprite = true) {
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
|
||||
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) {
|
||||
console.error('Failed to save sprite')
|
||||
return
|
||||
|
@ -25,7 +25,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Sprite } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -40,13 +42,13 @@ const hasScrolled = ref(false)
|
||||
const elementToScroll = ref()
|
||||
|
||||
function newButtonClickHandler() {
|
||||
gameStore.connection?.emit('gm:sprite:create', {}, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_CREATE, {}, (response: boolean) => {
|
||||
if (!response) {
|
||||
if (config.environment === 'development') console.error('Failed to create new sprite')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
@ -85,7 +87,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit('gm:sprite:list', {}, (response: Sprite[]) => {
|
||||
socketManager.emit(SocketEvent.GM_SPRITE_LIST, {}, (response: Sprite[]) => {
|
||||
assetManagerStore.setSpriteList(response)
|
||||
})
|
||||
})
|
||||
|
@ -34,7 +34,7 @@
|
||||
</div>
|
||||
<div class="flex flex-col justify-center gap-8 flex-1">
|
||||
<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" />
|
||||
</div>
|
||||
<div class="flex flex-col">
|
||||
@ -76,6 +76,11 @@ const localFrameRate = ref(props.frameRate)
|
||||
const zoomLevel = ref(100)
|
||||
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(() => {
|
||||
return props.sprites.map((sprite, index) => {
|
||||
if (index === props.tempOffsetIndex && props.tempOffset) {
|
||||
|
@ -24,8 +24,10 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Tile } from '@/application/types'
|
||||
import ChipsInput from '@/components/forms/ChipsInput.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { TileStorage } from '@/storage/storages'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
@ -56,7 +58,7 @@ watch(selectedTile, (tile: Tile | null) => {
|
||||
})
|
||||
|
||||
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) {
|
||||
console.error('Failed to delete tile')
|
||||
return
|
||||
@ -67,7 +69,7 @@ async function deleteTile() {
|
||||
}
|
||||
|
||||
function refreshTileList(unsetSelectedTile = true) {
|
||||
gameStore.connection?.emit('gm:tile:list', {}, (response: Tile[]) => {
|
||||
socketManager.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||
assetManagerStore.setTileList(response)
|
||||
|
||||
if (unsetSelectedTile) {
|
||||
@ -82,7 +84,7 @@ function saveTile() {
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit(
|
||||
socketManager.emit(
|
||||
'gm:tile:update',
|
||||
{
|
||||
id: selectedTile.value.id,
|
||||
|
@ -29,7 +29,9 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Tile } from '@/application/types'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useAssetManagerStore } from '@/stores/assetManagerStore'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useVirtualList } from '@vueuse/core'
|
||||
@ -47,13 +49,13 @@ const elementToScroll = ref()
|
||||
const handleFileUpload = (e: Event) => {
|
||||
const files = (e.target as HTMLInputElement).files
|
||||
if (!files) return
|
||||
gameStore.connection?.emit('gm:tile:upload', files, (response: boolean) => {
|
||||
socketManager.emit(SocketEvent.GM_TILE_UPLOAD, files, (response: boolean) => {
|
||||
if (!response) {
|
||||
if (config.environment === 'development') console.error('Failed to upload tile')
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:tile:list', {}, (response: Tile[]) => {
|
||||
socketManager.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||
assetManagerStore.setTileList(response)
|
||||
})
|
||||
})
|
||||
@ -92,7 +94,7 @@ function toTop() {
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
gameStore.connection?.emit('gm:tile:list', {}, (response: Tile[]) => {
|
||||
socketManager.emit(SocketEvent.GM_TILE_LIST, {}, (response: Tile[]) => {
|
||||
assetManagerStore.setTileList(response)
|
||||
})
|
||||
})
|
||||
|
@ -1,30 +1,141 @@
|
||||
<template>
|
||||
<MapTiles ref="mapTiles" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
||||
<PlacedMapObjects ref="mapObjects" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
||||
<MapEventTiles ref="eventTiles" v-if="tileMap" :tileMap />
|
||||
<MapTiles ref="mapTiles" @createCommand="addCommand" 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" @createCommand="addCommand" v-if="tileMap" :tileMap />
|
||||
</template>
|
||||
|
||||
<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 MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
|
||||
import PlacedMapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
|
||||
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 { useManualRefHistory, useRefHistory } from '@vueuse/core'
|
||||
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 tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
||||
|
||||
const mapEditor = useMapEditorComposable()
|
||||
|
||||
const scene = useScene()
|
||||
|
||||
const mapTiles = useTemplateRef('mapTiles')
|
||||
const mapObjects = useTemplateRef('mapObjects')
|
||||
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) {
|
||||
if (!mapTiles.value || !mapObjects.value || !eventTiles.value) return
|
||||
|
||||
@ -54,12 +165,12 @@ function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
//CTRL+Y
|
||||
if (event.key === 'y' && event.ctrlKey) {
|
||||
mapTiles.value!.redo()
|
||||
redoEdit()
|
||||
}
|
||||
|
||||
//CTRL+Z
|
||||
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) {
|
||||
if (mapEditor.drawMode.value === 'tile') {
|
||||
mapTiles.value?.finalizeCommand()
|
||||
switch (mapEditor.drawMode.value) {
|
||||
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
|
||||
if (!mapValue) return
|
||||
|
||||
//Clone
|
||||
originTiles = cloneArray(mapValue.tiles)
|
||||
originEventTiles = cloneArray(mapValue.mapEventTiles)
|
||||
|
||||
const tileStorage = new TileStorage()
|
||||
const allTiles = await tileStorage.getAll()
|
||||
const allTileIds = allTiles.map((tile) => tile.id)
|
||||
|
@ -5,20 +5,69 @@
|
||||
<script setup lang="ts">
|
||||
import { MapEventTileType, type MapEventTile, type Map as MapT, type UUID } from '@/application/types'
|
||||
import { uuidv4 } from '@/application/utilities'
|
||||
import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
|
||||
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 { shallowRef } from 'vue'
|
||||
|
||||
const mapEditor = useMapEditorComposable()
|
||||
|
||||
defineExpose({ handlePointer })
|
||||
defineExpose({ handlePointer, finalizeCommand, clearTiles })
|
||||
|
||||
const emit = defineEmits(['createCommand'])
|
||||
|
||||
const props = defineProps<{
|
||||
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) {
|
||||
return {
|
||||
@ -39,19 +88,17 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||
if (existingEventTile) return
|
||||
|
||||
// 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 = {
|
||||
id: uuidv4() as UUID,
|
||||
mapId: map.id,
|
||||
map: map.id,
|
||||
type: mapEditor.drawMode.value === 'blocking tile' ? MapEventTileType.BLOCK : MapEventTileType.TELEPORT,
|
||||
positionX: tile.x,
|
||||
positionY: tile.y,
|
||||
teleport:
|
||||
mapEditor.drawMode.value === 'teleport'
|
||||
? {
|
||||
toMap: mapEditor.teleportSettings.value.toMapId,
|
||||
toMap: mapEditor.teleportSettings.value.toMap,
|
||||
toPositionX: mapEditor.teleportSettings.value.toPositionX,
|
||||
toPositionY: mapEditor.teleportSettings.value.toPositionY,
|
||||
toRotation: mapEditor.teleportSettings.value.toRotation
|
||||
@ -59,7 +106,9 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||
: undefined
|
||||
}
|
||||
|
||||
map.mapEventTiles.push(newEventTile)
|
||||
createCommandUpdate(newEventTile as MapEventTile, 'draw')
|
||||
|
||||
map.mapEventTiles.push(newEventTile as MapEventTile)
|
||||
}
|
||||
|
||||
function erase(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||
@ -77,6 +126,8 @@ function erase(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||
else return
|
||||
}
|
||||
|
||||
createCommandUpdate(existingEventTile, 'erase')
|
||||
|
||||
// Remove existing event tile
|
||||
map.mapEventTiles = map.mapEventTiles.filter((eventTile) => eventTile.id !== existingEventTile.id)
|
||||
}
|
||||
@ -96,4 +147,10 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
function clearTiles() {
|
||||
if (mapEditor.currentMap.value?.mapEventTiles.length === 0) return
|
||||
createCommandUpdate(null, 'clear')
|
||||
finalizeCommand()
|
||||
}
|
||||
</script>
|
||||
|
@ -3,59 +3,74 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
|
||||
import Controls from '@/components/utilities/Controls.vue'
|
||||
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'
|
||||
|
||||
const mapEditor = useMapEditorComposable()
|
||||
|
||||
defineExpose({ handlePointer, finalizeCommand, undo, redo })
|
||||
defineExpose({ handlePointer, finalizeCommand, clearTiles })
|
||||
|
||||
const emit = defineEmits(['createCommand'])
|
||||
|
||||
const props = defineProps<{
|
||||
tileMap: Phaser.Tilemaps.Tilemap
|
||||
tileMapLayer: Phaser.Tilemaps.TilemapLayer
|
||||
}>()
|
||||
|
||||
class EditorCommand {
|
||||
public operation: 'draw' | 'erase' = 'draw'
|
||||
public tileName: string = 'blank_tile'
|
||||
public affectedTiles: number[][]
|
||||
// *** COMMAND STATE ***
|
||||
|
||||
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.tileName = tileName
|
||||
this.affectedTiles = []
|
||||
}
|
||||
}
|
||||
|
||||
//Record of commands
|
||||
let commandStack: EditorCommand[] = []
|
||||
let currentCommand: EditorCommand | null = null
|
||||
let commandIndex = ref(0)
|
||||
let originTiles: string[][] = []
|
||||
function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase' | 'clear') {
|
||||
if (!currentCommand) {
|
||||
currentCommand = new TileCommand(operation, tileName)
|
||||
}
|
||||
|
||||
function pencil(pointer: Phaser.Input.Pointer) {
|
||||
let map = mapEditor.currentMap.value
|
||||
if (!map) return
|
||||
//If position is already in, do not proceed
|
||||
for (const vec of currentCommand.affectedTiles) {
|
||||
if (vec[0] === x && vec[1] === y) 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
|
||||
currentCommand.affectedTiles.push([x, y])
|
||||
}
|
||||
|
||||
function eraser(pointer: Phaser.Input.Pointer) {
|
||||
function finalizeCommand() {
|
||||
if (!currentCommand) return
|
||||
emit('createCommand', currentCommand)
|
||||
currentCommand = null
|
||||
}
|
||||
|
||||
// *** HANDLERS ***
|
||||
|
||||
function draw(pointer: Phaser.Input.Pointer, tileName: string) {
|
||||
let map = mapEditor.currentMap.value
|
||||
if (!map) return
|
||||
|
||||
@ -64,12 +79,12 @@ function eraser(pointer: Phaser.Input.Pointer) {
|
||||
if (!tile) return
|
||||
|
||||
// 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
|
||||
map.tiles[tile.y][tile.x] = 'blank_tile'
|
||||
map.tiles[tile.y][tile.x] = tileName
|
||||
}
|
||||
|
||||
function paint(pointer: Phaser.Input.Pointer) {
|
||||
@ -113,10 +128,10 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
||||
// Check if draw mode is tile
|
||||
switch (mapEditor.tool.value) {
|
||||
case 'pencil':
|
||||
pencil(pointer)
|
||||
draw(pointer, mapEditor.selectedTile.value!)
|
||||
break
|
||||
case 'eraser':
|
||||
eraser(pointer)
|
||||
draw(pointer, 'blank_tile')
|
||||
break
|
||||
case 'paint':
|
||||
paint(pointer)
|
||||
@ -124,90 +139,19 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
||||
}
|
||||
}
|
||||
|
||||
function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase') {
|
||||
if (!currentCommand) {
|
||||
currentCommand = new EditorCommand(operation, tileName)
|
||||
}
|
||||
// *** LIFECYCLE ***
|
||||
|
||||
//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 clearTiles() {
|
||||
const tileArray = createTileArray(props.tileMap.width, props.tileMap.height, 'blank_tile')
|
||||
placeTiles(props.tileMap, props.tileMapLayer, tileArray)
|
||||
createCommandUpdate(0, 0, 'blank_tile', 'clear')
|
||||
finalizeCommand()
|
||||
}
|
||||
|
||||
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 () => {
|
||||
if (!mapEditor.currentMap.value) return
|
||||
const mapState = mapEditor.currentMap.value
|
||||
|
||||
//Clone
|
||||
originTiles = cloneArray(mapState.tiles)
|
||||
|
||||
placeTiles(props.tileMap, props.tileMapLayer, mapState.tiles)
|
||||
})
|
||||
</script>
|
||||
|
@ -1,4 +1,11 @@
|
||||
<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" />
|
||||
<PlacedMapObject v-for="placedMapObject in mapEditor.currentMap.value?.placedMapObjects" :tileMap :tileMapLayer :placedMapObject @pointerdown="clickPlacedMapObject(placedMapObject)" />
|
||||
</template>
|
||||
@ -11,7 +18,7 @@ import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { getTile } from '@/services/mapService'
|
||||
import { useScene } from 'phavuer'
|
||||
import { computed } from 'vue'
|
||||
import { computed, onMounted, onUnmounted, ref, watch } from 'vue'
|
||||
|
||||
import Tilemap = Phaser.Tilemaps.Tilemap
|
||||
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
|
||||
@ -20,6 +27,8 @@ const scene = useScene()
|
||||
const mapEditor = useMapEditorComposable()
|
||||
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 })
|
||||
|
||||
const props = defineProps<{
|
||||
@ -27,36 +36,59 @@ const props = defineProps<{
|
||||
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) {
|
||||
emit('pauseObjectTracking')
|
||||
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
||||
if (!tile) return
|
||||
|
||||
// Check if object already exists on position
|
||||
const existingPlacedMapObject = findObjectByPointer(pointer, map)
|
||||
const existingPlacedMapObject = findObjectByPointer(pointer, mapEditor.currentMap.value!)
|
||||
if (existingPlacedMapObject) return
|
||||
|
||||
if (!mapEditor.selectedMapObject.value) return
|
||||
|
||||
const newPlacedMapObject: PlacedMapObjectT = {
|
||||
id: uuidv4(),
|
||||
mapObject: mapEditor.selectedMapObject.value,
|
||||
mapObject: mapEditor.selectedMapObject.value.id,
|
||||
isRotated: false,
|
||||
positionX: tile.x,
|
||||
positionY: tile.y
|
||||
}
|
||||
|
||||
// Add new object to mapObjects
|
||||
map.placedMapObjects.push(newPlacedMapObject)
|
||||
mapEditor.selectedPlacedObject.value = newPlacedMapObject
|
||||
map.placedMapObjects.push(newPlacedMapObject)
|
||||
|
||||
emit('update', map)
|
||||
}
|
||||
|
||||
function eraser(pointer: Phaser.Input.Pointer, map: MapT) {
|
||||
emit('pauseObjectTracking')
|
||||
|
||||
// Check if object already exists on position
|
||||
const existingPlacedMapObject = findObjectByPointer(pointer, map)
|
||||
if (!existingPlacedMapObject) return
|
||||
|
||||
// Remove existing object
|
||||
map.placedMapObjects = map.placedMapObjects.filter((placedMapObject) => placedMapObject.id !== existingPlacedMapObject.id)
|
||||
|
||||
emit('update', map)
|
||||
}
|
||||
|
||||
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) {
|
||||
mapEditor.movingPlacedObject.value = map.placedMapObjects.find((object) => object.id === id) as PlacedMapObjectT
|
||||
|
||||
emit('pauseObjectTracking')
|
||||
|
||||
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
||||
if (!mapEditor.movingPlacedObject.value) return
|
||||
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)
|
||||
|
||||
function handlePointerUp() {
|
||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||
mapEditor.movingPlacedObject.value = null
|
||||
}
|
||||
|
||||
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) {
|
||||
const matchingObject = map.placedMapObjects.find((placedMapObject) => placedMapObject.id === id)
|
||||
matchingObject!.isRotated = !matchingObject!.isRotated
|
||||
map.placedMapObjects.map((placed) => {
|
||||
if (placed.id === id) {
|
||||
placed.isRotated = !placed.isRotated
|
||||
}
|
||||
})
|
||||
|
||||
emit('updateAndCommit', map)
|
||||
}
|
||||
|
||||
function deletePlacedMapObject(id: string, map: MapT) {
|
||||
let mapE = mapEditor.currentMap.value!
|
||||
mapE.placedMapObjects = map.placedMapObjects.filter((object) => object.id !== id)
|
||||
map.placedMapObjects = map.placedMapObjects.filter((object) => object.id !== id)
|
||||
mapEditor.selectedPlacedObject.value = null
|
||||
emit('updateAndCommit', map)
|
||||
}
|
||||
|
||||
function clickPlacedMapObject(placedMapObject: PlacedMapObjectT) {
|
||||
@ -137,4 +190,12 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
scene.input.on(Phaser.Input.Events.POINTER_MOVE, updatePreviewPosition)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, updatePreviewPosition)
|
||||
})
|
||||
</script>
|
||||
|
@ -35,9 +35,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { Map } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { MapStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { ref, useTemplateRef } from 'vue'
|
||||
@ -56,7 +58,7 @@ const pvp = ref(false)
|
||||
defineExpose({ open: () => modalRef.value?.open() })
|
||||
|
||||
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) {
|
||||
return
|
||||
}
|
||||
|
@ -29,13 +29,14 @@
|
||||
</template>
|
||||
|
||||
<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 Modal from '@/components/utilities/Modal.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { MapStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useMapEditorStore } from '@/stores/mapEditorStore'
|
||||
import { onMounted, ref, useTemplateRef } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
@ -60,15 +61,15 @@ async function fetchMaps() {
|
||||
mapList.value = await mapStorage.getAll()
|
||||
}
|
||||
|
||||
function loadMap(id: UUID) {
|
||||
gameStore.connection?.emit('gm:map:request', { mapId: id }, (response: Map) => {
|
||||
function loadMap(id: string) {
|
||||
socketManager.emit(SocketEvent.GM_MAP_REQUEST, { mapId: id }, (response: Map) => {
|
||||
mapEditor.loadMap(response)
|
||||
})
|
||||
modalRef.value?.close()
|
||||
}
|
||||
|
||||
async function deleteMap(id: UUID) {
|
||||
gameStore.connection?.emit('gm:map:delete', { mapId: id }, async (response: boolean) => {
|
||||
async function deleteMap(id: string) {
|
||||
socketManager.emit(SocketEvent.GM_MAP_DELETE, { mapId: id }, async (response: boolean) => {
|
||||
if (!response) {
|
||||
gameStore.addNotification({
|
||||
title: 'Error',
|
||||
|
@ -1,13 +1,18 @@
|
||||
<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="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" />
|
||||
<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" />
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex-grow">
|
||||
<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" />
|
||||
<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" />
|
||||
</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">
|
||||
<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="map_object">Objects</option>
|
||||
</select>
|
||||
|
@ -41,9 +41,11 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import type { MapObject, Map as MapT, PlacedMapObject } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { MapObjectStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, ref } from 'vue'
|
||||
@ -80,8 +82,8 @@ const handleDelete = () => {
|
||||
async function handleUpdate() {
|
||||
if (!mapObject.value) return
|
||||
|
||||
gameStore.connection?.emit(
|
||||
'gm:mapObject:update',
|
||||
socketManager.emit(
|
||||
SocketEvent.GM_MAPOBJECT_UPDATE,
|
||||
{
|
||||
id: props.placedMapObject.mapObject as string,
|
||||
name: mapObjectName.value,
|
||||
|
@ -1,5 +1,5 @@
|
||||
<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>
|
||||
<h3 class="m-0 font-medium shrink-0 text-white">Teleport settings</h3>
|
||||
</template>
|
||||
@ -28,7 +28,7 @@
|
||||
<label for="toMap">Map to teleport to</label>
|
||||
<select v-model="toMap" class="input-field" name="toMap" id="toMap">
|
||||
<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>
|
||||
</div>
|
||||
</div>
|
||||
@ -41,48 +41,48 @@
|
||||
<script setup lang="ts">
|
||||
import type { Map } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useMapEditorStore } from '@/stores/mapEditorStore'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { MapStorage } from '@/storage/storages'
|
||||
import { computed, onMounted, ref, useTemplateRef, watch } from 'vue'
|
||||
|
||||
const showTeleportModal = computed(() => mapEditorStore.tool === 'pencil' && mapEditorStore.drawMode === 'teleport')
|
||||
const mapEditorStore = useMapEditorStore()
|
||||
const gameStore = useGameStore()
|
||||
const mapList = ref<Map[]>([])
|
||||
const showTeleportModal = computed(() => mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'teleport')
|
||||
const mapStorage = new MapStorage()
|
||||
const mapEditor = useMapEditorComposable()
|
||||
const modalRef = useTemplateRef('modalRef')
|
||||
const mapList = ref<Map[]>([])
|
||||
|
||||
defineExpose({
|
||||
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()
|
||||
|
||||
function useRefTeleportSettings() {
|
||||
const settings = mapEditorStore.teleportSettings
|
||||
const settings = mapEditor.teleportSettings.value
|
||||
return {
|
||||
toPositionX: ref(settings.toPositionX),
|
||||
toPositionY: ref(settings.toPositionY),
|
||||
toRotation: ref(settings.toRotation),
|
||||
toMap: ref(settings.toMapId)
|
||||
toMap: ref(settings.toMap)
|
||||
}
|
||||
}
|
||||
|
||||
watch([toPositionX, toPositionY, toRotation, toMap], updateTeleportSettings)
|
||||
|
||||
function updateTeleportSettings() {
|
||||
mapEditorStore.setTeleportSettings({
|
||||
mapEditor.setTeleportSettings({
|
||||
toPositionX: toPositionX.value,
|
||||
toPositionY: toPositionY.value,
|
||||
toRotation: toRotation.value,
|
||||
toMapId: toMap.value
|
||||
toMap: toMap.value
|
||||
})
|
||||
}
|
||||
|
||||
async function fetchMaps() {
|
||||
mapList.value = await mapStorage.getAll()
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchMaps()
|
||||
})
|
||||
</script>
|
||||
|
@ -1,13 +1,18 @@
|
||||
<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="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" />
|
||||
<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" />
|
||||
<div class="flex justify-between items-center">
|
||||
<div class="flex-grow">
|
||||
<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" />
|
||||
<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" />
|
||||
</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">
|
||||
<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="map_object">Objects</option>
|
||||
</select>
|
||||
@ -37,8 +42,9 @@
|
||||
</div>
|
||||
<div v-else class="h-full overflow-auto">
|
||||
<div class="p-4">
|
||||
<button @click="closeGroup" class="btn-cyan mb-4">Back to All Tiles</button>
|
||||
<h4 class="text-lg mb-4">{{ selectedGroup.parent.name }} Group</h4>
|
||||
<div class="text-center mb-8">
|
||||
<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="flex flex-col items-center justify-center">
|
||||
<img
|
||||
|
@ -68,7 +68,7 @@
|
||||
|
||||
<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>
|
||||
|
||||
@ -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('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('close-editor')">Exit</button>
|
||||
<button class="btn-cyan px-3.5" @click="() => mapEditor.toggleActive()">Exit</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -89,9 +89,13 @@
|
||||
</template>
|
||||
<template #modalBody>
|
||||
<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>
|
||||
</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>
|
||||
</Modal>
|
||||
</template>
|
||||
@ -100,19 +104,20 @@
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||
import { onClickOutside } from '@vueuse/core'
|
||||
import { onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
import { computed, onBeforeUnmount, onMounted, ref } from 'vue'
|
||||
|
||||
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
|
||||
const toolbar = ref(null)
|
||||
const isMapEditorSettingsModalOpen = ref(false)
|
||||
const selectPencilOpen = ref(false)
|
||||
const selectEraserOpen = ref(false)
|
||||
const checkboxValue = ref<Boolean>(false)
|
||||
const listOpen = ref(false)
|
||||
const isContinuousDrawingEnabled = ref<Boolean>(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
|
||||
function setDrawMode(value: string) {
|
||||
@ -132,8 +137,8 @@ function setEraserMode() {
|
||||
selectEraserOpen.value = false
|
||||
}
|
||||
|
||||
function handleCheck() {
|
||||
mapEditor.setInputMode(checkboxValue.value ? 'hold' : 'tap')
|
||||
function toggleContinuousDrawing() {
|
||||
mapEditor.setInputMode(isContinuousDrawingEnabled.value ? 'hold' : 'tap')
|
||||
}
|
||||
|
||||
function handleModeClick(mode: string, type: 'pencil' | 'eraser') {
|
||||
@ -142,20 +147,11 @@ function handleModeClick(mode: string, type: 'pencil' | 'eraser') {
|
||||
}
|
||||
|
||||
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)
|
||||
} else {
|
||||
mapEditor.setTool(tool)
|
||||
}
|
||||
|
||||
mapEditor.setTool(tool)
|
||||
if (tool === 'paint') mapEditor.setDrawMode('tile')
|
||||
selectPencilOpen.value = tool === 'pencil' ? !selectPencilOpen.value : false
|
||||
selectEraserOpen.value = tool === 'eraser' ? !selectEraserOpen.value : false
|
||||
if (mapEditor.drawMode.value === 'teleport') emit('open-teleport')
|
||||
}
|
||||
|
||||
function cycleToolMode(tool: 'pencil' | 'eraser') {
|
||||
@ -171,7 +167,7 @@ function initKeyShortcuts(event: KeyboardEvent) {
|
||||
// Check if map is set
|
||||
if (!mapEditor.currentMap.value) return
|
||||
|
||||
// prevent if focused on composables
|
||||
// prevent if focused on inputs
|
||||
if (document.activeElement?.tagName === 'INPUT') return
|
||||
|
||||
if (event.ctrlKey) return
|
||||
|
@ -26,6 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { login } from '@/services/authenticationService'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||
@ -39,15 +40,6 @@ const password = ref('')
|
||||
const formError = ref('')
|
||||
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() {
|
||||
// check if username and password are valid
|
||||
if (username.value === '' || password.value === '') {
|
||||
@ -62,7 +54,7 @@ async function submit() {
|
||||
formError.value = response.error
|
||||
return
|
||||
}
|
||||
gameStore.setToken(response.token)
|
||||
socketManager.setToken(response.token)
|
||||
gameStore.initConnection()
|
||||
return true // Indicate success
|
||||
}
|
||||
|
@ -34,15 +34,6 @@ const password = ref('')
|
||||
const newPasswordError = ref('')
|
||||
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() {
|
||||
// check if username and password are valid
|
||||
if (password.value === '') {
|
||||
|
@ -26,6 +26,7 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { login, register } from '@/services/authenticationService'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { useCookies } from '@vueuse/integrations/useCookies'
|
||||
@ -40,15 +41,6 @@ const email = ref('')
|
||||
const formError = ref('')
|
||||
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() {
|
||||
// check if username and password are valid
|
||||
if (username.value === '' || email.value === '' || password.value === '') {
|
||||
@ -76,7 +68,7 @@ async function submit() {
|
||||
return
|
||||
}
|
||||
|
||||
gameStore.setToken(loginResponse.token)
|
||||
socketManager.setToken(loginResponse.token)
|
||||
gameStore.initConnection()
|
||||
}
|
||||
</script>
|
||||
|
@ -122,9 +122,11 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import { type CharacterHair, type Character as CharacterT, type Map } from '@/application/types'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { useSoundComposable } from '@/composables/useSoundComposable'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { CharacterHairStorage } from '@/storage/storages'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onBeforeUnmount, onMounted, ref, watch } from 'vue'
|
||||
@ -141,10 +143,10 @@ const selectedHairId = ref<string | null>(null)
|
||||
|
||||
// Fetch characters
|
||||
setTimeout(() => {
|
||||
gameStore.connection?.emit('character:list')
|
||||
socketManager.emit(SocketEvent.CHARACTER_LIST)
|
||||
}, 750)
|
||||
|
||||
gameStore.connection?.on('character:list', (data: any) => {
|
||||
socketManager.on(SocketEvent.CHARACTER_LIST, (data: any) => {
|
||||
characters.value = data
|
||||
isLoading.value = false
|
||||
})
|
||||
@ -153,8 +155,8 @@ gameStore.connection?.on('character:list', (data: any) => {
|
||||
function loginWithCharacter() {
|
||||
if (!selectedCharacterId.value) return
|
||||
|
||||
gameStore.connection?.emit(
|
||||
'character:connect',
|
||||
socketManager.emit(
|
||||
SocketEvent.CHARACTER_CONNECT,
|
||||
{
|
||||
characterId: selectedCharacterId.value,
|
||||
characterHairId: selectedHairId.value
|
||||
@ -167,7 +169,7 @@ function loginWithCharacter() {
|
||||
|
||||
// Create character logics
|
||||
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
|
||||
isCreateNewCharacterModalOpen.value = false
|
||||
})
|
||||
@ -176,7 +178,7 @@ function createCharacter() {
|
||||
// Watch changes for selected character and update hairs
|
||||
watch(selectedCharacterId, (characterId) => {
|
||||
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 () => {
|
||||
@ -186,8 +188,8 @@ onMounted(async () => {
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
gameStore.connection?.off('character:list')
|
||||
gameStore.connection?.off('character:connect')
|
||||
gameStore.connection?.off('character:create:success')
|
||||
socketManager.off(SocketEvent.CHARACTER_LIST)
|
||||
socketManager.off(SocketEvent.CHARACTER_CONNECT)
|
||||
socketManager.off(SocketEvent.CHARACTER_CREATE)
|
||||
})
|
||||
</script>
|
||||
|
@ -44,13 +44,4 @@ function switchToLogin() {
|
||||
currentForm.value = 'login'
|
||||
doesUrlHaveToken.value = false
|
||||
}
|
||||
|
||||
// automatic login because of development
|
||||
onMounted(async () => {
|
||||
const token = useCookies().get('token')
|
||||
if (token) {
|
||||
gameStore.setToken(token)
|
||||
gameStore.initConnection()
|
||||
}
|
||||
})
|
||||
</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-else>
|
||||
<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" />
|
||||
<TileList v-if="mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'tile'" />
|
||||
<MapObjectList v-if="mapEditor.tool.value === 'pencil' && mapEditor.drawMode.value === 'map_object'" />
|
||||
<TileList />
|
||||
<MapObjectList />
|
||||
<MapSettings ref="mapSettingsModal" />
|
||||
<TeleportModal ref="teleportModal" />
|
||||
</div>
|
||||
@ -19,6 +19,8 @@
|
||||
|
||||
<script setup lang="ts">
|
||||
import config from '@/application/config'
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import 'phaser'
|
||||
import type { Map as MapT } from '@/application/types'
|
||||
import Map from '@/components/gameMaster/mapEditor/Map.vue'
|
||||
@ -41,6 +43,7 @@ const gameStore = useGameStore()
|
||||
|
||||
const mapModal = useTemplateRef('mapModal')
|
||||
const mapSettingsModal = useTemplateRef('mapSettingsModal')
|
||||
const teleportModal = useTemplateRef('teleportModal')
|
||||
|
||||
const isLoaded = ref(false)
|
||||
|
||||
@ -86,18 +89,11 @@ function save() {
|
||||
if (!currentMap) return
|
||||
|
||||
const data = {
|
||||
mapId: currentMap.id,
|
||||
name: currentMap.name,
|
||||
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 })) ?? []
|
||||
...currentMap,
|
||||
mapId: currentMap.id
|
||||
}
|
||||
|
||||
gameStore.connection?.emit('gm:map:update', data, (response: MapT) => {
|
||||
socketManager.emit(SocketEvent.GM_MAP_UPDATE, data, (response: MapT) => {
|
||||
mapStorage.update(response.id, response)
|
||||
})
|
||||
}
|
||||
@ -106,7 +102,6 @@ function clear() {
|
||||
if (!mapEditor.currentMap.value) return
|
||||
|
||||
// Clear placed objects, event tiles and tiles
|
||||
mapEditor.clearMap()
|
||||
mapEditor.triggerClearTiles()
|
||||
}
|
||||
</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>
|
||||
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
CharacterHairStorage,
|
||||
CharacterTypeStorage,
|
||||
MapObjectStorage,
|
||||
MapStorage,
|
||||
SoundStorage,
|
||||
SpriteStorage,
|
||||
TileStorage
|
||||
} from '@/storage/storages'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { login } from '@/services/authenticationService'
|
||||
import { CharacterHairStorage, CharacterTypeStorage, MapObjectStorage, MapStorage, SoundStorage, SpriteStorage, TileStorage } from '@/storage/storages'
|
||||
import { TextureStorage } from '@/storage/textureStorage'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onMounted, onUnmounted } from 'vue'
|
||||
|
||||
const gameStore = useGameStore()
|
||||
const mapStorage = new MapStorage()
|
||||
const tileStorage = new TileStorage()
|
||||
const mapObjectStorage = new MapObjectStorage()
|
||||
@ -45,6 +41,17 @@ async function handleKeyPress(event: KeyboardEvent) {
|
||||
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
|
||||
setTimeout(() => {
|
||||
currentString = ''
|
||||
|
@ -10,7 +10,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import Modal from '@/components/utilities/Modal.vue'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { onBeforeMount, onBeforeUnmount, onMounted, onUnmounted, watch } from 'vue'
|
||||
|
||||
@ -26,7 +28,7 @@ type Notification = {
|
||||
}
|
||||
|
||||
function setupNotificationListener(connection: any) {
|
||||
connection.on('notification', (data: Notification) => {
|
||||
connection.on(SocketEvent.NOTIFICATION, (data: Notification) => {
|
||||
gameStore.addNotification({
|
||||
title: data.title,
|
||||
message: data.message
|
||||
@ -52,7 +54,7 @@ onMounted(() => {
|
||||
onUnmounted(() => {
|
||||
const connection = gameStore.connection
|
||||
if (connection) {
|
||||
connection.off('notification')
|
||||
connection.off(SocketEvent.NOTIFICATION)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { SocketEvent } from '@/application/enums'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { getTile } from '@/services/mapService'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
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) {
|
||||
const gameStore = useGameStore()
|
||||
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) {
|
||||
baseHandlers.startDragging(pointer)
|
||||
}
|
||||
@ -22,77 +94,37 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
|
||||
const pointerTile = getTile(layer, pointer.worldX, pointer.worldY)
|
||||
if (!pointerTile) return
|
||||
|
||||
gameStore.connection?.emit('map:character:move', {
|
||||
positionX: pointerTile.x,
|
||||
positionY: pointerTile.y
|
||||
})
|
||||
emitMovement(pointerTile.x, pointerTile.y)
|
||||
}
|
||||
|
||||
const pressedKeys = new Set<string>()
|
||||
let moveInterval: number | null = null
|
||||
|
||||
// Keyboard Handlers
|
||||
function handleKeyDown(event: KeyboardEvent) {
|
||||
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)
|
||||
|
||||
// Start movement loop if not already running
|
||||
if (!moveInterval) {
|
||||
moveInterval = window.setInterval(moveCharacter, 100) // Adjust timing as needed
|
||||
moveCharacter() // Move immediately on first press
|
||||
}
|
||||
updateCurrentPosition()
|
||||
startMovementLoop()
|
||||
}
|
||||
|
||||
// Attack on CTRL
|
||||
if (event.key === 'Control') {
|
||||
gameStore.connection?.emit('map:character:attack')
|
||||
socketManager.emit(SocketEvent.MAP_CHARACTER_ATTACK)
|
||||
}
|
||||
}
|
||||
|
||||
function handleKeyUp(event: KeyboardEvent) {
|
||||
pressedKeys.delete(event.key)
|
||||
|
||||
// If no movement keys are pressed, clear the interval
|
||||
if (pressedKeys.size === 0 && moveInterval) {
|
||||
clearInterval(moveInterval)
|
||||
moveInterval = null
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
})
|
||||
if (pressedKeys.size === 0) {
|
||||
stopMovementLoop()
|
||||
}
|
||||
}
|
||||
|
||||
const setupControls = () => {
|
||||
updateCurrentPosition() // Initialize position
|
||||
|
||||
scene.input.on(Phaser.Input.Events.POINTER_DOWN, handlePointerDown)
|
||||
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||
@ -102,6 +134,9 @@ export function useGameControlsComposable(scene: Phaser.Scene, layer: Phaser.Til
|
||||
}
|
||||
|
||||
const cleanupControls = () => {
|
||||
stopMovementLoop()
|
||||
pressedKeys.clear()
|
||||
|
||||
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_UP, handlePointerUp)
|
||||
|
@ -18,7 +18,7 @@ export function useCharacterSpriteComposable(scene: Phaser.Scene, tilemap: Phase
|
||||
const tween = ref<Phaser.Tweens.Tween | null>(null)
|
||||
|
||||
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) => {
|
||||
|
@ -1,8 +1,9 @@
|
||||
import type { Map, MapObject, PlacedMapObject, UUID } from '@/application/types'
|
||||
import { useGameStore } from '@/stores/gameStore'
|
||||
import { ref } from 'vue'
|
||||
|
||||
export type TeleportSettings = {
|
||||
toMapId: string
|
||||
toMap: string
|
||||
toPositionX: number
|
||||
toPositionY: number
|
||||
toRotation: number
|
||||
@ -14,12 +15,13 @@ const tool = ref('move')
|
||||
const drawMode = ref('tile')
|
||||
const inputMode = ref('tap')
|
||||
const selectedTile = ref('')
|
||||
const isPlacedMapObjectPreviewEnabled = ref(true)
|
||||
const selectedMapObject = ref<MapObject | null>(null)
|
||||
const movingPlacedObject = ref<PlacedMapObject | null>(null)
|
||||
const selectedPlacedObject = ref<PlacedMapObject | null>(null)
|
||||
const shouldClearTiles = ref(false)
|
||||
const teleportSettings = ref<TeleportSettings>({
|
||||
toMapId: '1000',
|
||||
toMap: '1000',
|
||||
toPositionX: 0,
|
||||
toPositionY: 0,
|
||||
toRotation: 0
|
||||
@ -42,15 +44,15 @@ export function useMapEditorComposable() {
|
||||
}
|
||||
}
|
||||
|
||||
const clearMap = () => {
|
||||
if (!currentMap.value) return
|
||||
currentMap.value.placedMapObjects = []
|
||||
currentMap.value.mapEventTiles = []
|
||||
}
|
||||
|
||||
const toggleActive = () => {
|
||||
if (active.value) reset()
|
||||
active.value = !active.value
|
||||
const gameStore = useGameStore()
|
||||
gameStore.uiSettings.isGmPanelOpen = false
|
||||
}
|
||||
|
||||
const togglePlacedMapObjectPreview = () => {
|
||||
isPlacedMapObjectPreviewEnabled.value = !isPlacedMapObjectPreviewEnabled.value
|
||||
}
|
||||
|
||||
const setTool = (newTool: string) => {
|
||||
@ -94,7 +96,9 @@ export function useMapEditorComposable() {
|
||||
drawMode.value = 'tile'
|
||||
inputMode.value = 'tap'
|
||||
selectedTile.value = ''
|
||||
isPlacedMapObjectPreviewEnabled.value = false
|
||||
selectedMapObject.value = null
|
||||
selectedPlacedObject.value = null
|
||||
shouldClearTiles.value = false
|
||||
refreshMapObject.value = 0
|
||||
}
|
||||
@ -107,6 +111,7 @@ export function useMapEditorComposable() {
|
||||
drawMode,
|
||||
inputMode,
|
||||
selectedTile,
|
||||
isPlacedMapObjectPreviewEnabled,
|
||||
selectedMapObject,
|
||||
movingPlacedObject,
|
||||
selectedPlacedObject,
|
||||
@ -117,12 +122,12 @@ export function useMapEditorComposable() {
|
||||
// Methods
|
||||
loadMap,
|
||||
updateProperty,
|
||||
clearMap,
|
||||
toggleActive,
|
||||
setTool,
|
||||
setDrawMode,
|
||||
setInputMode,
|
||||
setSelectedTile,
|
||||
togglePlacedMapObjectPreview,
|
||||
setSelectedMapObject,
|
||||
setTeleportSettings,
|
||||
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))
|
||||
}
|
||||
|
||||
export const calculateIsometricDepth = (positionX: number, positionY: number, width: number = 0, height: number = 0, isCharacter: boolean = false) => {
|
||||
const baseDepth = positionX + positionY
|
||||
if (isCharacter) {
|
||||
return baseDepth
|
||||
}
|
||||
return baseDepth + (width + height) / (2 * config.tile_size.width)
|
||||
export const calculateIsometricDepth = (positionX: number, positionY: number, pivotPoints: { x: number; y: number }[] = []) => {
|
||||
return Math.max(positionX + positionY)
|
||||
}
|
||||
|
||||
async function loadTileTextures(tiles: TileT[], scene: Phaser.Scene) {
|
||||
@ -151,3 +147,8 @@ export function createTileLayer(tileMap: Phaser.Tilemaps.Tilemap, tilesArray: st
|
||||
|
||||
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 { useCookies } from '@vueuse/integrations/useCookies'
|
||||
import { socketManager } from '@/managers/SocketManager'
|
||||
import { defineStore } from 'pinia'
|
||||
import { io, Socket } from 'socket.io-client'
|
||||
|
||||
export const useGameStore = defineStore('game', {
|
||||
state: () => {
|
||||
return {
|
||||
notifications: [] as Notification[],
|
||||
token: '',
|
||||
connection: null as Socket | null,
|
||||
user: null as User | null,
|
||||
character: null as Character | null,
|
||||
world: {
|
||||
@ -50,9 +47,6 @@ export const useGameStore = defineStore('game', {
|
||||
removeNotification(id: string) {
|
||||
this.notifications = this.notifications.filter((notification: Notification) => notification.id !== id)
|
||||
},
|
||||
setToken(token: string) {
|
||||
this.token = token
|
||||
},
|
||||
setUser(user: User | null) {
|
||||
this.user = user
|
||||
},
|
||||
@ -72,40 +66,34 @@ export const useGameStore = defineStore('game', {
|
||||
this.uiSettings.isCharacterProfileOpen = !this.uiSettings.isCharacterProfileOpen
|
||||
},
|
||||
initConnection() {
|
||||
this.connection = io(config.server_endpoint, {
|
||||
secure: config.environment === 'production',
|
||||
withCredentials: true,
|
||||
transports: ['websocket'],
|
||||
reconnectionAttempts: 5
|
||||
})
|
||||
const socket = socketManager.initConnection()
|
||||
|
||||
// #99 - If we can't connect, disconnect
|
||||
this.connection.on('connect_error', () => {
|
||||
// Handle connect error
|
||||
socket.on(SocketEvent.CONNECT_ERROR, () => {
|
||||
this.disconnectSocket()
|
||||
})
|
||||
|
||||
// Let the server know the user is logged in
|
||||
this.connection.emit('login')
|
||||
// Handle failed reconnection
|
||||
socket.on(SocketEvent.RECONNECT_FAILED, () => {
|
||||
this.disconnectSocket()
|
||||
})
|
||||
|
||||
// set user
|
||||
this.connection.on('logged_in', (user: User) => {
|
||||
// Emit login event
|
||||
socketManager.emit(SocketEvent.LOGIN)
|
||||
|
||||
// Handle logged in event
|
||||
socketManager.on(SocketEvent.LOGGED_IN, (user: User) => {
|
||||
this.setUser(user)
|
||||
})
|
||||
|
||||
// When we can't reconnect, disconnect
|
||||
this.connection.on('reconnect_failed', () => {
|
||||
this.disconnectSocket()
|
||||
// Handle date updates
|
||||
socketManager.on(SocketEvent.DATE, (data: Date) => {
|
||||
this.world.date = new Date(data)
|
||||
})
|
||||
},
|
||||
disconnectSocket() {
|
||||
this.connection?.disconnect()
|
||||
socketManager.disconnect()
|
||||
|
||||
useCookies().remove('token', {
|
||||
domain: config.domain
|
||||
})
|
||||
|
||||
this.connection = null
|
||||
this.token = ''
|
||||
this.user = null
|
||||
this.character = null
|
||||
|
||||
|
@ -2,7 +2,7 @@ import type { MapObject, Map as MapT } from '@/application/types'
|
||||
import { defineStore } from 'pinia'
|
||||
|
||||
export type TeleportSettings = {
|
||||
toMapId: string
|
||||
toMap: string
|
||||
toPositionX: number
|
||||
toPositionY: number
|
||||
toRotation: number
|
||||
@ -18,7 +18,7 @@ export const useMapEditorStore = defineStore('mapEditor', {
|
||||
selectedMapObject: null as MapObject | null,
|
||||
shouldClearTiles: false,
|
||||
teleportSettings: {
|
||||
toMapId: '',
|
||||
toMap: '',
|
||||
toPositionX: 0,
|
||||
toPositionY: 0,
|
||||
toRotation: 0
|
||||
|
@ -5,8 +5,7 @@ export const useMapStore = defineStore('map', {
|
||||
state: () => {
|
||||
return {
|
||||
mapId: '',
|
||||
characters: [] as MapCharacter[],
|
||||
characterLoaded: false
|
||||
characters: [] as MapCharacter[]
|
||||
}
|
||||
},
|
||||
getters: {
|
||||
@ -36,22 +35,18 @@ export const useMapStore = defineStore('map', {
|
||||
removeCharacter(characterId: UUID) {
|
||||
this.characters = this.characters.filter((char) => char.character.id !== characterId)
|
||||
},
|
||||
setCharacterLoaded(loaded: boolean) {
|
||||
this.characterLoaded = loaded
|
||||
},
|
||||
updateCharacterPosition(data: { characterId: UUID; positionX: number; positionY: number; rotation: number; isMoving: boolean }) {
|
||||
const character = this.characters.find((char) => char.character.id === data.characterId)
|
||||
updateCharacterPosition([characterId, posX, posY, rot, isMoving]: [UUID, number, number, number, boolean]) {
|
||||
const character = this.characters.find((char) => char.character.id === characterId)
|
||||
if (character) {
|
||||
character.character.positionX = data.positionX
|
||||
character.character.positionY = data.positionY
|
||||
character.character.rotation = data.rotation
|
||||
character.isMoving = data.isMoving
|
||||
character.character.positionX = posX
|
||||
character.character.positionY = posY
|
||||
character.character.rotation = rot
|
||||
character.isMoving = isMoving
|
||||
}
|
||||
},
|
||||
reset() {
|
||||
this.mapId = ''
|
||||
this.characters = []
|
||||
this.characterLoaded = false
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -2,13 +2,37 @@ import { fileURLToPath, URL } from 'node:url'
|
||||
import { defineConfig } from 'vite'
|
||||
import vue from '@vitejs/plugin-vue';
|
||||
import viteCompression from 'vite-plugin-compression';
|
||||
import {ViteImageOptimizer} from "vite-plugin-image-optimizer";
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
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: {
|
||||
alias: {
|
||||
'@': fileURLToPath(new URL('./src', import.meta.url))
|
||||
|
Reference in New Issue
Block a user