forked from noxious/server
Loading sprites and animations now works
This commit is contained in:
parent
9273d877f8
commit
e3d556dce2
@ -39,7 +39,6 @@ export default function (socket: TSocket, io: Server) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
callback(true)
|
callback(true)
|
||||||
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.log(e)
|
console.log(e)
|
||||||
callback(false)
|
callback(false)
|
||||||
|
@ -3,8 +3,7 @@ import { TSocket } from '../../../utilities/Types'
|
|||||||
import prisma from '../../../utilities/Prisma'
|
import prisma from '../../../utilities/Prisma'
|
||||||
import type { Prisma, SpriteAction } from '@prisma/client'
|
import type { Prisma, SpriteAction } from '@prisma/client'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { writeFile } from 'node:fs/promises'
|
import { writeFile, mkdir } from 'node:fs/promises'
|
||||||
import fs from 'fs/promises'
|
|
||||||
import sharp from 'sharp'
|
import sharp from 'sharp'
|
||||||
|
|
||||||
type SpriteActionInput = Omit<SpriteAction, 'id' | 'spriteId' | 'frameWidth' | 'frameHeight'> & {
|
type SpriteActionInput = Omit<SpriteAction, 'id' | 'spriteId' | 'frameWidth' | 'frameHeight'> & {
|
||||||
@ -17,6 +16,16 @@ type Payload = {
|
|||||||
spriteActions: Prisma.JsonValue
|
spriteActions: Prisma.JsonValue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ProcessedSpriteAction extends SpriteActionInput {
|
||||||
|
frameWidth: number
|
||||||
|
frameHeight: number
|
||||||
|
buffersWithDimensions: Array<{
|
||||||
|
buffer: Buffer
|
||||||
|
width: number | undefined
|
||||||
|
height: number | undefined
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
export default function (socket: TSocket, io: Server) {
|
export default function (socket: TSocket, io: Server) {
|
||||||
socket.on('gm:sprite:update', async (data: Payload, callback: (success: boolean) => void) => {
|
socket.on('gm:sprite:update', async (data: Payload, callback: (success: boolean) => void) => {
|
||||||
if (socket.character?.role !== 'gm') {
|
if (socket.character?.role !== 'gm') {
|
||||||
@ -25,29 +34,42 @@ export default function (socket: TSocket, io: Server) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Parse and validate spriteActions
|
const parsedSpriteActions = validateSpriteActions(data.spriteActions)
|
||||||
let parsedSpriteActions: SpriteActionInput[]
|
const processedActions = await processSprites(parsedSpriteActions)
|
||||||
|
|
||||||
|
await updateDatabase(data.id, data.name, processedActions)
|
||||||
|
await saveSpritesToDisk(data.id, processedActions)
|
||||||
|
|
||||||
|
callback(true)
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error updating sprite:', error)
|
||||||
|
callback(false)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function validateSpriteActions(spriteActions: Prisma.JsonValue): SpriteActionInput[] {
|
||||||
try {
|
try {
|
||||||
parsedSpriteActions = JSON.parse(JSON.stringify(data.spriteActions)) as SpriteActionInput[]
|
const parsed = JSON.parse(JSON.stringify(spriteActions)) as SpriteActionInput[]
|
||||||
if (!Array.isArray(parsedSpriteActions)) {
|
if (!Array.isArray(parsed)) {
|
||||||
throw new Error('spriteActions is not an array')
|
throw new Error('spriteActions is not an array')
|
||||||
}
|
}
|
||||||
|
return parsed
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error parsing spriteActions:', error)
|
console.error('Error parsing spriteActions:', error)
|
||||||
callback(false)
|
throw error
|
||||||
return
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Process the sprites to determine the largest dimensions
|
async function processSprites(spriteActions: SpriteActionInput[]): Promise<ProcessedSpriteAction[]> {
|
||||||
const processedActions = await Promise.all(
|
return Promise.all(
|
||||||
parsedSpriteActions.map(async (spriteAction) => {
|
spriteActions.map(async (spriteAction) => {
|
||||||
const { action, sprites } = spriteAction
|
const { action, sprites } = spriteAction
|
||||||
|
|
||||||
if (!Array.isArray(sprites) || sprites.length === 0) {
|
if (!Array.isArray(sprites) || sprites.length === 0) {
|
||||||
throw new Error(`Invalid sprites array for action: ${action}`)
|
throw new Error(`Invalid sprites array for action: ${action}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert base64 strings to buffers and get dimensions
|
|
||||||
const buffersWithDimensions = await Promise.all(
|
const buffersWithDimensions = await Promise.all(
|
||||||
sprites.map(async (sprite: string) => {
|
sprites.map(async (sprite: string) => {
|
||||||
const buffer = Buffer.from(sprite.split(',')[1], 'base64')
|
const buffer = Buffer.from(sprite.split(',')[1], 'base64')
|
||||||
@ -56,7 +78,6 @@ export default function (socket: TSocket, io: Server) {
|
|||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
// Find the largest width and height
|
|
||||||
const frameWidth = Math.max(...buffersWithDimensions.map((b) => b.width || 0))
|
const frameWidth = Math.max(...buffersWithDimensions.map((b) => b.width || 0))
|
||||||
const frameHeight = Math.max(...buffersWithDimensions.map((b) => b.height || 0))
|
const frameHeight = Math.max(...buffersWithDimensions.map((b) => b.height || 0))
|
||||||
|
|
||||||
@ -68,38 +89,37 @@ export default function (socket: TSocket, io: Server) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Update the database with the new sprite actions (including calculated frame sizes)
|
async function updateDatabase(id: string, name: string, processedActions: ProcessedSpriteAction[]) {
|
||||||
await prisma.sprite.update({
|
await prisma.sprite.update({
|
||||||
where: { id: data.id },
|
where: { id },
|
||||||
data: {
|
data: {
|
||||||
name: data.name,
|
name,
|
||||||
spriteActions: {
|
spriteActions: {
|
||||||
deleteMany: { spriteId: data.id },
|
deleteMany: { spriteId: id },
|
||||||
create: processedActions.map((spriteAction) => ({
|
create: processedActions.map(({ action, sprites, origin_x, origin_y, isAnimated, isLooping, frameWidth, frameHeight, frameSpeed }) => ({
|
||||||
action: spriteAction.action,
|
action,
|
||||||
sprites: spriteAction.sprites,
|
sprites,
|
||||||
origin_x: spriteAction.origin_x,
|
origin_x,
|
||||||
origin_y: spriteAction.origin_y,
|
origin_y,
|
||||||
isAnimated: spriteAction.isAnimated,
|
isAnimated,
|
||||||
isLooping: spriteAction.isLooping,
|
isLooping,
|
||||||
frameWidth: spriteAction.frameWidth,
|
frameWidth,
|
||||||
frameHeight: spriteAction.frameHeight,
|
frameHeight,
|
||||||
frameSpeed: spriteAction.frameSpeed
|
frameSpeed
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
const public_folder = path.join(process.cwd(), 'public', 'sprites', data.id)
|
async function saveSpritesToDisk(id: string, processedActions: ProcessedSpriteAction[]) {
|
||||||
await fs.mkdir(public_folder, { recursive: true })
|
const publicFolder = path.join(process.cwd(), 'public', 'sprites', id)
|
||||||
|
await mkdir(publicFolder, { recursive: true })
|
||||||
|
|
||||||
// Process and save each spriteAction
|
|
||||||
await Promise.all(
|
await Promise.all(
|
||||||
processedActions.map(async (spriteAction) => {
|
processedActions.map(async ({ action, buffersWithDimensions, frameWidth, frameHeight }) => {
|
||||||
const { action, buffersWithDimensions, frameWidth, frameHeight } = spriteAction
|
|
||||||
|
|
||||||
// Combine all sprites into a single image
|
|
||||||
const combinedImage = await sharp({
|
const combinedImage = await sharp({
|
||||||
create: {
|
create: {
|
||||||
width: frameWidth * buffersWithDimensions.length,
|
width: frameWidth * buffersWithDimensions.length,
|
||||||
@ -118,16 +138,8 @@ export default function (socket: TSocket, io: Server) {
|
|||||||
.png()
|
.png()
|
||||||
.toBuffer()
|
.toBuffer()
|
||||||
|
|
||||||
// Save the combined image
|
const filename = path.join(publicFolder, `${action}.png`)
|
||||||
const filename = path.join(public_folder, `${action}.png`)
|
|
||||||
await writeFile(filename, combinedImage)
|
await writeFile(filename, combinedImage)
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|
||||||
callback(true)
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error updating sprite:', error)
|
|
||||||
callback(false)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,7 @@ import prisma from '../utilities/Prisma' // Import the global Prisma instance
|
|||||||
import { Sprite, SpriteAction } from '@prisma/client'
|
import { Sprite, SpriteAction } from '@prisma/client'
|
||||||
|
|
||||||
class SpriteRepository {
|
class SpriteRepository {
|
||||||
async getById(id: string): Promise<Sprite | null> {
|
async getById(id: string) {
|
||||||
return prisma.sprite.findUnique({
|
return prisma.sprite.findUnique({
|
||||||
where: { id },
|
where: { id },
|
||||||
include: {
|
include: {
|
||||||
@ -11,7 +11,7 @@ class SpriteRepository {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
async getAll(): Promise<Sprite[]> {
|
async getAll() {
|
||||||
return prisma.sprite.findMany({
|
return prisma.sprite.findMany({
|
||||||
include: {
|
include: {
|
||||||
spriteActions: true
|
spriteActions: true
|
||||||
|
@ -1,7 +1,3 @@
|
|||||||
/**
|
|
||||||
* Resources:
|
|
||||||
* https://stackoverflow.com/questions/76131891/what-is-the-best-method-for-socket-io-authentication
|
|
||||||
*/
|
|
||||||
import { Application, Request, Response } from 'express'
|
import { Application, Request, Response } from 'express'
|
||||||
import UserService from '../services/UserService'
|
import UserService from '../services/UserService'
|
||||||
import jwt from 'jsonwebtoken'
|
import jwt from 'jsonwebtoken'
|
||||||
@ -11,6 +7,7 @@ import path from 'path'
|
|||||||
import { TAsset } from './Types'
|
import { TAsset } from './Types'
|
||||||
import tileRepository from '../repositories/TileRepository'
|
import tileRepository from '../repositories/TileRepository'
|
||||||
import objectRepository from '../repositories/ObjectRepository'
|
import objectRepository from '../repositories/ObjectRepository'
|
||||||
|
import spriteRepository from '../repositories/SpriteRepository'
|
||||||
|
|
||||||
async function addHttpRoutes(app: Application) {
|
async function addHttpRoutes(app: Application) {
|
||||||
app.get('/assets', async (req: Request, res: Response) => {
|
app.get('/assets', async (req: Request, res: Response) => {
|
||||||
@ -20,7 +17,7 @@ async function addHttpRoutes(app: Application) {
|
|||||||
assets.push({
|
assets.push({
|
||||||
key: tile.id,
|
key: tile.id,
|
||||||
url: '/assets/tiles/' + tile.id + '.png',
|
url: '/assets/tiles/' + tile.id + '.png',
|
||||||
group: 'tiles',
|
group: 'tiles'
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -29,7 +26,19 @@ async function addHttpRoutes(app: Application) {
|
|||||||
assets.push({
|
assets.push({
|
||||||
key: object.id,
|
key: object.id,
|
||||||
url: '/assets/objects/' + object.id + '.png',
|
url: '/assets/objects/' + object.id + '.png',
|
||||||
group: 'objects',
|
group: 'objects'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const sprites = await spriteRepository.getAll()
|
||||||
|
// sprites all contain spriteActions, loop through these
|
||||||
|
sprites.forEach((sprite) => {
|
||||||
|
sprite.spriteActions.forEach((spriteAction) => {
|
||||||
|
assets.push({
|
||||||
|
key: spriteAction.id,
|
||||||
|
url: '/assets/sprites/' + sprite.id + '/' + spriteAction.action + '.png',
|
||||||
|
group: spriteAction.isAnimated ? 'sprite_animations' : 'sprites'
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -26,5 +26,7 @@ export type TZoneCharacter = Character & {}
|
|||||||
export type TAsset = {
|
export type TAsset = {
|
||||||
key: string
|
key: string
|
||||||
url: string
|
url: string
|
||||||
group: 'tiles' | 'objects' | 'sound' | 'music' | 'ui' | 'font' | 'other' | 'sprite'
|
group: 'tiles' | 'objects' | 'sprites' | 'sprite_animations' | 'sound' | 'music' | 'ui' | 'font' | 'other'
|
||||||
|
frameWidth?: number
|
||||||
|
frameHeight?: number
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user