forked from noxious/server
#174: Refactor character manager into zoneManager for better DX, major refactor of time and weather system (data is stored in DB now instead of JSON file), npm update, npm format, many other improvements
This commit is contained in:
@ -1,43 +1,57 @@
|
||||
import { ExtendedCharacter } from '../../utilities/types'
|
||||
import { AStar } from '../../utilities/character/aStar'
|
||||
import ZoneManager from '../../managers/zoneManager'
|
||||
import Rotation from '../../utilities/character/rotation'
|
||||
import { gameLogger } from '../../utilities/logger'
|
||||
import { Character } from '@prisma/client'
|
||||
|
||||
interface Position {
|
||||
x: number
|
||||
y: number
|
||||
}
|
||||
|
||||
export class CharacterMoveService {
|
||||
public updatePosition(character: ExtendedCharacter, position: { x: number; y: number }, newZoneId?: number) {
|
||||
private static readonly MOVEMENT_DELAY_MS = 250
|
||||
|
||||
public updatePosition(character: Character, position: Position, newZoneId?: number): void {
|
||||
if (!this.isValidPosition(position)) {
|
||||
gameLogger.error(`Invalid position coordinates: ${position.x}, ${position.y}`)
|
||||
}
|
||||
|
||||
Object.assign(character, {
|
||||
positionX: position.x,
|
||||
positionY: position.y,
|
||||
rotation: Rotation.calculate(character.positionX, character.positionY, position.x, position.y),
|
||||
zoneId: newZoneId || character.zoneId
|
||||
zoneId: newZoneId ?? character.zoneId
|
||||
})
|
||||
|
||||
// await prisma.character.update({
|
||||
// where: { id: character.id },
|
||||
// data: {
|
||||
// positionX: position.x,
|
||||
// positionY: position.y,
|
||||
// rotation: character.rotation,
|
||||
// zoneId: newZoneId
|
||||
// }
|
||||
// })
|
||||
}
|
||||
|
||||
public async calculatePath(character: ExtendedCharacter, targetX: number, targetY: number): Promise<Array<{ x: number; y: number }> | null> {
|
||||
const grid = await ZoneManager.getZoneById(character.zoneId)?.getGrid()
|
||||
public async calculatePath(character: Character, targetX: number, targetY: number): Promise<Position[] | null> {
|
||||
const zone = ZoneManager.getZoneById(character.zoneId)
|
||||
const grid = await zone?.getGrid()
|
||||
|
||||
if (!grid?.length) {
|
||||
gameLogger.error('character:move error', 'Grid not found or empty')
|
||||
return null
|
||||
}
|
||||
|
||||
const start = { x: Math.floor(character.positionX), y: Math.floor(character.positionY) }
|
||||
const end = { x: Math.floor(targetX), y: Math.floor(targetY) }
|
||||
const start: Position = {
|
||||
x: Math.floor(character.positionX),
|
||||
y: Math.floor(character.positionY)
|
||||
}
|
||||
|
||||
const end: Position = {
|
||||
x: Math.floor(targetX),
|
||||
y: Math.floor(targetY)
|
||||
}
|
||||
|
||||
return AStar.findPath(start, end, grid)
|
||||
}
|
||||
|
||||
public async applyMovementDelay(): Promise<void> {
|
||||
await new Promise((resolve) => setTimeout(resolve, 250)) // 250ms delay between steps
|
||||
await new Promise((resolve) => setTimeout(resolve, CharacterMoveService.MOVEMENT_DELAY_MS))
|
||||
}
|
||||
|
||||
private isValidPosition(position: Position): boolean {
|
||||
return Number.isFinite(position.x) && Number.isFinite(position.y) && position.x >= 0 && position.y >= 0
|
||||
}
|
||||
}
|
||||
|
37
src/services/worldService.ts
Normal file
37
src/services/worldService.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import prisma from '../utilities/prisma'
|
||||
import { gameLogger } from '../utilities/logger'
|
||||
import { World } from '@prisma/client'
|
||||
import WorldRepository from '../repositories/worldRepository'
|
||||
|
||||
class WorldService {
|
||||
async update(worldData: Partial<World>): Promise<boolean> {
|
||||
try {
|
||||
const currentWorld = await WorldRepository.getFirst()
|
||||
if (!currentWorld) {
|
||||
// If no world exists, create first record
|
||||
await prisma.world.create({
|
||||
data: {
|
||||
...worldData,
|
||||
date: worldData.date || new Date()
|
||||
}
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
// Update existing world using its date as unique identifier
|
||||
await prisma.world.update({
|
||||
where: {
|
||||
date: currentWorld.date
|
||||
},
|
||||
data: worldData
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error: any) {
|
||||
gameLogger.error(`Failed to update world: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new WorldService()
|
@ -3,7 +3,7 @@ import prisma from '../utilities/prisma'
|
||||
import ZoneRepository from '../repositories/zoneRepository'
|
||||
import { ZoneEventTileTeleport } from '@prisma/client'
|
||||
import { Server } from 'socket.io'
|
||||
import CharacterManager from '../managers/characterManager'
|
||||
import ZoneManager from '../managers/zoneManager'
|
||||
|
||||
export class ZoneEventTileService {
|
||||
public async handleTeleport(io: Server, socket: TSocket, character: ExtendedCharacter, teleport: ZoneEventTileTeleport): Promise<void> {
|
||||
@ -12,8 +12,6 @@ export class ZoneEventTileService {
|
||||
const zone = await ZoneRepository.getById(teleport.toZoneId)
|
||||
if (!zone) return
|
||||
|
||||
// CharacterManager.moveCharacterBetweenZones(character, zone)
|
||||
|
||||
const oldZoneId = character.zoneId
|
||||
const newZoneId = teleport.toZoneId
|
||||
|
||||
@ -46,7 +44,7 @@ export class ZoneEventTileService {
|
||||
// Send teleport information to the client
|
||||
socket.emit('zone:character:teleport', {
|
||||
zone,
|
||||
characters: CharacterManager.getCharactersInZone(zone)
|
||||
characters: ZoneManager.getZoneById(zone.id)?.getCharactersInZone()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,84 +1,38 @@
|
||||
import prisma from '../utilities/prisma'
|
||||
import { AssetData } from '../utilities/types'
|
||||
import tileRepository from '../repositories/tileRepository'
|
||||
import zoneRepository from '../repositories/zoneRepository'
|
||||
import { Object, Zone, ZoneObject } from '@prisma/client'
|
||||
|
||||
type getZoneAsetsZoneType = Zone & {
|
||||
zoneObjects: (ZoneObject & {
|
||||
object: Object
|
||||
})[]
|
||||
}
|
||||
import { gameLogger } from '../utilities/logger'
|
||||
|
||||
class ZoneService {
|
||||
async createDemoZone(): Promise<boolean> {
|
||||
const tiles = [
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile']
|
||||
]
|
||||
try {
|
||||
const tiles = [
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
|
||||
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile']
|
||||
]
|
||||
|
||||
await prisma.zone.create({
|
||||
data: {
|
||||
name: 'Demo zone',
|
||||
width: 10,
|
||||
height: 10,
|
||||
tiles
|
||||
}
|
||||
})
|
||||
await prisma.zone.create({
|
||||
data: {
|
||||
name: 'Demo zone',
|
||||
width: 10,
|
||||
height: 10,
|
||||
tiles
|
||||
}
|
||||
})
|
||||
|
||||
console.log('Demo zone created.')
|
||||
return true
|
||||
}
|
||||
gameLogger.info('Demo zone created.')
|
||||
|
||||
async getZoneAssets(zone: getZoneAsetsZoneType): Promise<AssetData[]> {
|
||||
const assets: AssetData[] = []
|
||||
|
||||
// zone.tiles is prisma jsonvalue
|
||||
let tiles = JSON.parse(JSON.stringify(zone.tiles))
|
||||
tiles = [...new Set(tiles.flat())]
|
||||
|
||||
// Add tile assets
|
||||
for (const tile of tiles) {
|
||||
const tileInfo = await tileRepository.getById(tile)
|
||||
if (!tileInfo) continue
|
||||
|
||||
assets.push({
|
||||
key: tileInfo.id,
|
||||
data: '/assets/tiles/' + tileInfo.id + '.png',
|
||||
group: 'tiles',
|
||||
updatedAt: tileInfo?.updatedAt || new Date()
|
||||
} as AssetData)
|
||||
return true
|
||||
} catch (error: any) {
|
||||
gameLogger.error(`Failed to create demo zone: ${error instanceof Error ? error.message : String(error)}`)
|
||||
return false
|
||||
}
|
||||
|
||||
// Add object assets
|
||||
for (const zoneObject of zone.zoneObjects) {
|
||||
if (!zoneObject.object) continue
|
||||
|
||||
assets.push({
|
||||
key: zoneObject.object.id,
|
||||
data: '/assets/objects/' + zoneObject.object.id + '.png',
|
||||
group: 'objects',
|
||||
updatedAt: zoneObject.object.updatedAt || new Date()
|
||||
} as AssetData)
|
||||
}
|
||||
|
||||
// Filter out duplicate assets
|
||||
return assets.reduce((acc: AssetData[], current) => {
|
||||
const x = acc.find((item) => item.key === current.key && item.group === current.group)
|
||||
if (!x) {
|
||||
return acc.concat([current])
|
||||
} else {
|
||||
return acc
|
||||
}
|
||||
}, [])
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user