Compare commits

..

No commits in common. "main" and "v0.0.6" have entirely different histories.
main ... v0.0.6

155 changed files with 4899 additions and 3036 deletions

17
.dockerignore Normal file
View File

@ -0,0 +1,17 @@
node_modules
npm-debug.log
Dockerfile
.dockerignore
.git
.gitignore
README.md
.env
.env.*
docker-compose*
*.md
coverage
.vscode
.idea
dist
build
temp

View File

@ -6,10 +6,10 @@ JWT_SECRET="secret"
CLIENT_URL="http://localhost:5173" CLIENT_URL="http://localhost:5173"
# Database configuration # Database configuration
REDIS_URL="redis://@127.0.0.1:6379/4" REDIS_URL="redis://@redis:6379/4"
DB_HOST="localhost" DB_HOST="mariadb"
DB_USER="root" DB_USER="mariadb"
DB_PASS="" DB_PASS="mariadb"
DB_PORT="3306" DB_PORT="3306"
DB_NAME="game" DB_NAME="game"
@ -26,7 +26,3 @@ SMTP_HOST=my.directonline.io
SMTP_PORT=587 SMTP_PORT=587
SMTP_USER=no-reply@noxious.gg SMTP_USER=no-reply@noxious.gg
SMTP_PASSWORD="" SMTP_PASSWORD=""
# SSL
#PUBLIC_KEY_PATH=
#PRIVATE_KEY_PATH=

View File

@ -1 +0,0 @@
migrations

View File

@ -4,8 +4,5 @@
"tabWidth": 2, "tabWidth": 2,
"singleQuote": true, "singleQuote": true,
"printWidth": 200, "printWidth": 200,
"trailingComma": "none", "trailingComma": "none"
"plugins": ["@ianvs/prettier-plugin-sort-imports"],
"importOrderParserPlugins": ["typescript", "jsx", "decorators-legacy", "classProperties"],
"importOrderCaseSensitive": false
} }

16
Dockerfile Normal file
View File

@ -0,0 +1,16 @@
FROM node:lts-alpine
# Install packages
RUN apk update
RUN apk add --no-cache tmux coreutils
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm ci
COPY . .
# Modify CMD to use tmux
CMD npx mikro-orm-esm migration:up && npm run start

View File

@ -5,7 +5,7 @@ This is the server for the Noxious game.
## Projects requirements ## Projects requirements
- NodeJS 20.x or higher - NodeJS 20.x or higher
- MariaDB 11.x or higher - MySQL 8.x or higher
- Redis 7.x or higher - Redis 7.x or higher
## Installation ## Installation
@ -14,7 +14,7 @@ This is the server for the Noxious game.
2. Install dependencies with `npm install` 2. Install dependencies with `npm install`
3. Copy the `.env.example` file to `.env` and fill in the required variables 3. Copy the `.env.example` file to `.env` and fill in the required variables
4. Extract assets.zip to the `public` folder 4. Extract assets.zip to the `public` folder
5. After MySQL and Redis are running, run `npm run mikro-orm migration:up` to create the database schema 5. After MySQL and Redis are running, run `npx mikro-orm-esm migration:up` to create the database schema
6. Run the server with `npm run dev` 6. Run the server with `npm run dev`
7. Write `init` in the console to import default data and restart the server 7. Write `init` in the console to import default data and restart the server
8. Write `tiles` in the console to fix tile sizes 8. Write `tiles` in the console to fix tile sizes
@ -39,15 +39,15 @@ MikroORM is used as the ORM for the server.
### Create init. migrations ### Create init. migrations
Run `npm run mikro-orm migration:create --initial` to create a new initial migration. Run `npx mikro-orm-esm migration:create --initial` to create a new initial migration.
### Create migrations ### Create migrations
Run `npm run mikro-orm migration:create` to create a new migration. You do this when you want to add a new table or change an existing one. Run `npx mikro-orm-esm migration:create` to create a new migration. You do this when you want to add a new table or change an existing one.
### Apply migrations ### Apply migrations
Run `npm run mikro-orm migration:up` to apply all pending migrations. Run `npx mikro-orm-esm migration:up` to apply all pending migrations.
### Import default data ### Import default data

95
docker-compose.yml Normal file
View File

@ -0,0 +1,95 @@
services:
app:
build:
context: .
dockerfile: Dockerfile
ports:
- "${PORT}:${PORT}"
environment:
- ENV=${ENV}
- HOST=${HOST}
- PORT=${PORT}
- JWT_SECRET=${JWT_SECRET}
- CLIENT_URL=${CLIENT_URL}
- REDIS_URL=${REDIS_URL}
- DB_HOST=${DB_HOST}
- DB_USER=${DB_USER}
- DB_PASS=${DB_PASS}
- DB_PORT=${DB_PORT}
- DB_NAME=${DB_NAME}
- ALLOW_DIAGONAL_MOVEMENT=${ALLOW_DIAGONAL_MOVEMENT}
- DEFAULT_CHARACTER_ZONE=${DEFAULT_CHARACTER_ZONE}
- DEFAULT_CHARACTER_POS_X=${DEFAULT_CHARACTER_POS_X}
- DEFAULT_CHARACTER_POS_Y=${DEFAULT_CHARACTER_POS_Y}
- SMTP_HOST=${SMTP_HOST}
- SMTP_PORT=${SMTP_PORT}
- SMTP_USER=${SMTP_USER}
- SMTP_PASSWORD=${SMTP_PASSWORD}
volumes:
- app-public:/user/src/app/public
- app-logs:/user/src/app/logs
depends_on:
- mariadb
- redis
restart: unless-stopped
networks:
- app-network
labels:
- "traefik.enable=true"
- "traefik.http.routers.app.rule=Host(`${HOST}`)"
- "traefik.http.routers.app.entrypoints=websecure"
- "traefik.http.routers.app.tls.certresolver=le"
- "traefik.http.services.app.loadbalancer.server.port=${PORT}"
- "traefik.http.routers.app.middlewares=websocket"
traefik:
image: traefik:v3.3.3
ports:
- "80:80"
- "443:443"
- "8080:8080"
volumes:
- traefik_data:/data
- ./traefik.yml:/etc/traefik/traefik.yml
- /var/run/docker.sock:/var/run/docker.sock:ro
restart: unless-stopped
networks:
- app-network
mariadb:
image: mariadb:lts
environment:
- MARIADB_USER=${DB_USER}
- MARIADB_PASSWORD=${DB_PASS}
- MARIADB_DATABASE=${DB_NAME}
- MARIADB_RANDOM_ROOT_PASSWORD=yes
volumes:
- mariadb-data:/var/lib/mysql
ports:
- "${DB_PORT}:3306"
restart: unless-stopped
networks:
- app-network
command: [ 'mariadbd', '--character-set-server=utf8mb4', '--collation-server=utf8mb4_unicode_ci' ]
redis:
image: redis:7.4.2-alpine
command: redis-server --appendonly yes
volumes:
- redis-data:/data
ports:
- "6379:6379"
restart: unless-stopped
networks:
- app-network
networks:
app-network:
driver: bridge
volumes:
app-public:
app-logs:
mariadb-data:
redis-data:
traefik_data:

42
eslint.config.js Normal file
View File

@ -0,0 +1,42 @@
import eslint from '@eslint/js';
import tseslint from '@typescript-eslint/eslint-plugin';
import tsparser from '@typescript-eslint/parser';
import importPlugin from 'eslint-plugin-import';
export default [
eslint.configs.recommended,
{
files: ['**/*.ts'],
languageOptions: {
parser: tsparser,
parserOptions: {
project: './tsconfig.json',
ecmaVersion: 2023,
},
},
plugins: {
'@typescript-eslint': tseslint,
'import': importPlugin,
},
rules: {
...tseslint.configs['recommended'].rules,
...tseslint.configs['recommended-requiring-type-checking'].rules,
'import/order': ['error', {
'groups': [
'builtin',
'external',
'internal',
['parent', 'sibling'],
'index',
'object',
'type'
],
'newlines-between': 'always',
'alphabetize': {
'order': 'asc',
'caseInsensitive': true
}
}]
}
}
];

