Teleport modal restored, and expanded undo/redo to include all placed and erase edits across each map element type (map object advanced actions WIP)
This commit is contained in:
parent
5f2c7a09b1
commit
0c450b24ed
@ -1,30 +1,109 @@
|
|||||||
<template>
|
<template>
|
||||||
<MapTiles ref="mapTiles" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
<MapTiles ref="mapTiles" @createCommand="addCommand" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
||||||
<PlacedMapObjects ref="mapObjects" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
<PlacedMapObjects ref="mapObjects" @createCommand="addCommand" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
|
||||||
<MapEventTiles ref="eventTiles" v-if="tileMap" :tileMap />
|
<MapEventTiles ref="eventTiles" @createCommand="addCommand" v-if="tileMap" :tileMap />
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue'
|
import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue'
|
||||||
import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
|
import MapTiles from '@/components/gameMaster/mapEditor/mapPartials/MapTiles.vue'
|
||||||
import PlacedMapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
|
import PlacedMapObjects from '@/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { createTileLayer, createTileMap } from '@/services/mapService'
|
import { cloneArray, createTileLayer, createTileMap, placeTiles } from '@/services/mapService'
|
||||||
import { TileStorage } from '@/storage/storages'
|
import { TileStorage } from '@/storage/storages'
|
||||||
import { useScene } from 'phavuer'
|
import { useScene } from 'phavuer'
|
||||||
import { onBeforeUnmount, onMounted, onUnmounted, shallowRef, useTemplateRef } from 'vue'
|
|
||||||
|
import { onBeforeUnmount, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue'
|
||||||
|
import type { MapEventTile, PlacedMapObject as PlacedMapObjectT } from '@/application/types'
|
||||||
|
|
||||||
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
const tileMap = shallowRef<Phaser.Tilemaps.Tilemap>()
|
||||||
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
||||||
|
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
|
|
||||||
const scene = useScene()
|
const scene = useScene()
|
||||||
|
|
||||||
const mapTiles = useTemplateRef('mapTiles')
|
const mapTiles = useTemplateRef('mapTiles')
|
||||||
const mapObjects = useTemplateRef('mapObjects')
|
const mapObjects = useTemplateRef('mapObjects')
|
||||||
const eventTiles = useTemplateRef('eventTiles')
|
const eventTiles = useTemplateRef('eventTiles')
|
||||||
|
|
||||||
|
//Record of commands
|
||||||
|
let commandStack: EditorCommand[] = []
|
||||||
|
let commandIndex = ref(0)
|
||||||
|
|
||||||
|
let originTiles: string[][] = []
|
||||||
|
let originEventTiles: MapEventTile[] = []
|
||||||
|
let originObjects: PlacedMapObjectT[] = []
|
||||||
|
|
||||||
|
//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' | 'place' | 'move' | 'delete' | 'rotate'
|
||||||
|
}
|
||||||
|
|
||||||
|
function applyCommands(tiles: any[], ...commands: EditorCommand[]): any[] {
|
||||||
|
let tileVersion = cloneArray(tiles)
|
||||||
|
for (let command of commands) {
|
||||||
|
tileVersion = command.apply(tileVersion)
|
||||||
|
}
|
||||||
|
return tileVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
watch(() => commandIndex.value!, (val) => {
|
||||||
|
if (val !== undefined) {
|
||||||
|
update(commandStack.slice(0, val))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
function update(commands: EditorCommand[]) {
|
||||||
|
if (!mapEditor.currentMap.value) return
|
||||||
|
|
||||||
|
const tileCommands = commands.filter((command) => command.type === 'tile')
|
||||||
|
const eventTileCommands = commands.filter((command) => command.type === 'event_tile')
|
||||||
|
const objectCommands = commands.filter((command) => command.type === 'map_object')
|
||||||
|
|
||||||
|
let modifiedTiles = applyCommands(originTiles, ...tileCommands)
|
||||||
|
placeTiles(tileMap.value!, tileMapLayer.value!, modifiedTiles)
|
||||||
|
|
||||||
|
mapEditor.currentMap.value.tiles = modifiedTiles
|
||||||
|
mapEditor.currentMap.value.mapEventTiles = applyCommands(originEventTiles, ...eventTileCommands)
|
||||||
|
mapEditor.currentMap.value.placedMapObjects = applyCommands(originObjects, ...objectCommands)
|
||||||
|
}
|
||||||
|
|
||||||
|
function addCommand(command: EditorCommand) {
|
||||||
|
commandStack = commandStack.slice(0, commandIndex.value)
|
||||||
|
commandStack.push(command)
|
||||||
|
|
||||||
|
if (commandStack.length >= 9) {
|
||||||
|
switch (commandStack[0].type) {
|
||||||
|
case 'tile':
|
||||||
|
originTiles = commandStack.shift()?.apply(originTiles) as string[][]
|
||||||
|
break
|
||||||
|
case 'map_object':
|
||||||
|
originObjects = commandStack.shift()?.apply(originObjects) as PlacedMapObjectT[]
|
||||||
|
break
|
||||||
|
case 'event_tile':
|
||||||
|
originEventTiles = commandStack.shift()?.apply(originEventTiles) as MapEventTile[]
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
commandIndex.value = commandStack.length
|
||||||
|
}
|
||||||
|
|
||||||
|
function undo() {
|
||||||
|
if (commandIndex.value > 0) {
|
||||||
|
commandIndex.value--
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function redo() {
|
||||||
|
if (commandIndex.value <= 9 && commandIndex.value <= commandStack.length) {
|
||||||
|
commandIndex.value++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
||||||
if (!mapTiles.value || !mapObjects.value || !eventTiles.value) return
|
if (!mapTiles.value || !mapObjects.value || !eventTiles.value) return
|
||||||
|
|
||||||
@ -54,12 +133,12 @@ function handlePointerDown(pointer: Phaser.Input.Pointer) {
|
|||||||
function handleKeyDown(event: KeyboardEvent) {
|
function handleKeyDown(event: KeyboardEvent) {
|
||||||
//CTRL+Y
|
//CTRL+Y
|
||||||
if (event.key === 'y' && event.ctrlKey) {
|
if (event.key === 'y' && event.ctrlKey) {
|
||||||
mapTiles.value!.redo()
|
redo()
|
||||||
}
|
}
|
||||||
|
|
||||||
//CTRL+Z
|
//CTRL+Z
|
||||||
if (event.key === 'z' && event.ctrlKey) {
|
if (event.key === 'z' && event.ctrlKey) {
|
||||||
mapTiles.value!.undo()
|
undo()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -70,8 +149,19 @@ function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
function handlePointerUp(pointer: Phaser.Input.Pointer) {
|
||||||
if (mapEditor.drawMode.value === 'tile') {
|
switch(mapEditor.drawMode.value) {
|
||||||
mapTiles.value?.finalizeCommand()
|
case 'tile':
|
||||||
|
mapTiles.value!.finalizeCommand()
|
||||||
|
break
|
||||||
|
case 'map_object':
|
||||||
|
mapObjects.value!.finalizeCommand()
|
||||||
|
break
|
||||||
|
case 'teleport':
|
||||||
|
eventTiles.value!.finalizeCommand()
|
||||||
|
break
|
||||||
|
case 'blocking tile':
|
||||||
|
eventTiles.value!.finalizeCommand()
|
||||||
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +169,11 @@ onMounted(async () => {
|
|||||||
let mapValue = mapEditor.currentMap.value
|
let mapValue = mapEditor.currentMap.value
|
||||||
if (!mapValue) return
|
if (!mapValue) return
|
||||||
|
|
||||||
|
//Clone
|
||||||
|
originTiles = cloneArray(mapValue.tiles)
|
||||||
|
originObjects = cloneArray(mapValue.placedMapObjects)
|
||||||
|
originEventTiles = cloneArray(mapValue.mapEventTiles)
|
||||||
|
|
||||||
const tileStorage = new TileStorage()
|
const tileStorage = new TileStorage()
|
||||||
const allTiles = await tileStorage.getAll()
|
const allTiles = await tileStorage.getAll()
|
||||||
const allTileIds = allTiles.map((tile) => tile.id)
|
const allTileIds = allTiles.map((tile) => tile.id)
|
||||||
|
@ -3,22 +3,72 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
|
||||||
import { MapEventTileType, type MapEventTile, type Map as MapT, type UUID } from '@/application/types'
|
import { MapEventTileType, type MapEventTile, type Map as MapT, type UUID } from '@/application/types'
|
||||||
import { uuidv4 } from '@/application/utilities'
|
import { uuidv4 } from '@/application/utilities'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { getTile, tileToWorldX, tileToWorldY } from '@/services/mapService'
|
import { cloneArray, getTile, tileToWorldX, tileToWorldY } from '@/services/mapService'
|
||||||
import { Image } from 'phavuer'
|
import { Image } from 'phavuer'
|
||||||
import { shallowRef } from 'vue'
|
import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
|
||||||
|
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
|
|
||||||
defineExpose({ handlePointer })
|
defineExpose({ handlePointer, finalizeCommand })
|
||||||
|
|
||||||
|
const emit = defineEmits(['createCommand'])
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tileMap: Phaser.Tilemaps.Tilemap
|
tileMap: Phaser.Tilemaps.Tilemap
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const tileLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
|
|
||||||
|
// *** COMMAND STATE ***
|
||||||
|
|
||||||
|
let currentCommand: EventTileCommand | null = null
|
||||||
|
|
||||||
|
class EventTileCommand implements EditorCommand {
|
||||||
|
public operation: 'draw' | 'erase' = '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))
|
||||||
|
}
|
||||||
|
return tileVersion
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(operation: 'draw' | 'erase') {
|
||||||
|
this.operation = operation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function createCommandUpdate(tile: MapEventTile, operation: 'draw' | 'erase') {
|
||||||
|
if (!currentCommand) {
|
||||||
|
currentCommand = new EventTileCommand(operation)
|
||||||
|
}
|
||||||
|
|
||||||
|
//If position is already in, do not proceed
|
||||||
|
for (const priorTile of currentCommand.affectedTiles) {
|
||||||
|
if (priorTile.positionX === tile.positionX && priorTile.positionY == tile.positionY) return
|
||||||
|
}
|
||||||
|
|
||||||
|
currentCommand.affectedTiles.push(tile)
|
||||||
|
}
|
||||||
|
|
||||||
|
function finalizeCommand() {
|
||||||
|
if (!currentCommand) return
|
||||||
|
emit('createCommand', currentCommand)
|
||||||
|
currentCommand = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// *** HANDLERS ***
|
||||||
|
|
||||||
|
|
||||||
function getImageProps(tile: MapEventTile) {
|
function getImageProps(tile: MapEventTile) {
|
||||||
return {
|
return {
|
||||||
@ -44,7 +94,7 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
const newEventTile = {
|
const newEventTile = {
|
||||||
id: uuidv4() as UUID,
|
id: uuidv4() as UUID,
|
||||||
mapId: map.id,
|
mapId: map.id,
|
||||||
map: map.id,
|
map: map,
|
||||||
type: mapEditor.drawMode.value === 'blocking tile' ? MapEventTileType.BLOCK : MapEventTileType.TELEPORT,
|
type: mapEditor.drawMode.value === 'blocking tile' ? MapEventTileType.BLOCK : MapEventTileType.TELEPORT,
|
||||||
positionX: tile.x,
|
positionX: tile.x,
|
||||||
positionY: tile.y,
|
positionY: tile.y,
|
||||||
@ -59,6 +109,8 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
: undefined
|
: undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createCommandUpdate(newEventTile, 'draw')
|
||||||
|
|
||||||
map.mapEventTiles.push(newEventTile)
|
map.mapEventTiles.push(newEventTile)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,6 +129,8 @@ function erase(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
else return
|
else return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
createCommandUpdate(existingEventTile, 'erase')
|
||||||
|
|
||||||
// Remove existing event tile
|
// Remove existing event tile
|
||||||
map.mapEventTiles = map.mapEventTiles.filter((eventTile) => eventTile.id !== existingEventTile.id)
|
map.mapEventTiles = map.mapEventTiles.filter((eventTile) => eventTile.id !== existingEventTile.id)
|
||||||
}
|
}
|
||||||
|
@ -5,57 +5,70 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import Controls from '@/components/utilities/Controls.vue'
|
import Controls from '@/components/utilities/Controls.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { createTileArray, getTile, placeTile, placeTiles } from '@/services/mapService'
|
import { cloneArray, createTileArray, getTile, placeTile, placeTiles } from '@/services/mapService'
|
||||||
import { onMounted, ref, watch } from 'vue'
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
|
||||||
|
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
|
|
||||||
defineExpose({ handlePointer, finalizeCommand, undo, redo })
|
defineExpose({ handlePointer, finalizeCommand })
|
||||||
|
|
||||||
|
const emit = defineEmits(['createCommand'])
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tileMap: Phaser.Tilemaps.Tilemap
|
tileMap: Phaser.Tilemaps.Tilemap
|
||||||
tileMapLayer: Phaser.Tilemaps.TilemapLayer
|
tileMapLayer: Phaser.Tilemaps.TilemapLayer
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
class EditorCommand {
|
|
||||||
|
// *** COMMAND STATE ***
|
||||||
|
|
||||||
|
let currentCommand: TileCommand | null = null
|
||||||
|
|
||||||
|
class TileCommand implements EditorCommand {
|
||||||
public operation: 'draw' | 'erase' = 'draw'
|
public operation: 'draw' | 'erase' = 'draw'
|
||||||
|
public type: 'tile' = 'tile'
|
||||||
public tileName: string = 'blank_tile'
|
public tileName: string = 'blank_tile'
|
||||||
public affectedTiles: number[][]
|
public affectedTiles: number[][] = []
|
||||||
|
|
||||||
|
apply(elements: string[][]) {
|
||||||
|
let tileVersion = cloneArray(elements) as string[][]
|
||||||
|
for (const position of this.affectedTiles) {
|
||||||
|
tileVersion[position[1]][position[0]] = this.tileName
|
||||||
|
}
|
||||||
|
return tileVersion
|
||||||
|
}
|
||||||
|
|
||||||
constructor(operation: 'draw' | 'erase', tileName: string) {
|
constructor(operation: 'draw' | 'erase', tileName: string) {
|
||||||
this.operation = operation
|
this.operation = operation
|
||||||
this.tileName = tileName
|
this.tileName = tileName
|
||||||
this.affectedTiles = []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Record of commands
|
function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase') {
|
||||||
let commandStack: EditorCommand[] = []
|
if (!currentCommand) {
|
||||||
let currentCommand: EditorCommand | null = null
|
currentCommand = new TileCommand(operation, tileName)
|
||||||
let commandIndex = ref(0)
|
}
|
||||||
let originTiles: string[][] = []
|
|
||||||
|
|
||||||
function pencil(pointer: Phaser.Input.Pointer) {
|
//If position is already in, do not proceed
|
||||||
let map = mapEditor.currentMap.value
|
for (const vec of currentCommand.affectedTiles) {
|
||||||
if (!map) return
|
if (vec[0] === x && vec[1] === y) return
|
||||||
|
}
|
||||||
|
|
||||||
// Check if there is a selected tile
|
currentCommand.affectedTiles.push([x, y])
|
||||||
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) {
|
function finalizeCommand() {
|
||||||
|
if (!currentCommand) return
|
||||||
|
emit('createCommand', currentCommand)
|
||||||
|
currentCommand = null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// *** HANDLERS ***
|
||||||
|
|
||||||
|
|
||||||
|
function draw(pointer: Phaser.Input.Pointer, tileName: string) {
|
||||||
let map = mapEditor.currentMap.value
|
let map = mapEditor.currentMap.value
|
||||||
if (!map) return
|
if (!map) return
|
||||||
|
|
||||||
@ -64,12 +77,12 @@ function eraser(pointer: Phaser.Input.Pointer) {
|
|||||||
if (!tile) return
|
if (!tile) return
|
||||||
|
|
||||||
// Place tile
|
// Place tile
|
||||||
placeTile(props.tileMap, props.tileMapLayer, tile.x, tile.y, 'blank_tile')
|
placeTile(props.tileMap, props.tileMapLayer, tile.x, tile.y, tileName)
|
||||||
|
|
||||||
createCommandUpdate(tile.x, tile.y, 'blank_tile', 'erase')
|
createCommandUpdate(tile.x, tile.y, tileName, tileName === 'blank_tile' ? 'erase': 'draw')
|
||||||
|
|
||||||
// Adjust mapEditorStore.map.tiles
|
// Adjust mapEditorStore.map.tiles
|
||||||
map.tiles[tile.y][tile.x] = 'blank_tile'
|
map.tiles[tile.y][tile.x] = tileName
|
||||||
}
|
}
|
||||||
|
|
||||||
function paint(pointer: Phaser.Input.Pointer) {
|
function paint(pointer: Phaser.Input.Pointer) {
|
||||||
@ -113,10 +126,10 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
|||||||
// Check if draw mode is tile
|
// Check if draw mode is tile
|
||||||
switch (mapEditor.tool.value) {
|
switch (mapEditor.tool.value) {
|
||||||
case 'pencil':
|
case 'pencil':
|
||||||
pencil(pointer)
|
draw(pointer, mapEditor.selectedTile.value!)
|
||||||
break
|
break
|
||||||
case 'eraser':
|
case 'eraser':
|
||||||
eraser(pointer)
|
draw(pointer, 'blank_tile')
|
||||||
break
|
break
|
||||||
case 'paint':
|
case 'paint':
|
||||||
paint(pointer)
|
paint(pointer)
|
||||||
@ -124,70 +137,9 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
// *** LIFECYCLE ***
|
||||||
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)
|
|
||||||
|
|
||||||
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(
|
watch(
|
||||||
() => mapEditor.shouldClearTiles.value,
|
() => mapEditor.shouldClearTiles.value,
|
||||||
@ -205,9 +157,6 @@ onMounted(async () => {
|
|||||||
if (!mapEditor.currentMap.value) return
|
if (!mapEditor.currentMap.value) return
|
||||||
const mapState = mapEditor.currentMap.value
|
const mapState = mapEditor.currentMap.value
|
||||||
|
|
||||||
//Clone
|
|
||||||
originTiles = cloneArray(mapState.tiles)
|
|
||||||
|
|
||||||
placeTiles(props.tileMap, props.tileMapLayer, mapState.tiles)
|
placeTiles(props.tileMap, props.tileMapLayer, mapState.tiles)
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
@ -16,7 +16,7 @@ import { uuidv4 } from '@/application/utilities'
|
|||||||
import PlacedMapObject from '@/components/game/map/partials/PlacedMapObject.vue'
|
import PlacedMapObject from '@/components/game/map/partials/PlacedMapObject.vue'
|
||||||
import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/partials/SelectedPlacedMapObject.vue'
|
import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/partials/SelectedPlacedMapObject.vue'
|
||||||
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
|
||||||
import { getTile } from '@/services/mapService'
|
import { cloneArray, getTile } from '@/services/mapService'
|
||||||
import { useScene } from 'phavuer'
|
import { useScene } from 'phavuer'
|
||||||
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
import { computed, onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
|
||||||
@ -27,7 +27,9 @@ const scene = useScene()
|
|||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
const map = computed(() => mapEditor.currentMap.value!)
|
const map = computed(() => mapEditor.currentMap.value!)
|
||||||
|
|
||||||
defineExpose({ handlePointer })
|
defineExpose({ handlePointer, finalizeCommand })
|
||||||
|
|
||||||
|
const emit = defineEmits(['createCommand'])
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
tileMap: Tilemap
|
tileMap: Tilemap
|
||||||
@ -71,6 +73,9 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
|
|
||||||
// Add new object to mapObjects
|
// Add new object to mapObjects
|
||||||
map.placedMapObjects.push(newPlacedMapObject)
|
map.placedMapObjects.push(newPlacedMapObject)
|
||||||
|
|
||||||
|
createCommandUpdate(newPlacedMapObject, 'place')
|
||||||
|
|
||||||
mapEditor.selectedPlacedObject.value = newPlacedMapObject
|
mapEditor.selectedPlacedObject.value = newPlacedMapObject
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +84,8 @@ function eraser(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
const existingPlacedMapObject = findObjectByPointer(pointer, map)
|
const existingPlacedMapObject = findObjectByPointer(pointer, map)
|
||||||
if (!existingPlacedMapObject) return
|
if (!existingPlacedMapObject) return
|
||||||
|
|
||||||
|
createCommandUpdate(existingPlacedMapObject, 'delete')
|
||||||
|
|
||||||
// Remove existing object
|
// Remove existing object
|
||||||
map.placedMapObjects = map.placedMapObjects.filter((placedMapObject) => placedMapObject.id !== existingPlacedMapObject.id)
|
map.placedMapObjects = map.placedMapObjects.filter((placedMapObject) => placedMapObject.id !== existingPlacedMapObject.id)
|
||||||
}
|
}
|
||||||
@ -102,11 +109,15 @@ function objectPicker(pointer: Phaser.Input.Pointer, map: MapT) {
|
|||||||
function moveMapObject(id: string, map: MapT) {
|
function moveMapObject(id: string, map: MapT) {
|
||||||
mapEditor.movingPlacedObject.value = map.placedMapObjects.find((object) => object.id === id) as PlacedMapObjectT
|
mapEditor.movingPlacedObject.value = map.placedMapObjects.find((object) => object.id === id) as PlacedMapObjectT
|
||||||
|
|
||||||
|
let t: Tile
|
||||||
|
|
||||||
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
function handlePointerMove(pointer: Phaser.Input.Pointer) {
|
||||||
if (!mapEditor.movingPlacedObject.value) return
|
if (!mapEditor.movingPlacedObject.value) return
|
||||||
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
|
||||||
if (!tile) return
|
if (!tile) return
|
||||||
|
|
||||||
|
t = tile
|
||||||
|
|
||||||
mapEditor.movingPlacedObject.value.positionX = tile.x
|
mapEditor.movingPlacedObject.value.positionX = tile.x
|
||||||
mapEditor.movingPlacedObject.value.positionY = tile.y
|
mapEditor.movingPlacedObject.value.positionY = tile.y
|
||||||
}
|
}
|
||||||
@ -116,6 +127,9 @@ function moveMapObject(id: string, map: MapT) {
|
|||||||
function handlePointerUp() {
|
function handlePointerUp() {
|
||||||
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
|
||||||
mapEditor.movingPlacedObject.value = null
|
mapEditor.movingPlacedObject.value = null
|
||||||
|
|
||||||
|
createCommandUpdate(mapEditor.movingPlacedObject.value!, 'move', new Vector2(t.x, t.y))
|
||||||
|
finalizeCommand()
|
||||||
}
|
}
|
||||||
|
|
||||||
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
|
||||||
@ -128,6 +142,13 @@ function rotatePlacedMapObject(id: string, map: MapT) {
|
|||||||
|
|
||||||
function deletePlacedMapObject(id: string, map: MapT) {
|
function deletePlacedMapObject(id: string, map: MapT) {
|
||||||
let mapE = mapEditor.currentMap.value!
|
let mapE = mapEditor.currentMap.value!
|
||||||
|
|
||||||
|
const foundObject = mapE.placedMapObjects.find((obj) => obj.id === id)
|
||||||
|
if (!foundObject) return
|
||||||
|
|
||||||
|
createCommandUpdate(foundObject, 'delete')
|
||||||
|
finalizeCommand()
|
||||||
|
|
||||||
mapE.placedMapObjects = map.placedMapObjects.filter((object) => object.id !== id)
|
mapE.placedMapObjects = map.placedMapObjects.filter((object) => object.id !== id)
|
||||||
mapEditor.selectedPlacedObject.value = null
|
mapEditor.selectedPlacedObject.value = null
|
||||||
}
|
}
|
||||||
|
@ -150,6 +150,7 @@ function handleClick(tool: string) {
|
|||||||
mapEditor.setTool(tool)
|
mapEditor.setTool(tool)
|
||||||
selectPencilOpen.value = tool === 'pencil' ? !selectPencilOpen.value : false
|
selectPencilOpen.value = tool === 'pencil' ? !selectPencilOpen.value : false
|
||||||
selectEraserOpen.value = tool === 'eraser' ? !selectEraserOpen.value : false
|
selectEraserOpen.value = tool === 'eraser' ? !selectEraserOpen.value : false
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function cycleToolMode(tool: 'pencil' | 'eraser') {
|
function cycleToolMode(tool: 'pencil' | 'eraser') {
|
||||||
|
@ -34,6 +34,7 @@ import { MapStorage } from '@/storage/storages'
|
|||||||
import { useGameStore } from '@/stores/gameStore'
|
import { useGameStore } from '@/stores/gameStore'
|
||||||
import { Game, Scene } from 'phavuer'
|
import { Game, Scene } from 'phavuer'
|
||||||
import { ref, useTemplateRef } from 'vue'
|
import { ref, useTemplateRef } from 'vue'
|
||||||
|
import teleportModal from '@/components/gameMaster/mapEditor/partials/TeleportModal.vue'
|
||||||
|
|
||||||
const mapStorage = new MapStorage()
|
const mapStorage = new MapStorage()
|
||||||
const mapEditor = useMapEditorComposable()
|
const mapEditor = useMapEditorComposable()
|
||||||
@ -41,6 +42,7 @@ const gameStore = useGameStore()
|
|||||||
|
|
||||||
const mapModal = useTemplateRef('mapModal')
|
const mapModal = useTemplateRef('mapModal')
|
||||||
const mapSettingsModal = useTemplateRef('mapSettingsModal')
|
const mapSettingsModal = useTemplateRef('mapSettingsModal')
|
||||||
|
const teleportSettings = useTemplateRef('teleportModal')
|
||||||
|
|
||||||
const isLoaded = ref(false)
|
const isLoaded = ref(false)
|
||||||
|
|
||||||
@ -86,15 +88,9 @@ function save() {
|
|||||||
if (!currentMap) return
|
if (!currentMap) return
|
||||||
|
|
||||||
const data = {
|
const data = {
|
||||||
|
...currentMap,
|
||||||
mapId: currentMap.id,
|
mapId: currentMap.id,
|
||||||
name: currentMap.name,
|
placedMapObjects: currentMap.placedMapObjects.map(({ id, mapObject, depth, isRotated, positionX, positionY }) => ({ id, mapObject, depth, isRotated, positionX, positionY })) ?? []
|
||||||
width: currentMap.width,
|
|
||||||
height: currentMap.height,
|
|
||||||
tiles: currentMap.tiles,
|
|
||||||
pvp: currentMap.pvp,
|
|
||||||
mapEffects: currentMap.mapEffects,
|
|
||||||
mapEventTiles: currentMap.mapEventTiles,
|
|
||||||
placedMapObjects: currentMap.placedMapObjects.map(({ id, mapObject, isRotated, positionX, positionY }) => ({ id, mapObject, isRotated, positionX, positionY })) ?? []
|
|
||||||
}
|
}
|
||||||
|
|
||||||
gameStore.connection?.emit('gm:map:update', data, (response: MapT) => {
|
gameStore.connection?.emit('gm:map:update', data, (response: MapT) => {
|
||||||
|
@ -151,3 +151,8 @@ export function createTileLayer(tileMap: Phaser.Tilemaps.Tilemap, tilesArray: st
|
|||||||
|
|
||||||
return layer
|
return layer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//Recursive Array Clone
|
||||||
|
export function cloneArray(arr: any[]): any[] {
|
||||||
|
return arr.map((item) => (item instanceof Array ? cloneArray(item) : item))
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user