More event progress
This commit is contained in:
parent
ab89d0cbb0
commit
f7dbf09bf5
@ -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<Character | null> {
|
||||
return CharacterRepository.getById(this.socket.characterId!)
|
||||
}
|
||||
|
||||
protected async isCharacterGM(): Promise<boolean> {
|
||||
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}`)
|
||||
|
@ -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<void> {
|
||||
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<boolean> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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<void> {
|
||||
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')
|
||||
|
||||
|
@ -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<void> {
|
||||
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<void> {
|
||||
await prisma.sprite.delete({
|
||||
where: {
|
||||
id: spriteId
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -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<void> {
|
||||
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<void> {
|
||||
if (!(await this.isCharacterGM())) return
|
||||
|
||||
// get all sprites
|
||||
const sprites = await SpriteRepository.getAll()
|
||||
|
@ -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<SpriteAction, 'id' | 'spriteId' | 'fram
|
||||
interface UpdatePayload {
|
||||
id: string
|
||||
name: string
|
||||
spriteActions: Prisma.JsonValue
|
||||
spriteActions: SpriteAction[]
|
||||
}
|
||||
|
||||
interface ProcessedSpriteAction extends SpriteActionInput {
|
||||
@ -62,21 +58,14 @@ interface SpriteAnalysis {
|
||||
contentBounds: ContentBounds
|
||||
}
|
||||
|
||||
export default class SpriteUpdateEvent {
|
||||
constructor(
|
||||
private readonly io: Server,
|
||||
private readonly socket: TSocket
|
||||
) {}
|
||||
|
||||
export default class SpriteUpdateEvent extends BaseEvent {
|
||||
public listen(): void {
|
||||
this.socket.on('gm:sprite:update', this.handleEvent.bind(this))
|
||||
}
|
||||
|
||||
private async handleEvent(payload: UpdatePayload, callback: (success: boolean) => void): Promise<void> {
|
||||
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<boolean> {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -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<void> {
|
||||
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<void> {
|
||||
await prisma.tile.delete({
|
||||
where: {
|
||||
id: tileId
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private async deleteTileFile(tileId: string): Promise<void> {
|
||||
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.`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
@ -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<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<void> {
|
||||
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')
|
||||
|
||||
|
@ -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<void> {
|
||||
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([])
|
||||
}
|
||||
|
@ -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<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<void> {
|
||||
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([])
|
||||
}
|
||||
}
|
||||
|
@ -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<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<void> {
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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,9 +123,7 @@ 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)}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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()
|
||||
])
|
||||
|
Loading…
x
Reference in New Issue
Block a user