diff --git a/src/events/character/move.ts b/src/events/character/move.ts
index 0dba817..88f3ce2 100644
--- a/src/events/character/move.ts
+++ b/src/events/character/move.ts
@@ -1,93 +1,156 @@
-import { Server } from 'socket.io'
-import { TSocket, ExtendedCharacter } from '../../utilities/types'
-import ZoneManager from '../../managers/zoneManager'
-import prisma from '../../utilities/prisma'
-import { AStar } from '../../utilities/player/aStar'
-import Rotation from '../../utilities/player/rotation'
+import { Server } from 'socket.io';
+import { TSocket, ExtendedCharacter } from '../../utilities/types';
+import ZoneManager from '../../managers/zoneManager';
+import prisma from '../../utilities/prisma';
+import { AStar } from '../../utilities/player/aStar';
+import Rotation from '../../utilities/player/rotation';
+import ZoneRepository from '../../repositories/zoneRepository';
+import { Character } from '@prisma/client';
 
-const moveTokens = new Map<number, symbol>()
+const moveTokens = new Map<number, symbol>();
 
 export default function setupCharacterMove(socket: TSocket, io: Server) {
-  socket.on('character:move', async ({ positionX, positionY }: { positionX: number; positionY: number }) => {
-    const { character } = socket
-    if (!character) return console.error('character:move error', 'Character not found')
-
-    moveTokens.set(character.id, Symbol('moveToken'))
-    const grid = await ZoneManager.getGrid(character.zoneId)
-    if (!grid?.length) return console.error('character:move error', 'Grid not found or empty')
-
-    const start = { x: Math.floor(character.positionX), y: Math.floor(character.positionY) }
-    const end = { x: Math.floor(positionX), y: Math.floor(positionY) }
-
-    if (isObstacle(end, grid)) return socket.emit('character:moveError', 'Destination is an obstacle')
-
-    const path = AStar.findPath(start, end, grid)
-    if (!path.length) return socket.emit('character:moveError', 'No valid path found')
-
-    character.isMoving = true
-    io.in(character.zoneId.toString()).emit('character:moved', character)
-    moveAlongPath(socket, io, path, grid).catch(console.error)
-  })
+  socket.on('character:initMove', handleCharacterMove(socket, io));
 }
 
