todo!(game-logic): migrate game files to the engine

This commit is contained in:
Walid 2023-07-31 11:00:50 +01:00
parent bbb1376f95
commit ce46a43061
Signed by: Walidoux
GPG Key ID: CCF21881FE8BEBAF
12 changed files with 3424 additions and 0 deletions

62
todo/Game.ts Normal file
View File

@ -0,0 +1,62 @@
import { Stage } from '@pixi/layers'
import type { Application as GameApplication, ICanvas } from 'pixi.js'
import { Application, BaseTexture, Container, SCALE_MODES, settings } from 'pixi.js'
import { GameLogger } from './GameLogger'
import type { GameConfigType } from './config'
import { GameConfig } from './config'
export interface IGame extends GameConfigType {
container: HTMLCanvasElement
width: number
height: number
}
export class Game {
private static INSTANCE: Game
private static readonly logger = new GameLogger('🕹️ Habbo Game')
private readonly _canvas: ICanvas
private readonly _application: GameApplication
// private readonly _manager = RoomManager
constructor(renderer: IGame) {
/** Change the PixiJS settings and default settings */
settings.RESOLUTION = 1
Container.defaultSortableChildren = true
BaseTexture.defaultOptions.scaleMode = SCALE_MODES.NEAREST
/** Create the PixiJS application */
this._application = new Application({
width: renderer.width,
height: renderer.height,
resolution: 1,
antialias: false
})
/** Support for PIXI.js dev-tool */
// @ts-expect-error
if (import.meta.env.DEV) globalThis.__PIXI_APP__ = this._application
/** Initiate configuration */
this._application.stage = new Stage()
// RoomManager.init()
/** Append it to the canvas */
this._canvas = this._application.view
renderer.container.append(this._application.view as unknown as Node)
}
public static launch(config: GameConfigType): void {
if (Object.keys(GameConfig.current).length === 0) GameConfig.init(config)
if (this.INSTANCE != null) return this.logger.error('An instance of the game already exists.')
this.INSTANCE = new this({
container: document.getElementById('root') as HTMLCanvasElement,
width: window.innerWidth,
height: window.innerHeight,
config
})
}
}

74
todo/GameConfig.ts Normal file
View File

@ -0,0 +1,74 @@
import type { ZodError, z } from 'zod'
import { fromZodError } from 'zod-validation-error'
import { GameConfigSchema } from './schemas'
import { GameLogger } from '../GameLogger'
type KeyOf<T> = T extends object
? {
[K in keyof T]-?: K | `${K & string}.${KeyOf<T[K]> & string}`
}[keyof T]
: never
type ValueOf<T, K extends KeyOf<T>> = K extends `${infer U}.${infer R}`
? U extends keyof T
? ValueOf<T[U], Extract<R, KeyOf<T[U]>>>
: never
: K extends keyof T
? T[K]
: never
export type GameCoreConfig = z.infer<typeof GameConfigSchema>
export type GameConfigType = Record<string, any>
export class GameConfig {
private static _config = {} as GameCoreConfig
private static readonly _logger = new GameLogger('🛠️ GameConfig', false)
private static _isValid = false
public static init(config: GameConfigType): void {
try {
GameConfigSchema.parse(config)
this._config = config as GameCoreConfig
this._isValid = true
} catch (error) {
const validationError = fromZodError(error as ZodError, {
issueSeparator: '\n',
prefixSeparator: '',
prefix: ''
})
const lines = validationError.message.split('\n')
const errors = lines
.map((error, index) => {
const shouldSkipLine = index === 0 && lines.length > 1
return (error = `${shouldSkipLine ? '\n\n' : ''}${error}`)
})
.join('\n')
this._isValid = false
this._logger.error(errors)
}
}
public static getValue<K extends KeyOf<GameCoreConfig>>(key: K): ValueOf<GameCoreConfig, K> {
if (Object.keys(GameConfig.current).length === 0) throw new Error('Cannot log this out')
const keys = key.split('.')
const value = keys.reduce((obj: any, key) => obj[key], this._config)
// refactor!(): obj value changes but its type doesn't | temporary any type for now
return value
}
public static get current(): GameCoreConfig {
return this._config
}
public static get valid(): boolean {
return this._isValid
}
}

66
todo/GameLogger.ts Normal file
View File

