forked from noxious/server
Major refractor, cleaning and improvements.
This commit is contained in:
145
src/events/gameMaster/assetManager/sprite/update.ts
Normal file
145
src/events/gameMaster/assetManager/sprite/update.ts
Normal file
@ -0,0 +1,145 @@
|
||||
import { Server } from 'socket.io'
|
||||
import { TSocket } from '../../../../utilities/types'
|
||||
import prisma from '../../../../utilities/prisma'
|
||||
import type { Prisma, SpriteAction } from '@prisma/client'
|
||||
import path from 'path'
|
||||
import { writeFile, mkdir } from 'node:fs/promises'
|
||||
import sharp from 'sharp'
|
||||
|
||||
type SpriteActionInput = Omit<SpriteAction, 'id' | 'spriteId' | 'frameWidth' | 'frameHeight'> & {
|
||||
sprites: string[]
|
||||
}
|
||||
|
||||
type Payload = {
|
||||
id: string
|
||||
name: string
|
||||
spriteActions: Prisma.JsonValue
|
||||
}
|
||||
|
||||
interface ProcessedSpriteAction extends SpriteActionInput {
|
||||
frameWidth: number
|
||||
frameHeight: number
|
||||
buffersWithDimensions: Array<{
|
||||
buffer: Buffer
|
||||
width: number | undefined
|
||||
height: number | undefined
|
||||
}>
|
||||
}
|
||||
|
||||
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 {
|
||||
const parsedSpriteActions = validateSpriteActions(data.spriteActions)
|
||||
const processedActions = await processSprites(parsedSpriteActions)
|
||||
|
||||
await updateDatabase(data.id, data.name, processedActions)
|
||||
await saveSpritesToDisk(data.id, processedActions)
|
||||
|
||||
callback(true)
|
||||
} catch (error) {
|
||||
console.error('Error updating sprite:', error)
|
||||
callback(false)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
function validateSpriteActions(spriteActions: Prisma.JsonValue): SpriteActionInput[] {
|
||||
try {
|
||||
const parsed = JSON.parse(JSON.stringify(spriteActions)) as SpriteActionInput[]
|
||||
if (!Array.isArray(parsed)) {
|
||||
throw new Error('spriteActions is not an array')
|
||||
}
|
||||
return parsed
|
||||
} catch (error) {
|
||||
console.error('Error parsing spriteActions:', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
async function processSprites(spriteActions: SpriteActionInput[]): Promise<ProcessedSpriteAction[]> {
|
||||
return Promise.all(
|
||||
spriteActions.map(async (spriteAction) => {
|
||||
const { action, sprites } = spriteAction
|
||||
|
||||
if (!Array.isArray(sprites) || sprites.length === 0) {
|
||||
throw new Error(`Invalid sprites array for action: ${action}`)
|
||||
}
|
||||
|
||||
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 }
|
||||
})
|
||||
)
|
||||
|
||||
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
|
||||
}
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
async function updateDatabase(id: string, name: string, processedActions: ProcessedSpriteAction[]) {
|
||||
await prisma.sprite.update({
|
||||
where: { id },
|
||||
data: {
|
||||
name,
|
||||
spriteActions: {
|
||||
deleteMany: { spriteId: id },
|
||||
create: processedActions.map(({ action, sprites, originX, originY, isAnimated, isLooping, frameWidth, frameHeight, frameSpeed }) => ({
|
||||
action,
|
||||
sprites,
|
||||
originX,
|
||||
originY,
|
||||
isAnimated,
|
||||
isLooping,
|
||||
frameWidth,
|
||||
frameHeight,
|
||||
frameSpeed
|
||||
}))
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async function saveSpritesToDisk(id: string, processedActions: ProcessedSpriteAction[]) {
|
||||
const publicFolder = path.join(process.cwd(), 'public', 'sprites', id)
|
||||
await mkdir(publicFolder, { recursive: true })
|
||||
|
||||
await Promise.all(
|
||||
processedActions.map(async ({ action, buffersWithDimensions, frameWidth, frameHeight }) => {
|
||||
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()
|
||||
|
||||
const filename = path.join(publicFolder, `${action}.png`)
|
||||
await writeFile(filename, combinedImage)
|
||||
})
|
||||
)
|
||||
}
|
Reference in New Issue
Block a user