From ca307d4de3dcc3bc507801d462eca96c3879e770 Mon Sep 17 00:00:00 2001
From: Andrei <amborn02@gmail.com>
Date: Sat, 8 Feb 2025 15:07:21 -0600
Subject: [PATCH 1/2] Teleport modal restored, and expanded undo/redo to
 include all placed and erase edits across each map element type (map object
 advanced actions WIP)

---
 src/components/gameMaster/mapEditor/Map.vue   | 115 ++++++++++++--
 .../mapEditor/mapPartials/MapEventTiles.vue   |  66 +++++++-
 .../mapEditor/mapPartials/MapTiles.vue        | 145 ++++++------------
 .../mapPartials/PlacedMapObjects.vue          |  96 +++++++++++-
 .../gameMaster/mapEditor/partials/Toolbar.vue |   4 +-
 src/components/screens/MapEditor.vue          |  11 +-
 src/services/mapService.ts                    |   5 +
 7 files changed, 316 insertions(+), 126 deletions(-)

diff --git a/src/components/gameMaster/mapEditor/Map.vue b/src/components/gameMaster/mapEditor/Map.vue
index 2a926ae..7acceb7 100644
--- a/src/components/gameMaster/mapEditor/Map.vue
+++ b/src/components/gameMaster/mapEditor/Map.vue
@@ -1,30 +1,109 @@
 <template>
-  <MapTiles ref="mapTiles" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
-  <PlacedMapObjects ref="mapObjects" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
-  <MapEventTiles ref="eventTiles" v-if="tileMap" :tileMap />
+  <MapTiles ref="mapTiles" @createCommand="addCommand" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
+  <PlacedMapObjects ref="mapObjects" @createCommand="addCommand" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
+  <MapEventTiles ref="eventTiles" @createCommand="addCommand" v-if="tileMap" :tileMap />
 </template>
 
 <script setup lang="ts">
+
 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 { createTileLayer, createTileMap } from '@/services/mapService'
+import { cloneArray, createTileLayer, createTileMap, placeTiles } from '@/services/mapService'
 import { TileStorage } from '@/storage/storages'
 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 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[] = []
