From c59b391a6a691baa19eb7516e50ebcddfab00ae5 Mon Sep 17 00:00:00 2001 From: Dennis Postma Date: Fri, 21 Feb 2025 01:46:53 +0100 Subject: [PATCH] Stash --- public/assets.zip | Bin 141295 -> 148367 bytes src/commands/init.ts | 2 +- src/controllers/avatar.ts | 24 +++++- .../gameMaster/assetManager/sprite/update.ts | 74 +++++++++--------- 4 files changed, 60 insertions(+), 40 deletions(-) diff --git a/public/assets.zip b/public/assets.zip index a9981cf4b5b2553a0bba52287288c3a32f689a41..7ff57dbc8f13d766f9dfcb8023b521ededf36f05 100644 GIT binary patch delta 8118 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 delta 2205 zcmZWp3sh896n*DDm>Fhp1QbD0L_rB5nc*k$(HR(J20wsk5i-h{h?+mfOwtjGh^DTUnJfID#X?K*lb;+ATV;0No8dDxYu&kP-+lHz=bpFj+4`Z~ zNcb zp%`ET>8#5tHI~@GEV16JH2pI#vk~WJ3rahIi<84^0%z#vF3Mq*jljhWfFx_(`2pY~ z6Sz)yNVMjR!=c&C>3l7oJ^9Z2Lb$!46NmQdIC`l>8@8%`7so|F1dBUkz<>%3h~$%< zCMMUGHL7O=!^{ShX+b4!_9{v5ZiU>HVVFS@5&gfOm6C^TDsav2r_-osW|n-lqh8vi zu+KFyda3@~v`g{QU!)%_Tk~>ttTO4QF|sB0#s-h+pZ@jb-g7?o z%S_3oU3)I4hs<3a)!9;2cf2b)!{zYwC6e-Yv?U`7Tl{ZHGDbXbjqX3Gt+sk}?y1GB ze9GkwXJohMYr^Fq$eOeIy!&O^I@MB# zvXT8EAvxC9>&n6P?G4w`n&y3$*Le8dhpOP+icZ7k%8dmxw!@Pb;tjH->yI2)WNJF@ zR|aL+Jjv8H)yPMWyfwIZh_j?^O`_&Rg~^cFmU>8XGw(SQdtmf?pM31Pf9@XEQ!@G4 zxQyJ6E0;P&@%OJyz4Kj503MwVgB-vkpnhQ8Z+Ff_`(*mstTnyNu5v9==zp$R^!q8N>W>1$TJEOE zvQO-o6DIBFnX!0g>e}3@hSE{(T_MjjPEA!8R|Y)tE_B`L|1=rTN8zOH+ZPyhgKqql zP9=A%q+V0MZ0Mo28yGqWuZ0Ur#PItE)v@5VF>XcTBw902MJ@9Hr zkWm-5@o&~73Vb@W{6!ewnu|S8iTffBih+k5eOZlw(MfPxU=wD82RG&o@FD_Bp@Hot zl-!*;py+`f-1vF0kFc98ul9Hvq}lc$E`Kq+A?blg;V903CFJ*>ac%2hp+&I`v|LdR z#EZ=e9**4v3ITG4ZSbdEFL7WOnEJ>bwSrNKvnn8_J4DCbt$>z33j5=5wU1(K9TZy1 z;1fyDz`mZmI-MwD#I-Ihyhtsn8Z$nvOHwgCFeJe7+C7pM3`uvAxh- zUkV{Hea;A}75BmpDfvj!?MLkid0UKXH>wck0H34Tjrhy>>4ze3;s?U$8;B=;i4<#J zA?~=`o7m$*IgCQ75AhO4kkW^^kz@vMjv@Y-h zr?HA|E%-$nCVa4*{{O^9kehO&r*byfkZUIdh{|(uvM*Y5rQC8L>oW{GUkMlp@PI}v{*@Q zM_O{wQ6uZC!>IwZgcTD3)p4|h-2o(uy=q3G=o&~=Y_ZzH`6Yl1i}@o>G~nSGba07- z$Ve;-rZ=jHM*ng2TiyyLQN&4O1`m?J85f5T6-k-Qp9EoB2$@Febv(!=UenBBFAU&> z4@1aM;qvGjO8wTyp>jO+OAn=fw=JMyKJ|MXO8r*O;^`dnSkLc4c)Q-mme3yLfD*&QsRQ#iW`z?YS+@@V;P=(6NLb|gj&jY^IN~6Z9 zXtz!`11`TuQ3XY$wG|(#Xl&grG^qL5hcuQDC5oeH?021j8B&VY@o4WqfDKV3gB%c{ VGMdE81DF6t;leQG4urmle*trT?p^=@ diff --git a/src/commands/init.ts b/src/commands/init.ts index 62757b4..d34b853 100644 --- a/src/commands/init.ts +++ b/src/commands/init.ts @@ -489,7 +489,7 @@ export default class InitCommand extends BaseCommand { private async createCharacterHair(): Promise { const hairSprite = new Sprite() - hairSprite.setId('922ee95f-1500-49c0-8ead-f8cc46dad136').setName('Hair 1') + hairSprite.setId('922ee95f-1500-49c0-8ead-f8cc46dad136').setName('Hair 1').setWidth(30).setHeight(40) await hairSprite.save() const frontAction = new SpriteAction() diff --git a/src/controllers/avatar.ts b/src/controllers/avatar.ts index 803d607..a4e5984 100644 --- a/src/controllers/avatar.ts +++ b/src/controllers/avatar.ts @@ -65,9 +65,12 @@ export class AvatarController extends BaseController { return this.sendError(res, 'Body sprite file not found', 404) } + // Get body sprite metadata + const bodyMetadata = await sharp(bodySpritePath).metadata() + let avatar = sharp(bodySpritePath).extend({ - top: 2, - bottom: 2, + top: 0, + bottom: 0, background: { r: 0, g: 0, b: 0, alpha: 0 } }) @@ -76,7 +79,21 @@ export class AvatarController extends BaseController { if (characterHair?.sprite?.id) { const hairSpritePath = Storage.getPublicPath('sprites', characterHair.sprite.id, 'front.png') if (fs.existsSync(hairSpritePath)) { - avatar = avatar.composite([{ input: hairSpritePath, gravity: 'north' }]) + // Resize hair sprite to match body dimensions + const resizedHair = await sharp(hairSpritePath) + .resize(bodyMetadata.width, bodyMetadata.height, { + fit: 'contain', + background: { r: 0, g: 0, b: 0, alpha: 0 } + }) + .toBuffer() + + avatar = avatar.composite([ + { + input: resizedHair, + left: 0, + top: -27 // Apply vertical offset + } + ]) } } } @@ -84,6 +101,7 @@ export class AvatarController extends BaseController { res.setHeader('Content-Type', 'image/png') return avatar.pipe(res) } catch (error) { + console.error('Avatar generation error:', error) return this.sendError(res, 'Error generating avatar', 500) } } diff --git a/src/events/gameMaster/assetManager/sprite/update.ts b/src/events/gameMaster/assetManager/sprite/update.ts index 2e64f0f..91a3936 100644 --- a/src/events/gameMaster/assetManager/sprite/update.ts +++ b/src/events/gameMaster/assetManager/sprite/update.ts @@ -31,6 +31,8 @@ interface EffectiveDimensions { type Payload = { id: UUID name: string + width: number + height: number spriteActions: Array<{ action: string sprites: SpriteImage[] @@ -56,7 +58,7 @@ export default class SpriteUpdateEvent extends BaseEvent { await spriteRepository.getEntityManager().populate(sprite, ['spriteActions']) // Update sprite in database - await sprite.setName(data.name).setUpdatedAt(new Date()).save() + await sprite.setName(data.name).setWidth(data.width).setHeight(data.height).setUpdatedAt(new Date()).save() // First verify all sprite sheets can be generated for (const actionData of data.spriteActions) { @@ -89,13 +91,13 @@ export default class SpriteUpdateEvent extends BaseEvent { 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) + .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) } @@ -126,27 +128,27 @@ export default class SpriteUpdateEvent extends BaseEvent { // Process images and create sprite sheet const processedImages = await Promise.all( - sprites.map(async (sprite, index) => { - const { width, height, offsetX, offsetY } = await this.processImage(sprite) - const uri = sprite.url.split(';base64,').pop() - if (!uri) throw new Error('Invalid base64 image') - const buffer = Buffer.from(uri, 'base64') + sprites.map(async (sprite, index) => { + const { width, height, offsetX, offsetY } = await this.processImage(sprite) + const uri = sprite.url.split(';base64,').pop() + if (!uri) throw new Error('Invalid base64 image') + const buffer = Buffer.from(uri, 'base64') - // Create individual frame - const left = offsetX >= 0 ? offsetX : 0 - const verticalOffset = totalHeight - height - (offsetY >= 0 ? offsetY : 0) - return sharp({ - create: { - width: maxWidth, - height: totalHeight, - channels: 4, - background: { r: 0, g: 0, b: 0, alpha: 0 } - } + // Create individual frame + const left = offsetX >= 0 ? offsetX : 0 + const verticalOffset = totalHeight - height - (offsetY >= 0 ? offsetY : 0) + return sharp({ + create: { + width: maxWidth, + height: totalHeight, + channels: 4, + background: { r: 0, g: 0, b: 0, alpha: 0 } + } + }) + .composite([{ input: buffer, left, top: verticalOffset }]) + .png() + .toBuffer() }) - .composite([{ input: buffer, left, top: verticalOffset }]) - .png() - .toBuffer() - }) ) // Combine frames into sprite sheet @@ -158,15 +160,15 @@ export default class SpriteUpdateEvent extends BaseEvent { background: { r: 0, g: 0, b: 0, alpha: 0 } } }) - .composite( - processedImages.map((buffer, index) => ({ - input: buffer, - left: index * maxWidth, - top: 0 - })) - ) - .png() - .toBuffer() + .composite( + processedImages.map((buffer, index) => ({ + input: buffer, + left: index * maxWidth, + top: 0 + })) + ) + .png() + .toBuffer() // Ensure directory exists const dir = `public/sprites/${spriteId}`