241 lines
7.7 KiB
Vue
241 lines
7.7 KiB
Vue
<template>
|
|
<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 { 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, 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
|
|
|
|
// Check if left mouse button is pressed
|
|
if (!pointer.isDown) return
|
|
|
|
// Check if shift is not pressed, this means we are moving the camera
|
|
if (pointer.event.shiftKey) return
|
|
|
|
// Check if draw mode is tile
|
|
switch (mapEditor.drawMode.value) {
|
|
case 'tile':
|
|
mapTiles.value.handlePointer(pointer)
|
|
break
|
|
case 'map_object':
|
|
mapObjects.value.handlePointer(pointer)
|
|
break
|
|
case 'teleport':
|
|
eventTiles.value.handlePointer(pointer)
|
|
break
|
|
case 'blocking tile':
|
|
eventTiles.value.handlePointer(pointer)
|
|
break
|
|
}
|
|
}
|
|
|
|
function handleKeyDown(event: KeyboardEvent) {
|
|
//CTRL+Y
|
|
if (event.key === 'y' && event.ctrlKey) {
|
|
redoEdit()
|
|
}
|
|
|
|
//CTRL+Z
|
|
if (event.key === 'z' && event.ctrlKey) {
|
|
undoEdit()
|
|
}
|
|
}
|
|
|
|
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
|
if (mapEditor.inputMode.value === 'hold' && pointer.isDown) {
|
|
handlePointerDown(pointer)
|
|
}
|
|
}
|
|
|
|
function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
|
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
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
tileMap.value = createTileMap(scene, mapValue)
|
|
tileMapLayer.value = createTileLayer(tileMap.value, allTileIds)
|
|
|
|
addEventListener('keydown', handleKeyDown)
|
|
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)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (tileMap.value) {
|
|
tileMap.value.destroyLayer('tiles')
|
|
tileMap.value.removeAllLayers()
|
|
tileMap.value.destroy()
|
|
}
|
|
|
|
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)
|
|
mapEditor.reset()
|
|
})
|
|
|
|
onBeforeUnmount(() => {
|
|
removeEventListener('keydown', handleKeyDown)
|
|
})
|
|
</script>
|