forked from noxious/server
Simplified code
This commit is contained in:
parent
bfafd41c46
commit
c9e8b29f11
@ -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)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user