From f7dbf09bf5e8bdf42372d0987efcf7f6409e1293 Mon Sep 17 00:00:00 2001 From: Dennis Postma Date: Thu, 2 Jan 2025 02:24:09 +0100 Subject: [PATCH] More event progress --- src/application/base/baseEvent.ts | 11 ++ .../gameMaster/assetManager/sprite/copy.ts | 68 ++------ .../gameMaster/assetManager/sprite/create.ts | 13 +- .../gameMaster/assetManager/sprite/delete.ts | 41 +---- .../gameMaster/assetManager/sprite/list.ts | 20 +-- .../gameMaster/assetManager/sprite/update.ts | 32 +--- .../gameMaster/assetManager/tile/delete.ts | 54 ++---- .../gameMaster/assetManager/tile/list.ts | 12 +- .../gameMaster/assetManager/tile/update.ts | 18 +- .../gameMaster/assetManager/tile/upload.ts | 10 +- src/events/gameMaster/zoneEditor/create.ts | 51 ++---- src/events/gameMaster/zoneEditor/delete.ts | 59 ++----- src/events/gameMaster/zoneEditor/list.ts | 32 +--- src/events/gameMaster/zoneEditor/request.ts | 49 ++---- src/events/gameMaster/zoneEditor/update.ts | 154 +++++++----------- src/managers/dateManager.ts | 7 +- src/managers/weatherManager.ts | 23 +-- src/server.ts | 4 +- 18 files changed, 194 insertions(+), 464 deletions(-) diff --git a/src/application/base/baseEvent.ts b/src/application/base/baseEvent.ts index d838beb..22dc524 100644 --- a/src/application/base/baseEvent.ts +++ b/src/application/base/baseEvent.ts @@ -2,6 +2,8 @@ import { Server } from 'socket.io' import Logger, { LoggerType } from '#application/logger' import { TSocket } from '#application/types' +import { Character } from '#entities/character' +import CharacterRepository from '#repositories/characterRepository' export abstract class BaseEvent { protected readonly logger = Logger.type(LoggerType.GAME) @@ -11,6 +13,15 @@ export abstract class BaseEvent { readonly socket: TSocket ) {} + protected async getCharacter(): Promise { + return CharacterRepository.getById(this.socket.characterId!) + } + + protected async isCharacterGM(): Promise { + const character = await this.getCharacter() + return character?.getRole() === 'gm' + } + protected emitError(message: string): void { this.socket.emit('notification', { title: 'Server message', message }) this.logger.error('character:connect error', `Player ${this.socket.userId}: ${message}`) diff --git a/src/events/gameMaster/assetManager/sprite/copy.ts b/src/events/gameMaster/assetManager/sprite/copy.ts index a782473..5565944 100644 --- a/src/events/gameMaster/assetManager/sprite/copy.ts +++ b/src/events/gameMaster/assetManager/sprite/copy.ts @@ -1,79 +1,35 @@ -import { Server } from 'socket.io' - -import type { Prisma } from '@prisma/client' - -import { gameMasterLogger } from '#application/logger' -import prisma from '#application/prisma' -import { TSocket } from '#application/types' +import { BaseEvent } from '#application/base/baseEvent' +import { UUID } from '#application/types' +import { Sprite } from '#entities/sprite' import CharacterRepository from '#repositories/characterRepository' +import SpriteRepository from '#repositories/spriteRepository' interface CopyPayload { - id: string + id: UUID } -export default class SpriteCopyEvent { - constructor( - private readonly io: Server, - private readonly socket: TSocket - ) {} - +export default class SpriteCopyEvent extends BaseEvent { public listen(): void { this.socket.on('gm:sprite:copy', this.handleEvent.bind(this)) } private async handleEvent(payload: CopyPayload, callback: (success: boolean) => void): Promise { try { - if (!(await this.validateGameMasterAccess())) { - return callback(false) - } + if (!(await this.isCharacterGM())) return - const sourceSprite = await prisma.sprite.findUnique({ - where: { id: payload.id }, - include: { - spriteActions: true - } - }) + const sourceSprite = await SpriteRepository.getById(payload.id) if (!sourceSprite) { throw new Error('Source sprite not found') } - const newSprite = await prisma.sprite.create({ - data: { - name: `${sourceSprite.name} (Copy)`, - spriteActions: { - create: sourceSprite.spriteActions.map((action) => ({ - action: action.action, - sprites: action.sprites as Prisma.InputJsonValue, - originX: action.originX, - originY: action.originY, - isAnimated: action.isAnimated, - isLooping: action.isLooping, - frameWidth: action.frameWidth, - frameHeight: action.frameHeight, - frameRate: action.frameRate - })) - } - } - }) + const newSprite = new Sprite() + await newSprite.setName(`${sourceSprite.getName()} (Copy)`).setSpriteActions(sourceSprite.getSpriteActions()).save() callback(true) } catch (error) { - this.handleError(error, payload.id, callback) + this.logger.error(`Error copying sprite:`, String(error)) + callback(false) } } - - private async validateGameMasterAccess(): Promise { - const character = await CharacterRepository.getById(this.socket.characterId!) - return character?.role === 'gm' - } - - private handleError(error: unknown, spriteId: string, callback: (success: boolean) => void): void { - gameMasterLogger.error(`Error copying sprite ${spriteId}: ${this.getErrorMessage(error)}`) - callback(false) - } - - private getErrorMessage(error: unknown): string { - return error instanceof Error ? error.message : String(error) - } } diff --git a/src/events/gameMaster/assetManager/sprite/create.ts b/src/events/gameMaster/assetManager/sprite/create.ts index fc61769..25103b0 100644 --- a/src/events/gameMaster/assetManager/sprite/create.ts +++ b/src/events/gameMaster/assetManager/sprite/create.ts @@ -2,12 +2,10 @@ import fs from 'fs/promises' import { Server } from 'socket.io' -import prisma from '#application/prisma' +import { BaseEvent } from '#application/base/baseEvent' import Storage from '#application/storage' -import { TSocket } from '#application/types' -import characterRepository from '#repositories/characterRepository' -export default class SpriteCreateEvent { +export default class SpriteCreateEvent extends BaseEvent { constructor( private readonly io: Server, private readonly socket: TSocket @@ -19,12 +17,7 @@ export default class SpriteCreateEvent { private async handleEvent(data: undefined, callback: (response: boolean) => void): Promise { try { - const character = await characterRepository.getById(this.socket.characterId!) - if (!character) return callback(false) - - if (character.role !== 'gm') { - return callback(false) - } + if (!(await this.isCharacterGM())) return const public_folder = Storage.getPublicPath('sprites') diff --git a/src/events/gameMaster/assetManager/sprite/delete.ts b/src/events/gameMaster/assetManager/sprite/delete.ts index e299547..ae792d0 100644 --- a/src/events/gameMaster/assetManager/sprite/delete.ts +++ b/src/events/gameMaster/assetManager/sprite/delete.ts @@ -1,45 +1,30 @@ import fs from 'fs' -import { Server } from 'socket.io' - -import { gameMasterLogger } from '#application/logger' -import prisma from '#application/prisma' +import { BaseEvent } from '#application/base/baseEvent' import Storage from '#application/storage' -import { TSocket } from '#application/types' -import CharacterRepository from '#repositories/characterRepository' +import { UUID } from '#application/types' +import SpriteRepository from '#repositories/spriteRepository' type Payload = { - id: string + id: UUID } -export default class GMSpriteDeleteEvent { - private readonly public_folder: string - - constructor( - private readonly io: Server, - private readonly socket: TSocket - ) { - this.public_folder = Storage.getPublicPath('sprites') - } - +export default class GMSpriteDeleteEvent extends BaseEvent { public listen(): void { this.socket.on('gm:sprite:delete', this.handleEvent.bind(this)) } private async handleEvent(data: Payload, callback: (response: boolean) => void): Promise { - const character = await CharacterRepository.getById(this.socket.characterId!) - if (character?.role !== 'gm') { - return callback(false) - } + if (!(await this.isCharacterGM())) return try { await this.deleteSpriteFolder(data.id) - await this.deleteSpriteFromDatabase(data.id) + await (await SpriteRepository.getById(data.id))?.delete() - gameMasterLogger.info(`Sprite ${data.id} deleted.`) + this.logger.info(`Sprite ${data.id} deleted.`) callback(true) } catch (error: any) { - gameMasterLogger.error('gm:sprite:delete error', error.message) + this.logger.error('gm:sprite:delete error', error.message) callback(false) } } @@ -51,12 +36,4 @@ export default class GMSpriteDeleteEvent { await fs.promises.rmdir(finalFilePath, { recursive: true }) } } - - private async deleteSpriteFromDatabase(spriteId: string): Promise { - await prisma.sprite.delete({ - where: { - id: spriteId - } - }) - } } diff --git a/src/events/gameMaster/assetManager/sprite/list.ts b/src/events/gameMaster/assetManager/sprite/list.ts index b337367..b8b0359 100644 --- a/src/events/gameMaster/assetManager/sprite/list.ts +++ b/src/events/gameMaster/assetManager/sprite/list.ts @@ -1,29 +1,17 @@ import { Sprite } from '@prisma/client' -import { Server } from 'socket.io' -import { TSocket } from '#application/types' -import characterRepository from '#repositories/characterRepository' +import { BaseEvent } from '#application/base/baseEvent' import SpriteRepository from '#repositories/spriteRepository' interface IPayload {} -export default class SpriteListEvent { - constructor( - private readonly io: Server, - private readonly socket: TSocket - ) {} - +export default class SpriteListEvent extends BaseEvent { public listen(): void { this.socket.on('gm:sprite:list', this.handleEvent.bind(this)) } - private async handleEvent(data: any, callback: (response: Sprite[]) => void): Promise { - const character = await characterRepository.getById(this.socket.characterId!) - if (!character) return callback([]) - - if (character.role !== 'gm') { - return callback([]) - } + private async handleEvent(data: IPayload, callback: (response: Sprite[]) => void): Promise { + if (!(await this.isCharacterGM())) return // get all sprites const sprites = await SpriteRepository.getAll() diff --git a/src/events/gameMaster/assetManager/sprite/update.ts b/src/events/gameMaster/assetManager/sprite/update.ts index afcefa7..5787fa5 100644 --- a/src/events/gameMaster/assetManager/sprite/update.ts +++ b/src/events/gameMaster/assetManager/sprite/update.ts @@ -1,15 +1,11 @@ import { writeFile, mkdir } from 'node:fs/promises' import sharp from 'sharp' -import { Server } from 'socket.io' -import type { Prisma, SpriteAction } from '@prisma/client' - -import { gameMasterLogger } from '#application/logger' -import prisma from '#application/prisma' +import { BaseEvent } from '#application/base/baseEvent' import Storage from '#application/storage' -import { TSocket } from '#application/types' -import CharacterRepository from '#repositories/characterRepository' +import { SpriteAction } from '#entities/spriteAction' +import SpriteRepository from '#repositories/spriteRepository' // Constants const ISOMETRIC_CONFIG = { @@ -41,7 +37,7 @@ interface SpriteActionInput extends Omit void): Promise { try { - if (!(await this.validateGameMasterAccess())) { - return callback(false) - } + if (!(await this.isCharacterGM())) return const parsedActions = this.validateSpriteActions(payload.spriteActions) @@ -111,11 +100,6 @@ export default class SpriteUpdateEvent { } } - private async validateGameMasterAccess(): Promise { - const character = await CharacterRepository.getById(this.socket.characterId!) - return character?.role === 'gm' - } - private validateSpriteActions(actions: Prisma.JsonValue): SpriteActionInput[] { try { const parsed = JSON.parse(JSON.stringify(actions)) as SpriteActionInput[] @@ -375,6 +359,8 @@ export default class SpriteUpdateEvent { } } }) + + await (await SpriteRepository.getById(id))?.setName(name).setSpriteActions(actions).update() } private mapActionToDatabase(action: ProcessedSpriteAction) { @@ -392,7 +378,7 @@ export default class SpriteUpdateEvent { } private handleError(error: unknown, spriteId: string, callback: (success: boolean) => void): void { - gameMasterLogger.error(`Error updating sprite ${spriteId}: ${this.getErrorMessage(error)}`) + this.logger.error(`Error updating sprite ${spriteId}: ${this.getErrorMessage(error)}`) callback(false) } diff --git a/src/events/gameMaster/assetManager/tile/delete.ts b/src/events/gameMaster/assetManager/tile/delete.ts index 3a97703..99ed68e 100644 --- a/src/events/gameMaster/assetManager/tile/delete.ts +++ b/src/events/gameMaster/assetManager/tile/delete.ts @@ -1,60 +1,36 @@ import fs from 'fs/promises' -import { Server } from 'socket.io' - -import { gameMasterLogger } from '#application/logger' -import prisma from '#application/prisma' +import { BaseEvent } from '#application/base/baseEvent' import Storage from '#application/storage' -import { TSocket } from '#application/types' -import characterRepository from '#repositories/characterRepository' +import { UUID } from '#application/types' +import TileRepository from '#repositories/tileRepository' type Payload = { - id: string + id: UUID } -export default class GMTileDeleteEvent { - private readonly public_folder: string - - constructor( - private readonly io: Server, - private readonly socket: TSocket - ) { - this.public_folder = Storage.getPublicPath('tiles') - } - +export default class GMTileDeleteEvent extends BaseEvent { public listen(): void { this.socket.on('gm:tile:delete', this.handleEvent.bind(this)) } private async handleEvent(data: Payload, callback: (response: boolean) => void): Promise { - const character = await characterRepository.getById(this.socket.characterId as number) - if (!character) return callback(false) - - if (character.role !== 'gm') { - return - } + if (!(await this.isCharacterGM())) return try { - gameMasterLogger.info(`Deleting tile ${data.id}`) - await this.deleteTileFromDatabase(data.id) + this.logger.info(`Deleting tile ${data.id}`) + await this.deleteTileFile(data.id) + await (await TileRepository.getById(data.id))?.delete() - gameMasterLogger.info(`Tile ${data.id} deleted successfully.`) - callback(true) - } catch (error: any) { - gameMasterLogger.error('gm:tile:delete error', error.message) - callback(false) + this.logger.info(`Tile ${data.id} deleted successfully.`) + return callback(true) + } catch (error: unknown) { + this.logger.error('gm:tile:delete error', error) + return callback(false) } } - private async deleteTileFromDatabase(tileId: string): Promise { - await prisma.tile.delete({ - where: { - id: tileId - } - }) - } - private async deleteTileFile(tileId: string): Promise { const finalFilePath = Storage.getPublicPath('tiles', `${tileId}.png`) try { @@ -63,7 +39,7 @@ export default class GMTileDeleteEvent { if (error.code !== 'ENOENT') { throw error } - gameMasterLogger.warn(`File ${finalFilePath} does not exist.`) + this.logger.warn(`File ${finalFilePath} does not exist.`) } } } diff --git a/src/events/gameMaster/assetManager/tile/list.ts b/src/events/gameMaster/assetManager/tile/list.ts index 180ad3a..f9d26fd 100644 --- a/src/events/gameMaster/assetManager/tile/list.ts +++ b/src/events/gameMaster/assetManager/tile/list.ts @@ -1,7 +1,6 @@ -import characterRepository from '#repositories/characterRepository' -import TileRepository from '#repositories/tileRepository' import { BaseEvent } from '#application/base/baseEvent' import { Tile } from '#entities/tile' +import TileRepository from '#repositories/tileRepository' interface IPayload {} @@ -11,15 +10,10 @@ export default class TileListEven extends BaseEvent { } private async handleEvent(data: IPayload, callback: (response: Tile[]) => void): Promise { - const character = await characterRepository.getById(this.socket.characterId!) - if (!character) return - - if (character.role !== 'gm') { - return - } + if (!(await this.isCharacterGM())) return // get all tiles const tiles = await TileRepository.getAll() - callback(tiles) + return callback(tiles) } } diff --git a/src/events/gameMaster/assetManager/tile/update.ts b/src/events/gameMaster/assetManager/tile/update.ts index fb85d6d..6a04e45 100644 --- a/src/events/gameMaster/assetManager/tile/update.ts +++ b/src/events/gameMaster/assetManager/tile/update.ts @@ -1,7 +1,6 @@ -import characterRepository from '#repositories/characterRepository' import { BaseEvent } from '#application/base/baseEvent' -import TileRepository from '#repositories/tileRepository' import { UUID } from '#application/types' +import TileRepository from '#repositories/tileRepository' type Payload = { id: UUID @@ -9,29 +8,22 @@ type Payload = { tags: string[] } -export default class TileUpdateEvent extends BaseEvent{ +export default class TileUpdateEvent extends BaseEvent { public listen(): void { this.socket.on('gm:tile:update', this.handleEvent.bind(this)) } private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise { - const character = await characterRepository.getById(this.socket.characterId!) - if (!character) return callback(false) - - if (character.role !== 'gm') { - return - } + if (!(await this.isCharacterGM())) return try { const tile = await TileRepository.getById(data.id) if (!tile) return callback(false) await tile.setName(data.name).setTags(data.tags).update() - - callback(true) + return callback(true) } catch (error) { - console.error(error) - callback(false) + return callback(false) } } } diff --git a/src/events/gameMaster/assetManager/tile/upload.ts b/src/events/gameMaster/assetManager/tile/upload.ts index 984bf23..0cb0d8f 100644 --- a/src/events/gameMaster/assetManager/tile/upload.ts +++ b/src/events/gameMaster/assetManager/tile/upload.ts @@ -1,9 +1,8 @@ import fs from 'fs/promises' import { writeFile } from 'node:fs/promises' -import Storage from '#application/storage' -import characterRepository from '#repositories/characterRepository' import { BaseEvent } from '#application/base/baseEvent' +import Storage from '#application/storage' import { Tile } from '#entities/tile' interface ITileData { @@ -17,12 +16,7 @@ export default class TileUploadEvent extends BaseEvent { private async handleEvent(data: ITileData, callback: (response: boolean) => void): Promise { try { - const character = await characterRepository.getById(this.socket.characterId!) - if (!character) return callback(false) - - if (character.role !== 'gm') { - return - } + if (!(await this.isCharacterGM())) return const public_folder = Storage.getPublicPath('tiles') diff --git a/src/events/gameMaster/zoneEditor/create.ts b/src/events/gameMaster/zoneEditor/create.ts index 8bff8b9..a94e2e8 100644 --- a/src/events/gameMaster/zoneEditor/create.ts +++ b/src/events/gameMaster/zoneEditor/create.ts @@ -1,10 +1,5 @@ -import { Zone } from '@prisma/client' -import { Server } from 'socket.io' - -import { gameMasterLogger } from '#application/logger' -import prisma from '#application/prisma' -import { TSocket } from '#application/types' -import CharacterRepository from '#repositories/characterRepository' +import { BaseEvent } from '#application/base/baseEvent' +import { Zone } from '#entities/zone' import ZoneRepository from '#repositories/zoneRepository' type Payload = { @@ -13,49 +8,29 @@ type Payload = { height: number } -export default class ZoneCreateEvent { - constructor( - private readonly io: Server, - private readonly socket: TSocket - ) {} - +export default class ZoneCreateEvent extends BaseEvent { public listen(): void { this.socket.on('gm:zone_editor:zone:create', this.handleEvent.bind(this)) } private async handleEvent(data: Payload, callback: (response: Zone[]) => void): Promise { try { - const character = await CharacterRepository.getById(this.socket.characterId as number) - if (!character) { - gameMasterLogger.error('gm:zone_editor:zone:create error', 'Character not found') - callback([]) - return - } + if (!(await this.isCharacterGM())) return - if (character.role !== 'gm') { - gameMasterLogger.info(`User ${character.id} tried to create zone but is not a game master.`) - callback([]) - return - } + this.logger.info(`User ${(await this.getCharacter())!.getId()} has created a new zone via zone editor.`) - gameMasterLogger.info(`User ${character.id} has created a new zone via zone editor.`) - - const zone = await prisma.zone.create({ - data: { - name: data.name, - width: data.width, - height: data.height, - tiles: Array.from({ length: data.height }, () => Array.from({ length: data.width }, () => 'blank_tile')) - } - }) + const zone = new Zone() + await zone + .setName(data.name) + .setWidth(data.width) + .setHeight(data.height) + .setTiles(Array.from({ length: data.height }, () => Array.from({ length: data.width }, () => 'blank_tile'))) + .save() const zoneList = await ZoneRepository.getAll() callback(zoneList) - - // You might want to emit an event to notify other clients about the new zone - // this.io.emit('gm:zone_created', zone); } catch (error: any) { - gameMasterLogger.error('gm:zone_editor:zone:create error', error.message) + this.logger.error('gm:zone_editor:zone:create error', error.message) this.socket.emit('notification', { message: 'Failed to create zone.' }) callback([]) } diff --git a/src/events/gameMaster/zoneEditor/delete.ts b/src/events/gameMaster/zoneEditor/delete.ts index 89af896..44ac4e6 100644 --- a/src/events/gameMaster/zoneEditor/delete.ts +++ b/src/events/gameMaster/zoneEditor/delete.ts @@ -1,62 +1,29 @@ -import { Server } from 'socket.io' - -import { gameMasterLogger } from '#application/logger' -import prisma from '#application/prisma' -import { TSocket } from '#application/types' -import CharacterRepository from '#repositories/characterRepository' +import { BaseEvent } from '#application/base/baseEvent' +import { UUID } from '#application/types' import ZoneRepository from '#repositories/zoneRepository' type Payload = { - zoneId: number + zoneId: UUID } -export default class ZoneDeleteEvent { - constructor( - private readonly io: Server, - private readonly socket: TSocket - ) {} - +export default class ZoneDeleteEvent extends BaseEvent { public listen(): void { this.socket.on('gm:zone_editor:zone:delete', this.handleEvent.bind(this)) } private async handleEvent(data: Payload, callback: (response: boolean) => void): Promise { + if (!(await this.isCharacterGM())) return + try { - const character = await CharacterRepository.getById(this.socket.characterId as number) - if (!character) { - gameMasterLogger.error('gm:zone_editor:zone:delete error', 'Character not found') - callback(false) - return - } + this.logger.info(`Deleting zone ${data.zoneId}`) - if (character.role !== 'gm') { - gameMasterLogger.info(`User ${character.id} tried to delete zone but is not a game master.`) - callback(false) - return - } + await (await ZoneRepository.getById(data.zoneId))?.delete() - gameMasterLogger.info(`User ${character.id} has deleted a zone via zone editor.`) - - const zone = await ZoneRepository.getById(data.zoneId) - if (!zone) { - gameMasterLogger.error('gm:zone_editor:zone:delete error', 'Zone not found') - callback(false) - return - } - - await prisma.zone.delete({ - where: { - id: data.zoneId - } - }) - - callback(true) - - // You might want to emit an event to notify other clients about the deleted zone - // this.io.emit('gm:zone_deleted', data.zoneId); - } catch (error: any) { - gameMasterLogger.error('gm:zone_editor:zone:delete error', error.message) - callback(false) + this.logger.info(`Zone ${data.zoneId} deleted successfully.`) + return callback(true) + } catch (error: unknown) { + this.logger.error('gm:zone_editor:zone:delete error', error) + return callback(false) } } } diff --git a/src/events/gameMaster/zoneEditor/list.ts b/src/events/gameMaster/zoneEditor/list.ts index dd4f8f5..c2e043c 100644 --- a/src/events/gameMaster/zoneEditor/list.ts +++ b/src/events/gameMaster/zoneEditor/list.ts @@ -1,44 +1,24 @@ -import { Zone } from '@prisma/client' -import { Server } from 'socket.io' - -import { gameMasterLogger } from '#application/logger' -import { TSocket } from '#application/types' -import CharacterRepository from '#repositories/characterRepository' +import { BaseEvent } from '#application/base/baseEvent' +import { Zone } from '#entities/zone' import ZoneRepository from '#repositories/zoneRepository' interface IPayload {} -export default class ZoneListEvent { - constructor( - private readonly io: Server, - private readonly socket: TSocket - ) {} - +export default class ZoneListEvent extends BaseEvent { public listen(): void { this.socket.on('gm:zone_editor:zone:list', this.handleEvent.bind(this)) } private async handleEvent(data: IPayload, callback: (response: Zone[]) => void): Promise { try { - const character = await CharacterRepository.getById(this.socket.characterId as number) - if (!character) { - gameMasterLogger.error('gm:zone_editor:zone:list error', 'Character not found') - callback([]) - return - } + if (!(await this.isCharacterGM())) return - if (character.role !== 'gm') { - gameMasterLogger.info(`User ${character.id} tried to list zones but is not a game master.`) - callback([]) - return - } - - gameMasterLogger.info(`User ${character.id} has requested zone list via zone editor.`) + this.logger.info(`User ${(await this.getCharacter())!.getId()} has created a new zone via zone editor.`) const zones = await ZoneRepository.getAll() callback(zones) } catch (error: any) { - gameMasterLogger.error('gm:zone_editor:zone:list error', error.message) + this.logger.error('gm:zone_editor:zone:list error', error.message) callback([]) } } diff --git a/src/events/gameMaster/zoneEditor/request.ts b/src/events/gameMaster/zoneEditor/request.ts index d2b843f..81895dd 100644 --- a/src/events/gameMaster/zoneEditor/request.ts +++ b/src/events/gameMaster/zoneEditor/request.ts @@ -1,60 +1,39 @@ -import { Zone } from '@prisma/client' -import { Server } from 'socket.io' - -import { gameMasterLogger } from '#application/logger' -import { TSocket } from '#application/types' -import CharacterRepository from '#repositories/characterRepository' +import { BaseEvent } from '#application/base/baseEvent' +import { UUID } from '#application/types' +import { Zone } from '#entities/zone' import ZoneRepository from '#repositories/zoneRepository' interface IPayload { - zoneId: number + zoneId: UUID } -export default class ZoneRequestEvent { - constructor( - private readonly io: Server, - private readonly socket: TSocket - ) {} - +export default class ZoneRequestEvent extends BaseEvent { public listen(): void { this.socket.on('gm:zone_editor:zone:request', 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:request error', 'Character not found') - callback(null) - return - } + if (!(await this.isCharacterGM())) return - if (character.role !== 'gm') { - gameMasterLogger.info(`User ${character.id} tried to request zone but is not a game master.`) - callback(null) - return - } - - gameMasterLogger.info(`User ${character.id} has requested zone via zone editor.`) + this.logger.info(`User ${(await this.getCharacter())!.getId()} has requested zone via zone editor.`) if (!data.zoneId) { - gameMasterLogger.info(`User ${character.id} tried to request zone but did not provide a zone id.`) - callback(null) - return + this.logger.info(`User ${(await this.getCharacter())!.getId()} tried to request zone but did not provide a zone id.`) + return callback(null) } const zone = await ZoneRepository.getById(data.zoneId) if (!zone) { - gameMasterLogger.info(`User ${character.id} tried to request zone ${data.zoneId} but it does not exist.`) - callback(null) - return + this.logger.info(`User ${(await this.getCharacter())!.getId()} tried to request zone ${data.zoneId} but it does not exist.`) + return callback(null) } - callback(zone) + return callback(zone) } catch (error: any) { - gameMasterLogger.error('gm:zone_editor:zone:request error', error.message) - callback(null) + this.logger.error('gm:zone_editor:zone:request error', error.message) + return callback(null) } } } diff --git a/src/events/gameMaster/zoneEditor/update.ts b/src/events/gameMaster/zoneEditor/update.ts index 3629213..00dff01 100644 --- a/src/events/gameMaster/zoneEditor/update.ts +++ b/src/events/gameMaster/zoneEditor/update.ts @@ -1,15 +1,16 @@ -import { Zone, ZoneEffect, ZoneEventTileType, ZoneObject } from '@prisma/client' -import { Server } from 'socket.io' - -import { gameMasterLogger } from '#application/logger' -import prisma from '#application/prisma' -import { TSocket } from '#application/types' +import { BaseEvent } from '#application/base/baseEvent' +import { ZoneEventTileType } from '#application/enums' +import { UUID } from '#application/types' +import { Zone } from '#entities/zone' +import { ZoneEffect } from '#entities/zoneEffect' +import { ZoneEventTile } from '#entities/zoneEventTile' +import { ZoneEventTileTeleport } from '#entities/zoneEventTileTeleport' +import { ZoneObject } from '#entities/zoneObject' import zoneManager from '#managers/zoneManager' -import CharacterRepository from '#repositories/characterRepository' import ZoneRepository from '#repositories/zoneRepository' interface IPayload { - zoneId: number + zoneId: UUID name: string width: number height: number @@ -20,7 +21,7 @@ interface IPayload { positionX: number positionY: number teleport?: { - toZoneId: number + toZoneId: UUID toPositionX: number toPositionY: number toRotation: number @@ -33,44 +34,31 @@ interface IPayload { zoneObjects: ZoneObject[] } -export default class ZoneUpdateEvent { - constructor( - private readonly io: Server, - private readonly socket: TSocket - ) {} - +export default class ZoneUpdateEvent extends BaseEvent { 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 (!(await this.isCharacterGM())) return - 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.`) + const character = await this.getCharacter() + this.logger.info(`User ${character!.getId()} 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.`) + this.logger.info(`User ${character!.getId()} 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.`) + this.logger.info(`User ${character!.getId()} 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 + // Validation logic remains the same if (data.tiles.length > data.height) { data.tiles = data.tiles.slice(0, data.height) } @@ -80,81 +68,65 @@ export default class ZoneUpdateEvent { } } - // 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() - } - }) + // Clear existing collections + zone.zoneEventTiles.removeAll() + zone.zoneObjects.removeAll() + zone.zoneEffects.removeAll() + // Create and add new zone event tiles + for (const tile of data.zoneEventTiles) { + const zoneEventTile = new ZoneEventTile().setType(tile.type).setPositionX(tile.positionX).setPositionY(tile.positionY).setZone(zone) + + if (tile.teleport) { + const teleport = new ZoneEventTileTeleport() + .setToZone((await ZoneRepository.getById(tile.teleport.toZoneId))!) + .setToPositionX(tile.teleport.toPositionX) + .setToPositionY(tile.teleport.toPositionY) + .setToRotation(tile.teleport.toRotation) + + zoneEventTile.setTeleport(teleport) + } + + zone.zoneEventTiles.add(zoneEventTile) + } + + // Create and add new zone objects + for (const object of data.zoneObjects) { + const zoneObject = new ZoneObject().setMapObject(object.mapObject).setDepth(object.depth).setIsRotated(object.isRotated).setPositionX(object.positionX).setPositionY(object.positionY).setZone(zone) + + zone.zoneObjects.add(zoneObject) + } + + // Create and add new zone effects + for (const effect of data.zoneEffects) { + const zoneEffect = new ZoneEffect().setEffect(effect.effect).setStrength(effect.strength).setZone(zone) + + zone.zoneEffects.add(zoneEffect) + } + + // Update zone properties + await zone.setName(data.name).setWidth(data.width).setHeight(data.height).setTiles(data.tiles).setPvp(data.pvp).setUpdatedAt(new Date()).update() + + // Reload zone from database to get fresh data 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 + this.logger.info(`User ${character!.getId()} tried to update zone ${data.zoneId} but it does not exist after update.`) + return callback(null) } - 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 - */ + // Reload zone for players zoneManager.unloadZone(data.zoneId) await zoneManager.loadZone(zone) + + return callback(zone) } catch (error: any) { - gameMasterLogger.error(`gm:zone_editor:zone:update error: ${error instanceof Error ? error.message : String(error)}`) - callback(null) + this.logger.error(`gm:zone_editor:zone:update error: ${error instanceof Error ? error.message : String(error)}`) + return callback(null) } } } diff --git a/src/managers/dateManager.ts b/src/managers/dateManager.ts index 2bafddd..1f1a90e 100644 --- a/src/managers/dateManager.ts +++ b/src/managers/dateManager.ts @@ -1,13 +1,14 @@ import { Server } from 'socket.io' + import Logger, { LoggerType } from '#application/logger' +import SocketManager from '#managers/socketManager' import worldRepository from '#repositories/worldRepository' import worldService from '#services/worldService' -import SocketManager from '#managers/socketManager' class DateManager { private static readonly CONFIG = { GAME_SPEED: 8, // 24 game hours / 3 real hours - UPDATE_INTERVAL: 1000, // 1 second + UPDATE_INTERVAL: 1000 // 1 second } as const private io: Server | null = null @@ -98,4 +99,4 @@ class DateManager { } } -export default new DateManager() \ No newline at end of file +export default new DateManager() diff --git a/src/managers/weatherManager.ts b/src/managers/weatherManager.ts index bc9c9b7..eb3f6d6 100644 --- a/src/managers/weatherManager.ts +++ b/src/managers/weatherManager.ts @@ -1,8 +1,9 @@ import { Server } from 'socket.io' + import Logger, { LoggerType } from '#application/logger' +import SocketManager from '#managers/socketManager' import worldRepository from '#repositories/worldRepository' import worldService from '#services/worldService' -import SocketManager from '#managers/socketManager' type WeatherState = { isRainEnabled: boolean @@ -91,22 +92,12 @@ class WeatherManager { private updateWeatherProperty(type: 'rain' | 'fog'): void { if (type === 'rain') { this.weatherState.isRainEnabled = !this.weatherState.isRainEnabled - this.weatherState.rainPercentage = this.weatherState.isRainEnabled - ? this.getRandomNumber( - WeatherManager.CONFIG.RAIN_PERCENTAGE_RANGE.min, - WeatherManager.CONFIG.RAIN_PERCENTAGE_RANGE.max - ) - : 0 + this.weatherState.rainPercentage = this.weatherState.isRainEnabled ? this.getRandomNumber(WeatherManager.CONFIG.RAIN_PERCENTAGE_RANGE.min, WeatherManager.CONFIG.RAIN_PERCENTAGE_RANGE.max) : 0 } if (type === 'fog') { this.weatherState.isFogEnabled = !this.weatherState.isFogEnabled - this.weatherState.fogDensity = this.weatherState.isFogEnabled - ? this.getRandomNumber( - WeatherManager.CONFIG.FOG_DENSITY_RANGE.min, - WeatherManager.CONFIG.FOG_DENSITY_RANGE.max - ) - : 0 + this.weatherState.fogDensity = this.weatherState.isFogEnabled ? this.getRandomNumber(WeatherManager.CONFIG.FOG_DENSITY_RANGE.min, WeatherManager.CONFIG.FOG_DENSITY_RANGE.max) : 0 } } @@ -132,10 +123,8 @@ class WeatherManager { } private logError(operation: string, error: unknown): void { - this.logger.error( - `Failed to ${operation} weather: ${error instanceof Error ? error.message : String(error)}` - ) + this.logger.error(`Failed to ${operation} weather: ${error instanceof Error ? error.message : String(error)}`) } } -export default new WeatherManager() \ No newline at end of file +export default new WeatherManager() diff --git a/src/server.ts b/src/server.ts index 39c46f1..c747ec2 100644 --- a/src/server.ts +++ b/src/server.ts @@ -43,8 +43,8 @@ export class Server { SocketManager.boot(this.app, this.http), QueueManager.boot(), UserManager.boot(), - DateManager.boot(), - WeatherManager.boot(), + // DateManager.boot(), + // WeatherManager.boot(), ZoneManager.boot(), ConsoleManager.boot() ])