diff --git a/prisma/migrations/20241217160850_init/migration.sql b/prisma/migrations/20241222165332_init/migration.sql similarity index 98% rename from prisma/migrations/20241217160850_init/migration.sql rename to prisma/migrations/20241222165332_init/migration.sql index d1cd314..7eed25f 100644 --- a/prisma/migrations/20241217160850_init/migration.sql +++ b/prisma/migrations/20241222165332_init/migration.sql @@ -55,6 +55,7 @@ CREATE TABLE `Item` ( `itemType` ENUM('WEAPON', 'HELMET', 'CHEST', 'LEGS', 'BOOTS', 'GLOVES', 'RING', 'NECKLACE') NOT NULL, `stackable` BOOLEAN NOT NULL DEFAULT false, `rarity` ENUM('COMMON', 'UNCOMMON', 'RARE', 'EPIC', 'LEGENDARY') NOT NULL DEFAULT 'COMMON', + `spriteId` VARCHAR(191) NULL, `createdAt` DATETIME(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3), `updatedAt` DATETIME(3) NOT NULL, @@ -256,6 +257,9 @@ ALTER TABLE `Chat` ADD CONSTRAINT `Chat_zoneId_fkey` FOREIGN KEY (`zoneId`) REFE -- AddForeignKey ALTER TABLE `SpriteAction` ADD CONSTRAINT `SpriteAction_spriteId_fkey` FOREIGN KEY (`spriteId`) REFERENCES `Sprite`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; +-- AddForeignKey +ALTER TABLE `Item` ADD CONSTRAINT `Item_spriteId_fkey` FOREIGN KEY (`spriteId`) REFERENCES `Sprite`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; + -- AddForeignKey ALTER TABLE `PasswordResetToken` ADD CONSTRAINT `PasswordResetToken_userId_fkey` FOREIGN KEY (`userId`) REFERENCES `User`(`id`) ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/migrations/migration_lock.toml b/prisma/migrations/migration_lock.toml index e5a788a..8a21669 100644 --- a/prisma/migrations/migration_lock.toml +++ b/prisma/migrations/migration_lock.toml @@ -1,3 +1,3 @@ # Please do not edit this file manually -# It should be added in your version-control system (i.e. Git) +# It should be added in your version-control system (e.g., Git) provider = "mysql" \ No newline at end of file diff --git a/prisma/schema/game.prisma b/prisma/schema/game.prisma index 694547c..576c009 100644 --- a/prisma/schema/game.prisma +++ b/prisma/schema/game.prisma @@ -24,6 +24,7 @@ model Sprite { spriteActions SpriteAction[] characterTypes CharacterType[] characterHairs CharacterHair[] + Item Item[] } model SpriteAction { @@ -38,7 +39,7 @@ model SpriteAction { isLooping Boolean @default(false) frameWidth Int @default(0) frameHeight Int @default(0) - frameRate Int @default(0) + frameRate Int @default(0) } model Item { @@ -48,6 +49,8 @@ model Item { itemType ItemType stackable Boolean @default(false) rarity ItemRarity @default(COMMON) + spriteId String? + sprite Sprite? @relation(fields: [spriteId], references: [id], onDelete: Cascade) createdAt DateTime @default(now()) updatedAt DateTime @updatedAt characters CharacterItem[] diff --git a/src/repositories/itemRepository.ts b/src/repositories/itemRepository.ts new file mode 100644 index 0000000..5644459 --- /dev/null +++ b/src/repositories/itemRepository.ts @@ -0,0 +1,39 @@ +import prisma from '../utilities/prisma' // Import the global Prisma instance +import { Tile } from '@prisma/client' +import zoneRepository from './zoneRepository' +import { unduplicateArray } from '../utilities/utilities' +import { FlattenZoneArray } from '../utilities/zone' + +class ItemRepository { + async getById(id: string) { + return prisma.item.findUnique({ + where: { id }, + include: { + sprite: true + } + }) + } + + async getByIds(ids: string[]) { + return prisma.item.findMany({ + where: { + id: { + in: ids + } + }, + include: { + sprite: true + } + }) + } + + async getAll() { + return prisma.item.findMany({ + include: { + sprite: true + } + }) + } +} + +export default new ItemRepository() diff --git a/src/socketEvents/gameMaster/assetManager/item/create.ts b/src/socketEvents/gameMaster/assetManager/item/create.ts new file mode 100644 index 0000000..dc86dd3 --- /dev/null +++ b/src/socketEvents/gameMaster/assetManager/item/create.ts @@ -0,0 +1,41 @@ +import { Server } from 'socket.io' +import { TSocket } from '../../../../utilities/types' +import prisma from '../../../../utilities/prisma' +import characterRepository from '../../../../repositories/characterRepository' + +export default class ItemCreateEvent { + constructor( + private readonly io: Server, + private readonly socket: TSocket + ) {} + + public listen(): void { + this.socket.on('gm:item:create', this.handleEvent.bind(this)) + } + + private async handleEvent(data: undefined, callback: (response: boolean, item?: 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 newItem = await prisma.item.create({ + data: { + name: 'New Item', + itemType: 'WEAPON', + stackable: false, + rarity: 'COMMON', + spriteId: null + } + }) + + callback(true, newItem) + } catch (error) { + console.error('Error creating item:', error) + callback(false) + } + } +} \ No newline at end of file diff --git a/src/socketEvents/gameMaster/assetManager/item/delete.ts b/src/socketEvents/gameMaster/assetManager/item/delete.ts new file mode 100644 index 0000000..cc78667 --- /dev/null +++ b/src/socketEvents/gameMaster/assetManager/item/delete.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 { gameMasterLogger } from '../../../../utilities/logger' + +interface IPayload { + id: string +} + +export default class ItemDeleteEvent { + constructor( + private readonly io: Server, + private readonly socket: TSocket + ) {} + + public listen(): void { + this.socket.on('gm:item:remove', this.handleEvent.bind(this)) + } + + private async handleEvent(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.item.delete({ + where: { id: data.id } + }) + + callback(true) + } catch (error) { + gameMasterLogger.error(`Error deleting item ${data.id}: ${error instanceof Error ? error.message : String(error)}`) + callback(false) + } + } +} \ No newline at end of file diff --git a/src/socketEvents/gameMaster/assetManager/item/list.ts b/src/socketEvents/gameMaster/assetManager/item/list.ts new file mode 100644 index 0000000..58513a3 --- /dev/null +++ b/src/socketEvents/gameMaster/assetManager/item/list.ts @@ -0,0 +1,36 @@ +import { Server } from 'socket.io' +import { TSocket } from '../../../../utilities/types' +import { Item } from '@prisma/client' +import characterRepository from '../../../../repositories/characterRepository' +import { gameMasterLogger } from '../../../../utilities/logger' +import itemRepository from '../../../../repositories/itemRepository' + +interface IPayload {} + +export default class ItemListEvent { + constructor( + private readonly io: Server, + private readonly socket: TSocket + ) {} + + public listen(): void { + this.socket.on('gm:item:list', this.handleEvent.bind(this)) + } + + private async handleEvent(data: IPayload, callback: (response: Item[]) => void): Promise { + const character = await characterRepository.getById(this.socket.characterId as number) + if (!character) { + gameMasterLogger.error('gm:item:list error', 'Character not found') + return callback([]) + } + + if (character.role !== 'gm') { + gameMasterLogger.info(`User ${character.id} tried to list items but is not a game master.`) + return callback([]) + } + + // get all items + const items = await itemRepository.getAll() + callback(items) + } +} \ No newline at end of file diff --git a/src/socketEvents/gameMaster/assetManager/item/update.ts b/src/socketEvents/gameMaster/assetManager/item/update.ts new file mode 100644 index 0000000..03d3044 --- /dev/null +++ b/src/socketEvents/gameMaster/assetManager/item/update.ts @@ -0,0 +1,55 @@ +import { Server } from 'socket.io' +import { TSocket } from '../../../../utilities/types' +import prisma from '../../../../utilities/prisma' +import characterRepository from '../../../../repositories/characterRepository' +import { ItemType, ItemRarity } from '@prisma/client' +import { gameMasterLogger } from '../../../../utilities/logger' + +type Payload = { + id: string + name: string + description: string | null + itemType: ItemType + stackable: boolean + rarity: ItemRarity + spriteId: string | null +} + +export default class ItemUpdateEvent { + constructor( + private readonly io: Server, + private readonly socket: TSocket + ) {} + + public listen(): void { + this.socket.on('gm:item: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 { + await prisma.item.update({ + where: { id: data.id }, + data: { + name: data.name, + description: data.description, + itemType: data.itemType, + stackable: data.stackable, + rarity: data.rarity, + spriteId: data.spriteId + } + }) + + return callback(true) + } catch (error) { + gameMasterLogger.error(`Error updating item: ${error instanceof Error ? error.message : String(error)}`) + return callback(false) + } + } +} \ No newline at end of file