import { Server } from 'socket.io' import { TSocket } from '../../../utilities/types' import ZoneRepository from '../../../repositories/zoneRepository' import { Zone, ZoneEffect, ZoneEventTileType, ZoneObject } from '@prisma/client' import prisma from '../../../utilities/prisma' import zoneManager from '../../../managers/zoneManager' import CharacterRepository from '../../../repositories/characterRepository' import { gameMasterLogger } from '../../../utilities/logger' interface IPayload { zoneId: number name: string width: number height: number tiles: string[][] pvp: boolean zoneEventTiles: { type: ZoneEventTileType positionX: number positionY: number teleport?: { toZoneId: number toPositionX: number toPositionY: number toRotation: number } }[] zoneEffects: { effect: string strength: number }[] zoneObjects: ZoneObject[] } export default class ZoneUpdateEvent { constructor( private readonly io: Server, private readonly socket: TSocket ) {} public listen(): void { this.socket.on('gm:zone_editor:zone:update', this.handleEvent.bind(this)) } private async handleEvent(data: IPayload, callback: (response: Zone | null) => void): Promise { try { const character = await CharacterRepository.getById(this.socket.characterId as number) if (!character) { gameMasterLogger.error('gm:zone_editor:zone:update error', 'Character not found') return callback(null) } if (character.role !== 'gm') { gameMasterLogger.info(`User ${character.id} tried to update zone but is not a game master.`) return callback(null) } gameMasterLogger.info(`User ${character.id} has updated zone via zone editor.`) if (!data.zoneId) { gameMasterLogger.info(`User ${character.id} tried to update zone but did not provide a zone id.`) return callback(null) } let zone = await ZoneRepository.getById(data.zoneId) if (!zone) { gameMasterLogger.info(`User ${character.id} tried to update zone ${data.zoneId} but it does not exist.`) return callback(null) } // If tiles are larger than the zone, remove the extra tiles if (data.tiles.length > data.height) { data.tiles = data.tiles.slice(0, data.height) } for (let i = 0; i < data.tiles.length; i++) { if (data.tiles[i].length > data.width) { data.tiles[i] = data.tiles[i].slice(0, data.width) } } // If zone event tiles are placed outside the zone's bounds, remove these data.zoneEventTiles = data.zoneEventTiles.filter((tile) => tile.positionX >= 0 && tile.positionX < data.width && tile.positionY >= 0 && tile.positionY < data.height) // If zone objects are placed outside the zone's bounds, remove these data.zoneObjects = data.zoneObjects.filter((obj) => obj.positionX >= 0 && obj.positionX < data.width && obj.positionY >= 0 && obj.positionY < data.height) await prisma.zone.update({ where: { id: data.zoneId }, data: { name: data.name, width: data.width, height: data.height, tiles: data.tiles, pvp: data.pvp, zoneEventTiles: { deleteMany: { zoneId: data.zoneId }, create: data.zoneEventTiles.map((zoneEventTile) => ({ 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, toRotation: zoneEventTile.teleport.toRotation } } } : {}) })) }, zoneObjects: { deleteMany: { zoneId: data.zoneId }, create: data.zoneObjects.map((zoneObject) => ({ objectId: zoneObject.objectId, depth: zoneObject.depth, isRotated: zoneObject.isRotated, positionX: zoneObject.positionX, positionY: zoneObject.positionY })) }, zoneEffects: { deleteMany: { zoneId: data.zoneId }, create: data.zoneEffects.map((zoneEffect) => ({ effect: zoneEffect.effect, strength: zoneEffect.strength })) }, updatedAt: new Date() } }) zone = await ZoneRepository.getById(data.zoneId) if (!zone) { gameMasterLogger.info(`User ${character.id} tried to update zone ${data.zoneId} but it does not exist after update.`) callback(null) return } gameMasterLogger.info(`User ${character.id} has updated zone via zone editor.`) callback(zone) /** * @TODO #246: Reload zone for players who are currently in the zone */ zoneManager.unloadZone(data.zoneId) await zoneManager.loadZone(zone) } catch (error: any) { gameMasterLogger.error(`gm:zone_editor:zone:update error: ${error instanceof Error ? error.message : String(error)}`) callback(null) } } }