+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) {
   if (!mapTiles.value || !mapObjects.value || !eventTiles.value) return
 
@@ -54,12 +133,12 @@ function handlePointerDown(pointer: Phaser.Input.Pointer) {
 function handleKeyDown(event: KeyboardEvent) {
   //CTRL+Y
   if (event.key === 'y' && event.ctrlKey) {
-    mapTiles.value!.redo()
+    redo()
   }
 
   //CTRL+Z
   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) {
-  if (mapEditor.drawMode.value === 'tile') {
-    mapTiles.value?.finalizeCommand()
+  switch(mapEditor.drawMode.value) {
+    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
   if (!mapValue) return
 
+  //Clone
+  originTiles = cloneArray(mapValue.tiles)
+  originObjects = cloneArray(mapValue.placedMapObjects)
+  originEventTiles = cloneArray(mapValue.mapEventTiles)
+
   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 ca613b9..77db3a2 100644
--- a/src/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue
+++ b/src/components/gameMaster/mapEditor/mapPartials/MapEventTiles.vue
@@ -3,22 +3,72 @@
 </template>
 
 <script setup lang="ts">
+
 import { MapEventTileType, type MapEventTile, type Map as MapT, type UUID } from '@/application/types'
 import { uuidv4 } from '@/application/utilities'
 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 { shallowRef } from 'vue'
+import { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
 
 const mapEditor = useMapEditorComposable()
 
-defineExpose({ handlePointer })
+defineExpose({ handlePointer, finalizeCommand })
+
+const emit = defineEmits(['createCommand'])
 
 const props = defineProps<{
   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) {
   return {
@@ -44,7 +94,7 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
   const newEventTile = {
     id: uuidv4() as UUID,
     mapId: map.id,
-    map: map.id,
+    map: map,
     type: mapEditor.drawMode.value === 'blocking tile' ? MapEventTileType.BLOCK : MapEventTileType.TELEPORT,
     positionX: tile.x,
     positionY: tile.y,
@@ -59,6 +109,8 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
         : undefined
   }
 
+  createCommandUpdate(newEventTile, 'draw')
+
   map.mapEventTiles.push(newEventTile)
 }
 
@@ -70,12 +122,14 @@ function erase(pointer: Phaser.Input.Pointer, map: MapT) {
   // Check if event tile already exists on position
   const existingEventTile = map.mapEventTiles.find((eventTile) => eventTile.positionX === tile.x && eventTile.positionY === tile.y)
   if (!existingEventTile) return
-  
+
   if (mapEditor.drawMode.value !== existingEventTile.type.toLowerCase()) {
     if (mapEditor.drawMode.value === 'blocking tile' && existingEventTile.type === MapEventTileType.BLOCK) null //skip this case
     else return;
   }
 
+  createCommandUpdate(existingEventTile, 'erase')
+
   // Remove existing event tile
   map.mapEventTiles = map.mapEventTiles.filter((eventTile) => eventTile.id !== existingEventTile.id)
 }
diff --git a/src/components/gameMaster/mapEditor/mapPartials/MapTiles.vue b/src/components/gameMaster/mapEditor/mapPartials/MapTiles.vue
index 755b3ef..671e8b0 100644
--- a/src/components/gameMaster/mapEditor/mapPartials/MapTiles.vue
+++ b/src/components/gameMaster/mapEditor/mapPartials/MapTiles.vue
@@ -5,57 +5,70 @@
 <script setup lang="ts">
 import Controls from '@/components/utilities/Controls.vue'
 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 { type EditorCommand } from '@/components/gameMaster/mapEditor/Map.vue'
 
 const mapEditor = useMapEditorComposable()
 
-defineExpose({ handlePointer, finalizeCommand, undo, redo })
+defineExpose({ handlePointer, finalizeCommand })
+
+const emit = defineEmits(['createCommand'])
 
 const props = defineProps<{
   tileMap: Phaser.Tilemaps.Tilemap
   tileMapLayer: Phaser.Tilemaps.TilemapLayer
 }>()
 
-class EditorCommand {
+
+// *** COMMAND STATE ***
+
+let currentCommand: TileCommand | null = null
+
+class TileCommand implements EditorCommand {
   public operation: 'draw' | 'erase' = 'draw'
+  public type: 'tile' = '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) {
     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 createCommandUpdate(x: number, y: number, tileName: string, operation: 'draw' | 'erase') {
+  if (!currentCommand) {
+    currentCommand = new TileCommand(operation, tileName)
+  }
 
-function pencil(pointer: Phaser.Input.Pointer) {
-  let map = mapEditor.currentMap.value
-  if (!map) return
+  //If position is already in, do not proceed
+  for (const vec of currentCommand.affectedTiles) {
+    if (vec[0] === x && vec[1] === y) 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
+  currentCommand.affectedTiles.push([x, y])
 }
 
-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
   if (!map) return
 
@@ -64,12 +77,12 @@ function eraser(pointer: Phaser.Input.Pointer) {
   if (!tile) return
 
   // 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
-  map.tiles[tile.y][tile.x] = 'blank_tile'
+  map.tiles[tile.y][tile.x] = tileName
 }
 
 function paint(pointer: Phaser.Input.Pointer) {
@@ -113,10 +126,10 @@ function handlePointer(pointer: Phaser.Input.Pointer) {
   // Check if draw mode is tile
   switch (mapEditor.tool.value) {
     case 'pencil':
-      pencil(pointer)
+      draw(pointer, mapEditor.selectedTile.value!)
       break
     case 'eraser':
-      eraser(pointer)
+      draw(pointer, 'blank_tile')
       break
     case 'paint':
       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
-  for (const vec of currentCommand.affectedTiles) {
-    if (vec[0] === x && vec[1] === y) return
-  }
+// *** LIFECYCLE ***
 
-  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(
   () => mapEditor.shouldClearTiles.value,
@@ -205,9 +157,6 @@ onMounted(async () => {
   if (!mapEditor.currentMap.value) return
   const mapState = mapEditor.currentMap.value
 
-  //Clone
-  originTiles = cloneArray(mapState.tiles)
-
   placeTiles(props.tileMap, props.tileMapLayer, mapState.tiles)
 })
 </script>
diff --git a/src/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue b/src/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue
index 63096e1..81aabfe 100644
--- a/src/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue
+++ b/src/components/gameMaster/mapEditor/mapPartials/PlacedMapObjects.vue
@@ -4,29 +4,98 @@
 </template>
 
 <script setup lang="ts">
-import type { MapObject, Map as MapT, PlacedMapObject as PlacedMapObjectT, UUID } from '@/application/types'
+import type {
+  MapObject,
+  Map as MapT,
+  PlacedMapObject as PlacedMapObjectT,
+  UUID,
+  MapEventTile
+} 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 { getTile } from '@/services/mapService'
+import { cloneArray, 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)
+const map = computed(() => mapEditor.currentMap.value!)
 
-defineExpose({ handlePointer })
+defineExpose({ handlePointer, finalizeCommand })
+
+const emit = defineEmits(['createCommand'])
 
 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
@@ -47,6 +116,9 @@ function pencil(pointer: Phaser.Input.Pointer, map: MapT) {
 
   // Add new object to mapObjects
   map.placedMapObjects.push(newPlacedMapObject)
+
+  createCommandUpdate(newPlacedMapObject, 'place')
+
   mapEditor.selectedPlacedObject.value = newPlacedMapObject
 }
 
@@ -55,6 +127,8 @@ 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)
 }
@@ -78,11 +152,15 @@ 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
   }
@@ -92,6 +170,9 @@ function moveMapObject(id: string, map: MapT) {
   function handlePointerUp() {
     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()
   }
 
   scene.input.on(Phaser.Input.Events.POINTER_UP, handlePointerUp)
@@ -104,6 +185,13 @@ function rotatePlacedMapObject(id: string, map: MapT) {
 
 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)
   mapEditor.selectedPlacedObject.value = null
 }
diff --git a/src/components/gameMaster/mapEditor/partials/Toolbar.vue b/src/components/gameMaster/mapEditor/partials/Toolbar.vue
index 8a92fca..9e40f46 100644
--- a/src/components/gameMaster/mapEditor/partials/Toolbar.vue
+++ b/src/components/gameMaster/mapEditor/partials/Toolbar.vue
@@ -93,7 +93,7 @@ import { onBeforeUnmount, onMounted, ref } from 'vue'
 
 const mapEditor = useMapEditorComposable()
 
-const emit = defineEmits(['save', 'clear', 'open-maps', 'open-settings', 'close-editor', 'open-tile-list', 'open-map-object-list', 'close-lists'])
+const emit = defineEmits(['save', 'clear', 'open-maps', 'open-settings', 'open-teleport-settings', 'close-editor', 'open-tile-list', 'open-map-object-list', 'close-lists'])
 
 // track when clicked outside of toolbar items
 const toolbar = ref(null)
@@ -115,6 +115,7 @@ function setDrawMode(value: string) {
     emit('close-lists')
     if (value === 'tile') emit('open-tile-list')
     if (value === 'map_object') emit('open-map-object-list')
+    if (value === 'teleport') emit('open-teleport-settings')
   }
 
   mapEditor.setDrawMode(value)
@@ -155,6 +156,7 @@ function handleClick(tool: string) {
 
   selectPencilOpen.value = tool === 'pencil' ? !selectPencilOpen.value : false
   selectEraserOpen.value = tool === 'eraser' ? !selectEraserOpen.value : false
+
 }
 
 function cycleToolMode(tool: 'pencil' | 'eraser') {
diff --git a/src/components/screens/MapEditor.vue b/src/components/screens/MapEditor.vue
index c518393..df868ea 100644
--- a/src/components/screens/MapEditor.vue
+++ b/src/components/screens/MapEditor.vue
@@ -11,6 +11,7 @@
             @clear="clear"
             @open-maps="mapModal?.open"
             @open-settings="mapSettingsModal?.open"
+            @open-teleport-settings="teleportModal?.open"
             @close-editor="mapEditor.toggleActive"
             @close-lists="tileList?.close"
             @closeLists="objectList?.close"
@@ -45,6 +46,7 @@ import { MapStorage } from '@/storage/storages'
 import { useGameStore } from '@/stores/gameStore'
 import { Game, Scene } from 'phavuer'
 import { ref, useTemplateRef } from 'vue'
+import teleportModal from '@/components/gameMaster/mapEditor/partials/TeleportModal.vue'
 
 const mapStorage = new MapStorage()
 const mapEditor = useMapEditorComposable()
@@ -54,6 +56,7 @@ const mapModal = useTemplateRef('mapModal')
 const tileList = useTemplateRef('tileList')
 const objectList = useTemplateRef('objectList')
 const mapSettingsModal = useTemplateRef('mapSettingsModal')
+const teleportSettings = useTemplateRef('teleportModal')
 
 const isLoaded = ref(false)
 
@@ -96,14 +99,8 @@ function save() {
   if (!currentMap) return
 
   const data = {
+    ...currentMap,
     mapId: currentMap.id,
-    name: currentMap.name,
-    width: currentMap.width,
-    height: currentMap.height,
-    tiles: currentMap.tiles,
-    pvp: currentMap.pvp,
-    mapEffects: currentMap.mapEffects,
-    mapEventTiles: currentMap.mapEventTiles,
     placedMapObjects: currentMap.placedMapObjects.map(({ id, mapObject, depth, isRotated, positionX, positionY }) => ({ id, mapObject, depth, isRotated, positionX, positionY })) ?? []
   }
 
diff --git a/src/services/mapService.ts b/src/services/mapService.ts
index 3d89795..2691180 100644
--- a/src/services/mapService.ts
+++ b/src/services/mapService.ts
@@ -147,3 +147,8 @@ export function createTileLayer(tileMap: Phaser.Tilemaps.Tilemap, tilesArray: st
 
   return layer
 }
+
+//Recursive Array Clone
+export function cloneArray(arr: any[]): any[] {
+  return arr.map((item) => (item instanceof Array ? cloneArray(item) : item))
+}
\ No newline at end of file

From 94596394978589e023e85cc5422202de37b6e182 Mon Sep 17 00:00:00 2001
From: Andrei <amborn02@gmail.com>
Date: Mon, 10 Feb 2025 16:02:38 -0600
Subject: [PATCH 2/2] Best undo/redo function across all map editor elements

---
 src/components/gameMaster/mapEditor/Map.vue   | 122 +++++++++++------
 .../mapEditor/mapPartials/MapEventTiles.vue   |  18 ++-
 .../mapEditor/mapPartials/MapTiles.vue        |  40 +++---
 .../mapPartials/PlacedMapObjects.vue          | 125 +++++-------------
 src/components/screens/MapEditor.vue          |   1 -
 src/composables/useMapEditorComposable.ts     |   7 -
 6 files changed, 147 insertions(+), 166 deletions(-)

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 @@
 <template>
   <MapTiles ref="mapTiles" @createCommand="addCommand" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
-  <PlacedMapObjects ref="mapObjects" @createCommand="addCommand" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
+  <PlacedMapObjects ref="mapObjects" @update="updateMapObjects" @updateAndCommit="updateAndCommit" v-if="tileMap && tileMapLayer" :tileMap :tileMapLayer />
   <MapEventTiles ref="eventTiles" @createCommand="addCommand" v-if="tileMap" :tileMap />
 </template>
 
@@ -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<Phaser.Tilemaps.Tilemap>()
 const tileMapLayer = shallowRef<Phaser.Tilemaps.TilemapLayer>()
@@ -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<PlacedMapObjectT[]>(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()
+}
+
 </script>
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 @@
 <template>
-  <SelectedPlacedMapObjectComponent v-if="mapEditor.selectedPlacedObject.value" :map :placedMapObject="mapEditor.selectedPlacedObject.value" @move="moveMapObject" @rotate="rotatePlacedMapObject" @delete="deletePlacedMapObject" />
+  <SelectedPlacedMapObjectComponent v-if="mapEditor.selectedPlacedObject.value" :map="mapEditor.currentMap.value!" :placedMapObject="mapEditor.selectedPlacedObject.value" @move="moveMapObject" @rotate="rotatePlacedMapObject" @delete="deletePlacedMapObject" />
   <PlacedMapObject v-for="placedMapObject in mapEditor.currentMap.value?.placedMapObjects" :tileMap :tileMapLayer :placedMapObject @pointerdown="clickPlacedMapObject(placedMapObject)" />
 </template>
 
@@ -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()
 }
 </script>
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,