@ -0,0 +1,66 @@
import chalk from 'chalk'
import { GameConfig } from './config/GameConfig'
type LoggerTypes = 'DEBUG' | 'WARNING' | 'ERROR' | 'EVENTS' | 'PACKETS'
const GAME_VERSION = '0.0.0'
export class GameLogger {
private readonly _SUB_PREFIX!: string
private readonly _PREFIX = (color: { bg: `#${string}`; text: `#${string}` }, type: LoggerTypes): string =>
chalk.bgHex('#3742fa').white.bold(` ${this._SUB_PREFIX} `) + chalk.bgHex(color.bg).hex(color.text).bold(` ${type} `)
private LOG_DEBUG = true
private LOG_WARN = true
private LOG_ERROR = true
private LOG_EVENTS = true
private LOG_PACKETS = true
constructor(subPrefix: string, setupConfig: boolean = true) {
this._SUB_PREFIX = subPrefix
if (setupConfig) this.initConfig()
}
private initConfig(): void {
this.LOG_DEBUG = !GameConfig.getValue('system.logs.debug')
this.LOG_WARN = !GameConfig.getValue('system.logs.warn')
this.LOG_ERROR = !GameConfig.getValue('system.logs.error')
this.LOG_EVENTS = !GameConfig.getValue('system.logs.events')
this.LOG_PACKETS = !GameConfig.getValue('system.logs.packets')
}
public log(...messages: unknown[]): void {
if (!this.LOG_DEBUG) return
return console.log(this._PREFIX({ bg: '#353b48', text: '#FFF' }, 'DEBUG'), ...messages)
}
public warn(...messages: unknown[]): void {
if (!this.LOG_WARN) return
return console.warn(this._PREFIX({ bg: '#f39c12', text: '#000' }, 'WARNING'), ...messages)
}
public error(...messages: unknown[]): void {
if (!this.LOG_ERROR) return
return console.error(this._PREFIX({ bg: '#c0392b', text: '#FFF' }, 'ERROR'), ...messages)
}
public events(...messages: unknown[]): void {
if (!this.LOG_EVENTS) return
return console.log(this._PREFIX({ bg: '#27ae60', text: '#FFF' }, 'EVENTS'), ...messages)
}
public packets(...messages: unknown[]): void {
if (!this.LOG_PACKETS) return
return console.log(this._PREFIX({ bg: '#e17055', text: '#FFF' }, 'PACKETS'), ...messages)
}
public welcome(): void {
return console.log(
`%c Game Version | 🏷️ ${GAME_VERSION}`,
'padding: 0.6rem 1rem 0.5rem 0.6rem; font-size: 1.1em; font-weight: bold; font-family: Courier New; text-transform: uppercase; color: white; border-radius: 4px; background-image: linear-gradient(-225deg, #AC32E4 0%, #7918F2 48%, #4801FF 100%)'
)
}
}

15
todo/GameManager.ts Normal file
View File

@ -0,0 +1,15 @@
import type { RoomManager } from './RoomManager'
export interface IGameManager {
init: () => void
}
export class GameManager implements IGameManager {
private readonly _soundManager
private readonly _cameraManager
private readonly _roomManager: RoomManager
init(): void {
console.log('feur')
}
}

20
todo/SocketConnection.ts Normal file
View File

@ -0,0 +1,20 @@
import type { Socket } from 'socket.io-client'
import io from 'socket.io-client'
import { GameLogger } from './GameLogger'
import { GameConfig } from './config'
export class SocketConnection {
private static _connection: Socket
private static readonly _logger = new GameLogger('📶 SocketConnection')
public static createConnection(): void {
if (this._connection == null) this._connection = io(GameConfig.getValue('url.socket'))
else return this._logger.error('Cannot establish connection, a connection is already up and going!')
this._connection.on('message', (message) => {
console.log(message)
})
}
}

7
todo/avatar/Avatar.ts Normal file
View File

@ -0,0 +1,7 @@
export class Avatar {
private static readonly DEFAULT_FIGURE = 'hd-99999-99999'
private readonly _placeholder: this
// create a figure
}

View File