3605
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,23 +5,36 @@
"start": "tsx src/server.ts", "start": "tsx src/server.ts",
"dev": "nodemon --exec tsx src/server.ts", "dev": "nodemon --exec tsx src/server.ts",
"format": "prettier --write src/", "format": "prettier --write src/",
"mikro-orm": "tsx ./node_modules/.bin/mikro-orm-esm" "lint": "eslint .",
"lint:fix": "eslint . --fix"
},
"imports": {
"#root/*": "./src/*.js",
"#application/*": "./src/application/*.js",
"#commands/*": "./src/commands/*.js",
"#entities/*": "./src/entities/*.js",
"#controllers/*": "./src/controllers/*.js",
"#jobs/*": "./src/jobs/*.js",
"#managers/*": "./src/managers/*.js",
"#middleware/*": "./src/middleware/*.js",
"#models/*": "./src/models/*.js",
"#repositories/*": "./src/repositories/*.js",
"#services/*": "./src/services/*.js",
"#events/*": "./src/events/*.js"
}, },
"dependencies": { "dependencies": {
"@mikro-orm/cli": "^6.4.2",
"@mikro-orm/core": "^6.4.2", "@mikro-orm/core": "^6.4.2",
"@mikro-orm/mariadb": "^6.4.2", "@mikro-orm/mariadb": "^6.4.2",
"@mikro-orm/migrations": "^6.4.2", "@mikro-orm/migrations": "^6.4.2",
"@mikro-orm/mysql": "^6.4.2", "@mikro-orm/mysql": "^6.4.2",
"@mikro-orm/reflection": "^6.4.2", "@mikro-orm/reflection": "^6.4.2",
"@mikro-orm/cli": "^6.4.2",
"@types/ioredis": "^4.28.10", "@types/ioredis": "^4.28.10",
"bcryptjs": "^2.4.3", "bcryptjs": "^2.4.3",
"bufferutil": "^4.0.9",
"bullmq": "^5.13.2", "bullmq": "^5.13.2",
"cors": "^2.8.5", "cors": "^2.8.5",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
"express": "^4.19.2", "express": "^4.19.2",
"https": "^1.0.0",
"ioredis": "^5.4.1", "ioredis": "^5.4.1",
"jsonwebtoken": "^9.0.2", "jsonwebtoken": "^9.0.2",
"nodemailer": "^6.9.15", "nodemailer": "^6.9.15",
@ -29,18 +42,21 @@
"reflect-metadata": "^0.2.2", "reflect-metadata": "^0.2.2",
"sharp": "^0.33.4", "sharp": "^0.33.4",
"socket.io": "^4.7.5", "socket.io": "^4.7.5",
"ts-node": "^10.9.2",
"typescript": "^5.5.3", "typescript": "^5.5.3",
"utf-8-validate": "^6.0.5",
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"ts-node": "^10.9.2",
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
"@types/bcryptjs": "^2.4.6", "@types/bcryptjs": "^2.4.6",
"@types/express": "^4.17.21", "@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6", "@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.14.11", "@types/node": "^20.14.11",
"@types/nodemailer": "^6.4.16", "@types/nodemailer": "^6.4.16",
"@typescript-eslint/eslint-plugin": "^8.18.2",
"@typescript-eslint/parser": "^8.18.2",
"eslint": "^9.17.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-import": "^2.31.0",
"nodemon": "^3.1.4", "nodemon": "^3.1.4",
"prettier": "^3.3.3", "prettier": "^3.3.3",
"tsx": "^4.19.2" "tsx": "^4.19.2"

Binary file not shown.

View File

@ -1,6 +1,7 @@
import Logger, { LoggerType } from '@/application/logger'
import { Server } from 'socket.io' import { Server } from 'socket.io'
import Logger, { LoggerType } from '#application/logger'
export abstract class BaseCommand { export abstract class BaseCommand {
protected readonly logger = Logger.type(LoggerType.COMMAND) protected readonly logger = Logger.type(LoggerType.COMMAND)
constructor(readonly io: Server) {} constructor(readonly io: Server) {}

View File

@ -1,7 +1,9 @@
import fs from 'fs' import fs from 'fs'
import Logger, { LoggerType } from '@/application/logger'
import type { Response } from 'express' import type { Response } from 'express'
import Logger, { LoggerType } from '#application/logger'
export abstract class BaseController { export abstract class BaseController {
protected readonly logger = Logger.type(LoggerType.HTTP) protected readonly logger = Logger.type(LoggerType.HTTP)

View File

@ -1,7 +1,8 @@
import Database from '@/application/database'
import Logger, { LoggerType } from '@/application/logger'
import { EntityManager } from '@mikro-orm/core' import { EntityManager } from '@mikro-orm/core'
import Database from '#application/database'
import Logger, { LoggerType } from '#application/logger'
export abstract class BaseEntity { export abstract class BaseEntity {
protected readonly logger = Logger.type(LoggerType.ENTITY) protected readonly logger = Logger.type(LoggerType.ENTITY)
protected entityManager?: EntityManager protected entityManager?: EntityManager

View File

@ -1,43 +1,22 @@
import { SocketEvent } from '@/application/enums'
import Logger, { LoggerType } from '@/application/logger'
import type { TSocket } from '@/application/types'
import { Character } from '@/entities/character'
import MapManager from '@/managers/mapManager'
import type MapCharacter from '@/models/mapCharacter'
import CharacterRepository from '@/repositories/characterRepository'
import { Server } from 'socket.io' import { Server } from 'socket.io'
import type { TSocket } from '#application/types'
import Logger, { LoggerType } from '#application/logger'
import { Character } from '#entities/character'
import CharacterRepository from '#repositories/characterRepository'
export abstract class BaseEvent { export abstract class BaseEvent {
protected readonly logger = Logger.type(LoggerType.GAME) protected readonly logger = Logger.type(LoggerType.GAME)
private lastActionTimes: Map<string, number> = new Map()
constructor( constructor(
readonly io: Server, readonly io: Server,
readonly socket: TSocket readonly socket: TSocket
) {} ) {}
protected isThrottled(actionId: string, throttleTime: number): boolean {
const now = Date.now()
const lastActionTime = this.lastActionTimes.get(actionId) || 0
if (now - lastActionTime < throttleTime) {
return true
}
this.lastActionTimes.set(actionId, now)
return false
}
protected getMapCharacter(): MapCharacter | null {
if (!this.socket.characterId) return null
return MapManager.getCharacterById(this.socket.characterId)
}
protected async getCharacter(): Promise<Character | null> { protected async getCharacter(): Promise<Character | null> {
if (!this.socket.characterId) return null
const characterRepository = new CharacterRepository() const characterRepository = new CharacterRepository()
return characterRepository.getById(this.socket.characterId) return characterRepository.getById(this.socket.characterId!)
} }
protected async isCharacterGM(): Promise<boolean> { protected async isCharacterGM(): Promise<boolean> {
@ -45,16 +24,15 @@ export abstract class BaseEvent {
return character?.getRole() === 'gm' return character?.getRole() === 'gm'
} }
protected sendNotificationAndLog(message: string): void { protected emitError(message: string): void {
console.log(message) this.socket.emit('notification', { title: 'Server message', message })
this.socket.emit(SocketEvent.NOTIFICATION, { title: 'Server message', message }) this.logger.error('Base event error', `Player ${this.socket.userId}: ${message}`)
this.logger.error('Base event error' + `Player ${this.socket.userId}: ${message}`)
} }
protected handleError(context: string, error: unknown): void { protected handleError(context: string, error: unknown): void {
console.log(error) console.log(error)
const errorMessage = error instanceof Error ? error.message : error && typeof error === 'object' && 'toString' in error ? error.toString() : String(error) const errorMessage = error instanceof Error ? error.message : error && typeof error === 'object' && 'toString' in error ? error.toString() : String(error)
this.socket.emit(SocketEvent.NOTIFICATION, { title: 'Server message', message: `Server error occured. Please contact the server administrator.` }) this.socket.emit('notification', { title: 'Server message', message: `Server error occured. Please contact the server administrator.` })
this.logger.error('Base event error: ' + errorMessage) this.logger.error('Base event error', errorMessage)
} }
} }

View File

@ -1,7 +1,8 @@
import Database from '@/application/database'
import Logger, { LoggerType } from '@/application/logger'
import { EntityManager } from '@mikro-orm/core' import { EntityManager } from '@mikro-orm/core'
import Database from '#application/database'
import Logger, { LoggerType } from '#application/logger'
export abstract class BaseRepository { export abstract class BaseRepository {
protected readonly logger = Logger.type(LoggerType.REPOSITORY) protected readonly logger = Logger.type(LoggerType.REPOSITORY)
private entityManager?: EntityManager private entityManager?: EntityManager

View File

@ -1,4 +1,4 @@
import Logger, { LoggerType } from '@/application/logger' import Logger, { LoggerType } from '#application/logger'
export abstract class BaseService { export abstract class BaseService {
protected readonly logger = Logger.type(LoggerType.GAME) protected readonly logger = Logger.type(LoggerType.GAME)

View File

@ -6,7 +6,7 @@ class config {
// Server configuration // Server configuration
static ENV: string = process.env.ENV || 'development' static ENV: string = process.env.ENV || 'development'
static HOST: string = process.env.HOST || '0.0.0.0' static HOST: string = process.env.HOST || '0.0.0.0'
static PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 4000 static PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 6969
static JWT_SECRET: string = process.env.JWT_SECRET || 'secret' static JWT_SECRET: string = process.env.JWT_SECRET || 'secret'
static CLIENT_URL: string = process.env.CLIENT_URL ? process.env.CLIENT_URL : 'https://noxious.gg' static CLIENT_URL: string = process.env.CLIENT_URL ? process.env.CLIENT_URL : 'https://noxious.gg'
@ -31,10 +31,6 @@ class config {
static SMTP_PORT: number = process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT) : 587 static SMTP_PORT: number = process.env.SMTP_PORT ? parseInt(process.env.SMTP_PORT) : 587
static SMTP_USER: string = process.env.SMTP_USER || 'no-reply@noxious.gg' static SMTP_USER: string = process.env.SMTP_USER || 'no-reply@noxious.gg'
static SMTP_PASSWORD: string = process.env.SMTP_PASSWORD || 'password' static SMTP_PASSWORD: string = process.env.SMTP_PASSWORD || 'password'
// SSL
static PUBLIC_KEY_PATH: string = process.env.PUBLIC_KEY_PATH || ''
static PRIVATE_KEY_PATH: string = process.env.PRIVATE_KEY_PATH || ''
} }
export default config export default config

View File

@ -1,9 +1,11 @@
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import { pathToFileURL } from 'url' import { pathToFileURL } from 'url'
import Logger, { LoggerType } from '@/application/logger'
import Storage from '@/application/storage' import type { Command } from '#application/types'
import type { Command } from '@/application/types'
import Logger, { LoggerType } from '#application/logger'
import Storage from '#application/storage'
export class CommandRegistry { export class CommandRegistry {
private readonly commands: Map<string, Command> = new Map() private readonly commands: Map<string, Command> = new Map()

View File

@ -1,6 +1,7 @@
import * as fs from 'fs' import * as fs from 'fs'
import * as path from 'path' import * as path from 'path'
import Logger, { LoggerType } from '@/application/logger'
import Logger, { LoggerType } from '#application/logger'
export class LogReader { export class LogReader {
private logger = Logger.type(LoggerType.CONSOLE) private logger = Logger.type(LoggerType.CONSOLE)

View File

@ -1,8 +1,7 @@
// import { MikroORM } from '@mikro-orm/mysql' import { MikroORM } from '@mikro-orm/mysql'
import Logger, { LoggerType } from '@/application/logger' import Logger, { LoggerType } from '#application/logger'
import config from '@/root/mikro-orm.config' import config from '#root/mikro-orm.config'
import { MikroORM } from '@mikro-orm/mariadb'
class Database { class Database {
private static orm: MikroORM private static orm: MikroORM

View File

@ -1,59 +1,17 @@
export enum SocketEvent { export enum SocketEvent {
CONNECT_ERROR = 'connect_error', CHARACTER_CONNECT = 1,
RECONNECT_FAILED = 'reconnect_failed', CHARACTER_MOVE = 2,
CLOSE = '52', CHARACTER_MOVE_ERROR = 3,
DATA = '51', CHARACTER_TELEPORT = 4,
CHARACTER_CONNECT = '50', ZONE_CHARACTER_LEAVE = 5,
CHARACTER_CREATE = '49', ZONE_CHARACTER_JOIN = 6,
CHARACTER_DELETE = '48', ZONE_CHARACTER_LIST = 7,
CHARACTER_LIST = '47', ZONE_CHARACTER_DELETE = 8,
GM_CHARACTERHAIR_CREATE = '46', ZONE_CHARACTER_CREATE = 9,
GM_CHARACTERHAIR_REMOVE = '45', ZONE_CHARACTER_UPDATE = 10,
GM_CHARACTERHAIR_LIST = '44', ZONE_CHARACTER_HAIR_UPDATE = 11,
GM_CHARACTERHAIR_UPDATE = '43', ZONE_CHARACTER_HAIR_LIST = 12,
GM_CHARACTERTYPE_CREATE = '42', ZONE_CHARACTER_TELEPORT = 13
GM_CHARACTERTYPE_REMOVE = '41',
GM_CHARACTERTYPE_LIST = '40',
GM_CHARACTERTYPE_UPDATE = '39',
GM_ITEM_CREATE = '38',
GM_ITEM_REMOVE = '37',
GM_ITEM_LIST = '36',
GM_ITEM_UPDATE = '35',
GM_MAPOBJECT_LIST = '34',
GM_MAPOBJECT_REMOVE = '33',
GM_MAPOBJECT_UPDATE = '32',
GM_MAPOBJECT_UPLOAD = '31',
GM_SPRITE_COPY = '30',
GM_SPRITE_CREATE = '29',
GM_SPRITE_DELETE = '28',
GM_SPRITE_LIST = '27',
GM_SPRITE_UPDATE = '26',
GM_TILE_DELETE = '25',
GM_TILE_LIST = '24',
GM_TILE_UPDATE = '23',
GM_TILE_UPLOAD = '22',
GM_MAP_CREATE = '21',
GM_MAP_DELETE = '20',
GM_MAP_REQUEST = '19',
GM_MAP_UPDATE = '18',
MAP_CHARACTER_MOVEERROR = '17',
DISCONNECT = 'disconnect',
USER_DISCONNECT = '15',
LOGIN = '14',
LOGGED_IN = '13',
NOTIFICATION = '12',
DATE = '11',
FAILED = '10',
COMPLETED = '9',
CONNECTION = 'connection',
WEATHER = '7',
CHARACTER_DISCONNECT = '6',
MAP_CHARACTER_ATTACK = '5',
MAP_CHARACTER_TELEPORT = '4',
MAP_CHARACTER_JOIN = '3',
MAP_CHARACTER_LEAVE = '2',
MAP_CHARACTER_MOVE = '1',
CHAT_MESSAGE = '0'
} }
export enum ItemType { export enum ItemType {

View File

@ -1,5 +1,4 @@
import pino from 'pino' import pino from 'pino'
const logger = pino.pino const logger = pino.pino
export enum LoggerType { export enum LoggerType {

View File

@ -1,6 +1,7 @@
import fs from 'fs' import fs from 'fs'
import path from 'path' import path from 'path'
import config from '@/application/config'
import config from '#application/config'
class Storage { class Storage {
private readonly baseDir: string private readonly baseDir: string
@ -8,7 +9,7 @@ class Storage {
constructor() { constructor() {
this.rootDir = process.cwd() this.rootDir = process.cwd()
this.baseDir = config.ENV === 'development' ? 'src' : 'src' this.baseDir = config.ENV === 'development' ? 'src' : 'dist'
} }
/** /**

View File

@ -1,8 +1,9 @@
import { Character } from '@/entities/character'
import { MapEventTile } from '@/entities/mapEventTile'
import { MapEventTileTeleport } from '@/entities/mapEventTileTeleport'
import { Server, Socket } from 'socket.io' import { Server, Socket } from 'socket.io'
import { Character } from '#entities/character'
import { MapEventTile } from '#entities/mapEventTile'
import { MapEventTileTeleport } from '#entities/mapEventTileTeleport'
export type UUID = `${string}-${string}-${string}-${string}-${string}` export type UUID = `${string}-${string}-${string}-${string}-${string}`
export type TSocket = Socket & { export type TSocket = Socket & {

View File

@ -58,16 +58,3 @@ export const ZCharacterCreate = z.object({
.max(255, { message: 'Name must be at most 255 characters long' }) .max(255, { message: 'Name must be at most 255 characters long' })
.regex(/^[A-Za-z][A-Za-z0-9_-]*$/, { message: 'Name must start with a letter and can only contain letters, numbers, underscores, or dashes' }) .regex(/^[A-Za-z][A-Za-z0-9_-]*$/, { message: 'Name must start with a letter and can only contain letters, numbers, underscores, or dashes' })
}) })
export const ZCharacterConnect = z.object({
characterId: z.string(),
characterHairId: z.string().optional().nullable(),
newNickname: z
.string()
.min(3, { message: 'Name must be at least 3 characters long' })
.max(255, { message: 'Name must be at most 255 characters long' })
.regex(/^[A-Za-z][A-Za-z0-9_-]*$/, {
message: 'Name must start with a letter and can only contain letters, numbers, underscores, or dashes'
})
.optional()
})

View File

@ -1,13 +1,13 @@
import { BaseCommand } from '@/application/base/baseCommand'
import { SocketEvent } from '@/application/enums'
import { Server } from 'socket.io' import { Server } from 'socket.io'
import { BaseCommand } from '#application/base/baseCommand'
type CommandInput = string[] type CommandInput = string[]
export default class AlertCommand extends BaseCommand { export default class AlertCommand extends BaseCommand {
public execute(input: CommandInput): void { public execute(input: CommandInput): void {
const message: string = input.join(' ') ?? null const message: string = input.join(' ') ?? null
if (!message) return console.log('message is required') if (!message) return console.log('message is required')
this.io.emit(SocketEvent.NOTIFICATION, { message: message }) this.io.emit('notification', { message: message })
} }
} }

View File

@ -1,23 +1,26 @@
import fs from 'fs' import fs from 'fs'
import { BaseCommand } from '@/application/base/baseCommand'
import { CharacterGender, CharacterRace } from '@/application/enums'
import Storage from '@/application/storage'
import type { UUID } from '@/application/types'
import { Character } from '@/entities/character'
import { CharacterHair } from '@/entities/characterHair'
import { CharacterType } from '@/entities/characterType'
import { Map } from '@/entities/map'
import { MapEffect } from '@/entities/mapEffect'
import { MapObject } from '@/entities/mapObject'
import { Sprite } from '@/entities/sprite'
import { SpriteAction } from '@/entities/spriteAction'
import { Tile } from '@/entities/tile'
import { User } from '@/entities/user'
import CharacterHairRepository from '@/repositories/characterHairRepository'
import CharacterTypeRepository from '@/repositories/characterTypeRepository'
import MapRepository from '@/repositories/mapRepository'
import sharp from 'sharp' import sharp from 'sharp'
import type { UUID } from '#application/types'
import { BaseCommand } from '#application/base/baseCommand'
import { CharacterGender, CharacterRace } from '#application/enums'
import Storage from '#application/storage'
import { Character } from '#entities/character'
import { CharacterHair } from '#entities/characterHair'
import { CharacterType } from '#entities/characterType'
import { Map } from '#entities/map'
import { MapEffect } from '#entities/mapEffect'
import { MapObject } from '#entities/mapObject'
import { Sprite } from '#entities/sprite'
import { SpriteAction } from '#entities/spriteAction'
import { Tile } from '#entities/tile'
import { User } from '#entities/user'
import CharacterHairRepository from '#repositories/characterHairRepository'
import CharacterTypeRepository from '#repositories/characterTypeRepository'
import MapRepository from '#repositories/mapRepository'
// @TODO : Replace this with seeding // @TODO : Replace this with seeding
// https://mikro-orm.io/docs/seeding // https://mikro-orm.io/docs/seeding
@ -30,8 +33,7 @@ export default class InitCommand extends BaseCommand {
// Assets // Assets
await this.importTiles() await this.importTiles()
await this.importMapObjects() await this.importMapObjects()
await this.createMaleCharacterType() await this.createCharacterType()
// await this.createFemaleCharacterType()
await this.createCharacterHair() await this.createCharacterHair()
// await this.createCharacterEquipment() // await this.createCharacterEquipment()
@ -75,9 +77,9 @@ export default class InitCommand extends BaseCommand {
} }
} }
private async createMaleCharacterType(): Promise<void> { private async createCharacterType(): Promise<void> {
const characterSprite = new Sprite() const characterSprite = new Sprite()
characterSprite.setId('023d1e9d-f57f-4faa-8412-86c07107cf85').setName('Male character') characterSprite.setId('023d1e9d-f57f-4faa-8412-86c07107cf85').setName('Character')
await characterSprite.save() await characterSprite.save()
const idleRightDownAction = new SpriteAction() const idleRightDownAction = new SpriteAction()
@ -273,213 +275,7 @@ export default class InitCommand extends BaseCommand {
const characterType = new CharacterType() const characterType = new CharacterType()
await characterType await characterType
.setId('75b70c78-17f0-44c0-a4fa-15043cb95be0') .setId('75b70c78-17f0-44c0-a4fa-15043cb95be0')
.setName('Male character') .setName('New character type')
.setGender(CharacterGender.MALE)
.setRace(CharacterRace.HUMAN)
.setIsSelectable(true)
.setSprite(characterSprite)
.save()
}
private async createFemaleCharacterType(): Promise<void> {
const characterSprite = new Sprite()
characterSprite.setId('023d1e9d-f57f-4faa-8412-86c07107cf85').setName('Male character')
await characterSprite.save()
const idleRightDownAction = new SpriteAction()
await idleRightDownAction
.setAction('idle_right_down')
.setSprites([
{
url: 'data:image/png;base64,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',
offset: {
x: 0,
y: 0
}
}
])
.setOriginX(0)
.setOriginY(0)
.setFrameWidth(28)
.setFrameHeight(94)
.setFrameRate(0)
.setSprite(characterSprite)
.save()
const idleLeftUpAction = new SpriteAction()
await idleLeftUpAction
.setAction('idle_left_up')
.setSprites([
{
url: 'data:image/png;base64,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',
offset: {
x: 0,
y: 0
}
}
])
.setOriginX(0)
.setOriginY(0)
.setFrameWidth(26)
.setFrameHeight(93)
.setFrameRate(0)
.setSprite(characterSprite)
.save()
const walkRightDownAction = new SpriteAction()
await walkRightDownAction
.setAction('walk_right_down')
.setSprites([
{
url: 'data:image/png;base64,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',
offset: {
x: 7,
y: 8
}
},
{
url: 'data:image/png;base64,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',
offset: {
x: 7,
y: 2
}
},
{
url: 'data:image/png;base64,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',
offset: {
x: 2,
y: 2
}
},
{
url: 'data:image/png;base64,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',
offset: {
x: 0,
y: 0
}
}
])
.setOriginX(0)
.setOriginY(0)
.setFrameWidth(36)
.setFrameHeight(102)
.setFrameRate(7)
.setSprite(characterSprite)
.save()
const walkLeftUpAction = new SpriteAction()
await walkLeftUpAction
.setAction('walk_left_up')
.setSprites([
{
url: 'data:image/png;base64,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',
offset: {
x: 3,
y: 2
}
},
{
url: 'data:image/png;base64,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',
offset: {
x: 0,
y: 2
}
},
{
url: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABcAAABZCAYAAADVeL+8AAAFcElEQVR4nLVZzUtbWRT/JUqURAkkIWlBeIUmuDHQrqQI3Ygu/Qtk6LLMUldDqYtAF10ozKrMLjDC7LNsEMSdm7GQboIRfBDQ99BHxQ/aQM0sknM9971z33upmQOB5H78zu/ee+75uEks//EXoiTXqPWldm9tKxE2bzIO6MbqIqzqvNZ3tHeA+rDfpMQInmvU+huri6gsr2Lm+ROt7+bkHC+XXyNXLMFzHdQbtb6kQATPNWr9Pzd/w3RlAelCBhP5itY/A+AGgFUFLAxWJClImhinipYIrCZmZ5EqWoM5xZI8RmqsLK8imZ0VJ0hiVefx5sWzwMFr4MQ6TH5eHuPu4nYwOYJAYM+t6ryadHdxizSOtX4CjiOhphgFdn91DQCwW22xX9xzmhRXPNfBq7e/R4ObWJgIHO0diMAiuH9yWN/hP38r4O/HXwPjAnvuuU4sBYBu39KKReY91w4FpTHkb74ff9VIkWjMvbWtRL1R6+eKJVhVfWCqaAWU2q02LANrI3PPdSIPlvrtVltkLYJ7a1uJ+pfTgAJ+G6ndcx0jsJn5UAEHkoC51L+cBvy60RS9ta3EzudDIysOKgGHgo8ipkgUG3xUlwAACX+A5j6Z3K8/fpIc7R0AkPc7AJ5r1PpvXjxTN8+qzgfsm6IPtZEpkgFwJZMSMDFNFa1BOMNAAf1OFzK4Gc6jOCoFa+2GSsDpQgZ3AOAOxqQLGQADu7+/ulYrkYJ1krPmwCQT+YoC5G1cZp4/Qe7VoiJIoqyFGqVrz8Ek4Il8BRP5CqYrC1qwVuDcIvgB/rzUY6i/7ebkXP1OFzLaqjU7t1tt7Hw+DLDnYH5lPdfGzcm5WlUyO6t2QYFzjaMIEZFWOJbr7xfa4kjwuHnK3cVtgH0s5mEKRvbnJCZnZVLmb49kblLA200BfeQDlVhHBuieaxv3z88+rm8fmymSQu6HjOCmfZRYe66DnmurSoQu5KOYR2VmY9kWikIjgcfJGcPEmM5xBaSEK+u5dmTKZ2RumsiVRUkA/LI0j+2zqchkNCwBJRELrpWVFWw3m8DZN3xAUAGBbp9NAQDyQ2UUR2llxmpufX0djuPgXbMp9E4pEgDw7+4DAe52RfBSqYRyuYxyuYylpSUAQKfTEUk4jnlrRPByuSwO9rd3Oh2USnLdD7AD9R/e3Nyc+pgUmkgQlmiKBOj/7VcYJQo8yqyiCJBwf58EHsoUk48g6Xa7xj7Pdcxhjqe++/v7AVCylm63qz4mCyIJWMvm0x/Y3t0F8HBgBCKB+U2RF7xifv6hCLwbKiChC0OmR6DNZhN5BmxkzvPz3apeNYgXdbjSOiNvt9qqjNHA+dvW/dX1oERvAcApNp/+UAQAPSvmNSu3Os3OqZKg78CDiXJL4kUCT2BplWQcGjgFWH8lAehlOwEns7NIZmfx6eN7VezyV7rINy7OxP+qMZ1dUKv89PE9i06nD9tClsJLElPiQ2W73Wor6+BjxbKFREriRSXD7SFgKiUBBCsLXoXRNTaFOdp/u9VWMZUXw6RYYx6XtZ89MfcTCmwLdz6jekoSMgINnIDjZrH+1+fQS3R/da0dUFwxVYKhT3+mpw5ezoeVmGIF/RgxXn8uUXmgsuWQN/SxpNB+JxcA/5Xy3D8nVyxpjivgcklGtXGJ3CSdPAGTZzNZCpeeayMF/c1LA6cv6UJmpP8jPNcZRqm23uYH99+0qC0hv05+W+pX4L9i41FbBvxP7y2h4Ka/w8YCPi4RwcflZ4zMpT/yHgWeLmQwXVlAqmjh5fJrbKwuPkqBAh/lAsUVdUPvr64HD8LjBrdbbVhVaH4iToUcCe6tbSV2GrX+BgDyE6bH9lFFvfz7D+4xoCT/AaNCPNJBtKgMAAAAAElFTkSuQmCC',
offset: {
x: 5,
y: 6
}
},
{
url: 'data:image/png;base64,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',
offset: {
x: 2,
y: 6
}
}
])
.setOriginX(0)
.setOriginY(0)
.setFrameWidth(34)
.setFrameHeight(101)
.setFrameRate(7)
.setSprite(characterSprite)
.save()
const attackRightDownAction = new SpriteAction()
await attackRightDownAction
.setAction('attack_right_down')
.setSprites([
{
url: 'data:image/png;base64,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',
offset: {
x: 20,
y: 0
}
},
{
url: 'data:image/png;base64,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',
offset: {
x: 19,
y: 8
}
},
{
url: 'data:image/png;base64,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',
offset: {
x: 17,
y: 3
}
}
])
.setOriginX(0)
.setOriginY(0)
.setFrameWidth(69)
.setFrameHeight(111)
.setFrameRate(5)
.setSprite(characterSprite)
.save()
const attackLeftUpAction = new SpriteAction()
await attackLeftUpAction
.setAction('attack_left_up')
.setSprites([
{
url: 'data:image/png;base64,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',
offset: {
x: 2,
y: 0
}
},
{
url: 'data:image/png;base64,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',
offset: {
x: 5,
y: 0
}
},
{
url: 'data:image/png;base64,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',
offset: {
x: 6,
y: 1
}
}
])
.setOriginX(0)
.setOriginY(0)
.setFrameWidth(34)
.setFrameHeight(100)
.setFrameRate(5)
.setSprite(characterSprite)
.save()
const characterType = new CharacterType()
await characterType
.setId('75b70c78-17f0-44c0-a4fa-15043cb95be0')
.setName('Male character')
.setGender(CharacterGender.MALE) .setGender(CharacterGender.MALE)
.setRace(CharacterRace.HUMAN) .setRace(CharacterRace.HUMAN)
.setIsSelectable(true) .setIsSelectable(true)
@ -489,7 +285,7 @@ export default class InitCommand extends BaseCommand {
private async createCharacterHair(): Promise<void> { private async createCharacterHair(): Promise<void> {
const hairSprite = new Sprite() const hairSprite = new Sprite()
hairSprite.setId('922ee95f-1500-49c0-8ead-f8cc46dad136').setName('Hair 1').setWidth(30).setHeight(40) hairSprite.setId('922ee95f-1500-49c0-8ead-f8cc46dad136').setName('Hair 1')
await hairSprite.save() await hairSprite.save()
const frontAction = new SpriteAction() const frontAction = new SpriteAction()
@ -533,7 +329,7 @@ export default class InitCommand extends BaseCommand {
.save() .save()
const characterHair = new CharacterHair() const characterHair = new CharacterHair()
await characterHair.setId('a2471230-d238-4ffb-9eca-9eab869f1b67').setName('Hair 1').setGender(CharacterGender.MALE).setColor('#1B1212').setIsSelectable(true).setSprite(hairSprite).save() await characterHair.setId('a2471230-d238-4ffb-9eca-9eab869f1b67').setName('Hair 1').setGender(CharacterGender.MALE).setIsSelectable(true).setSprite(hairSprite).save()
} }
private async createMap(): Promise<void> { private async createMap(): Promise<void> {
@ -552,8 +348,6 @@ export default class InitCommand extends BaseCommand {
private async createUser(): Promise<void> { private async createUser(): Promise<void> {
const user = new User() const user = new User()
await user.setId('6f9a58b4-172d-425e-b9ea-71e1d13d81ee').setUsername('root').setEmail('local@host').setPassword('password').setOnline(false).save() await user.setId('6f9a58b4-172d-425e-b9ea-71e1d13d81ee').setUsername('root').setEmail('local@host').setPassword('password').setOnline(false).save()
const map = await this.mapRepository.getFirst()
if (!map) return
const character = new Character() const character = new Character()
await character await character
@ -561,7 +355,7 @@ export default class InitCommand extends BaseCommand {
.setUser(user) .setUser(user)
.setName('root') .setName('root')
.setRole('gm') .setRole('gm')
.setMap(map) .setMap((await this.mapRepository.getFirst())!)
.setCharacterType(await this.characterTypeRepository.getFirst()) .setCharacterType(await this.characterTypeRepository.getFirst())
.setCharacterHair(await this.characterHairRepository.getFirst()) .setCharacterHair(await this.characterHairRepository.getFirst())
.save() .save()

View File

@ -1,5 +1,5 @@
import { BaseCommand } from '@/application/base/baseCommand' import { BaseCommand } from '#application/base/baseCommand'
import MapManager from '@/managers/mapManager' import MapManager from '#managers/mapManager'
type CommandInput = string[] type CommandInput = string[]

View File

@ -1,8 +1,10 @@
import fs from 'fs' import fs from 'fs'
import { BaseCommand } from '@/application/base/baseCommand'
import Storage from '@/application/storage'
import sharp from 'sharp' import sharp from 'sharp'
import { BaseCommand } from '#application/base/baseCommand'
import Storage from '#application/storage'
export default class TilesCommand extends BaseCommand { export default class TilesCommand extends BaseCommand {
public async execute(): Promise<void> { public async execute(): Promise<void> {
// Get all tiles // Get all tiles

View File

@ -1,10 +1,12 @@
import { BaseController } from '@/application/base/baseController'
import config from '@/application/config'
import { loginAccountSchema, newPasswordSchema, registerAccountSchema, resetPasswordSchema } from '@/application/zodTypes'
import UserService from '@/services/userService'
import type { Request, Response } from 'express'
import jwt from 'jsonwebtoken' import jwt from 'jsonwebtoken'
import type { Request, Response } from 'express'
import { BaseController } from '#application/base/baseController'
import config from '#application/config'
import { loginAccountSchema, registerAccountSchema, resetPasswordSchema, newPasswordSchema } from '#application/zodTypes'
import UserService from '#services/userService'
export class AuthController extends BaseController { export class AuthController extends BaseController {
/** /**
* Login user * Login user

View File

@ -1,13 +1,16 @@
import fs from 'fs' import fs from 'fs'
import { BaseController } from '@/application/base/baseController'
import Storage from '@/application/storage'
import type { UUID } from '@/application/types'
import CharacterHairRepository from '@/repositories/characterHairRepository'
import CharacterRepository from '@/repositories/characterRepository'
import CharacterTypeRepository from '@/repositories/characterTypeRepository'
import type { Request, Response } from 'express'
import sharp from 'sharp' import sharp from 'sharp'
import type { UUID } from '#application/types'
import type { Request, Response } from 'express'
import { BaseController } from '#application/base/baseController'
import Storage from '#application/storage'
import CharacterHairRepository from '#repositories/characterHairRepository'
import CharacterRepository from '#repositories/characterRepository'
import CharacterTypeRepository from '#repositories/characterTypeRepository'
interface AvatarOptions { interface AvatarOptions {
characterTypeId: UUID characterTypeId: UUID
characterHairId?: UUID characterHairId?: UUID
@ -65,12 +68,9 @@ export class AvatarController extends BaseController {
return this.sendError(res, 'Body sprite file not found', 404) 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({ let avatar = sharp(bodySpritePath).extend({
top: 0, top: 2,
bottom: 0, bottom: 2,
background: { r: 0, g: 0, b: 0, alpha: 0 } background: { r: 0, g: 0, b: 0, alpha: 0 }
}) })
@ -79,21 +79,7 @@ export class AvatarController extends BaseController {
if (characterHair?.sprite?.id) { if (characterHair?.sprite?.id) {
const hairSpritePath = Storage.getPublicPath('sprites', characterHair.sprite.id, 'front.png') const hairSpritePath = Storage.getPublicPath('sprites', characterHair.sprite.id, 'front.png')
if (fs.existsSync(hairSpritePath)) { if (fs.existsSync(hairSpritePath)) {
// Resize hair sprite to match body dimensions avatar = avatar.composite([{ input: hairSpritePath, gravity: 'north' }])
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
}
])
} }
} }
} }
@ -101,7 +87,6 @@ export class AvatarController extends BaseController {
res.setHeader('Content-Type', 'image/png') res.setHeader('Content-Type', 'image/png')
return avatar.pipe(res) return avatar.pipe(res)
} catch (error) { } catch (error) {
console.error('Avatar generation error:', error)
return this.sendError(res, 'Error generating avatar', 500) return this.sendError(res, 'Error generating avatar', 500)
} }
} }

View File

@ -1,12 +1,13 @@
import { BaseController } from '@/application/base/baseController'
import CharacterHairRepository from '@/repositories/characterHairRepository'
import CharacterTypeRepository from '@/repositories/characterTypeRepository'
import MapObjectRepository from '@/repositories/mapObjectRepository'
import MapRepository from '@/repositories/mapRepository'
import SpriteRepository from '@/repositories/spriteRepository'
import TileRepository from '@/repositories/tileRepository'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { BaseController } from '#application/base/baseController'
import CharacterHairRepository from '#repositories/characterHairRepository'
import CharacterTypeRepository from '#repositories/characterTypeRepository'
import MapObjectRepository from '#repositories/mapObjectRepository'
import MapRepository from '#repositories/mapRepository'
import SpriteRepository from '#repositories/spriteRepository'
import TileRepository from '#repositories/tileRepository'
export class CacheController extends BaseController { export class CacheController extends BaseController {
/** /**
* Serve a list of tiles and send as JSON * Serve a list of tiles and send as JSON

View File

@ -1,7 +1,8 @@
import { BaseController } from '@/application/base/baseController'
import Storage from '@/application/storage'
import type { Request, Response } from 'express' import type { Request, Response } from 'express'
import { BaseController } from '#application/base/baseController'
import Storage from '#application/storage'
export class TexturesController extends BaseController { export class TexturesController extends BaseController {
/** /**
* Download texture * Download texture

View File

@ -1,15 +1,18 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import type { CharacterEquipment } from '@/entities/characterEquipment'
import type { CharacterHair } from '@/entities/characterHair'
import type { CharacterItem } from '@/entities/characterItem'
import type { CharacterType } from '@/entities/characterType'
import type { Chat } from '@/entities/chat'
import type { Map } from '@/entities/map'
import type { User } from '@/entities/user'
import { Collection, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core' import { Collection, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { CharacterEquipment } from '#entities/characterEquipment'
import type { CharacterHair } from '#entities/characterHair'
import type { CharacterItem } from '#entities/characterItem'
import type { CharacterType } from '#entities/characterType'
import type { Chat } from '#entities/chat'
import type { Map } from '#entities/map'
import type { User } from '#entities/user'
import { BaseEntity } from '#application/base/baseEntity'
export class BaseCharacter extends BaseEntity { export class BaseCharacter extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()

View File

@ -1,11 +1,14 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import { CharacterEquipmentSlotType } from '@/application/enums'
import type { UUID } from '@/application/types'
import type { Character } from '@/entities/character'
import type { CharacterItem } from '@/entities/characterItem'
import { Enum, ManyToOne, PrimaryKey } from '@mikro-orm/core' import { Enum, ManyToOne, PrimaryKey } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { Character } from '#entities/character'
import type { CharacterItem } from '#entities/characterItem'
import { BaseEntity } from '#application/base/baseEntity'
import { CharacterEquipmentSlotType } from '#application/enums'
export class BaseCharacterEquipment extends BaseEntity { export class BaseCharacterEquipment extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()

View File

@ -1,11 +1,14 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import { CharacterGender } from '@/application/enums'
import type { UUID } from '@/application/types'
import { Character } from '@/entities/character'
import { Sprite } from '@/entities/sprite'
import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core' import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import { BaseEntity } from '#application/base/baseEntity'
import { CharacterGender } from '#application/enums'
import { Character } from '#entities/character'
import { Sprite } from '#entities/sprite'
export class BaseCharacterHair extends BaseEntity { export class BaseCharacterHair extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()
@ -16,14 +19,11 @@ export class BaseCharacterHair extends BaseEntity {
@Property() @Property()
gender: CharacterGender = CharacterGender.MALE gender: CharacterGender = CharacterGender.MALE
@Property()
color: string = '#000000'
@Property() @Property()
isSelectable = false isSelectable = false
@ManyToOne() @ManyToOne()
sprite!: Sprite sprite?: Sprite
@Property() @Property()
createdAt = new Date() createdAt = new Date()
@ -58,15 +58,6 @@ export class BaseCharacterHair extends BaseEntity {
return this.gender return this.gender
} }
setColor(color: string) {
this.color = color
return this
}
getColor() {
return this.color
}
setIsSelectable(isSelectable: boolean) { setIsSelectable(isSelectable: boolean) {
this.isSelectable = isSelectable this.isSelectable = isSelectable
return this return this

View File

@ -1,10 +1,13 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import type { Character } from '@/entities/character'
import type { Item } from '@/entities/item'
import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core' import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { Character } from '#entities/character'
import type { Item } from '#entities/item'
import { BaseEntity } from '#application/base/baseEntity'
export class BaseCharacterItem extends BaseEntity { export class BaseCharacterItem extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()

View File

@ -1,11 +1,14 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import { CharacterGender, CharacterRace } from '@/application/enums'
import type { UUID } from '@/application/types'
import { Character } from '@/entities/character'
import { Sprite } from '@/entities/sprite'
import { Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core' import { Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import { BaseEntity } from '#application/base/baseEntity'
import { CharacterGender, CharacterRace } from '#application/enums'
import { Character } from '#entities/character'
import { Sprite } from '#entities/sprite'
export class BaseCharacterType extends BaseEntity { export class BaseCharacterType extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()

View File

@ -1,10 +1,13 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import type { Character } from '@/entities/character'
import type { Map } from '@/entities/map'
import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core' import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { Character } from '#entities/character'
import type { Map } from '#entities/map'
import { BaseEntity } from '#application/base/baseEntity'
export class BaseChat extends BaseEntity { export class BaseChat extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()

View File

@ -1,11 +1,14 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import { ItemRarity, ItemType } from '@/application/enums'
import type { UUID } from '@/application/types'
import { CharacterItem } from '@/entities/characterItem'
import { Sprite } from '@/entities/sprite'
import { Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core' import { Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import { BaseEntity } from '#application/base/baseEntity'
import { ItemType, ItemRarity } from '#application/enums'
import { CharacterItem } from '#entities/characterItem'
import { Sprite } from '#entities/sprite'
export class BaseItem extends BaseEntity { export class BaseItem extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()
@ -25,8 +28,8 @@ export class BaseItem extends BaseEntity {
@Enum(() => ItemRarity) @Enum(() => ItemRarity)
rarity: ItemRarity = ItemRarity.COMMON rarity: ItemRarity = ItemRarity.COMMON
@ManyToOne() @ManyToOne(() => Sprite)
sprite!: Sprite sprite?: Sprite
@Property() @Property()
createdAt = new Date() createdAt = new Date()

View File

@ -1,17 +1,20 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import type { MapEffect } from '@/entities/mapEffect'
import type { MapEventTile } from '@/entities/mapEventTile'
import type { PlacedMapObject } from '@/entities/placedMapObject'
import { Collection, OneToMany, PrimaryKey, Property } from '@mikro-orm/core' import { Collection, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { MapEffect } from '#entities/mapEffect'
import type { MapEventTile } from '#entities/mapEventTile'
import type { PlacedMapObject } from '#entities/placedMapObject'
import { BaseEntity } from '#application/base/baseEntity'
export class BaseMap extends BaseEntity { export class BaseMap extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()
@Property() @Property()
name: string = '' name!: string
@Property() @Property()
width = 10 width = 10
@ -19,7 +22,7 @@ export class BaseMap extends BaseEntity {
@Property() @Property()
height = 10 height = 10
@Property({ type: 'json' }) @Property({ type: 'json', nullable: true })
tiles: Array<Array<string>> = [] tiles: Array<Array<string>> = []
@Property() @Property()

View File

@ -1,9 +1,12 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import type { Map } from '@/entities/map'
import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core' import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { Map } from '#entities/map'
import { BaseEntity } from '#application/base/baseEntity'
export class BaseMapEffect extends BaseEntity { export class BaseMapEffect extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()

View File

@ -1,11 +1,14 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import { MapEventTileType } from '@/application/enums'
import type { UUID } from '@/application/types'
import type { Map } from '@/entities/map'
import type { MapEventTileTeleport } from '@/entities/mapEventTileTeleport'
import { Enum, ManyToOne, OneToOne, PrimaryKey, Property } from '@mikro-orm/core' import { Enum, ManyToOne, OneToOne, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { Map } from '#entities/map'
import type { MapEventTileTeleport } from '#entities/mapEventTileTeleport'
import { BaseEntity } from '#application/base/baseEntity'
import { MapEventTileType } from '#application/enums'
export class BaseMapEventTile extends BaseEntity { export class BaseMapEventTile extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()
@ -17,12 +20,12 @@ export class BaseMapEventTile extends BaseEntity {
type!: MapEventTileType type!: MapEventTileType
@Property() @Property()
positionX: number = 0 positionX!: number
@Property() @Property()
positionY: number = 0 positionY!: number
@OneToOne({ eager: true, deleteRule: 'cascade', orphanRemoval: true }) @OneToOne({ eager: true })
teleport?: MapEventTileTeleport teleport?: MapEventTileTeleport
setId(id: UUID) { setId(id: UUID) {

View File

@ -1,15 +1,18 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import type { Map } from '@/entities/map'
import type { MapEventTile } from '@/entities/mapEventTile'
import { ManyToOne, OneToOne, PrimaryKey, Property } from '@mikro-orm/core' import { ManyToOne, OneToOne, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { Map } from '#entities/map'
import type { MapEventTile } from '#entities/mapEventTile'
import { BaseEntity } from '#application/base/baseEntity'
export class BaseMapEventTileTeleport extends BaseEntity { export class BaseMapEventTileTeleport extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()
@OneToOne({ deleteRule: 'cascade', orphanRemoval: true }) @OneToOne({ deleteRule: 'cascade' })
mapEventTile!: MapEventTile mapEventTile!: MapEventTile
@ManyToOne({ deleteRule: 'cascade', eager: true }) @ManyToOne({ deleteRule: 'cascade', eager: true })

View File

@ -1,8 +1,11 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import { Entity, PrimaryKey, Property } from '@mikro-orm/core' import { Entity, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import { BaseEntity } from '#application/base/baseEntity'
export class BaseMapObject extends BaseEntity { export class BaseMapObject extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()
@ -10,11 +13,8 @@ export class BaseMapObject extends BaseEntity {
@Property() @Property()
name!: string name!: string
@Property({ type: 'json' }) @Property({ type: 'json', nullable: true })
tags: string[] = [] tags?: any
@Property({ type: 'json' })
depthOffsets: number[] = [0]
@Property({ type: 'decimal', precision: 10, scale: 2 }) @Property({ type: 'decimal', precision: 10, scale: 2 })
originX = 0 originX = 0
@ -64,14 +64,6 @@ export class BaseMapObject extends BaseEntity {
return this.tags return this.tags
} }
setDepthOffsets(offsets: number[]) {
this.depthOffsets = offsets
}
getDepthOffsets() {
return this.depthOffsets
}
setOriginX(originX: number) { setOriginX(originX: number) {
this.originX = originX this.originX = originX
return this return this

View File

@ -1,9 +1,12 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import type { User } from '@/entities/user'
import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core' import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { User } from '#entities/user'
import { BaseEntity } from '#application/base/baseEntity'
export class BasePasswordResetToken extends BaseEntity { export class BasePasswordResetToken extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()

View File

@ -1,10 +1,13 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import type { Map } from '@/entities/map'
import type { MapObject } from '@/entities/mapObject'
import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core' import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { Map } from '#entities/map'
import type { MapObject } from '#entities/mapObject'
import { BaseEntity } from '#application/base/baseEntity'
export class BasePlacedMapObject extends BaseEntity { export class BasePlacedMapObject extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()

View File

@ -1,9 +1,12 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import { SpriteAction } from '@/entities/spriteAction'
import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core' import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import { BaseEntity } from '#application/base/baseEntity'
import { SpriteAction } from '#entities/spriteAction'
export class BaseSprite extends BaseEntity { export class BaseSprite extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()
@ -11,15 +14,9 @@ export class BaseSprite extends BaseEntity {
@Property() @Property()
name!: string name!: string
@OneToMany({ mappedBy: 'sprite', orphanRemoval: true }) @OneToMany(() => SpriteAction, (action) => action.sprite)
spriteActions = new Collection<SpriteAction>(this) spriteActions = new Collection<SpriteAction>(this)
@Property({ nullable: true })
width: number | null = null
@Property({ nullable: true })
height: number | null = null
@Property() @Property()
createdAt = new Date() createdAt = new Date()
@ -53,24 +50,6 @@ export class BaseSprite extends BaseEntity {
return this.spriteActions return this.spriteActions
} }
setWidth(width: number | null) {
this.width = width
return this
}
getWidth() {
return this.width
}
setHeight(height: number | null) {
this.height = height
return this
}
getHeight() {
return this.height
}
setCreatedAt(createdAt: Date) { setCreatedAt(createdAt: Date) {
this.createdAt = createdAt this.createdAt = createdAt
return this return this

View File

@ -1,9 +1,12 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import type { Sprite } from '@/entities/sprite'
import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core' import { ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import type { Sprite } from '#entities/sprite'
import { BaseEntity } from '#application/base/baseEntity'
export interface SpriteImage { export interface SpriteImage {
url: string url: string
offset: { offset: {

View File

@ -1,8 +1,11 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import { Entity, PrimaryKey, Property } from '@mikro-orm/core' import { Entity, PrimaryKey, Property } from '@mikro-orm/core'
import type { UUID } from '#application/types'
import { BaseEntity } from '#application/base/baseEntity'
export class BaseTile extends BaseEntity { export class BaseTile extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()

View File

@ -1,11 +1,14 @@
import { randomUUID } from 'node:crypto' import { randomUUID } from 'node:crypto'
import { BaseEntity } from '@/application/base/baseEntity'
import type { UUID } from '@/application/types'
import { Character } from '@/entities/character'
import { PasswordResetToken } from '@/entities/passwordResetToken'
import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core' import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
import bcrypt from 'bcryptjs' import bcrypt from 'bcryptjs'
import type { UUID } from '#application/types'
import { BaseEntity } from '#application/base/baseEntity'
import { Character } from '#entities/character'
import { PasswordResetToken } from '#entities/passwordResetToken'
export class BaseUser extends BaseEntity { export class BaseUser extends BaseEntity {
@PrimaryKey() @PrimaryKey()
id = randomUUID() id = randomUUID()

View File

@ -1,6 +1,7 @@
import { BaseEntity } from '@/application/base/baseEntity'
import { Entity, PrimaryKey, Property } from '@mikro-orm/core' import { Entity, PrimaryKey, Property } from '@mikro-orm/core'
import { BaseEntity } from '#application/base/baseEntity'
export class BaseWorld extends BaseEntity { export class BaseWorld extends BaseEntity {
@PrimaryKey() @PrimaryKey()
date = new Date() date = new Date()

View File

@ -1,5 +1,6 @@
import { BaseCharacter } from '@/entities/base/character'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseCharacter } from '#entities/base/character'
@Entity() @Entity()
export class Character extends BaseCharacter {} export class Character extends BaseCharacter {}

View File

@ -1,5 +1,6 @@
import { BaseCharacterEquipment } from '@/entities/base/characterEquipment'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseCharacterEquipment } from '#entities/base/characterEquipment'
@Entity() @Entity()
export class CharacterEquipment extends BaseCharacterEquipment {} export class CharacterEquipment extends BaseCharacterEquipment {}

View File

@ -1,6 +1,7 @@
import { BaseCharacterHair } from '@/entities/base/characterHair'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseCharacterHair } from '#entities/base/characterHair'
@Entity() @Entity()
export class CharacterHair extends BaseCharacterHair { export class CharacterHair extends BaseCharacterHair {
public async cache() { public async cache() {

View File

@ -1,5 +1,6 @@
import { BaseCharacterItem } from '@/entities/base/characterItem'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseCharacterItem } from '#entities/base/characterItem'
@Entity() @Entity()
export class CharacterItem extends BaseCharacterItem {} export class CharacterItem extends BaseCharacterItem {}

View File

@ -1,6 +1,7 @@
import { BaseCharacterType } from '@/entities/base/characterType'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseCharacterType } from '#entities/base/characterType'
@Entity() @Entity()
export class CharacterType extends BaseCharacterType { export class CharacterType extends BaseCharacterType {
public async cache() { public async cache() {

View File

@ -1,5 +1,6 @@
import { BaseChat } from '@/entities/base/chat'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseChat } from '#entities/base/chat'
@Entity() @Entity()
export class Chat extends BaseChat {} export class Chat extends BaseChat {}

View File

@ -1,5 +1,6 @@
import { BaseItem } from '@/entities/base/item'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseItem } from '#entities/base/item'
@Entity() @Entity()
export class Item extends BaseItem {} export class Item extends BaseItem {}

View File

@ -1,8 +1,8 @@
import { BaseMap } from '@/entities/base/map'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseMap } from '#entities/base/map'
export type MapCacheT = ReturnType<Map['cache']> | {} export type MapCacheT = ReturnType<Map['cache']> | {}
export type MapEditorMapT = ReturnType<Map['mapEditorObject']> | {}
@Entity() @Entity()
export class Map extends BaseMap { export class Map extends BaseMap {
@ -36,42 +36,4 @@ export class Map extends BaseMap {
return {} return {}
} }
} }
public async mapEditorObject() {
try {
await this.getPlacedMapObjects().load()
await this.getMapEffects().load()
await this.getMapEventTiles().load()
return {
id: this.getId(),
name: this.getName(),
width: this.getWidth(),
height: this.getHeight(),
tiles: this.getTiles(),
pvp: this.getPvp(),
createdAt: this.getCreatedAt(),
updatedAt: this.getUpdatedAt(),
placedMapObjects: this.getPlacedMapObjects(),
mapEffects: this.getMapEffects(),
mapEventTiles: this.getMapEventTiles().map((mapEventTile) => ({
id: mapEventTile.getId(),
type: mapEventTile.getType(),
positionX: mapEventTile.getPositionX(),
positionY: mapEventTile.getPositionY(),
teleport: mapEventTile.getTeleport()
? {
toMap: mapEventTile.getTeleport()?.getToMap().getId(),
toPositionX: mapEventTile.getTeleport()?.getToPositionX(),
toPositionY: mapEventTile.getTeleport()?.getToPositionY(),
toRotation: mapEventTile.getTeleport()?.getToRotation()
}
: undefined
}))
}
} catch (error) {
console.error(error)
return {}
}
}
} }

View File

@ -1,5 +1,6 @@
import { BaseMapEffect } from '@/entities/base/mapEffect'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseMapEffect } from '#entities/base/mapEffect'
@Entity() @Entity()
export class MapEffect extends BaseMapEffect {} export class MapEffect extends BaseMapEffect {}

View File

@ -1,5 +1,6 @@
import { BaseMapEventTile } from '@/entities/base/mapEventTile'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseMapEventTile } from '#entities/base/mapEventTile'
@Entity() @Entity()
export class MapEventTile extends BaseMapEventTile {} export class MapEventTile extends BaseMapEventTile {}

View File

@ -1,5 +1,6 @@
import { BaseMapEventTileTeleport } from '@/entities/base/mapEventTileTeleport'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseMapEventTileTeleport } from '#entities/base/mapEventTileTeleport'
@Entity() @Entity()
export class MapEventTileTeleport extends BaseMapEventTileTeleport {} export class MapEventTileTeleport extends BaseMapEventTileTeleport {}

View File

@ -1,6 +1,7 @@
import { BaseMapObject } from '@/entities/base/mapObject'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseMapObject } from '#entities/base/mapObject'
@Entity() @Entity()
export class MapObject extends BaseMapObject { export class MapObject extends BaseMapObject {
public async cache() { public async cache() {

View File

@ -1,5 +1,6 @@
import { BasePasswordResetToken } from '@/entities/base/passwordResetToken'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BasePasswordResetToken } from '#entities/base/passwordResetToken'
@Entity() @Entity()
export class PasswordResetToken extends BasePasswordResetToken {} export class PasswordResetToken extends BasePasswordResetToken {}

View File

@ -1,5 +1,6 @@
import { BasePlacedMapObject } from '@/entities/base/placedMapObject'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BasePlacedMapObject } from '#entities/base/placedMapObject'
@Entity() @Entity()
export class PlacedMapObject extends BasePlacedMapObject {} export class PlacedMapObject extends BasePlacedMapObject {}

View File

@ -1,6 +1,7 @@
import { BaseSprite } from '@/entities/base/sprite'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseSprite } from '#entities/base/sprite'
@Entity() @Entity()
export class Sprite extends BaseSprite { export class Sprite extends BaseSprite {
public async cache() { public async cache() {

View File

@ -1,5 +1,6 @@
import { BaseSpriteAction } from '@/entities/base/spriteAction'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseSpriteAction } from '#entities/base/spriteAction'
@Entity() @Entity()
export class SpriteAction extends BaseSpriteAction {} export class SpriteAction extends BaseSpriteAction {}

View File

@ -1,6 +1,7 @@
import { BaseTile } from '@/entities/base/tile'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseTile } from '#entities/base/tile'
@Entity() @Entity()
export class Tile extends BaseTile { export class Tile extends BaseTile {
public async cache() { public async cache() {

View File

@ -1,5 +1,6 @@
import { BaseUser } from '@/entities/base/user'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseUser } from '#entities/base/user'
@Entity() @Entity()
export class User extends BaseUser {} export class User extends BaseUser {}

View File

@ -1,5 +1,6 @@
import { BaseWorld } from '@/entities/base/world'
import { Entity } from '@mikro-orm/core' import { Entity } from '@mikro-orm/core'
import { BaseWorld } from '#entities/base/world'
@Entity() @Entity()
export class World extends BaseWorld {} export class World extends BaseWorld {}

View File

@ -1,16 +1,14 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import { ZCharacterConnect } from '@/application/zodTypes' import MapManager from '#managers/mapManager'
import MapManager from '@/managers/mapManager' import CharacterHairRepository from '#repositories/characterHairRepository'
import CharacterHairRepository from '@/repositories/characterHairRepository' import CharacterRepository from '#repositories/characterRepository'
import CharacterRepository from '@/repositories/characterRepository' import TeleportService from '#services/characterTeleportService'
import TeleportService from '@/services/characterTeleportService'
interface CharacterConnectPayload { interface CharacterConnectPayload {
characterId: UUID characterId: UUID
characterHairId: UUID | null characterHairId?: UUID
newNickname?: string
} }
export default class CharacterConnectEvent extends BaseEvent { export default class CharacterConnectEvent extends BaseEvent {
@ -18,40 +16,23 @@ export default class CharacterConnectEvent extends BaseEvent {
private readonly characterRepository = new CharacterRepository() private readonly characterRepository = new CharacterRepository()
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.CHARACTER_CONNECT, this.handleEvent.bind(this)) this.socket.on('character:connect', this.handleEvent.bind(this))
} }
private async handleEvent(data: CharacterConnectPayload, callback: (response: any) => void): Promise<void> { private async handleEvent(data: CharacterConnectPayload, callback: (response: any) => void): Promise<void> {
try { try {
if (data.newNickname === '') data.newNickname = undefined
const result = ZCharacterConnect.safeParse(data)
if (!result.success) {
this.sendNotificationAndLog(result.error?.errors[0]?.message ?? 'Invalid data')
return
}
if (await this.checkForActiveCharacters()) { if (await this.checkForActiveCharacters()) {
this.sendNotificationAndLog('You are already connected to another character') this.emitError('You are already connected to another character')
return return
} }
let character = await this.characterRepository.getByUserAndId(this.socket.userId!, data.characterId) const character = await this.characterRepository.getByUserAndId(this.socket.userId!, data.characterId)
if (!character) { if (!character) {
this.sendNotificationAndLog('Character not found or does not belong to this user') this.emitError('Character not found or does not belong to this user')
return return
} }
if (data.newNickname) {
const existingCharacter = await this.characterRepository.getByName(data.newNickname)
if (existingCharacter) {
this.sendNotificationAndLog('Nickname already in use: ' + data.newNickname)
return
}
await character.setName(data.newNickname).save()
}
// Set character id // Set character id
this.socket.characterId = character.id this.socket.characterId = character.id
@ -59,8 +40,6 @@ export default class CharacterConnectEvent extends BaseEvent {
if (data.characterHairId !== undefined && data.characterHairId !== null) { if (data.characterHairId !== undefined && data.characterHairId !== null) {
const characterHair = await this.characterHairRepository.getById(data.characterHairId) const characterHair = await this.characterHairRepository.getById(data.characterHairId)
await character.setCharacterHair(characterHair).save() await character.setCharacterHair(characterHair).save()
} else {
await character.setCharacterHair(null).save()
} }
// Emit character connect event // Emit character connect event

View File

@ -1,84 +1,72 @@
import { BaseEvent } from '@/application/base/baseEvent' import { ZodError } from 'zod'
import { SocketEvent } from '@/application/enums'
import { ZCharacterCreate } from '@/application/zodTypes'
import { Character } from '@/entities/character'
import CharacterRepository from '@/repositories/characterRepository'
import CharacterTypeRepository from '@/repositories/characterTypeRepository'
import MapRepository from '@/repositories/mapRepository'
import UserRepository from '@/repositories/userRepository'
import { z, ZodError } from 'zod'
const MAX_CHARACTERS = 4 import { BaseEvent } from '#application/base/baseEvent'
import { ZCharacterCreate } from '#application/zodTypes'
import { Character } from '#entities/character'
import CharacterRepository from '#repositories/characterRepository'
import CharacterTypeRepository from '#repositories/characterTypeRepository'
import MapRepository from '#repositories/mapRepository'
import UserRepository from '#repositories/userRepository'
export default class CharacterCreateEvent extends BaseEvent { export default class CharacterCreateEvent extends BaseEvent {
private readonly userRepository: UserRepository = new UserRepository()
private readonly characterRepository: CharacterRepository = new CharacterRepository()
private readonly characterTypeRepository: CharacterTypeRepository = new CharacterTypeRepository()
private readonly mapRepository: MapRepository = new MapRepository()
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.CHARACTER_CREATE, this.handleEvent.bind(this)) this.socket.on('character:create', this.handleEvent.bind(this))
} }
private async handleEvent(data: z.infer<typeof ZCharacterCreate>, callback: (success: boolean) => void): Promise<void> { private async handleEvent(data: any): Promise<any> {
// zod validate
try { try {
const validatedData = ZCharacterCreate.parse(data) data = ZCharacterCreate.parse(data)
await this.createCharacter(validatedData)
callback(true) const userRepository = new UserRepository()
} catch (error: unknown) { const characterRepository = new CharacterRepository()
this.returnError(error) const characterTypeRepository = new CharacterTypeRepository()
callback(false) const mapRepository = new MapRepository()
const user = await userRepository.getById(this.socket.userId!)
if (!user) {
return this.socket.emit('notification', { title: 'Error', message: 'You are not logged in' })
}
// Check if character name already exists
const characterExists = await characterRepository.getByName(data.name)
if (characterExists) {
return this.socket.emit('notification', { title: 'Error', message: 'Character name already exists' })
}
let characters: Character[] = await characterRepository.getByUserId(user.getId())
if (characters.length >= 4) {
return this.socket.emit('notification', { title: 'Error', message: 'You can only create 4 characters' })
}
// @TODO: Change to default location
const map = await mapRepository.getFirst()
// @TODO: Change to selected character type
const characterType = await characterTypeRepository.getFirst()
const newCharacter = new Character()
await newCharacter.setName(data.name).setUser(user).setMap(map!).setCharacterType(characterType).save()
if (!newCharacter) {
return this.socket.emit('notification', { title: 'Error', message: 'Failed to create character. Please try again (later).' })
}
characters = [...characters, newCharacter]
this.socket.emit('character:create:success')
this.socket.emit('character:list', characters)
this.logger.info('character:create success')
} catch (error: any) {
this.logger.error(`character:create error: ${error.message}`)
if (error instanceof ZodError) {
return this.socket.emit('notification', { title: 'Error', message: error.issues[0]!.message })
}
return this.socket.emit('notification', { title: 'Error', message: 'Could not create character. Please try again (later).' })
} }
} }
private async createCharacter(data: z.infer<typeof ZCharacterCreate>): Promise<void> {
const user = await this.userRepository.getById(this.socket.userId!)
if (!user) {
throw new Error('You are not logged in')
}
const characterExists = await this.characterRepository.getByName(data.name)
if (characterExists) {
throw new Error('Character name already exists')
}
let characters = await this.characterRepository.getByUserId(user.getId())
if (characters.length >= MAX_CHARACTERS) {
throw new Error(`You can only create ${MAX_CHARACTERS} characters`)
}
const map = await this.mapRepository.getFirst()
if (!map) {
throw new Error('No default map found')
}
const characterType = await this.characterTypeRepository.getFirst()
if (!characterType) {
throw new Error('No character type found')
}
const newCharacter = new Character()
await newCharacter.setName(data.name).setUser(user).setMap(map).setCharacterType(characterType).save()
characters = await this.characterRepository.getByUserId(user.getId())
this.socket.emit(SocketEvent.CHARACTER_LIST, characters)
this.logger.info('character:create success')
}
private returnError(error: unknown): void {
this.logger.error(`character:create error: ${error instanceof Error ? error.message : 'Unknown error'}`)
let errorMessage = 'Could not create character. Please try again later.'
if (error instanceof ZodError) {
errorMessage = error.issues[0]?.message || errorMessage
} else if (error instanceof Error) {
errorMessage = error.message
}
this.socket.emit(SocketEvent.NOTIFICATION, {
title: 'Error',
message: errorMessage
})
}
} }

View File

@ -1,8 +1,8 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import { Character } from '@/entities/character' import { Character } from '#entities/character'
import CharacterRepository from '@/repositories/characterRepository' import CharacterRepository from '#repositories/characterRepository'
type TypePayload = { type TypePayload = {
characterId: UUID characterId: UUID
@ -14,18 +14,18 @@ type TypeResponse = {
export default class CharacterDeleteEvent extends BaseEvent { export default class CharacterDeleteEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.CHARACTER_DELETE, this.handleEvent.bind(this)) this.socket.on('character:delete', this.handleEvent.bind(this))
} }
private async handleEvent(data: TypePayload, callback: (response: TypeResponse) => void): Promise<any> { private async handleEvent(data: TypePayload, callback: (response: TypeResponse) => void): Promise<any> {
try { try {
const characterRepository = new CharacterRepository() const characterRepository = new CharacterRepository()
await (await characterRepository.getByUserAndId(this.socket.userId, data.characterId))?.delete() await (await characterRepository.getByUserAndId(this.socket.userId!, data.characterId))?.delete()
const characters: Character[] = await characterRepository.getByUserId(this.socket.userId) const characters: Character[] = await characterRepository.getByUserId(this.socket.userId!)
this.socket.emit(SocketEvent.CHARACTER_LIST, characters) this.socket.emit('character:list', characters)
} catch (error: any) { } catch (error: any) {
return this.socket.emit(SocketEvent.NOTIFICATION, { message: 'Character delete failed. Please try again.' }) return this.socket.emit('notification', { message: 'Character delete failed. Please try again.' })
} }
} }
} }

View File

@ -1,19 +1,18 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import { Character } from '#entities/character'
import { Character } from '@/entities/character' import CharacterRepository from '#repositories/characterRepository'
import CharacterRepository from '@/repositories/characterRepository'
export default class CharacterListEvent extends BaseEvent { export default class CharacterListEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.CHARACTER_LIST, this.handleEvent.bind(this)) this.socket.on('character:list', this.handleEvent.bind(this))
} }
private async handleEvent(data: any): Promise<void> { private async handleEvent(data: any): Promise<void> {
try { try {
const characterRepository = new CharacterRepository() const characterRepository = new CharacterRepository()
let characters: Character[] = await characterRepository.getByUserId(this.socket.userId) let characters: Character[] = await characterRepository.getByUserId(this.socket.userId!)
this.socket.emit(SocketEvent.CHARACTER_LIST, characters) this.socket.emit('character:list', characters)
} catch (error: any) { } catch (error: any) {
this.logger.error('character:list error', error.message) this.logger.error('character:list error', error.message)
} }

View File

@ -1,7 +1,6 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import CharacterRepository from '#repositories/characterRepository'
import CharacterRepository from '@/repositories/characterRepository' import ChatService from '#services/chatService'
import ChatService from '@/services/chatService'
type TypePayload = { type TypePayload = {
message: string message: string
@ -9,7 +8,7 @@ type TypePayload = {
export default class AlertCommandEvent extends BaseEvent { export default class AlertCommandEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.CHAT_MESSAGE, this.handleEvent.bind(this)) this.socket.on('chat:message', this.handleEvent.bind(this))
} }
private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> {
@ -26,7 +25,7 @@ export default class AlertCommandEvent extends BaseEvent {
return callback(false) return callback(false)
} }
this.io.emit(SocketEvent.NOTIFICATION, { title: 'Message from GM', message: args.join(' ') }) this.io.emit('notification', { title: 'Message from GM', message: args.join(' ') })
return callback(true) return callback(true)
} catch (error: any) { } catch (error: any) {
this.logger.error('chat:alert_command error', error.message) this.logger.error('chat:alert_command error', error.message)

View File

@ -1,8 +1,7 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import DateManager from '#managers/dateManager'
import DateManager from '@/managers/dateManager' import CharacterRepository from '#repositories/characterRepository'
import CharacterRepository from '@/repositories/characterRepository' import ChatService from '#services/chatService'
import ChatService from '@/services/chatService'
type TypePayload = { type TypePayload = {
message: string message: string
@ -10,7 +9,7 @@ type TypePayload = {
export default class SetTimeCommand extends BaseEvent { export default class SetTimeCommand extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.CHAT_MESSAGE, this.handleEvent.bind(this)) this.socket.on('chat:message', this.handleEvent.bind(this))
} }
private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> {

View File

@ -1,10 +1,10 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import MapManager from '@/managers/mapManager' import MapManager from '#managers/mapManager'
import MapRepository from '@/repositories/mapRepository' import MapRepository from '#repositories/mapRepository'
import TeleportService from '@/services/characterTeleportService' import TeleportService from '#services/characterTeleportService'
import ChatService from '@/services/chatService' import ChatService from '#services/chatService'
type TypePayload = { type TypePayload = {
message: string message: string
@ -12,7 +12,7 @@ type TypePayload = {
export default class TeleportCommandEvent extends BaseEvent { export default class TeleportCommandEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.CHAT_MESSAGE, this.handleEvent.bind(this)) this.socket.on('chat:message', this.handleEvent.bind(this))
} }
private async handleEvent(data: TypePayload, callback: (response: boolean) => void) { private async handleEvent(data: TypePayload, callback: (response: boolean) => void) {
@ -29,7 +29,7 @@ export default class TeleportCommandEvent extends BaseEvent {
const args = ChatService.getArgs('teleport', data.message) const args = ChatService.getArgs('teleport', data.message)
if (!args || args.length === 0 || args.length > 3) { if (!args || args.length === 0 || args.length > 3) {
this.socket.emit(SocketEvent.NOTIFICATION, { this.socket.emit('notification', {
title: 'Server message', title: 'Server message',
message: 'Usage: /teleport <mapId> [x] [y]' message: 'Usage: /teleport <mapId> [x] [y]'
}) })
@ -41,7 +41,7 @@ export default class TeleportCommandEvent extends BaseEvent {
const targetY = args[2] ? parseInt(args[2], 10) : 0 const targetY = args[2] ? parseInt(args[2], 10) : 0
if (!mapId || isNaN(targetX) || isNaN(targetY)) { if (!mapId || isNaN(targetX) || isNaN(targetY)) {
this.socket.emit(SocketEvent.NOTIFICATION, { this.socket.emit('notification', {
title: 'Server message', title: 'Server message',
message: 'Invalid parameters. X and Y coordinates must be numbers.' message: 'Invalid parameters. X and Y coordinates must be numbers.'
}) })
@ -51,7 +51,7 @@ export default class TeleportCommandEvent extends BaseEvent {
const mapRepository = new MapRepository() const mapRepository = new MapRepository()
const map = await mapRepository.getById(mapId) const map = await mapRepository.getById(mapId)
if (!map) { if (!map) {
this.socket.emit(SocketEvent.NOTIFICATION, { this.socket.emit('notification', {
title: 'Server message', title: 'Server message',
message: 'Map not found' message: 'Map not found'
}) })
@ -59,7 +59,7 @@ export default class TeleportCommandEvent extends BaseEvent {
} }
if (character.map.id === map.id && targetX === character.positionX && targetY === character.positionY) { if (character.map.id === map.id && targetX === character.positionX && targetY === character.positionY) {
this.socket.emit(SocketEvent.NOTIFICATION, { this.socket.emit('notification', {
title: 'Server message', title: 'Server message',
message: 'You are already at that location' message: 'You are already at that location'
}) })
@ -74,20 +74,20 @@ export default class TeleportCommandEvent extends BaseEvent {
}) })
if (!success) { if (!success) {
return this.socket.emit(SocketEvent.NOTIFICATION, { return this.socket.emit('notification', {
title: 'Server message', title: 'Server message',
message: 'Failed to teleport' message: 'Failed to teleport'
}) })
} }
this.socket.emit(SocketEvent.NOTIFICATION, { this.socket.emit('notification', {
title: 'Server message', title: 'Server message',
message: `Teleported to ${map.name} (${targetX}, ${targetY})` message: `Teleported to ${map.name} (${targetX}, ${targetY})`
}) })
this.logger.info('teleport', `Character ${character.id} teleported to map ${map.id} at position (${targetX}, ${targetY})`) this.logger.info('teleport', `Character ${character.id} teleported to map ${map.id} at position (${targetX}, ${targetY})`)
} catch (error: any) { } catch (error: any) {
this.logger.error(`Error in teleport command: ${error.message}`) this.logger.error(`Error in teleport command: ${error.message}`)
this.socket.emit(SocketEvent.NOTIFICATION, { this.socket.emit('notification', {
title: 'Server message', title: 'Server message',
message: 'An error occurred while teleporting' message: 'An error occurred while teleporting'
}) })

View File

@ -1,7 +1,6 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import WeatherManager from '#managers/weatherManager'
import WeatherManager from '@/managers/weatherManager' import ChatService from '#services/chatService'
import ChatService from '@/services/chatService'
type TypePayload = { type TypePayload = {
message: string message: string
@ -9,7 +8,7 @@ type TypePayload = {
export default class ToggleFogCommand extends BaseEvent { export default class ToggleFogCommand extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.CHAT_MESSAGE, this.handleEvent.bind(this)) this.socket.on('chat:message', this.handleEvent.bind(this))
} }
private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> {

View File

@ -1,7 +1,6 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import WeatherManager from '#managers/weatherManager'
import WeatherManager from '@/managers/weatherManager' import ChatService from '#services/chatService'
import ChatService from '@/services/chatService'
type TypePayload = { type TypePayload = {
message: string message: string
@ -9,7 +8,7 @@ type TypePayload = {
export default class ToggleRainCommand extends BaseEvent { export default class ToggleRainCommand extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.CHAT_MESSAGE, this.handleEvent.bind(this)) this.socket.on('chat:message', this.handleEvent.bind(this))
} }
private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> {

View File

@ -1,7 +1,7 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import MapManager from '#managers/mapManager'
import MapManager from '@/managers/mapManager' import MapRepository from '#repositories/mapRepository'
import ChatService from '@/services/chatService' import ChatService from '#services/chatService'
type TypePayload = { type TypePayload = {
message: string message: string
@ -9,22 +9,31 @@ type TypePayload = {
export default class ChatMessageEvent extends BaseEvent { export default class ChatMessageEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.CHAT_MESSAGE, this.handleEvent.bind(this)) this.socket.on('chat:message', this.handleEvent.bind(this))
} }
private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> {
try { try {
if (!data.message || ChatService.isCommand(data.message)) return if (!data.message || ChatService.isCommand(data.message)) {
return callback(false)
}
const mapCharacter = MapManager.getCharacterById(this.socket.characterId!) const mapCharacter = MapManager.getCharacterById(this.socket.characterId!)
if (!mapCharacter) { if (!mapCharacter) {
this.logger.error('chat:message error', 'Character not found') this.logger.error('chat:message error', 'Character not found')
return return callback(false)
} }
const character = mapCharacter.character const character = mapCharacter.character
if (await ChatService.sendMapMessage(character.getId(), data.message)) { const mapRepository = new MapRepository()
const map = await mapRepository.getById(character.map.id)
if (!map) {
this.logger.error('chat:message error', 'Map not found')
return callback(false)
}
if (await ChatService.sendMapMessage(character.getId(), map.getId(), data.message)) {
return callback(true) return callback(true)
} }

View File

@ -1,10 +1,9 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import MapManager from '#managers/mapManager'
import MapManager from '@/managers/mapManager'
export default class DisconnectEvent extends BaseEvent { export default class DisconnectEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.DISCONNECT, this.handleEvent.bind(this)) this.socket.on('disconnect', this.handleEvent.bind(this))
} }
private async handleEvent(): Promise<void> { private async handleEvent(): Promise<void> {
@ -14,7 +13,7 @@ export default class DisconnectEvent extends BaseEvent {
return return
} }
this.io.emit(SocketEvent.USER_DISCONNECT, this.socket.userId) this.io.emit('user:disconnect', this.socket.userId)
const mapCharacter = MapManager.getCharacterById(this.socket.characterId!) const mapCharacter = MapManager.getCharacterById(this.socket.characterId!)
if (!mapCharacter) { if (!mapCharacter) {

View File

@ -1,28 +1,17 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { CharacterGender, SocketEvent } from '@/application/enums' import { CharacterHair } from '#entities/characterHair'
import { CharacterHair } from '@/entities/characterHair'
import SpriteRepository from '@/repositories/spriteRepository'
export default class CharacterHairCreateEvent extends BaseEvent { export default class CharacterHairCreateEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_CHARACTERHAIR_CREATE, this.handleEvent.bind(this)) this.socket.on('gm:characterHair:create', this.handleEvent.bind(this))
} }
private async handleEvent(data: undefined, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: undefined, callback: (response: boolean) => void): Promise<void> {
try { try {
if (!(await this.isCharacterGM())) return if (!(await this.isCharacterGM())) return
// Get first sprite
const spriteRepository = new SpriteRepository()
const firstSprite = await spriteRepository.getFirst()
if (!firstSprite) {
this.sendNotificationAndLog('No sprites found')
return callback(false)
}
const newCharacterHair = new CharacterHair() const newCharacterHair = new CharacterHair()
await newCharacterHair.setName('New hair').setGender(CharacterGender.MALE).setSprite(firstSprite).save() await newCharacterHair.setName('New hair').save()
return callback(true) return callback(true)
} catch (error) { } catch (error) {

View File

@ -1,7 +1,7 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import CharacterHairRepository from '@/repositories/characterHairRepository' import CharacterHairRepository from '#repositories/characterHairRepository'
interface IPayload { interface IPayload {
id: UUID id: UUID
@ -9,7 +9,7 @@ interface IPayload {
export default class CharacterHairDeleteEvent extends BaseEvent { export default class CharacterHairDeleteEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_CHARACTERHAIR_REMOVE, this.handleEvent.bind(this)) this.socket.on('gm:characterHair:remove', this.handleEvent.bind(this))
} }
private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> {

View File

@ -1,13 +1,12 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import { CharacterHair } from '#entities/characterHair'
import { CharacterHair } from '@/entities/characterHair' import CharacterHairRepository from '#repositories/characterHairRepository'
import CharacterHairRepository from '@/repositories/characterHairRepository'
interface IPayload {} interface IPayload {}
export default class characterHairListEvent extends BaseEvent { export default class characterHairListEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_CHARACTERHAIR_LIST, this.handleEvent.bind(this)) this.socket.on('gm:characterHair:list', this.handleEvent.bind(this))
} }
private async handleEvent(data: IPayload, callback: (response: CharacterHair[]) => void): Promise<void> { private async handleEvent(data: IPayload, callback: (response: CharacterHair[]) => void): Promise<void> {

View File

@ -1,21 +1,21 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { CharacterGender, SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import CharacterHairRepository from '@/repositories/characterHairRepository' import { CharacterGender } from '#application/enums'
import SpriteRepository from '@/repositories/spriteRepository' import CharacterHairRepository from '#repositories/characterHairRepository'
import SpriteRepository from '#repositories/spriteRepository'
type Payload = { type Payload = {
id: UUID id: UUID
name: string name: string
gender: CharacterGender gender: CharacterGender
color: string
isSelectable: boolean isSelectable: boolean
spriteId: UUID spriteId: UUID
} }
export default class CharacterHairUpdateEvent extends BaseEvent { export default class CharacterHairUpdateEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_CHARACTERHAIR_UPDATE, this.handleEvent.bind(this)) this.socket.on('gm:characterHair:update', this.handleEvent.bind(this))
} }
private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> { private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> {
@ -30,7 +30,7 @@ export default class CharacterHairUpdateEvent extends BaseEvent {
const characterHair = await characterHairRepository.getById(data.id) const characterHair = await characterHairRepository.getById(data.id)
if (!characterHair) return callback(false) if (!characterHair) return callback(false)
await characterHair.setName(data.name).setGender(data.gender).setColor(data.color).setIsSelectable(data.isSelectable).setSprite(sprite).setUpdatedAt(new Date()).save() await characterHair.setName(data.name).setGender(data.gender).setIsSelectable(data.isSelectable).setSprite(sprite).setUpdatedAt(new Date()).save()
return callback(true) return callback(true)
} catch (error) { } catch (error) {

View File

@ -1,10 +1,9 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import { CharacterType } from '#entities/characterType'
import { CharacterType } from '@/entities/characterType'
export default class CharacterTypeCreateEvent extends BaseEvent { export default class CharacterTypeCreateEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_CHARACTERTYPE_CREATE, this.handleEvent.bind(this)) this.socket.on('gm:characterType:create', this.handleEvent.bind(this))
} }
private async handleEvent(data: undefined, callback: (response: boolean, characterType?: any) => void): Promise<void> { private async handleEvent(data: undefined, callback: (response: boolean, characterType?: any) => void): Promise<void> {

View File

@ -1,7 +1,7 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import CharacterTypeRepository from '@/repositories/characterTypeRepository' import CharacterTypeRepository from '#repositories/characterTypeRepository'
interface IPayload { interface IPayload {
id: UUID id: UUID
@ -9,7 +9,7 @@ interface IPayload {
export default class CharacterTypeDeleteEvent extends BaseEvent { export default class CharacterTypeDeleteEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_CHARACTERTYPE_REMOVE, this.handleEvent.bind(this)) this.socket.on('gm:characterType:remove', this.handleEvent.bind(this))
} }
private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> {

View File

@ -1,13 +1,12 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import { CharacterType } from '#entities/characterType'
import { CharacterType } from '@/entities/characterType' import CharacterTypeRepository from '#repositories/characterTypeRepository'
import CharacterTypeRepository from '@/repositories/characterTypeRepository'
interface IPayload {} interface IPayload {}
export default class CharacterTypeListEvent extends BaseEvent { export default class CharacterTypeListEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_CHARACTERTYPE_LIST, this.handleEvent.bind(this)) this.socket.on('gm:characterType:list', this.handleEvent.bind(this))
} }
private async handleEvent(data: IPayload, callback: (response: CharacterType[]) => void): Promise<void> { private async handleEvent(data: IPayload, callback: (response: CharacterType[]) => void): Promise<void> {

View File

@ -1,8 +1,9 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { CharacterGender, CharacterRace, SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import CharacterTypeRepository from '@/repositories/characterTypeRepository' import { CharacterGender, CharacterRace } from '#application/enums'
import SpriteRepository from '@/repositories/spriteRepository' import CharacterTypeRepository from '#repositories/characterTypeRepository'
import SpriteRepository from '#repositories/spriteRepository'
type Payload = { type Payload = {
id: UUID id: UUID
@ -15,7 +16,7 @@ type Payload = {
export default class CharacterTypeUpdateEvent extends BaseEvent { export default class CharacterTypeUpdateEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_CHARACTERTYPE_UPDATE, this.handleEvent.bind(this)) this.socket.on('gm:characterType:update', this.handleEvent.bind(this))
} }
private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> { private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> {

View File

@ -1,11 +1,11 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { ItemRarity, ItemType, SocketEvent } from '@/application/enums' import { ItemRarity, ItemType } from '#application/enums'
import { Item } from '@/entities/item' import { Item } from '#entities/item'
import SpriteRepository from '@/repositories/spriteRepository' import SpriteRepository from '#repositories/spriteRepository'
export default class ItemCreateEvent extends BaseEvent { export default class ItemCreateEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_ITEM_CREATE, this.handleEvent.bind(this)) this.socket.on('gm:item:create', this.handleEvent.bind(this))
} }
private async handleEvent(data: undefined, callback: (response: boolean, item?: any) => void): Promise<void> { private async handleEvent(data: undefined, callback: (response: boolean, item?: any) => void): Promise<void> {
@ -14,10 +14,7 @@ export default class ItemCreateEvent extends BaseEvent {
const spriteRepository = new SpriteRepository() const spriteRepository = new SpriteRepository()
const sprite = await spriteRepository.getFirst() const sprite = await spriteRepository.getFirst()
if (!sprite) { if (!sprite) return callback(false)
this.sendNotificationAndLog('No sprites found')
return callback(false)
}
const newItem = new Item() const newItem = new Item()
await newItem.setName('New Item').setItemType(ItemType.WEAPON).setStackable(false).setRarity(ItemRarity.COMMON).setSprite(sprite).save() await newItem.setName('New Item').setItemType(ItemType.WEAPON).setStackable(false).setRarity(ItemRarity.COMMON).setSprite(sprite).save()

View File

@ -1,7 +1,7 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import ItemRepository from '@/repositories/itemRepository' import ItemRepository from '#repositories/itemRepository'
interface IPayload { interface IPayload {
id: UUID id: UUID
@ -9,7 +9,7 @@ interface IPayload {
export default class ItemDeleteEvent extends BaseEvent { export default class ItemDeleteEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_ITEM_REMOVE, this.handleEvent.bind(this)) this.socket.on('gm:item:remove', this.handleEvent.bind(this))
} }
private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> {

View File

@ -1,13 +1,12 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import { Item } from '#entities/item'
import { Item } from '@/entities/item' import ItemRepository from '#repositories/itemRepository'
import ItemRepository from '@/repositories/itemRepository'
interface IPayload {} interface IPayload {}
export default class ItemListEvent extends BaseEvent { export default class ItemListEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_ITEM_LIST, this.handleEvent.bind(this)) this.socket.on('gm:item:list', this.handleEvent.bind(this))
} }
private async handleEvent(data: IPayload, callback: (response: Item[]) => void): Promise<void> { private async handleEvent(data: IPayload, callback: (response: Item[]) => void): Promise<void> {

View File

@ -1,8 +1,9 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { ItemRarity, ItemType, SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import ItemRepository from '@/repositories/itemRepository' import { ItemType, ItemRarity } from '#application/enums'
import SpriteRepository from '@/repositories/spriteRepository' import ItemRepository from '#repositories/itemRepository'
import SpriteRepository from '#repositories/spriteRepository'
type Payload = { type Payload = {
id: UUID id: UUID
@ -16,7 +17,7 @@ type Payload = {
export default class ItemUpdateEvent extends BaseEvent { export default class ItemUpdateEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_ITEM_UPDATE, this.handleEvent.bind(this)) this.socket.on('gm:item:update', this.handleEvent.bind(this))
} }
private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> { private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> {

View File

@ -1,13 +1,12 @@
import { BaseEvent } from '@/application/base/baseEvent' import { BaseEvent } from '#application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import { MapObject } from '#entities/mapObject'
import { MapObject } from '@/entities/mapObject' import MapObjectRepository from '#repositories/mapObjectRepository'
import MapObjectRepository from '@/repositories/mapObjectRepository'
interface IPayload {} interface IPayload {}
export default class MapObjectListEvent extends BaseEvent { export default class MapObjectListEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_MAPOBJECT_LIST, this.handleEvent.bind(this)) this.socket.on('gm:mapObject:list', this.handleEvent.bind(this))
} }
private async handleEvent(data: IPayload, callback: (response: MapObject[]) => void): Promise<void> { private async handleEvent(data: IPayload, callback: (response: MapObject[]) => void): Promise<void> {

View File

@ -1,9 +1,10 @@
import fs from 'fs' import fs from 'fs'
import { BaseEvent } from '@/application/base/baseEvent'
import { SocketEvent } from '@/application/enums' import type { UUID } from '#application/types'
import Storage from '@/application/storage'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import MapObjectRepository from '@/repositories/mapObjectRepository' import Storage from '#application/storage'
import MapObjectRepository from '#repositories/mapObjectRepository'
interface IPayload { interface IPayload {
mapObjectId: UUID mapObjectId: UUID
@ -11,7 +12,7 @@ interface IPayload {
export default class MapObjectRemoveEvent extends BaseEvent { export default class MapObjectRemoveEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_MAPOBJECT_REMOVE, this.handleEvent.bind(this)) this.socket.on('gm:mapObject:remove', this.handleEvent.bind(this))
} }
private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> {

View File

@ -1,13 +1,12 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import MapObjectRepository from '@/repositories/mapObjectRepository' import MapObjectRepository from '#repositories/mapObjectRepository'
type Payload = { type Payload = {
id: UUID id: UUID
name: string name: string
tags: string[] tags: string[]
depthOffsets: number[]
originX: number originX: number
originY: number originY: number
frameRate: number frameRate: number
@ -17,7 +16,7 @@ type Payload = {
export default class MapObjectUpdateEvent extends BaseEvent { export default class MapObjectUpdateEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_MAPOBJECT_UPDATE, this.handleEvent.bind(this)) this.socket.on('gm:mapObject:update', this.handleEvent.bind(this))
} }
private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> { private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> {
@ -31,7 +30,6 @@ export default class MapObjectUpdateEvent extends BaseEvent {
if (data.name !== undefined) mapObject.name = data.name if (data.name !== undefined) mapObject.name = data.name
if (data.tags !== undefined) mapObject.tags = data.tags if (data.tags !== undefined) mapObject.tags = data.tags
if (data.depthOffsets !== undefined) mapObject.depthOffsets = data.depthOffsets
if (data.originX !== undefined) mapObject.originX = data.originX if (data.originX !== undefined) mapObject.originX = data.originX
if (data.originY !== undefined) mapObject.originY = data.originY if (data.originY !== undefined) mapObject.originY = data.originY
if (data.frameRate !== undefined) mapObject.frameRate = data.frameRate if (data.frameRate !== undefined) mapObject.frameRate = data.frameRate
@ -42,7 +40,7 @@ export default class MapObjectUpdateEvent extends BaseEvent {
return callback(true) return callback(true)
} catch (error) { } catch (error) {
this.socket.emit(SocketEvent.NOTIFICATION, { title: 'Error', message: 'Failed to update mapObject.' }) this.socket.emit('notification', { title: 'Error', message: 'Failed to update mapObject.' })
return callback(false) return callback(false)
} }
} }

View File

@ -1,18 +1,19 @@
import fs from 'fs/promises' import fs from 'fs/promises'
import { writeFile } from 'node:fs/promises' import { writeFile } from 'node:fs/promises'
import { BaseEvent } from '@/application/base/baseEvent'
import { SocketEvent } from '@/application/enums'
import Storage from '@/application/storage'
import { MapObject } from '@/entities/mapObject'
import sharp from 'sharp' import sharp from 'sharp'
import { BaseEvent } from '#application/base/baseEvent'
import Storage from '#application/storage'
import { MapObject } from '#entities/mapObject'
interface IObjectData { interface IObjectData {
[key: string]: Buffer [key: string]: Buffer
} }
export default class MapObjectUploadEvent extends BaseEvent { export default class MapObjectUploadEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_MAPOBJECT_UPLOAD, this.handleEvent.bind(this)) this.socket.on('gm:mapObject:upload', this.handleEvent.bind(this))
} }
private async handleEvent(data: IObjectData, callback: (response: boolean) => void): Promise<void> { private async handleEvent(data: IObjectData, callback: (response: boolean) => void): Promise<void> {

View File

@ -1,9 +1,8 @@
import { BaseEvent } from '@/application/base/baseEvent' import type { UUID } from '#application/types'
import { SocketEvent } from '@/application/enums'
import type { UUID } from '@/application/types' import { BaseEvent } from '#application/base/baseEvent'
import { Sprite } from '@/entities/sprite' import { Sprite } from '#entities/sprite'
import { SpriteAction } from '@/entities/spriteAction' import SpriteRepository from '#repositories/spriteRepository'
import SpriteRepository from '@/repositories/spriteRepository'
interface CopyPayload { interface CopyPayload {
id: UUID id: UUID
@ -11,7 +10,7 @@ interface CopyPayload {
export default class SpriteCopyEvent extends BaseEvent { export default class SpriteCopyEvent extends BaseEvent {
public listen(): void { public listen(): void {
this.socket.on(SocketEvent.GM_SPRITE_COPY, this.handleEvent.bind(this)) this.socket.on('gm:sprite:copy', this.handleEvent.bind(this))
} }
private async handleEvent(payload: CopyPayload, callback: (success: boolean) => void): Promise<void> { private async handleEvent(payload: CopyPayload, callback: (success: boolean) => void): Promise<void> {
@ -30,21 +29,7 @@ export default class SpriteCopyEvent extends BaseEvent {
await spriteRepository.getEntityManager().populate(sourceSprite, ['spriteActions']) await spriteRepository.getEntityManager().populate(sourceSprite, ['spriteActions'])
const newSprite = new Sprite() const newSprite = new Sprite()
await newSprite.setName(`${sourceSprite.getName()} (Copy)`).save() await newSprite.setName(`${sourceSprite.getName()} (Copy)`).setSpriteActions(sourceSprite.getSpriteActions()).save()
for (const spriteAction of sourceSprite.getSpriteActions()) {
const newSpriteAction = new SpriteAction()
await newSpriteAction
.setSprite(newSprite)
.setAction(spriteAction.getAction())
.setSprites(spriteAction.getSprites() ?? [])
.setOriginX(spriteAction.getOriginX())
.setOriginY(spriteAction.getOriginY())
.setFrameWidth(spriteAction.getFrameWidth())
.setFrameHeight(spriteAction.getFrameHeight())
.setFrameRate(spriteAction.getFrameRate())
.save()
}
return callback(true) return callback(true)
} catch (error) { } catch (error) {

Some files were not shown because too many files have changed in this diff Show More