diff --git a/src/events/gm/sprite/Update.ts b/src/events/gm/sprite/Update.ts index 29c3fa5..e6f2f49 100644 --- a/src/events/gm/sprite/Update.ts +++ b/src/events/gm/sprite/Update.ts @@ -1,38 +1,80 @@ import { Server } from 'socket.io' import { TSocket } from '../../../utilities/Types' import prisma from '../../../utilities/Prisma' -import type { SpriteAction } from '@prisma/client' +import type { Prisma, SpriteAction } from '@prisma/client' import path from 'path' import { writeFile } from 'node:fs/promises' import fs from 'fs/promises' -import spriteRepository from '../../../repositories/SpriteRepository' +import sharp from 'sharp' + +type SpriteActionInput = Omit & { + sprites: string[] +} type Payload = { id: string name: string - spriteActions: SpriteAction[] + spriteActions: Prisma.JsonValue } export default function (socket: TSocket, io: Server) { socket.on('gm:sprite:update', async (data: Payload, callback: (success: boolean) => void) => { if (socket.character?.role !== 'gm') { + callback(false) return } try { + // Parse and validate spriteActions + let parsedSpriteActions: SpriteActionInput[]; + try { + parsedSpriteActions = JSON.parse(JSON.stringify(data.spriteActions)) as SpriteActionInput[]; + if (!Array.isArray(parsedSpriteActions)) { + throw new Error('spriteActions is not an array'); + } + } catch (error) { + console.error('Error parsing spriteActions:', error); + callback(false); + return; + } + + // Process the sprites to determine the largest dimensions + const processedActions = await Promise.all(parsedSpriteActions.map(async (spriteAction) => { + const { action, sprites } = spriteAction; + + if (!Array.isArray(sprites) || sprites.length === 0) { + throw new Error(`Invalid sprites array for action: ${action}`); + } + + // Convert base64 strings to buffers and get dimensions + const buffersWithDimensions = await Promise.all(sprites.map(async (sprite: string) => { + const buffer = Buffer.from(sprite.split(',')[1], 'base64'); + const { width, height } = await sharp(buffer).metadata(); + return { buffer, width, height }; + })); + + // Find the largest width and height + const frameWidth = Math.max(...buffersWithDimensions.map(b => b.width || 0)); + const frameHeight = Math.max(...buffersWithDimensions.map(b => b.height || 0)); + + return { + ...spriteAction, + frameWidth, + frameHeight, + buffersWithDimensions + }; + })); + + // Update the database with the new sprite actions (including calculated frame sizes) await prisma.sprite.update({ - where: { - id: data.id - }, + where: { id: data.id }, data: { name: data.name, spriteActions: { - deleteMany: { - spriteId: data.id - }, - create: data.spriteActions.map((spriteAction) => ({ + deleteMany: { spriteId: data.id }, + create: processedActions.map((spriteAction) => ({ action: spriteAction.action, - sprites: spriteAction.sprites as string[], + sprites: spriteAction.sprites, origin_x: spriteAction.origin_x, origin_y: spriteAction.origin_y, isAnimated: spriteAction.isAnimated, @@ -43,19 +85,43 @@ export default function (socket: TSocket, io: Server) { })) } } - }) + }); - const public_folder = path.join(process.cwd(), 'public', 'sprites', data.id) + const public_folder = path.join(process.cwd(), 'public', 'sprites', data.id); + await fs.mkdir(public_folder, { recursive: true }); - // Ensure the folder exists - await fs.mkdir(public_folder, { recursive: true }) + // Process and save each spriteAction + await Promise.all(processedActions.map(async (spriteAction) => { + const { action, buffersWithDimensions, frameWidth, frameHeight } = spriteAction; - const sprite = await spriteRepository.getById(data.id) + // Combine all sprites into a single image + const combinedImage = await sharp({ + create: { + width: frameWidth * buffersWithDimensions.length, + height: frameHeight, + channels: 4, + background: { r: 0, g: 0, b: 0, alpha: 0 } + } + }) + .composite( + buffersWithDimensions.map(({ buffer }, index) => ({ + input: buffer, + left: index * frameWidth, + top: 0 + })) + ) + .png() + .toBuffer(); - callback(true) + // Save the combined image + const filename = path.join(public_folder, `${action}.png`); + await writeFile(filename, combinedImage); + })); + + callback(true); } catch (error) { - console.error(error) - callback(false) + console.error('Error updating sprite:', error); + callback(false); } - }) + }); } \ No newline at end of file diff --git a/src/repositories/SpriteRepository.ts b/src/repositories/SpriteRepository.ts index c0093d6..a618320 100644 --- a/src/repositories/SpriteRepository.ts +++ b/src/repositories/SpriteRepository.ts @@ -1,5 +1,5 @@ import prisma from '../utilities/Prisma' // Import the global Prisma instance -import { Sprite } from '@prisma/client' +import { Sprite, SpriteAction } from '@prisma/client' class SpriteRepository { async getById(id: string): Promise { @@ -19,7 +19,7 @@ class SpriteRepository { }) } - async getSpriteActions(spriteId: string) { + async getSpriteActions(spriteId: string): Promise { return prisma.spriteAction.findMany({ where: { spriteId