1
0
forked from noxious/server

Simplified code

This commit is contained in:
Dennis Postma 2025-03-08 01:40:23 +01:00
parent bfafd41c46
commit c9e8b29f11

View File

@ -28,82 +28,109 @@ interface EffectiveDimensions {
bottom: number bottom: number
} }
type Payload = { interface SpriteActionData {
action: string
sprites: SpriteImage[]
originX: number
originY: number
frameRate: number
}
interface UpdateSpritePayload {
id: UUID id: UUID
name: string name: string
spriteActions: Array<{ spriteActions: SpriteActionData[]
action: string
sprites: SpriteImage[]
originX: number
originY: number
frameRate: number
}>
} }
export default class SpriteUpdateEvent extends BaseEvent { export default class SpriteUpdateEvent extends BaseEvent {
private readonly spriteRepository: SpriteRepository = new SpriteRepository()
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_SPRITE_UPDATE, this.handleEvent.bind(this)) this.socket.on(SocketEvent.GM_SPRITE_UPDATE, this.handleEvent.bind(this))
} }
private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> { private async handleEvent(data: UpdateSpritePayload, callback: (success: boolean) => void): Promise<void> {
try { try {
if (!(await this.isCharacterGM())) return if (!(await this.isCharacterGM())) {
callback(false)
return
}
const spriteRepository = new SpriteRepository() const sprite = await this.spriteRepository.getById(data.id)
const sprite = await spriteRepository.getById(data.id) if (!sprite) {
if (!sprite) return callback(false) callback(false)
return
}
await spriteRepository.getEntityManager().populate(sprite, ['spriteActions']) await this.spriteRepository.getEntityManager().populate(sprite, ['spriteActions'])
// Update sprite in database // Update sprite in database
await sprite.setName(data.name).setUpdatedAt(new Date()).save() await sprite.setName(data.name).setUpdatedAt(new Date()).save()
// First verify all sprite sheets can be generated // First verify all sprite sheets can be generated
for (const actionData of data.spriteActions) { const allSheetsGenerated = await this.verifyAllSpriteSheets(data.spriteActions, sprite.getId())
if (!(await this.generateSpriteSheet(actionData.sprites, sprite.getId(), actionData.action))) { if (!allSheetsGenerated) {
return callback(false) callback(false)
} return
} }
const existingActions = sprite.getSpriteActions() // Remove existing actions
await this.removeExistingActions(sprite)
// Remove existing actions only after confirming sprite sheets generated successfully
for (const existingAction of existingActions) {
await spriteRepository.getEntityManager().removeAndFlush(existingAction)
}
// Create new actions // Create new actions
for (const actionData of data.spriteActions) { await this.createNewSpriteActions(data.spriteActions, sprite)
// Process images and calculate dimensions
const imageData = await Promise.all(actionData.sprites.map((sprite) => this.processImage(sprite)))
const effectiveDimensions = imageData.map((dimensions) => this.calculateEffectiveDimensions(dimensions))
// Calculate total height needed for the sprite sheet callback(true)
const maxHeight = Math.max(...effectiveDimensions.map((d) => d.height))
const maxTop = Math.max(...effectiveDimensions.map((d) => d.top))
const maxBottom = Math.max(...effectiveDimensions.map((d) => d.bottom))
const totalHeight = maxHeight + maxTop + maxBottom
const spriteAction = new SpriteAction()
spriteAction.setSprite(sprite)
sprite.getSpriteActions().add(spriteAction)
spriteAction
.setAction(actionData.action)
.setSprites(actionData.sprites)
.setOriginX(actionData.originX)
.setOriginY(actionData.originY)
.setFrameWidth(await this.calculateMaxWidth(actionData.sprites))
.setFrameHeight(totalHeight)
.setFrameRate(actionData.frameRate)
await spriteRepository.getEntityManager().persistAndFlush(spriteAction)
}
return callback(true)
} catch (error) { } catch (error) {
console.error(`Error updating sprite ${data.id}:`, error) console.error(`Error updating sprite ${data.id}:`, error)
return callback(false) callback(false)
}
}
private async verifyAllSpriteSheets(actionsData: SpriteActionData[], spriteId: string): Promise<boolean> {
for (const actionData of actionsData) {
if (!(await this.generateSpriteSheet(actionData.sprites, spriteId, actionData.action))) {
return false
}
}
return true
}
private async removeExistingActions(sprite: any): Promise<void> {
const existingActions = sprite.getSpriteActions()
for (const existingAction of existingActions) {
await this.spriteRepository.getEntityManager().removeAndFlush(existingAction)
}
}
private async createNewSpriteActions(actionsData: SpriteActionData[], sprite: any): Promise<void> {
for (const actionData of actionsData) {
// Process images and calculate dimensions
const imageData = await Promise.all(actionData.sprites.map((sprite) => this.processImage(sprite)))
const effectiveDimensions = imageData.map((dimensions) => this.calculateEffectiveDimensions(dimensions))
// Calculate total height needed for the sprite sheet
const maxHeight = Math.max(...effectiveDimensions.map((d) => d.height), 0)
const maxTop = Math.max(...effectiveDimensions.map((d) => d.top), 0)
const maxBottom = Math.max(...effectiveDimensions.map((d) => d.bottom), 0)
const totalHeight = maxHeight + maxTop + maxBottom
const spriteAction = new SpriteAction()
spriteAction.setSprite(sprite)
sprite.getSpriteActions().add(spriteAction)
const maxWidth = await this.calculateMaxWidth(actionData.sprites)
spriteAction
.setAction(actionData.action)
.setSprites(actionData.sprites)
.setOriginX(actionData.originX)
.setOriginY(actionData.originY)
.setFrameWidth(maxWidth)
.setFrameHeight(totalHeight)
.setFrameRate(actionData.frameRate)
await this.spriteRepository.getEntityManager().persistAndFlush(spriteAction)
} }
} }
@ -116,20 +143,19 @@ export default class SpriteUpdateEvent extends BaseEvent {
const effectiveDimensions = imageData.map((dimensions) => this.calculateEffectiveDimensions(dimensions)) const effectiveDimensions = imageData.map((dimensions) => this.calculateEffectiveDimensions(dimensions))
// Calculate maximum dimensions // Calculate maximum dimensions
const maxWidth = Math.max(...effectiveDimensions.map((d) => d.width)) const maxWidth = Math.max(...effectiveDimensions.map((d) => d.width), 0)
const maxHeight = Math.max(...effectiveDimensions.map((d) => d.height)) const maxHeight = Math.max(...effectiveDimensions.map((d) => d.height), 0)
const maxTop = Math.max(...effectiveDimensions.map((d) => d.top)) const maxTop = Math.max(...effectiveDimensions.map((d) => d.top), 0)
const maxBottom = Math.max(...effectiveDimensions.map((d) => d.bottom)) const maxBottom = Math.max(...effectiveDimensions.map((d) => d.bottom), 0)
// Calculate total height needed // Calculate total height needed
const totalHeight = maxHeight + maxTop + maxBottom const totalHeight = maxHeight + maxTop + maxBottom
// Process images and create sprite sheet // Process images and create sprite sheet
const processedImages = await Promise.all( const processedImages = await Promise.all(
sprites.map(async (sprite, index) => { sprites.map(async (sprite) => {
const { width, height, offsetX, offsetY } = await this.processImage(sprite) const { width, height, offsetX, offsetY } = await this.processImage(sprite)
const uri = sprite.url.split(';base64,').pop() const uri = this.extractBase64Data(sprite.url)
if (!uri) throw new Error('Invalid base64 image')
const buffer = Buffer.from(uri, 'base64') const buffer = Buffer.from(uri, 'base64')
// Create individual frame // Create individual frame
@ -150,30 +176,10 @@ export default class SpriteUpdateEvent extends BaseEvent {
) )
// Combine frames into sprite sheet // Combine frames into sprite sheet
const spriteSheet = await sharp({ const spriteSheet = await this.createSpriteSheetImage(processedImages, maxWidth, totalHeight)
create: {
width: maxWidth * sprites.length,
height: totalHeight,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 }
}
})
.composite(
processedImages.map((buffer, index) => ({
input: buffer,
left: index * maxWidth,
top: 0
}))
)
.png()
.toBuffer()
// Ensure directory exists
const dir = `public/sprites/${spriteId}`
await fs.promises.mkdir(dir, { recursive: true })
// Save the sprite sheet // Save the sprite sheet
await fs.promises.writeFile(`${dir}/${action}.png`, spriteSheet) await this.saveSpriteSheet(spriteId, action, spriteSheet)
return true return true
} catch (error) { } catch (error) {
@ -182,9 +188,43 @@ export default class SpriteUpdateEvent extends BaseEvent {
} }
} }
private async processImage(sprite: SpriteImage): Promise<ImageDimensions> { private async createSpriteSheetImage(processedImages: Buffer[], maxWidth: number, totalHeight: number): Promise<Buffer> {
const uri = sprite.url.split(';base64,').pop() return sharp({
create: {
width: maxWidth * processedImages.length,
height: totalHeight,
channels: 4,
background: { r: 0, g: 0, b: 0, alpha: 0 }
}
})
.composite(
processedImages.map((buffer, index) => ({
input: buffer,
left: index * maxWidth,
top: 0
}))
)
.png()
.toBuffer()
}
private async saveSpriteSheet(spriteId: string, action: string, spriteSheet: Buffer): Promise<void> {
// Ensure directory exists
const dir = `public/sprites/${spriteId}`
await fs.promises.mkdir(dir, { recursive: true })
// Save the sprite sheet
await fs.promises.writeFile(`${dir}/${action}.png`, spriteSheet)
}
private extractBase64Data(url: string): string {
const uri = url.split(';base64,').pop()
if (!uri) throw new Error('Invalid base64 image') if (!uri) throw new Error('Invalid base64 image')
return uri
}
private async processImage(sprite: SpriteImage): Promise<ImageDimensions> {
const uri = this.extractBase64Data(sprite.url)
const buffer = Buffer.from(uri, 'base64') const buffer = Buffer.from(uri, 'base64')
const metadata = await sharp(buffer).metadata() const metadata = await sharp(buffer).metadata()
return { return {
@ -212,6 +252,6 @@ export default class SpriteUpdateEvent extends BaseEvent {
const effectiveDimensions = imageData.map((dimensions) => this.calculateEffectiveDimensions(dimensions)) const effectiveDimensions = imageData.map((dimensions) => this.calculateEffectiveDimensions(dimensions))
// Calculate maximum width needed // Calculate maximum width needed
return Math.max(...effectiveDimensions.map((d) => d.width)) return Math.max(...effectiveDimensions.map((d) => d.width), 0)
} }
} }