forked from noxious/client
250 lines
7.3 KiB
Vue
250 lines
7.3 KiB
Vue
<template>
|
|
<Controls v-if="tileLayer" :layer="tileLayer" :depth="0" />
|
|
</template>
|
|
|
|
<script setup lang="ts">
|
|
import config from '@/application/config'
|
|
import Controls from '@/components/utilities/Controls.vue'
|
|
import { createTileMap, createTileLayer, createTileArray, getTile, placeTile, setLayerTiles } from '@/composables/mapComposable'
|
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
|
import { TileStorage } from '@/storage/storages'
|
|
import { useScene } from 'phavuer'
|
|
import { onMounted, onUnmounted, ref, shallowRef, watch } from 'vue'
|
|
|
|
const emit = defineEmits(['tileMap:create'])
|
|
const scene = useScene()
|
|
const mapEditor = useMapEditorComposable()
|
|
const tileStorage = new TileStorage()
|
|
|
|
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
|
const tileLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
|
|
|
defineExpose({ handlePointer, finalizeCommand, undo, redo })
|
|
|
|
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
|
|
|
|
if (!tileMap.value || !tileLayer.value) return
|
|
|
|
// Check if there is a tile
|
|
const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY)
|
|
if (!tile) return
|
|
|
|
// Place tile
|
|
placeTile(tileMap.value, tileLayer.value, 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
|
|
|
|
if (!tileMap.value || !tileLayer.value) return
|
|
|
|
// Check if there is a tile
|
|
const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY)
|
|
if (!tile) return
|
|
|
|
// Place tile
|
|
placeTile(tileMap.value, tileLayer.value, 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) {
|
|
if (!tileMap.value || !tileLayer.value) return
|
|
// Set new tileArray with selected tile
|
|
const tileArray = createTileArray(tileMap.value.width, tileMap.value.height, mapEditor.selectedTile.value)
|
|
setLayerTiles(tileMap.value, tileLayer.value, tileArray)
|
|
|
|
// Adjust mapEditorStore.map.tiles
|
|
if (mapEditor.currentMap.value) {
|
|
mapEditor.currentMap.value.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
|
|
|
|
if (!tileMap.value || !tileLayer.value) return
|
|
|
|
// Check if there is a tile
|
|
const tile = getTile(tileLayer.value, pointer.worldX, pointer.worldY)
|
|
if (!tile) return
|
|
|
|
// Select the tile
|
|
mapEditor.setSelectedTile(map.tiles[tile.y][tile.x])
|
|
}
|
|
|
|
function handlePointer(pointer: Phaser.Input.Pointer) {
|
|
if (!tileMap.value || !tileLayer.value) return
|
|
|
|
// 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 (!tileMap.value || !tileLayer.value || !mapEditor.currentMap.value) return
|
|
|
|
let indexedCommands = commandStack.slice(0, commandIndex.value)
|
|
let modifiedTiles = applyCommands(originTiles, ...indexedCommands)
|
|
|
|
//replaceTiles(mapEditor.currentMap.value.tiles, layer, tileMap.value.width, tileMap.value.height)
|
|
setLayerTiles(tileMap.value, tileLayer.value, 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,
|
|
(shouldClear) => {
|
|
if (shouldClear && mapEditor.currentMap.value && tileMap.value && tileLayer.value) {
|
|
const blankTiles = createTileArray(tileLayer.value.width, tileLayer.value.height, 'blank_tile')
|
|
setLayerTiles(tileMap.value, tileLayer.value, blankTiles)
|
|
replaceTiles(mapEditor.currentMap.value.tiles, blankTiles, tileLayer.value.width, tileLayer.value.height)
|
|
mapEditor.resetClearTilesFlag()
|
|
}
|
|
}
|
|
)
|
|
|
|
// Then overlay the map tiles, but only within the current map dimensions
|
|
function replaceTiles(originalTiles: string[][], mapTiles: string[][], width: number, height: number) {
|
|
for (let y = 0; y < height; y++) {
|
|
for (let x = 0; x < width; x++) {
|
|
if (mapTiles[y] && mapTiles[y][x] !== undefined) {
|
|
originalTiles[y][x] = mapTiles[y][x]
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
onMounted(async () => {
|
|
if (!mapEditor.currentMap.value) return
|
|
const mapState = mapEditor.currentMap.value
|
|
|
|
//Clone
|
|
originTiles = cloneArray(mapEditor.currentMap.value.tiles)
|
|
|
|
tileMap.value = createTileMap(scene, mapEditor.currentMap.value)
|
|
emit('tileMap:create', tileMap.value)
|
|
tileLayer.value = createTileLayer(tileMap.value, mapEditor.currentMap.value)
|
|
|
|
setLayerTiles(tileMap.value, tileLayer.value, mapState.tiles)
|
|
})
|
|
|
|
onUnmounted(() => {
|
|
if (tileMap.value) {
|
|
tileMap.value.destroyLayer('tiles')
|
|
tileMap.value.removeAllLayers()
|
|
tileMap.value.destroy()
|
|
}
|
|
})
|
|
</script>
|