1
0
forked from noxious/client

216 lines
5.9 KiB
Vue

<template>
<Controls v-if="props.tileMapLayer" :layer="props.tileMapLayer" :depth="0" />
</template>
<script setup lang="ts">
import Controls from '@/components/utilities/Controls.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
import { createTileArray, getTile, placeTile, setLayerTiles } from '@/services/mapService'
import { useScene } from 'phavuer'
import { onMounted, ref, watch } from 'vue'
const emit = defineEmits(['tileMap:create'])
const mapEditor = useMapEditorComposable()
defineExpose({ handlePointer, finalizeCommand, undo, redo })
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[][]
constructor(operation: 'draw' | 'erase', 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 pencil(pointer: Phaser.Input.Pointer) {
let map = mapEditor.currentMap.value
if (!map) return
// Check if there is a selected tile
if (!mapEditor.selectedTile.value) return
// Check if there is a tile
const tile = getTile(props.tileMapLayer, pointer.worldX, pointer.worldY)
if (!tile) return
// Place tile
placeTile(props.tileMap, props.tileMapLayer, tile.x, tile.y, mapEditor.selectedTile.value)
createCommandUpdate(tile.x, tile.y, mapEditor.selectedTile.value, 'draw')
// Adjust mapEditorStore.map.tiles
map.tiles[tile.y][tile.x] = mapEditor.selectedTile.value
}
function eraser(pointer: Phaser.Input.Pointer) {
let map = mapEditor.currentMap.value
if (!map) 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, 'blank_tile')
createCommandUpdate(tile.x, tile.y, 'blank_tile', 'erase')
// Adjust mapEditorStore.map.tiles
map.tiles[tile.y][tile.x] = 'blank_tile'
}
function paint(pointer: Phaser.Input.Pointer) {
let map = mapEditor.currentMap.value
if (!map) return
// Set new tileArray with selected tile
const tileArray = createTileArray(props.tileMap.width, props.tileMap.height, mapEditor.selectedTile.value)
setLayerTiles(props.tileMap, props.tileMapLayer, tileArray)
// Adjust mapEditorStore.map.tiles
map.tiles = tileArray
}
// When alt is pressed, and the pointer is down, select the tile that the pointer is over
function tilePicker(pointer: Phaser.Input.Pointer) {
let map = mapEditor.currentMap.value
if (!map) return
// Check if there is a tile
const tile = getTile(props.tileMapLayer, pointer.worldX, pointer.worldY)
if (!tile) return
// Select the tile
mapEditor.setSelectedTile(map.tiles[tile.y][tile.x])
}
function handlePointer(pointer: Phaser.Input.Pointer) {
// Check if left mouse button is pressed
if (!pointer.isDown && pointer.button === 0) return
// Check if shift is not pressed, this means we are moving the camera
if (pointer.event.shiftKey) return
// Check if alt is pressed
if (pointer.event.altKey) {
tilePicker(pointer)
return
}
// Check if draw mode is tile
switch (mapEditor.tool.value) {
case 'pencil':
pencil(pointer)
break
case 'eraser':
eraser(pointer)
break
case 'paint':
paint(pointer)
break
}
}
function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase') {
if (!currentCommand) {
currentCommand = new EditorCommand(operation, tileName)
}
//If position is already in, do not proceed
for (const vec of currentCommand.affectedTiles) {
if (vec[0] === x && vec[1] === y) return
}
currentCommand.affectedTiles.push([x, y])
}
function finalizeCommand() {
if (!currentCommand) return
//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)
setLayerTiles(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')
setLayerTiles(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)
setLayerTiles(props.tileMap, props.tileMapLayer, mapState.tiles)
})
</script>