diff --git a/prisma/migrations/20241018212416_init/migration.sql b/prisma/migrations/20241018233516_init/migration.sql similarity index 99% rename from prisma/migrations/20241018212416_init/migration.sql rename to prisma/migrations/20241018233516_init/migration.sql index 9eec7ec..4e7124f 100644 --- a/prisma/migrations/20241018212416_init/migration.sql +++ b/prisma/migrations/20241018233516_init/migration.sql @@ -53,7 +53,7 @@ CREATE TABLE `CharacterType` ( `name` VARCHAR(191) NOT NULL, `gender` ENUM('MALE', 'FEMALE') NOT NULL, `race` ENUM('HUMAN', 'ELF', 'DWARF', 'ORC', 'GOBLIN') NOT NULL, - `spriteId` VARCHAR(191) NOT NULL, + `spriteId` VARCHAR(191) NULL, `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updatedAt` DATETIME(3) NOT NULL, diff --git a/prisma/schema/user.prisma b/prisma/schema/user.prisma index 1c007b2..0259610 100644 --- a/prisma/schema/user.prisma +++ b/prisma/schema/user.prisma @@ -25,8 +25,8 @@ model CharacterType { gender CharacterGender race CharacterRace characters Character[] - spriteId String - sprite Sprite @relation(fields: [spriteId], references: [id], onDelete: Cascade) + spriteId String? + sprite Sprite? @relation(fields: [spriteId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt } diff --git a/src/managers/dateManager.ts b/src/managers/dateManager.ts index 9a1ae1a..36836bd 100644 --- a/src/managers/dateManager.ts +++ b/src/managers/dateManager.ts @@ -65,4 +65,4 @@ class DateManager { } } -export default new DateManager() \ No newline at end of file +export default new DateManager() diff --git a/src/repositories/characterTypeRepository.ts b/src/repositories/characterTypeRepository.ts new file mode 100644 index 0000000..86ea075 --- /dev/null +++ b/src/repositories/characterTypeRepository.ts @@ -0,0 +1,10 @@ +import prisma from '../utilities/prisma' // Import the global Prisma instance +import { CharacterType } from '@prisma/client' + +class CharacterTypeRepository { + async getAll(): Promise { + return prisma.characterType.findMany() + } +} + +export default new CharacterTypeRepository() diff --git a/src/socketEvents/gameMaster/assetManager/characterType/create.ts b/src/socketEvents/gameMaster/assetManager/characterType/create.ts new file mode 100644 index 0000000..46cd6a5 --- /dev/null +++ b/src/socketEvents/gameMaster/assetManager/characterType/create.ts @@ -0,0 +1,40 @@ +import { Server } from 'socket.io' +import { TSocket } from '../../../../utilities/types' +import prisma from '../../../../utilities/prisma' +import characterRepository from '../../../../repositories/characterRepository' +import { CharacterGender, CharacterRace } from '@prisma/client' + +export default class CharacterTypeCreateEvent { + constructor( + private readonly io: Server, + private readonly socket: TSocket + ) {} + + public listen(): void { + this.socket.on('gm:characterType:create', this.handleCharacterTypeCreate.bind(this)) + } + + private async handleCharacterTypeCreate(data: undefined, callback: (response: boolean, characterType?: any) => void): Promise { + try { + const character = await characterRepository.getById(this.socket.characterId as number) + if (!character) return callback(false) + + if (character.role !== 'gm') { + return callback(false) + } + + const newCharacterType = await prisma.characterType.create({ + data: { + name: 'New character type', + gender: CharacterGender.MALE, + race: CharacterRace.HUMAN + } + }) + + callback(true, newCharacterType) + } catch (error) { + console.error('Error creating character type:', error) + callback(false) + } + } +} diff --git a/src/socketEvents/gameMaster/assetManager/characterType/list.ts b/src/socketEvents/gameMaster/assetManager/characterType/list.ts new file mode 100644 index 0000000..53df172 --- /dev/null +++ b/src/socketEvents/gameMaster/assetManager/characterType/list.ts @@ -0,0 +1,36 @@ +import { Server } from 'socket.io' +import { TSocket } from '../../../../utilities/types' +import { CharacterType } from '@prisma/client' +import characterRepository from '../../../../repositories/characterRepository' +import { gameMasterLogger } from '../../../../utilities/logger' +import CharacterTypeRepository from '../../../../repositories/characterTypeRepository' + +interface IPayload {} + +export default class CharacterTypeListEvent { + constructor( + private readonly io: Server, + private readonly socket: TSocket + ) {} + + public listen(): void { + this.socket.on('gm:characterType:list', this.handleCharacterTypeList.bind(this)) + } + + private async handleCharacterTypeList(data: IPayload, callback: (response: CharacterType[]) => void): Promise { + const character = await characterRepository.getById(this.socket.characterId as number) + if (!character) { + gameMasterLogger.error('gm:characterType:list error', 'Character not found') + return callback([]) + } + + if (character.role !== 'gm') { + gameMasterLogger.info(`User ${character.id} tried to list character types but is not a game master.`) + return callback([]) + } + + // get all objects + const items = await CharacterTypeRepository.getAll() + callback(items) + } +} diff --git a/src/socketEvents/gameMaster/assetManager/characterType/remove.ts b/src/socketEvents/gameMaster/assetManager/characterType/remove.ts new file mode 100644 index 0000000..883e577 --- /dev/null +++ b/src/socketEvents/gameMaster/assetManager/characterType/remove.ts @@ -0,0 +1,56 @@ +import fs from 'fs' +import { Server } from 'socket.io' +import { TSocket } from '../../../../utilities/types' +import prisma from '../../../../utilities/prisma' +import characterRepository from '../../../../repositories/characterRepository' +import { getPublicPath } from '../../../../utilities/storage' + +interface IPayload { + object: string +} + +export default class ObjectRemoveEvent { + constructor( + private readonly io: Server, + private readonly socket: TSocket + ) {} + + public listen(): void { + this.socket.on('gm:object:remove', this.handleObjectRemove.bind(this)) + } + + private async handleObjectRemove(data: IPayload, 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 callback(false) + } + + try { + await prisma.object.delete({ + where: { + id: data.object + } + }) + + // get root path + const public_folder = getPublicPath('objects') + + // remove the tile from the disk + const finalFilePath = getPublicPath('objects', data.object + '.png') + fs.unlink(finalFilePath, (err) => { + if (err) { + console.log(err) + callback(false) + return + } + + callback(true) + }) + } catch (e) { + console.log(e) + callback(false) + } + } +} diff --git a/src/socketEvents/gameMaster/assetManager/characterType/update.ts b/src/socketEvents/gameMaster/assetManager/characterType/update.ts new file mode 100644 index 0000000..e9cfae9 --- /dev/null +++ b/src/socketEvents/gameMaster/assetManager/characterType/update.ts @@ -0,0 +1,58 @@ +import { Server } from 'socket.io' +import { TSocket } from '../../../../utilities/types' +import prisma from '../../../../utilities/prisma' +import characterRepository from '../../../../repositories/characterRepository' + +type Payload = { + id: string + name: string + tags: string[] + originX: number + originY: number + isAnimated: boolean + frameSpeed: number + frameWidth: number + frameHeight: number +} + +export default class ObjectUpdateEvent { + constructor( + private readonly io: Server, + private readonly socket: TSocket + ) {} + + public listen(): void { + this.socket.on('gm:object:update', this.handleObjectUpdate.bind(this)) + } + + private async handleObjectUpdate(data: Payload, callback: (success: boolean) => void): Promise { + const character = await characterRepository.getById(this.socket.characterId as number) + if (!character) return callback(false) + + if (character.role !== 'gm') { + return callback(false) + } + + try { + const object = await prisma.object.update({ + where: { + id: data.id + }, + data: { + name: data.name, + tags: data.tags, + originX: data.originX, + originY: data.originY, + isAnimated: data.isAnimated, + frameSpeed: data.frameSpeed, + frameWidth: data.frameWidth, + frameHeight: data.frameHeight + } + }) + callback(true) + } catch (error) { + console.error(error) + callback(false) + } + } +} diff --git a/src/socketEvents/gameMaster/zoneEditor/update.ts b/src/socketEvents/gameMaster/zoneEditor/update.ts index 81e8879..4c69769 100644 --- a/src/socketEvents/gameMaster/zoneEditor/update.ts +++ b/src/socketEvents/gameMaster/zoneEditor/update.ts @@ -130,6 +130,8 @@ export default class ZoneUpdateEvent { return } + gameMasterLogger.info(`User ${character.id} has updated zone via zone editor.`) + callback(zone) zoneManager.unloadZone(data.zoneId) diff --git a/src/utilities/json.ts b/src/utilities/json.ts index b7735bf..9440bd7 100644 --- a/src/utilities/json.ts +++ b/src/utilities/json.ts @@ -1,49 +1,49 @@ -import * as fs from 'fs/promises'; -import { appLogger } from './logger'; +import * as fs from 'fs/promises' +import { appLogger } from './logger' export async function readJsonFile(filePath: string): Promise { try { - const fileContent = await fs.readFile(filePath, 'utf-8'); - return JSON.parse(fileContent) as T; + const fileContent = await fs.readFile(filePath, 'utf-8') + return JSON.parse(fileContent) as T } catch (error) { - appLogger.error(`Error reading JSON file: ${error instanceof Error ? error.message : String(error)}`); - throw error; + appLogger.error(`Error reading JSON file: ${error instanceof Error ? error.message : String(error)}`) + throw error } } export async function writeJsonFile(filePath: string, data: T): Promise { try { - const jsonString = JSON.stringify(data, null, 2); - await fs.writeFile(filePath, jsonString, 'utf-8'); + const jsonString = JSON.stringify(data, null, 2) + await fs.writeFile(filePath, jsonString, 'utf-8') } catch (error) { - appLogger.error(`Error writing JSON file: ${error instanceof Error ? error.message : String(error)}`); - throw error; + appLogger.error(`Error writing JSON file: ${error instanceof Error ? error.message : String(error)}`) + throw error } } export async function readJsonValue(filePath: string, paramPath: string): Promise { try { - const jsonContent = await readJsonFile(filePath); - const paramValue = paramPath.split('.').reduce((obj, key) => obj && obj[key], jsonContent); + const jsonContent = await readJsonFile(filePath) + const paramValue = paramPath.split('.').reduce((obj, key) => obj && obj[key], jsonContent) if (paramValue === undefined) { - throw new Error(`Parameter ${paramPath} not found in the JSON file`); + throw new Error(`Parameter ${paramPath} not found in the JSON file`) } - return paramValue as T; + return paramValue as T } catch (error) { - appLogger.error(`Error reading JSON parameter: ${error instanceof Error ? error.message : String(error)}`); - throw error; + appLogger.error(`Error reading JSON parameter: ${error instanceof Error ? error.message : String(error)}`) + throw error } } export async function setJsonValue(filePath: string, key: string, value: any): Promise { try { - const data = await readJsonFile(filePath); - const updatedData = { ...data, [key]: value }; - await writeJsonFile(filePath, updatedData); + const data = await readJsonFile(filePath) + const updatedData = { ...data, [key]: value } + await writeJsonFile(filePath, updatedData) } catch (error) { - appLogger.error(`Error setting JSON value: ${error instanceof Error ? error.message : String(error)}`); - throw error; + appLogger.error(`Error setting JSON value: ${error instanceof Error ? error.message : String(error)}`) + throw error } -} \ No newline at end of file +} diff --git a/src/utilities/storage.ts b/src/utilities/storage.ts index bcca0bf..0dcf364 100644 --- a/src/utilities/storage.ts +++ b/src/utilities/storage.ts @@ -17,17 +17,17 @@ export function getPublicPath(folder: string, ...additionalSegments: string[]) { export function doesPathExist(path: string) { try { - fs.accessSync(path, fs.constants.F_OK); - return true; + fs.accessSync(path, fs.constants.F_OK) + return true } catch (e) { - return false; + return false } } export function createDir(path: string) { try { - fs.mkdirSync(path, { recursive: true }); + fs.mkdirSync(path, { recursive: true }) } catch (e) { - console.error(e); + console.error(e) } -} \ No newline at end of file +}