-async function moveAlongPath(socket: TSocket, io: Server, path: Array<{ x: number; y: number }>, grid: number[][]) {
-  const { character } = socket
-  if (!character) return
+const handleCharacterMove = (socket: TSocket, io: Server) => async ({ positionX, positionY }: { positionX: number; positionY: number }) => {
+  const { character } = socket;
+  if (!character) return console.error('character:move error', 'Character not found');
 
-  const moveToken = moveTokens.get(character.id)
-  const stepDuration = 250
-  const updateInterval = 50
+  const grid = await ZoneManager.getGrid(character.zoneId);
+  if (!grid?.length) return console.error('character:move error', 'Grid not found or empty');
+
+  const start = { x: Math.floor(character.positionX), y: Math.floor(character.positionY) };
+  const end = { x: Math.floor(positionX), y: Math.floor(positionY) };
+
+  if (isObstacle(end, grid)) return socket.emit('character:moveError', 'Destination is an obstacle');
+
+  const path = AStar.findPath(start, end, grid);
+  if (!path.length) return socket.emit('character:moveError', 'No valid path found');
+
+  moveTokens.set(character.id, Symbol('moveToken'));
+  character.isMoving = true;
+  io.in(character.zoneId.toString()).emit('character:move', character);
+  moveAlongPath(socket, io, path, grid).catch(console.error);
+};
+
+async function moveAlongPath(socket: TSocket, io: Server, path: Array<{ x: number; y: number }>, grid: number[][]) {
+  const { character } = socket;
+  if (!character) return;
+
+  const moveToken = moveTokens.get(character.id);
+  const stepDuration = 250;
+  const updateInterval = 50;
 
   for (let i = 0; i < path.length - 1; i++) {
-    const startTime = Date.now()
-    const start = path[i]
-    const end = path[i + 1]
+    const startTime = Date.now();
+    const start = path[i];
+    const end = path[i + 1];
 
     while (Date.now() - startTime < stepDuration) {
-      if (moveTokens.get(character.id) !== moveToken) return
+      if (moveTokens.get(character.id) !== moveToken) return;
 
-      const progress = (Date.now() - startTime) / stepDuration
-      const current = interpolatePosition(start, end, progress)
+      const progress = (Date.now() - startTime) / stepDuration;
+      const current = interpolatePosition(start, end, progress);
 
       if (isObstacle(current, grid)) {
-        // If obstacle encountered, stop at the last valid position
-        await updateCharacter(character, start, Rotation.calculate(start.x, start.y, end.x, end.y))
-        io.in(character.zoneId.toString()).emit('character:moved', character)
-        return
+        await updateCharacterPosition(character, start, Rotation.calculate(start.x, start.y, end.x, end.y), socket, io);
+        return;
       }
 
-      await updateCharacter(character, current, Rotation.calculate(start.x, start.y, end.x, end.y))
-      io.in(character.zoneId.toString()).emit('character:moved', character)
-      await new Promise((resolve) => setTimeout(resolve, updateInterval))
+      const tp = await prisma.zoneEventTile.findFirst({
+        where: { zoneId: character.zoneId, type: 'TELEPORT', positionX: current.x, positionY: current.y },
+        include: { teleport: true }
+      });
+
+      if (tp?.teleport) {
+        await handleTeleport(socket, io, character, tp.teleport, start, end);
+        return;
+      }
+
+      await updateCharacterPosition(character, current, Rotation.calculate(start.x, start.y, end.x, end.y), socket,  io);
+      await new Promise(resolve => setTimeout(resolve, updateInterval));
     }
   }
 
-  // Ensure the character reaches the exact final position
   if (moveTokens.get(character.id) === moveToken) {
-    const finalPosition = path[path.length - 1]
-    await updateCharacter(character, finalPosition, character.rotation)
-    character.isMoving = false
-    moveTokens.delete(character.id)
-    io.in(character.zoneId.toString()).emit('character:moved', character)
+    await updateCharacterPosition(character, path[path.length - 1], character.rotation, socket, io);
+    character.isMoving = false;
+    moveTokens.delete(character.id);
   }
 }
 
+async function handleTeleport(socket: TSocket, io: Server, character: ExtendedCharacter, teleport: any, start: { x: number; y: number }, end: { x: number; y: number }) {
+  if (teleport.toZoneId === character.zoneId) return;
+
+  const zone = await ZoneRepository.getById(teleport.toZoneId);
+  if (!zone) return;
+
+  character.isMoving = false;
+  character.zoneId = teleport.toZoneId;
+
+  moveTokens.delete(character.id);
+
+  socket.leave(character.zoneId.toString());
+  socket.join(teleport.toZoneId.toString());
+
+  await updateCharacterPosition(
+    character,
+    { x: teleport.toPositionX, y: teleport.toPositionY },
+    Rotation.calculate(start.x, start.y, end.x, end.y),
+    socket,
+    io,
+    teleport.toZoneId
+  );
+}
+
+async function updateCharacterPosition(
+  character: ExtendedCharacter,
+  position: { x: number; y: number },
+  rotation: number,
+  socket: TSocket,
+  io: Server,
+  newZoneId?: number
+) {
+  const oldZoneId = character.zoneId;
+
+  Object.assign(character, {
+    positionX: position.x,
+    positionY: position.y,
+    rotation,
+    zoneId: newZoneId || character.zoneId
+  });
+
+  if (newZoneId && newZoneId !== oldZoneId) {
+    io.to(oldZoneId.toString()).emit('zone:character:leave', character);
+    io.to(newZoneId.toString()).emit('zone:character:join', character);
+    ZoneManager.removeCharacterFromZone(oldZoneId, character as Character);
+    ZoneManager.addCharacterToZone(newZoneId, character as Character);
+  } else {
+    ZoneManager.updateCharacterInZone(character.zoneId, character);
+  }
+
+  await prisma.character.update({
+    where: { id: character.id },
+    data: {
+      positionX: position.x,
+      positionY: position.y,
+      rotation,
+      zoneId: character.zoneId
+    }
+  });
+
+  io.in(character.zoneId.toString()).emit('character:move', character);
+  socket.emit('character:dataUpdated', character);
+}
+
 const interpolatePosition = (start: { x: number; y: number }, end: { x: number; y: number }, progress: number) => ({
   x: start.x + (end.x - start.x) * progress,
   y: start.y + (end.y - start.y) * progress
-})
+});
 
 const isObstacle = ({ x, y }: { x: number; y: number }, grid: number[][]) => {
-  const gridX = Math.floor(x)
-  const gridY = Math.floor(y)
-  return grid[gridY]?.[gridX] === 1 || grid[gridY]?.[Math.ceil(x)] === 1 || grid[Math.ceil(y)]?.[gridX] === 1 || grid[Math.ceil(y)]?.[Math.ceil(x)] === 1
-}
-
-async function updateCharacter(character: ExtendedCharacter, { x, y }: { x: number; y: number }, rotation: number) {
-  Object.assign(character, { positionX: x, positionY: y, rotation })
-  ZoneManager.updateCharacterInZone(character.zoneId, character)
-  await prisma.character.update({
-    where: { id: character.id },
-    data: { positionX: x, positionY: y, rotation }
-  })
-}
+  const gridX = Math.floor(x);
+  const gridY = Math.floor(y);
+  return grid[gridY]?.[gridX] === 1 || grid[gridY]?.[Math.ceil(x)] === 1 || grid[Math.ceil(y)]?.[gridX] === 1 || grid[Math.ceil(y)]?.[Math.ceil(x)] === 1;
+};
\ No newline at end of file
diff --git a/src/events/character/zoneLeave.ts b/src/events/character/zoneLeave.ts
index 4665269..adb5308 100644
--- a/src/events/character/zoneLeave.ts
+++ b/src/events/character/zoneLeave.ts
@@ -32,8 +32,6 @@ export default function (socket: TSocket, io: Server) {
 
     socket.leave(zone.id.toString())
 
-    socket.emit('character:zone:unload')
-
     // let other clients know of new character
     io.to(zone.id.toString()).emit('zone:character:leave', socket.character)
 
diff --git a/src/events/character/zoneRequest.ts b/src/events/character/zoneRequest.ts
index 790631e..06303e0 100644
--- a/src/events/character/zoneRequest.ts
+++ b/src/events/character/zoneRequest.ts
@@ -3,6 +3,7 @@ import { TSocket } from '../../utilities/types'
 import ZoneRepository from '../../repositories/zoneRepository'
 import ZoneManager from '../../managers/zoneManager'
 import { Character, Zone } from '@prisma/client'
+import logger from '../../utilities/logger'
 
 interface IPayload {
   zoneId: number
@@ -20,29 +21,42 @@ interface IResponse {
  */
 export default function (socket: TSocket, io: Server) {
   socket.on('character:zone:request', async (data: IPayload, callback: (response: IResponse) => void) => {
-    console.log(`---User ${socket.character?.id} has requested zone.`)
+    try {
+      console.log(`---User ${socket.character?.id} has requested zone.`)
 
-    if (!data.zoneId) {
-      console.log(`---Zone id not provided.`)
-      return
+      if (!socket.character) return;
+
+      if (!data.zoneId) {
+        console.log(`---Zone id not provided.`)
+        return
+      }
+
+      const zone = await ZoneRepository.getById(data.zoneId)
+
+      if (!zone) {
+        console.log(`---Zone not found.`)
+        return
+      }
+
+      if (socket.character?.zoneId) {
+        socket.leave(socket.character.zoneId.toString())
+        io.to(socket.character.zoneId.toString()).emit('zone:character:leave', socket.character)
+      }
+
+      socket.character.zoneId = zone.id
+      socket.join(zone.id.toString())
+
+      // let other clients know of new character
+      io.to(zone.id.toString()).emit('zone:character:join', socket.character)
+
+      // add character to zone manager
+      ZoneManager.addCharacterToZone(zone.id, socket.character as Character)
+
+      // send over zone and characters to socket
+      callback({ zone, characters: ZoneManager.getCharactersInZone(zone.id) })
+    } catch (error: any) {
+      console.log(`Error requesting zone: ${error.message}`)
+      logger.error(`Error requesting zone: ${error.message}`)
     }
-
-    const zone = await ZoneRepository.getById(data.zoneId)
-
-    if (!zone) {
-      console.log(`---Zone not found.`)
-      return
-    }
-
-    socket.join(zone.id.toString())
-
-    // send over zone and characters to socket
-    callback({ zone, characters: ZoneManager.getCharactersInZone(zone.id) })
-
-    // let other clients know of new character
-    io.to(zone.id.toString()).emit('zone:character:join', socket.character)
-
-    // add character to zone manager
-    ZoneManager.addCharacterToZone(zone.id, socket.character as Character)
   })
 }
diff --git a/src/events/gm/zone/update.ts b/src/events/gm/zone/update.ts
index 48e4c26..de4df14 100644
--- a/src/events/gm/zone/update.ts
+++ b/src/events/gm/zone/update.ts
@@ -73,17 +73,15 @@ export default function (socket: TSocket, io: Server) {
               type: zoneEventTile.type,
               positionX: zoneEventTile.positionX,
               positionY: zoneEventTile.positionY,
-              ...(zoneEventTile.type === 'TELEPORT' && zoneEventTile.teleport
-                ? {
-                    teleport: {
-                      create: {
-                        toZoneId: zoneEventTile.teleport.toZoneId,
-                        toPositionX: zoneEventTile.teleport.toPositionX,
-                        toPositionY: zoneEventTile.teleport.toPositionY
-                      }
+              ...(zoneEventTile.type === 'TELEPORT' && zoneEventTile.teleport ? {
+                  teleport: {
+                    create: {
+                      toZoneId: zoneEventTile.teleport.toZoneId,
+                      toPositionX: zoneEventTile.teleport.toPositionX,
+                      toPositionY: zoneEventTile.teleport.toPositionY
                     }
                   }
-                : {})
+                } : {})
             }))
           },
           zoneObjects: {