From bf58fc4944c222a319a57e11786104b9c0f633cf Mon Sep 17 00:00:00 2001 From: Dennis Postma Date: Sun, 22 Dec 2024 00:12:43 +0100 Subject: [PATCH] #16: Working PoC avatar image generator --- package-lock.json | 6 +- src/http/avatar.ts | 73 +++++++++++++++++++++ src/repositories/characterHairRepository.ts | 5 ++ src/repositories/characterTypeRepository.ts | 14 +++- 4 files changed, 94 insertions(+), 4 deletions(-) create mode 100644 src/http/avatar.ts diff --git a/package-lock.json b/package-lock.json index 322930a..f2928f1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -954,9 +954,9 @@ "license": "BSD-3-Clause" }, "node_modules/bullmq": { - "version": "5.34.3", - "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.3.tgz", - "integrity": "sha512-S8/V11w7p6jYAGvv+00skLza/4inTOupWPe0uCD8mZSUiYKzvmW4/YEB+KVjZI2CC2oD3KJ3t7/KkUd31MxMig==", + "version": "5.34.4", + "resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.4.tgz", + "integrity": "sha512-FPTN5eqsYO5/Blm6vh/bVJ0eADrVLjTBDjMkPCqZN3xx/5GqHov9NwxM6A6M3m6n2Vg+gNAoN98t7bTij5/UEA==", "license": "MIT", "dependencies": { "cron-parser": "^4.6.0", diff --git a/src/http/avatar.ts b/src/http/avatar.ts new file mode 100644 index 0000000..878acaf --- /dev/null +++ b/src/http/avatar.ts @@ -0,0 +1,73 @@ +/** + * Avatar generator API routes + */ +import { Router, Request, Response } from 'express' +import sharp from 'sharp' +import fs from 'fs' +import CharacterRepository from '../repositories/characterRepository' +import CharacterHairRepository from '../repositories/characterHairRepository' +import CharacterTypeRepository from '../repositories/characterTypeRepository' +import { getPublicPath } from '../utilities/storage' + +const router = Router() + +interface AvatarOptions { + characterTypeId: number + characterHairId?: number +} + +async function generateAvatar(res: Response, options: AvatarOptions) { + try { + const characterType = await CharacterTypeRepository.getById(options.characterTypeId) + if (!characterType?.spriteId) { + return res.status(404).json({ message: 'Character type not found' }) + } + + const bodySpritePath = getPublicPath('sprites', characterType.spriteId, 'idle_right_down.png') + if (!fs.existsSync(bodySpritePath)) { + console.error(`Body sprite file not found: ${bodySpritePath}`) + return res.status(404).json({ message: 'Body sprite file not found' }) + } + + let avatar = sharp(bodySpritePath) + + if (options.characterHairId) { + const characterHair = await CharacterHairRepository.getById(options.characterHairId) + if (characterHair?.spriteId) { + const hairSpritePath = getPublicPath('sprites', characterHair.spriteId, 'front.png') + if (fs.existsSync(hairSpritePath)) { + avatar = avatar.composite([{ input: hairSpritePath, gravity: 'north' }]) + } else { + console.error(`Hair sprite file not found: ${hairSpritePath}`) + } + } + } + + res.setHeader('Content-Type', 'image/png') + return avatar.pipe(res) + } catch (error) { + console.error('Error generating avatar:', error) + return res.status(500).json({ message: 'Error generating avatar' }) + } +} + +router.get('/avatar/:characterName', async (req: Request, res: Response) => { + const character = await CharacterRepository.getByName(req.params.characterName) + if (!character?.characterType) { + return res.status(404).json({ message: 'Character or character type not found' }) + } + + return generateAvatar(res, { + characterTypeId: character.characterType.id, + characterHairId: character.characterHair?.id + }) +}) + +router.get('/avatar/s/:characterTypeId/:characterHairId?', async (req: Request, res: Response) => { + return generateAvatar(res, { + characterTypeId: parseInt(req.params.characterTypeId), + characterHairId: req.params.characterHairId ? parseInt(req.params.characterHairId) : undefined + }) +}) + +export default router \ No newline at end of file diff --git a/src/repositories/characterHairRepository.ts b/src/repositories/characterHairRepository.ts index da42794..1790f43 100644 --- a/src/repositories/characterHairRepository.ts +++ b/src/repositories/characterHairRepository.ts @@ -12,6 +12,11 @@ class CharacterHairRepository { } }) } + async getById(id: number): Promise { + return prisma.characterHair.findUnique({ + where: { id } + }) + } } export default new CharacterHairRepository() diff --git a/src/repositories/characterTypeRepository.ts b/src/repositories/characterTypeRepository.ts index 86ea075..69d66f5 100644 --- a/src/repositories/characterTypeRepository.ts +++ b/src/repositories/characterTypeRepository.ts @@ -3,7 +3,19 @@ import { CharacterType } from '@prisma/client' class CharacterTypeRepository { async getAll(): Promise { - return prisma.characterType.findMany() + return prisma.characterType.findMany({ + include: { + sprite: true + } + }) + } + async getById(id: number): Promise { + return prisma.characterType.findUnique({ + where: { id }, + include: { + sprite: true + } + }) } }