@ -0,0 +1,827 @@
export const HabboAvatarAnimations = {
animations: [
{
id: 'Move',
parts: [
{
setType: 'bd',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'bds',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'ss',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'lg',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'sh',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'lh',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'lhs',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'ls',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'lc',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'rh',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'rhs',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'rs',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'rc',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
},
{
setType: 'ch',
frames: [
{
number: 0,
assetPartDefinition: 'wlk'
},
{
number: 1,
assetPartDefinition: 'wlk'
},
{
number: 2,
assetPartDefinition: 'wlk'
},
{
number: 3,
assetPartDefinition: 'wlk'
}
]
}
]
},
{
id: 'Wave',
parts: [
{
setType: 'lh',
frames: [
{
number: 0,
assetPartDefinition: 'wav'
},
{
number: 1,
assetPartDefinition: 'wav'
}
]
},
{
setType: 'lhs',
frames: [
{
number: 0,
assetPartDefinition: 'wav'
},
{
number: 1,
assetPartDefinition: 'wav'
}
]
},
{
setType: 'ls',
frames: [
{
number: 0,
assetPartDefinition: 'wav'
},
{
number: 1,
assetPartDefinition: 'wav'
}
]
},
{
setType: 'lc',
frames: [
{
number: 0,
assetPartDefinition: 'wav'
},
{
number: 1,
assetPartDefinition: 'wav'
}
]
},
{
setType: 'ch',
frames: [
{
number: 0,
assetPartDefinition: 'wav'
},
{
number: 1,
assetPartDefinition: 'wav'
},
{
number: 2,
assetPartDefinition: 'wav'
},
{
number: 3,
assetPartDefinition: 'wav'
}
]
}
]
},
{
id: 'Talk',
parts: [
{
setType: 'hd',
frames: [
{
number: 0,
assetPartDefinition: 'spk'
},
{
number: 1,
assetPartDefinition: 'spk'
}
]
},
{
setType: 'fc',
frames: [
{
number: 0,
assetPartDefinition: 'spk'
},
{
number: 1,
assetPartDefinition: 'spk'
}
]
},
{
setType: 'fa',
frames: [
{
number: 0,
assetPartDefinition: 'spk'
},
{
number: 1,
assetPartDefinition: 'spk'
}
]
}
]
},
{
id: 'Sign',
parts: [
{
setType: 'lh',
frames: [
{
number: 0,
assetPartDefinition: 'sig'
}
]
},
{
setType: 'li',
frames: [
{
number: 0,
assetPartDefinition: 'sig'
}
]
},
{
setType: 'ls',
frames: [
{
number: 0,
assetPartDefinition: 'wav'
}
]
},
{
setType: 'lc',
frames: [
{
number: 0,
assetPartDefinition: 'wav'
}
]
}
]
},
{
id: 'Respect',
parts: [
{
setType: 'lh',
frames: [
{
number: 0,
assetPartDefinition: 'respect',
repeats: 15
},
{
number: 1,
assetPartDefinition: 'respect',
repeats: 15
}
]
},
{
setType: 'ls',
frames: [
{
number: 0,
assetPartDefinition: 'wav',
repeats: 15
},
{
number: 1,
assetPartDefinition: 'wav',
repeats: 15
}
]
},
{
setType: 'lc',
frames: [
{
number: 0,
assetPartDefinition: 'wav',
repeats: 15
},
{
number: 1,
assetPartDefinition: 'wav',
repeats: 15
}
]
}
]
},
{
id: 'Blow',
parts: [
{
setType: 'rh',
frames: [
{
number: 0,
assetPartDefinition: 'blw',
repeats: 10
},
{
number: 1,
assetPartDefinition: 'blw',
repeats: 10
}
]
},
{
setType: 'rs',
frames: [
{
number: 0,
assetPartDefinition: 'drk'
}
]
},
{
setType: 'rc',
frames: [
{
number: 0,
assetPartDefinition: 'drk'
}
]
},
{
setType: 'ri',
frames: [
{
number: 0,
assetPartDefinition: ''
}
]
},
{
setType: 'ey',
frames: [
{
number: 0,
assetPartDefinition: 'std',
repeats: 10
},
{
number: 0,
assetPartDefinition: 'eyb',
repeats: 10
}
]
},
{
setType: 'fc',
frames: [
{
number: 0,
assetPartDefinition: 'std',
repeats: 10
},
{
number: 0,
assetPartDefinition: 'blw',
repeats: 10
}
]
}
]
},
{
id: 'Laugh',
parts: [
{
setType: 'rh',
frames: [
{
number: 0,
assetPartDefinition: 'blw'
}
]
},
{
setType: 'rs',
frames: [
{
number: 0,
assetPartDefinition: 'drk'
}
]
},
{
setType: 'rc',
frames: [
{
number: 0,
assetPartDefinition: 'drk'
}
]
},
{
setType: 'ri',
frames: [
{
number: 0,
assetPartDefinition: ''
}
]
},
{
setType: 'ey',
frames: [
{
number: 0,
assetPartDefinition: 'std',
repeats: 2
}
]
},
{
setType: 'fc',
frames: [
{
number: 0,
assetPartDefinition: 'sml'
}
]
}
],
offsets: {
frames: [
{
id: 0,
directions: [
{
id: 0,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 1
}
]
},
{
id: 1,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 1
}
]
},
{
id: 2,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 1
}
]
},
{
id: 3,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 1
}
]
},
{
id: 4,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 1
}
]
},
{
id: 5,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 1
}
]
},
{
id: 6,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 1
}
]
},
{
id: 7,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 1
}
]
}
]
},
{
id: 1,
directions: [
{
id: 0,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 0
}
]
},
{
id: 1,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 0
}
]
},
{
id: 2,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 0
}
]
},
{
id: 3,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 0
}
]
},
{
id: 4,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 0
}
]
},
{
id: 5,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 0
}
]
},
{
id: 6,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 0
}
]
},
{
id: 7,
bodyParts: [
{
id: 'head',
dx: 0,
dy: 0
}
]
}
]
}
]
}
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,418 @@
export const HabboAvatarPartSets = {
partSets: {
partSet: [
{
setType: 'ri',
flippedSetType: 'ri'
},
{
setType: 'ri',
flippedSetType: 'ri'
},
{
setType: 'rh',
flippedSetType: 'lh'
},
{
setType: 'rhs',
flippedSetType: 'lhs'
},
{
setType: 'rs',
swim: '0',
flippedSetType: 'ls'
},
{
setType: 'rc',
flippedSetType: 'lc'
},
{
setType: 'bd'
},
{
setType: 'bds'
},
{
setType: 'ss'
},
{
setType: 'sh'
},
{
setType: 'lg'
},
{
setType: 'ch'
},
{
setType: 'cp'
},
{
setType: 'cc'
},
{
setType: 'hd'
},
{
setType: 'fc'
},
{
setType: 'ey'
},
{
setType: 'hr'
},
{
setType: 'hrb',
removeSetType: 'hr'
},
{
setType: 'li',
flippedSetType: 'li'
},
{
setType: 'lh',
flippedSetType: 'rh'
},
{
setType: 'lhs',
flippedSetType: 'rhs'
},
{
setType: 'ls',
flippedSetType: 'rs'
},
{
setType: 'lc',
flippedSetType: 'rc'
},
{
setType: 'wa'
},
{
setType: 'ea'
},
{
setType: 'ca'
},
{
setType: 'fa'
},
{
setType: 'ha'
},
{
setType: 'he'
}
],
activePartSets: [
{
id: 'figure',
activeParts: [
{
setType: 'rh'
},
{
setType: 'rh'
},
{
setType: 'rhs'
},
{
setType: 'rs'
},
{
setType: 'rc'
},
{
setType: 'bd'
},
{
setType: 'bds'
},
{
setType: 'ss'
},
{
setType: 'sh'
},
{
setType: 'lg'
},
{
setType: 'ch'
},
{
setType: 'cp'
},
{
setType: 'cc'
},
{
setType: 'wa'
},
{
setType: 'hd'
},
{
setType: 'fc'
},
{
setType: 'ey'
},
{
setType: 'hr'
},
{
setType: 'hrb'
},
{
setType: 'lh'
},
{
setType: 'lhs'
},
{
setType: 'ls'
},
{
setType: 'lc'
},
{
setType: 'ea'
},
{
setType: 'ca'
},
{
setType: 'fa'
},
{
setType: 'ha'
},
{
setType: 'he'
}
]
},
{
id: 'head',
activeParts: [
{
setType: 'hd'
},
{
setType: 'fc'
},
{
setType: 'ey'
},
{
setType: 'hr'
},
{
setType: 'hrb'
},
{
setType: 'ea'
},
{
setType: 'fa'
},
{
setType: 'ha'
},
{
setType: 'he'
}
]
},
{
id: 'speak',
activeParts: [
{
setType: 'hd'
},
{
setType: 'hr'
},
{
setType: 'hrb'
},
{
setType: 'fc'
},
{
setType: 'fa'
},
{
setType: 'ha'
}
]
},
{
id: 'gesture',
activeParts: [
{
setType: 'ey'
},
{
setType: 'fc'
}
]
},
{
id: 'eye',
activeParts: [
{
setType: 'ey'
}
]
},
{
id: 'handRight',
activeParts: [
{
setType: 'rh'
},
{
setType: 'rhs'
},
{
setType: 'rs'
},
{
setType: 'rc'
},
{
setType: 'ri'
}
]
},
{
id: 'handRightAndHead',
activeParts: [
{
setType: 'rh'
},
{
setType: 'rhs'
},
{
setType: 'rs'
},
{
setType: 'rc'
},
{
setType: 'ri'
},
{
setType: 'ey'
},
{
setType: 'fc'
},
{
setType: 'hd'
}
]
},
{
id: 'handLeft',
activeParts: [
{
setType: 'lh'
},
{
setType: 'lhs'
},
{
setType: 'ls'
},
{
setType: 'lc'
},
{
setType: 'li'
}
]
},
{
id: 'walk',
activeParts: [
{
setType: 'bd'
},
{
setType: 'bds'
},
{
setType: 'ss'
},
{
setType: 'lg'
},
{
setType: 'lh'
},
{
setType: 'lhs'
},
{
setType: 'rh'
},
{
setType: 'rhs'
},
{
setType: 'ls'
},
{
setType: 'lc'
},
{
setType: 'rs'
},
{
setType: 'rc'
},
{
setType: 'sh'
}
]
},
{
id: 'sit',
activeParts: [
{
setType: 'bd'
},
{
setType: 'bds'
},
{
setType: 'ss'
},
{
setType: 'lg'
},
{
setType: 'sh'
},
{
setType: 'cc'
}
]
},
{
id: 'itemRight',
activeParts: [
{
setType: 'ri'
}
]
}
]
}
}

