diff --git a/public/assets.zip b/public/assets.zip index a9981cf..7ff57db 100644 Binary files a/public/assets.zip and b/public/assets.zip differ 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}`