diff --git a/src/components/gameMaster/mapEditor/Map.vue b/src/components/gameMaster/mapEditor/Map.vue
index 7acceb7..d55c0ba 100644
--- a/src/components/gameMaster/mapEditor/Map.vue
+++ b/src/components/gameMaster/mapEditor/Map.vue
@@ -1,6 +1,6 @@
-
+
@@ -10,12 +10,13 @@ import MapEventTiles from '@/components/gameMaster/mapEditor/mapPartials/MapEven
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, createTileLayer, createTileMap, placeTiles } from '@/services/mapService'
+import { cloneArray, createTileArray, createTileLayer, createTileMap, placeTiles } from '@/services/mapService'
import { TileStorage } from '@/storage/storages'
import { useScene } from 'phavuer'
import { onBeforeUnmount, onMounted, onUnmounted, ref, shallowRef, useTemplateRef, watch } from 'vue'
-import type { MapEventTile, PlacedMapObject as PlacedMapObjectT } from '@/application/types'
+import type { PlacedMapObject as PlacedMapObjectT, Map as MapT, MapEventTile } from '@/application/types'
+import { useManualRefHistory, useRefHistory } from '@vueuse/core'
const tileMap = shallowRef()
const tileMapLayer = shallowRef()
@@ -28,18 +29,20 @@ const mapObjects = useTemplateRef('mapObjects')
const eventTiles = useTemplateRef('eventTiles')
//Record of commands
-let commandStack: EditorCommand[] = []
+let commandStack: (EditorCommand | number) [] = []
let commandIndex = ref(0)
let originTiles: string[][] = []
let originEventTiles: MapEventTile[] = []
-let originObjects: PlacedMapObjectT[] = []
+let originObjects = ref(mapEditor.currentMap.value.placedMapObjects)
+
+const {undo, redo, commit, reset, history, canUndo, canRedo} = useRefHistory(originObjects, {clone:true, 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' | 'place' | 'move' | 'delete' | 'rotate'
+ operation: 'draw' | 'erase' | 'clear'
}
function applyCommands(tiles: any[], ...commands: EditorCommand[]): any[] {
@@ -50,57 +53,95 @@ function applyCommands(tiles: any[], ...commands: EditorCommand[]): any[] {
return tileVersion
}
-watch(() => commandIndex.value!, (val) => {
- if (val !== undefined) {
- update(commandStack.slice(0, val))
+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[]) {
+function update(commands: (EditorCommand | number)[]) {
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')
+ 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 = applyCommands(originEventTiles, ...eventTileCommands)
- mapEditor.currentMap.value.placedMapObjects = applyCommands(originObjects, ...objectCommands)
+ mapEditor.currentMap.value.mapEventTiles = eventTiles
+}
+
+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
+
+ console.log(history.value)
+ console.log(commandStack)
+ console.log(commandIndex.value)
}
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() {
+function undoEdit() {
if (commandIndex.value > 0) {
- commandIndex.value--
+ if (typeof(commandStack[--commandIndex.value]) === 'number' && canUndo) {
+ undo()
+ mapEditor.currentMap.value.placedMapObjects = originObjects.value
+ }
+ update(commandStack.slice(0, commandIndex.value))
}
}
-function redo() {
- if (commandIndex.value <= 9 && commandIndex.value <= commandStack.length) {
- commandIndex.value++
+function redoEdit() {
+ if (commandIndex.value <= 9 && commandIndex.value < commandStack.length) {
+ if (typeof(commandStack[commandIndex.value++]) === 'number' && canRedo) {
+ redo()
+ mapEditor.currentMap.value.placedMapObjects = originObjects.value
+ }
+ update(commandStack.slice(0, commandIndex.value))
}
}
@@ -133,12 +174,12 @@ function handlePointerDown(pointer: Phaser.Input.Pointer) {
function handleKeyDown(event: KeyboardEvent) {
//CTRL+Y
if (event.key === 'y' && event.ctrlKey) {
- redo()
+ redoEdit()
}
//CTRL+Z
if (event.key === 'z' && event.ctrlKey) {
- undo()
+ undoEdit()
}
}
@@ -154,7 +195,7 @@ function handlePointerUp(pointer: Phaser.Input.Pointer) {
mapTiles.value!.finalizeCommand()
break
case 'map_object':
- mapObjects.value!.finalizeCommand()
+ updateAndCommit()
break
case 'teleport':
eventTiles.value!.finalizeCommand()
@@ -171,9 +212,10 @@ onMounted(async () => {
//Clone
originTiles = cloneArray(mapValue.tiles)
- originObjects = cloneArray(mapValue.placedMapObjects)
originEventTiles = cloneArray(mapValue.mapEventTiles)
+ commit()
+
const tileStorage = new TileStorage()
const allTiles = await tileStorage.getAll()
const allTileIds = allTiles.map((tile) => tile.id)
diff --git a/src/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue b/src/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue
index 77db3a2..2a23ebb 100644
--- a/src/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue
+++ b/src/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue
@@ -13,7 +13,7 @@ import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
const mapEditor = useMapEditorComposable()
-defineExpose({ handlePointer, finalizeCommand })
+defineExpose({ handlePointer, finalizeCommand, clearTiles})
const emit = defineEmits(['createCommand'])
@@ -27,7 +27,7 @@ const props = defineProps<{
let currentCommand: EventTileCommand | null = null
class EventTileCommand implements EditorCommand {
- public operation: 'draw' | 'erase' = 'draw'
+ public operation: 'draw' | 'erase' | 'clear' = 'draw'
public type: 'event_tile' = 'event_tile'
public affectedTiles: MapEventTile[] = []
@@ -39,15 +39,18 @@ class EventTileCommand implements EditorCommand {
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') {
+ constructor(operation: 'draw' | 'erase' | 'clear') {
this.operation = operation
}
}
-function createCommandUpdate(tile: MapEventTile, operation: 'draw' | 'erase') {
+function createCommandUpdate(tile?: MapEventTile, operation: 'draw' | 'erase' | 'clear') {
if (!currentCommand) {
currentCommand = new EventTileCommand(operation)
}
@@ -149,4 +152,11 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
break
}
}
+
+function clearTiles() {
+ if (mapEditor.currentMap.value.mapEventTiles.length === 0) return
+ createCommandUpdate(null, 'clear')
+ finalizeCommand()
+}
+
diff --git a/src/components/gameMaster/mapEditor/mapPartials/MapTiles.vue b/src/components/gameMaster/mapEditor/mapPartials/MapTiles.vue
index 671e8b0..2d0e524 100644
--- a/src/components/gameMaster/mapEditor/mapPartials/MapTiles.vue
+++ b/src/components/gameMaster/mapEditor/mapPartials/MapTiles.vue
@@ -11,7 +11,7 @@ import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
const mapEditor = useMapEditorComposable()
-defineExpose({ handlePointer, finalizeCommand })
+defineExpose({ handlePointer, finalizeCommand, clearTiles })
const emit = defineEmits(['createCommand'])
@@ -20,32 +20,37 @@ const props = defineProps<{
tileMapLayer: Phaser.Tilemaps.TilemapLayer
}>()
-
// *** COMMAND STATE ***
let currentCommand: TileCommand | null = null
class TileCommand implements EditorCommand {
- public operation: 'draw' | 'erase' = 'draw'
+ public operation: 'draw' | 'erase' | 'clear' = 'draw'
public type: 'tile' = 'tile'
public tileName: string = 'blank_tile'
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
+ 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', tileName: string) {
+ constructor(operation: 'draw' | 'erase' | 'clear', tileName: string) {
this.operation = operation
this.tileName = tileName
}
}
-function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase') {
+function createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase' | 'clear') {
if (!currentCommand) {
currentCommand = new TileCommand(operation, tileName)
}
@@ -137,21 +142,14 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
}
}
-
// *** LIFECYCLE ***
-
-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()
- }
- }
-)
+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()
+}
onMounted(async () => {
if (!mapEditor.currentMap.value) return
diff --git a/src/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue b/src/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue
index 81aabfe..ba6a1ff 100644
--- a/src/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue
+++ b/src/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue
@@ -1,5 +1,5 @@
-
+
@@ -7,101 +7,38 @@
import type {
MapObject,
Map as MapT,
- PlacedMapObject as PlacedMapObjectT,
- UUID,
- MapEventTile
+ PlacedMapObject as PlacedMapObjectT
} from '@/application/types'
import { uuidv4 } from '@/application/utilities'
import PlacedMapObject from '@/components/game/map/partials/PlacedMapObject.vue'
import SelectedPlacedMapObjectComponent from '@/components/gameMaster/mapEditor/partials/SelectedPlacedMapObject.vue'
import { useMapEditorComposable } from '@/composables/useMapEditorComposable'
-import { cloneArray, getTile } from '@/services/mapService'
+import { getTile } from '@/services/mapService'
import { useScene } from 'phavuer'
import Tilemap = Phaser.Tilemaps.Tilemap
import TilemapLayer = Phaser.Tilemaps.TilemapLayer
-import { computed } from 'vue'
-import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
-import Vector2 = Phaser.Math.Vector2
-import Tile = Phaser.Tilemaps.Tile
const scene = useScene()
const mapEditor = useMapEditorComposable()
-const map = computed(() => mapEditor.currentMap.value!)
-defineExpose({ handlePointer, finalizeCommand })
+const emit = defineEmits<{(e: 'update', map: MapT): void, (e: 'updateAndCommit', map: MapT): void}>()
-const emit = defineEmits(['createCommand'])
+defineExpose({ handlePointer })
const props = defineProps<{
tileMap: Tilemap
tileMapLayer: TilemapLayer
}>()
-
-// *** COMMAND STATE ***
-
-let currentCommand: MapObjectCommand | null = null
-
-class MapObjectCommand implements EditorCommand {
- public operation: 'place' | 'move' | 'delete' | 'rotate' = 'place'
- public type: 'map_object' = 'map_object'
- public affectedTiles: PlacedMapObjectT[] = []
- public targetPosition?: Phaser.Math.Vector2
-
- apply(elements: PlacedMapObjectT[]) {
- let tileVersion = cloneArray(elements) as PlacedMapObjectT[]
- if (this.operation === 'place') {
- tileVersion = tileVersion.concat(this.affectedTiles)
- }
- else if (this.operation === 'delete') {
- tileVersion = tileVersion.filter((v) => !this.affectedTiles.includes(v))
- }
- else if (this.operation === 'move') {
- const targetObject = tileVersion.find((v) => this.affectedTiles[0].id === v.id)
- if (targetObject) {
- targetObject.positionX = this.targetPosition!.x
- targetObject.positionY = this.targetPosition!.y
- }
- }
-
- return tileVersion
- }
-
- constructor(operation: 'place' | 'move' | 'delete' | 'rotate') {
- this.operation = operation
- }
-}
-
-function createCommandUpdate(object: PlacedMapObjectT, operation: 'place' | 'move' | 'delete' | 'rotate', targetPosition?: Phaser.Math.Vector2) {
- if (!currentCommand) {
- currentCommand = new MapObjectCommand(operation)
- }
- else {
- if (targetPosition) {
- currentCommand.targetPosition = targetPosition
- }
- }
-
- currentCommand.affectedTiles.push(object)
-}
-
-function finalizeCommand() {
- if (!currentCommand) return
- emit('createCommand', currentCommand)
- currentCommand = null
-}
-
-
// *** HANDLERS ***
-
function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
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
@@ -115,11 +52,10 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
}
// Add new object to mapObjects
+ mapEditor.selectedPlacedObject.value = newPlacedMapObject
map.placedMapObjects.push(newPlacedMapObject)
- createCommandUpdate(newPlacedMapObject, 'place')
-
- mapEditor.selectedPlacedObject.value = newPlacedMapObject
+ emit('update', map)
}
function eraser(pointer: Phaser.Input.Pointer, map: MapT) {
@@ -127,10 +63,10 @@ function eraser(pointer: Phaser.Input.Pointer, map: MapT) {
const existingPlacedMapObject = findObjectByPointer(pointer, map)
if (!existingPlacedMapObject) return
- createCommandUpdate(existingPlacedMapObject, 'delete')
-
// 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 {
@@ -152,48 +88,51 @@ 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
- let t: Tile
-
function handlePointerMove(pointer: Phaser.Input.Pointer) {
if (!mapEditor.movingPlacedObject.value) return
const tile = getTile(props.tileMap, pointer.worldX, pointer.worldY)
if (!tile) return
- t = tile
-
mapEditor.movingPlacedObject.value.positionX = tile.x
mapEditor.movingPlacedObject.value.positionY = tile.y
}
scene.input.on(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
- function handlePointerUp() {
+ function handlePointerUp(pointer: Phaser.Input.Pointer) {
scene.input.off(Phaser.Input.Events.POINTER_MOVE, handlePointerMove)
- mapEditor.movingPlacedObject.value = null
- createCommandUpdate(mapEditor.movingPlacedObject.value!, 'move', new Vector2(t.x, t.y))
- finalizeCommand()
+ 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
}
+ emit('updateAndCommit', map)
scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
}
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) {
+ console.log(placed.id)
+ placed.isRotated = !placed.isRotated
+ }})
+
+ emit('updateAndCommit', map)
}
function deletePlacedMapObject(id: string, map: MapT) {
- 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)
+ map.placedMapObjects = map.placedMapObjects.filter((object) => object.id !== id)
mapEditor.selectedPlacedObject.value = null
+ emit('updateAndCommit', map)
}
function clickPlacedMapObject(placedMapObject: PlacedMapObjectT) {
diff --git a/src/components/screens/MapEditor.vue b/src/components/screens/MapEditor.vue
index df868ea..0175fe5 100644
--- a/src/components/screens/MapEditor.vue
+++ b/src/components/screens/MapEditor.vue
@@ -113,7 +113,6 @@ function clear() {
if (!mapEditor.currentMap.value) return
// Clear placed objects, event tiles and tiles
- mapEditor.clearMap()
mapEditor.triggerClearTiles()
}
diff --git a/src/composables/useMapEditorComposable.ts b/src/composables/useMapEditorComposable.ts
index 4b095ae..73ede23 100644
--- a/src/composables/useMapEditorComposable.ts
+++ b/src/composables/useMapEditorComposable.ts
@@ -36,12 +36,6 @@ 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
@@ -105,7 +99,6 @@ export function useMapEditorComposable() {
// Methods
loadMap,
updateProperty,
- clearMap,
toggleActive,
setTool,
setDrawMode,