31
todo/package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "game",
"version": "0.0.0",
"description": "Habbo Game core engine",
"license": "MIT",
"scripts": {
"build-fast": "tsup src/index.ts --format cjs,esm --dts",
"build": "pnpm run build-fast -- --dts-resolve",
"test": "vitest run",
"prepublishOnly": "pnpm run build"
},
"dependencies": {
"@pixi/layers": "^2.1.0",
"@pixi/utils": "^7.2.4",
"@pixi/core": "7.2.4",
"gsap": "^3.12.2",
"pixi.js": "^7.2.4"
},
"devDependencies": {
"config": "workspace:*",
"@types/node": "20.4.2",
"socket.io-client": "4.7.1",
"pako": "2.1.0",
"zod": "3.21.4",
"chalk": "5.3.0",
"zod-validation-error": "1.3.1",
"tsup": "7.1.0",
"typescript": "5.1.6",
"vitest": "0.33.0"
}
}

58
todo/schemas.ts Normal file
View File

@ -0,0 +1,58 @@
import { z } from 'zod'
const SystemConfig = z.strictObject({
logs: z.strictObject({
debug: z.boolean(),
warn: z.boolean(),
error: z.boolean(),
events: z.boolean(),
packets: z.boolean()
}),
fps: z
.strictObject({
max: z.number(),
animation: z.number().min(24)
})
.refine(
(ctx) => ctx.animation <= ctx.max,
(ctx) => ({ message: `Number must not exceed ${ctx.max} max FPS`, path: ['animation'] })
),
pong: z.strictObject({
manually: z.boolean(),
interval: z
.number()
.min(5 * 1000)
.max(60 * 1000)
})
})
const URLConfig = z.strictObject({
socket: z.string().refine((value) => value.startsWith('ws://') && /\b(?:\d{1,3}\.){3}\d{1,3}:\d{1,5}\b/.test(value), {
message: 'Invalid socket URL format'
}),
client: z
.string()
.refine((value) => /^(ht{2}ps?:\/{2})?((([\da-z-]+\.)+[a-z]{2,})|((?:\d{1,3}\.){3}\d{1,3}))(\/.*)?$/i.test(value), {
message: 'Invalid HTTP URL format'
})
})
const CommonKeys = {
enabled: z.boolean()
}
const GameSchema = z.strictObject({
hc: z.strictObject({ ...CommonKeys }),
camera: z.strictObject({ ...CommonKeys }),
pets: z.array(z.string())
})
const GameConfigSchema = z.strictObject({
system: SystemConfig,
url: URLConfig,
game: GameSchema
})
export { GameConfigSchema }

16
todo/tsconfig.json Normal file
View File

@ -0,0 +1,16 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"lib": ["ESNext", "DOM", "DOM.Iterable"],
"strict": true,
"esModuleInterop": true,
"moduleResolution": "node",
"noUnusedLocals": true,
"noImplicitAny": true,
"allowJs": true,
"noEmit": true,
"outDir": "dist",
"resolveJsonModule": true
}
}