NPM update, removed CRUD functions from object repository, added prettier

This commit is contained in:
Dennis Postma 2024-07-22 01:36:05 +02:00
parent 792abdfaf6
commit 6131a8455a
51 changed files with 1450 additions and 1460 deletions

8
.prettierrc.json Normal file
View File

@ -0,0 +1,8 @@
{
"$schema": "https://json.schemastore.org/prettierrc",
"semi": false,
"tabWidth": 2,
"singleQuote": true,
"printWidth": 300,
"trailingComma": "none"
}

27
package-lock.json generated
View File

@ -5,25 +5,26 @@
"packages": {
"": {
"dependencies": {
"@prisma/client": "^5.13.0",
"@prisma/client": "^5.17.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"prisma": "^5.13.0",
"prisma": "^5.17.0",
"sharp": "^0.33.4",
"socket.io": "^4.7.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"typescript": "^5.5.3",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.12.11",
"nodemon": "^3.1.0"
"@types/node": "^20.14.11",
"nodemon": "^3.1.4",
"prettier": "^3.3.3"
}
},
"node_modules/@cspotcode/source-map-support": {
@ -1886,6 +1887,22 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/prettier": {
"version": "3.3.3",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.3.3.tgz",
"integrity": "sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==",
"dev": true,
"license": "MIT",
"bin": {
"prettier": "bin/prettier.cjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/prisma": {
"version": "5.17.0",
"resolved": "https://registry.npmjs.org/prisma/-/prisma-5.17.0.tgz",

View File

@ -2,27 +2,29 @@
"scripts": {
"start": "npx prisma migrate deploy && node dist/server.js",
"dev": "nodemon --exec ts-node src/server.ts",
"build": "tsc"
"build": "tsc",
"format": "prettier --write src/"
},
"dependencies": {
"@prisma/client": "^5.13.0",
"@prisma/client": "^5.17.0",
"bcryptjs": "^2.4.3",
"cors": "^2.8.5",
"dotenv": "^16.4.5",
"express": "^4.19.2",
"jsonwebtoken": "^9.0.2",
"prisma": "^5.13.0",
"prisma": "^5.17.0",
"sharp": "^0.33.4",
"socket.io": "^4.7.5",
"ts-node": "^10.9.2",
"typescript": "^5.4.5",
"typescript": "^5.5.3",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/bcryptjs": "^2.4.6",
"@types/express": "^4.17.21",
"@types/jsonwebtoken": "^9.0.6",
"@types/node": "^20.12.11",
"nodemon": "^3.1.0"
"@types/node": "^20.14.11",
"nodemon": "^3.1.4",
"prettier": "^3.3.3"
}
}

View File

@ -1,9 +1,9 @@
import { Server } from "socket.io";
import { Server } from 'socket.io'
type CommandInput = string[]
export default function (input: CommandInput, io: Server) {
const message: string = input.join(' ') ?? null;
if (!message) return console.log('message is required');
io.emit('notification', {message: message});
};
const message: string = input.join(' ') ?? null
if (!message) return console.log('message is required')
io.emit('notification', { message: message })
}

View File

@ -1,8 +1,8 @@
import { Server } from "socket.io";
import ZoneManager from "../managers/ZoneManager";
import { Server } from 'socket.io'
import ZoneManager from '../managers/ZoneManager'
type CommandInput = string[]
export default function (input: CommandInput, io: Server) {
console.log(ZoneManager.getLoadedZones())
};
console.log(ZoneManager.getLoadedZones())
}

View File

@ -1,23 +1,23 @@
import { Server } from "socket.io";
import {TSocket} from "../utilities/Types";
import ZoneManager from "../managers/ZoneManager";
import { Server } from 'socket.io'
import { TSocket } from '../utilities/Types'
import ZoneManager from '../managers/ZoneManager'
export default function (socket: TSocket, io: Server) {
socket.on('disconnect', (data: any) => {
if (!socket.user) {
console.log('User disconnected but had no user set');
return;
}
socket.on('disconnect', (data: any) => {
if (!socket.user) {
console.log('User disconnected but had no user set')
return
}
io.emit('user:disconnect', socket.user.id);
io.emit('user:disconnect', socket.user.id)
if (!socket.character) {
console.log('User disconnected but had no character set');
return;
}
if (!socket.character) {
console.log('User disconnected but had no character set')
return
}
ZoneManager.removeCharacterFromZone(socket.character.zoneId, socket.character);
ZoneManager.removeCharacterFromZone(socket.character.zoneId, socket.character)
io.emit('character:disconnect', socket.character.id);
});
io.emit('character:disconnect', socket.character.id)
})
}

View File

@ -1,9 +1,9 @@
import { Server } from "socket.io";
import {TSocket} from "../utilities/Types";
import { Server } from 'socket.io'
import { TSocket } from '../utilities/Types'
export default function (socket: TSocket, io: Server) {
socket.on('login', () => {
// return user data
socket.emit('logged_in', {user: socket.user});
});
socket.on('login', () => {
// return user data
socket.emit('logged_in', { user: socket.user })
})
}

View File

@ -1,20 +1,20 @@
import { Socket, Server } from "socket.io";
import {TSocket} from "../../utilities/Types";
import CharacterRepository from "../../repositories/CharacterRepository";
import {Character, User} from "@prisma/client";
import { Socket, Server } from 'socket.io'
import { TSocket } from '../../utilities/Types'
import CharacterRepository from '../../repositories/CharacterRepository'
import { Character, User } from '@prisma/client'
type SocketResponseT = {
character_id: number
character_id: number
}
export default function (socket: TSocket, io: Server) {
socket.on('character:connect', async (data: SocketResponseT) => {
console.log('character:connect requested', data);
try {
socket.character = await CharacterRepository.getByUserAndId(socket.user?.id as number, data.character_id) as Character;
socket.emit('character:connect', socket.character)
} catch (error: any) {
console.log('character:connect error', error);
}
});
socket.on('character:connect', async (data: SocketResponseT) => {
console.log('character:connect requested', data)
try {
socket.character = (await CharacterRepository.getByUserAndId(socket.user?.id as number, data.character_id)) as Character
socket.emit('character:connect', socket.character)
} catch (error: any) {
console.log('character:connect error', error)
}
})
}

View File

@ -1,37 +1,37 @@
import { Server } from "socket.io";
import {TSocket} from "../../utilities/Types";
import {Character} from "@prisma/client";
import CharacterRepository from "../../repositories/CharacterRepository";
import {ZCharacterCreate} from "../../utilities/ZodTypes";
import { Server } from 'socket.io'
import { TSocket } from '../../utilities/Types'
import { Character } from '@prisma/client'
import CharacterRepository from '../../repositories/CharacterRepository'
import { ZCharacterCreate } from '../../utilities/ZodTypes'
export default function (socket: TSocket, io: Server) {
socket.on('character:create', async (data: any) => {
// zod validate
try {
data = ZCharacterCreate.parse(data);
socket.on('character:create', async (data: any) => {
// zod validate
try {
data = ZCharacterCreate.parse(data)
const user_id = socket.user?.id as number;
const user_id = socket.user?.id as number
// Check if character name already exists
const characterExists = await CharacterRepository.getByName(data.name);
// Check if character name already exists
const characterExists = await CharacterRepository.getByName(data.name)
if (characterExists) {
return socket.emit('notification', {message: 'Character name already exists'});
}
if (characterExists) {
return socket.emit('notification', { message: 'Character name already exists' })
}
let characters: Character[] = await CharacterRepository.getByUserId(user_id) as Character[];
let characters: Character[] = (await CharacterRepository.getByUserId(user_id)) as Character[]
if (characters.length >= 4) {
return socket.emit('notification', {message: 'You can only have 4 characters'});
}
if (characters.length >= 4) {
return socket.emit('notification', { message: 'You can only have 4 characters' })
}
const character: Character = await CharacterRepository.create(user_id, data.name, 'player') as Character;
characters = [...characters, character];
const character: Character = (await CharacterRepository.create(user_id, data.name, 'player')) as Character
characters = [...characters, character]
socket.emit('character:create:success');
socket.emit('character:list', characters);
} catch (error: any) {
return socket.emit('notification', {message: error.errors[0]?.message ?? 'Invalid data'});
}
});
socket.emit('character:create:success')
socket.emit('character:list', characters)
} catch (error: any) {
return socket.emit('notification', { message: error.errors[0]?.message ?? 'Invalid data' })
}
})
}

View File

@ -1,30 +1,30 @@
import {Server} from "socket.io";
import {TSocket} from "../../utilities/Types";
import { Server } from 'socket.io'
import { TSocket } from '../../utilities/Types'
import { Character, Zone } from '@prisma/client'
import CharacterRepository from "../../repositories/CharacterRepository";
import CharacterRepository from '../../repositories/CharacterRepository'
type TypePayload = {
character_id: number;
character_id: number
}
type TypeResponse = {
zone: Zone;
characters: Character[];
zone: Zone
characters: Character[]
}
export default function (socket: TSocket, io: Server) {
socket.on('character:delete', async (data: TypePayload, callback: (response: TypeResponse) => void) => {
// zod validate
try {
await CharacterRepository.deleteByUserIdAndId(socket.user?.id as number, data.character_id as number);
socket.on('character:delete', async (data: TypePayload, callback: (response: TypeResponse) => void) => {
// zod validate
try {
await CharacterRepository.deleteByUserIdAndId(socket.user?.id as number, data.character_id as number)
const user_id = socket.user?.id as number;
const characters: Character[] = await CharacterRepository.getByUserId(user_id) as Character[];
const user_id = socket.user?.id as number
const characters: Character[] = (await CharacterRepository.getByUserId(user_id)) as Character[]
socket.emit('character:list', characters);
} catch (error: any) {
console.log(error);
return socket.emit('notification', {message: 'Character delete failed. Please try again.'});
}
});
socket.emit('character:list', characters)
} catch (error: any) {
console.log(error)
return socket.emit('notification', { message: 'Character delete failed. Please try again.' })
}
})
}

View File

@ -1,17 +1,17 @@
import { Socket, Server } from "socket.io";
import {TSocket} from "../../utilities/Types";
import {Character} from "@prisma/client";
import CharacterRepository from "../../repositories/CharacterRepository";
import { Socket, Server } from 'socket.io'
import { TSocket } from '../../utilities/Types'
import { Character } from '@prisma/client'
import CharacterRepository from '../../repositories/CharacterRepository'
export default function CharacterList(socket: TSocket, io: Server) {
socket.on('character:list', async (data: any) => {
try {
console.log('character:list requested');
const user_id = socket.user?.id as number;
const characters: Character[] = await CharacterRepository.getByUserId(user_id) as Character[];
socket.emit('character:list', characters);
} catch (error: any) {
console.log('character:list error', error);
}
});
socket.on('character:list', async (data: any) => {
try {
console.log('character:list requested')
const user_id = socket.user?.id as number
const characters: Character[] = (await CharacterRepository.getByUserId(user_id)) as Character[]
socket.emit('character:list', characters)
} catch (error: any) {
console.log('character:list error', error)
}
})
}

View File

@ -1,33 +1,33 @@
import { Server } from "socket.io";
import {TSocket} from "../../utilities/Types";
import CharacterRepository from "../../repositories/CharacterRepository";
import ZoneManager from "../../managers/ZoneManager";
import { Server } from 'socket.io'
import { TSocket } from '../../utilities/Types'
import CharacterRepository from '../../repositories/CharacterRepository'
import ZoneManager from '../../managers/ZoneManager'
type SocketResponseT = {
position_x: number,
position_y: number,
position_x: number
position_y: number
}
export default function (socket: TSocket, io: Server) {
socket.on('character:move', async (data: SocketResponseT) => {
try {
console.log('character:move requested', data);
socket.on('character:move', async (data: SocketResponseT) => {
try {
console.log('character:move requested', data)
if (!socket.character) {
console.log('character:move error', 'Character not found');
return;
}
if (!socket.character) {
console.log('character:move error', 'Character not found')
return
}
socket.character.position_x = data.position_x;
socket.character.position_y = data.position_y;
socket.character.position_x = data.position_x
socket.character.position_y = data.position_y
await CharacterRepository.updatePosition(socket.character.id as number, data.position_x, data.position_y);
ZoneManager.updateCharacterInZone(socket.character.zoneId, socket.character);
console.log(socket.character);
await CharacterRepository.updatePosition(socket.character.id as number, data.position_x, data.position_y)
ZoneManager.updateCharacterInZone(socket.character.zoneId, socket.character)
console.log(socket.character)
io.in(socket.character.zoneId.toString()).emit('character:moved', socket.character);
} catch (error: any) {
console.log('character:move error', error);
}
});
io.in(socket.character.zoneId.toString()).emit('character:moved', socket.character)
} catch (error: any) {
console.log('character:move error', error)
}
})
}

View File

@ -1,8 +1,8 @@
import { Server } from "socket.io";
import {TSocket} from "../../utilities/Types";
import ZoneRepository from "../../repositories/ZoneRepository";
import ZoneManager from "../../managers/ZoneManager";
import {Character, Zone} from "@prisma/client";
import { Server } from 'socket.io'
import { TSocket } from '../../utilities/Types'
import ZoneRepository from '../../repositories/ZoneRepository'
import ZoneManager from '../../managers/ZoneManager'
import { Character, Zone } from '@prisma/client'
/**
* Handle character zone leave event
@ -10,36 +10,36 @@ import {Character, Zone} from "@prisma/client";
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('character:zone:leave', async () => {
console.log(`---Socket ${socket.character?.id} has leaved zone.`);
socket.on('character:zone:leave', async () => {
console.log(`---Socket ${socket.character?.id} has leaved zone.`)
if (!socket.character) {
console.log('Socket leaved zone but had no character set');
return;
}
if (!socket.character) {
console.log('Socket leaved zone but had no character set')
return
}
if (!socket.character.zoneId) {
console.log(`---Zone id not provided.`);
return;
}
if (!socket.character.zoneId) {
console.log(`---Zone id not provided.`)
return
}
const zone = await ZoneRepository.getById(socket.character.zoneId);
const zone = await ZoneRepository.getById(socket.character.zoneId)
if (!zone) {
console.log(`---Zone not found.`);
return;
}
if (!zone) {
console.log(`---Zone not found.`)
return
}
socket.leave(zone.id.toString());
socket.leave(zone.id.toString())
socket.emit('character:zone:unload');
socket.emit('character:zone:unload')
// let other clients know of new character
io.to(zone.id.toString()).emit('zone:character:leave', socket.character);
// let other clients know of new character
io.to(zone.id.toString()).emit('zone:character:leave', socket.character)
// add character to zone manager
ZoneManager.removeCharacterFromZone(zone.id, socket.character as Character);
});
// add character to zone manager
ZoneManager.removeCharacterFromZone(zone.id, socket.character as Character)
})
}
/**

View File

@ -1,16 +1,16 @@
import { Server } from "socket.io";
import {TSocket} from "../../utilities/Types";
import ZoneRepository from "../../repositories/ZoneRepository";
import ZoneManager from "../../managers/ZoneManager";
import {Character, Zone} from "@prisma/client";
import { Server } from 'socket.io'
import { TSocket } from '../../utilities/Types'
import ZoneRepository from '../../repositories/ZoneRepository'
import ZoneManager from '../../managers/ZoneManager'
import { Character, Zone } from '@prisma/client'
interface IPayload {
zoneId: number;
zoneId: number
}
interface IResponse {
zone: Zone;
characters: Character[];
zone: Zone
characters: Character[]
}
/**
@ -19,30 +19,30 @@ interface IResponse {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('character:zone:request', async (data: IPayload, callback: (response: IResponse) => void) => {
console.log(`---User ${socket.character?.id} has requested zone.`);
socket.on('character:zone:request', async (data: IPayload, callback: (response: IResponse) => void) => {
console.log(`---User ${socket.character?.id} has requested zone.`)
if (!data.zoneId) {
console.log(`---Zone id not provided.`);
return;
}
if (!data.zoneId) {
console.log(`---Zone id not provided.`)
return
}
const zone = await ZoneRepository.getById(data.zoneId);
const zone = await ZoneRepository.getById(data.zoneId)
if (!zone) {
console.log(`---Zone not found.`);
return;
}
if (!zone) {
console.log(`---Zone not found.`)
return
}
socket.join(zone.id.toString());
socket.join(zone.id.toString())
// send over zone and characters to socket
callback({zone, characters: ZoneManager.getCharactersInZone(zone.id)});
// send over zone and characters to socket
callback({ zone, characters: ZoneManager.getCharactersInZone(zone.id) })
// let other clients know of new character
io.to(zone.id.toString()).emit('zone:character:join', socket.character);
// let other clients know of new character
io.to(zone.id.toString()).emit('zone:character:join', socket.character)
// add character to zone manager
ZoneManager.addCharacterToZone(zone.id, socket.character as Character);
});
// add character to zone manager
ZoneManager.addCharacterToZone(zone.id, socket.character as Character)
})
}

View File

@ -1,10 +1,9 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import { Object } from '@prisma/client'
import ObjectRepository from '../../../repositories/ObjectRepository'
interface IPayload {
}
interface IPayload {}
/**
* Handle game master list object event
@ -12,15 +11,14 @@ interface IPayload {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:object:list', async (data: any, callback: (response: Object[]) => void) => {
socket.on('gm:object:list', async (data: any, callback: (response: Object[]) => void) => {
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`)
return
}
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`);
return;
}
// get all objects
const objects = await ObjectRepository.getAll();
callback(objects);
});
// get all objects
const objects = await ObjectRepository.getAll()
callback(objects)
})
}

View File

@ -1,11 +1,11 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import path from "path";
import fs from "fs";
import ObjectRepository from '../../../repositories/ObjectRepository'
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import path from 'path'
import fs from 'fs'
import prisma from '../../../utilities/Prisma'
interface IPayload {
object: string;
object: string
}
/**
@ -14,32 +14,35 @@ interface IPayload {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:object:remove', async (data: IPayload, callback: (response: boolean) => void) => {
socket.on('gm:object:remove', async (data: IPayload, callback: (response: boolean) => void) => {
if (socket.character?.role !== 'gm') {
return
}
if (socket.character?.role !== 'gm') {
return;
try {
await prisma.object.delete({
where: {
id: data.object
}
})
// get root path
const public_folder = path.join(process.cwd(), 'public', 'objects')
// remove the tile from the disk
const finalFilePath = path.join(public_folder, data.object + '.png')
fs.unlink(finalFilePath, (err) => {
if (err) {
console.log(err)
callback(false)
return
}
try {
await ObjectRepository.delete(data.object);
// get root path
const public_folder = path.join(process.cwd(), 'public', 'objects');
// remove the tile from the disk
const finalFilePath = path.join(public_folder, data.object + '.png');
fs.unlink(finalFilePath, (err) => {
if (err) {
console.log(err);
callback(false);
return;
}
callback(true);
});
} catch (e) {
console.log(e);
callback(false);
}
});
callback(true)
})
} catch (e) {
console.log(e)
callback(false)
}
})
}

View File

@ -1,14 +1,14 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import ObjectRepository from '../../../repositories/ObjectRepository'
import { Object } from '@prisma/client'
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import prisma from '../../../utilities/Prisma'
interface IPayload {
id: string;
name: string;
tags: string[];
origin_x: number;
origin_y: number;
id: string
name: string
tags: string[]
origin_x: number
origin_y: number
isAnimated: boolean
}
/**
@ -18,18 +18,27 @@ interface IPayload {
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:object:update', async (data: IPayload, callback: (success: boolean) => void) => {
if (socket.character?.role !== 'gm') {
return;
return
}
try {
const object = await ObjectRepository.update(data.id, data.name, data.tags, data.origin_x, data.origin_y);
callback(true);
const object = await prisma.object.update({
where: {
id: data.id
},
data: {
name: data.name,
tags: data.tags,
origin_x: data.origin_x,
origin_y: data.origin_y,
isAnimated: data.isAnimated
}
})
callback(true)
} catch (error) {
console.error(error);
callback(false);
console.error(error)
callback(false)
}
});
})
}

View File

@ -1,12 +1,12 @@
import { Server } from "socket.io";
import { TSocket } from "../../../utilities/Types";
import { writeFile } from "node:fs/promises";
import path from "path";
import fs from "fs/promises";
import objectRepository from '../../../repositories/ObjectRepository'
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import { writeFile } from 'node:fs/promises'
import path from 'path'
import fs from 'fs/promises'
import prisma from '../../../utilities/Prisma'
interface IObjectData {
[key: string]: Buffer;
[key: string]: Buffer
}
/**
@ -15,32 +15,41 @@ interface IObjectData {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:object:upload', async (data: IObjectData, callback: (response: boolean) => void) => {
try {
if (socket.character?.role !== 'gm') {
callback(false);
return;
}
socket.on('gm:object:upload', async (data: IObjectData, callback: (response: boolean) => void) => {
try {
if (socket.character?.role !== 'gm') {
callback(false)
return
}
const public_folder = path.join(process.cwd(), 'public', 'objects');
const public_folder = path.join(process.cwd(), 'public', 'objects')
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true });
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true })
const uploadPromises = Object.entries(data).map(async ([key, objectData]) => {
const object = await objectRepository.create('New object', 0, 0);
const uuid = object.id;
const filename = `${uuid}.png`;
const finalFilePath = path.join(public_folder, filename);
await writeFile(finalFilePath, objectData);
});
const uploadPromises = Object.entries(data).map(async ([key, objectData]) => {
const object = await prisma.object.create({
data: {
name: key,
tags: [],
origin_x: 0,
origin_y: 0,
isAnimated: false
}
})
await Promise.all(uploadPromises);
const uuid = object.id
const filename = `${uuid}.png`
const finalFilePath = path.join(public_folder, filename)
await writeFile(finalFilePath, objectData)
})
callback(true);
} catch (error) {
console.error('Error uploading tile:', error);
callback(false);
}
});
await Promise.all(uploadPromises)
callback(true)
} catch (error) {
console.error('Error uploading tile:', error)
callback(false)
}
})
}

View File

@ -1,10 +1,9 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import { Sprite } from '@prisma/client'
import SpriteRepository from '../../../repositories/SpriteRepository'
interface IPayload {
}
interface IPayload {}
/**
* Handle game master list sprite event
@ -12,15 +11,14 @@ interface IPayload {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:sprite:list', async (data: any, callback: (response: Sprite[]) => void) => {
socket.on('gm:sprite:list', async (data: any, callback: (response: Sprite[]) => void) => {
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`)
return
}
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`);
return;
}
// get all sprites
const sprites = await SpriteRepository.getAll();
callback(sprites);
});
// get all sprites
const sprites = await SpriteRepository.getAll()
callback(sprites)
})
}

View File

@ -1,11 +1,11 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import path from "path";
import fs from "fs";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import path from 'path'
import fs from 'fs'
import SpriteRepository from '../../../repositories/SpriteRepository'
interface IPayload {
sprite: string;
sprite: string
}
/**
@ -14,32 +14,31 @@ interface IPayload {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:sprite:remove', async (data: IPayload, callback: (response: boolean) => void) => {
socket.on('gm:sprite:remove', async (data: IPayload, callback: (response: boolean) => void) => {
if (socket.character?.role !== 'gm') {
return
}
if (socket.character?.role !== 'gm') {
return;
try {
await SpriteRepository.delete(data.sprite)
// get root path
const public_folder = path.join(process.cwd(), 'public', 'sprites')
// remove the tile from the disk
const finalFilePath = path.join(public_folder, data.sprite + '.png')
fs.unlink(finalFilePath, (err) => {
if (err) {
console.log(err)
callback(false)
return
}
try {
await SpriteRepository.delete(data.sprite);
// get root path
const public_folder = path.join(process.cwd(), 'public', 'sprites');
// remove the tile from the disk
const finalFilePath = path.join(public_folder, data.sprite + '.png');
fs.unlink(finalFilePath, (err) => {
if (err) {
console.log(err);
callback(false);
return;
}
callback(true);
});
} catch (e) {
console.log(e);
callback(false);
}
});
callback(true)
})
} catch (e) {
console.log(e)
callback(false)
}
})
}

View File

@ -1,13 +1,13 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import SpriteRepository from '../../../repositories/SpriteRepository'
import { Sprite } from '@prisma/client'
interface IPayload {
id: string;
name: string;
origin_x: number;
origin_y: number;
id: string
name: string
origin_x: number
origin_y: number
}
/**
@ -17,18 +17,17 @@ interface IPayload {
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:sprite:update', async (data: IPayload, callback: (success: boolean) => void) => {
if (socket.character?.role !== 'gm') {
return;
return
}
try {
const sprite = await SpriteRepository.update(data.id, data.name, data.origin_x, data.origin_y);
const sprite = await SpriteRepository.update(data.id, data.name, data.origin_x, data.origin_y)
callback(true);
callback(true)
} catch (error) {
console.error(error);
callback(false);
console.error(error)
callback(false)
}
});
})
}

View File

@ -1,12 +1,12 @@
import { Server } from "socket.io";
import { TSocket } from "../../../utilities/Types";
import { writeFile } from "node:fs/promises";
import path from "path";
import fs from "fs/promises";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import { writeFile } from 'node:fs/promises'
import path from 'path'
import fs from 'fs/promises'
import spriteRepository from '../../../repositories/SpriteRepository'
interface ISpriteData {
[key: string]: Buffer;
[key: string]: Buffer
}
/**
@ -15,32 +15,32 @@ interface ISpriteData {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:sprite:upload', async (data: ISpriteData, callback: (response: boolean) => void) => {
try {
if (socket.character?.role !== 'gm') {
callback(false);
return;
}
socket.on('gm:sprite:upload', async (data: ISpriteData, callback: (response: boolean) => void) => {
try {
if (socket.character?.role !== 'gm') {
callback(false)
return
}
const public_folder = path.join(process.cwd(), 'public', 'sprites');
const public_folder = path.join(process.cwd(), 'public', 'sprites')
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true });
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true })
const uploadPromises = Object.entries(data).map(async ([key, spriteData]) => {
const sprite = await spriteRepository.create('New sprite', 0, 0);
const uuid = sprite.id;
const filename = `${uuid}.png`;
const finalFilePath = path.join(public_folder, filename);
await writeFile(finalFilePath, spriteData);
});
const uploadPromises = Object.entries(data).map(async ([key, spriteData]) => {
const sprite = await spriteRepository.create('New sprite', 0, 0)
const uuid = sprite.id
const filename = `${uuid}.png`
const finalFilePath = path.join(public_folder, filename)
await writeFile(finalFilePath, spriteData)
})
await Promise.all(uploadPromises);
await Promise.all(uploadPromises)
callback(true);
} catch (error) {
console.error('Error uploading tile:', error);
callback(false);
}
});
callback(true)
} catch (error) {
console.error('Error uploading tile:', error)
callback(false)
}
})
}

View File

@ -1,10 +1,9 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import { Tile } from '@prisma/client'
import TileRepository from '../../../repositories/TileRepository'
interface IPayload {
}
interface IPayload {}
/**
* Handle game master list tile event
@ -12,15 +11,14 @@ interface IPayload {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:tile:list', async (data: any, callback: (response: Tile[]) => void) => {
socket.on('gm:tile:list', async (data: any, callback: (response: Tile[]) => void) => {
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`)
return
}
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`);
return;
}
// get all tiles
const tiles = await TileRepository.getAll();
callback(tiles);
});
// get all tiles
const tiles = await TileRepository.getAll()
callback(tiles)
})
}

View File

@ -1,11 +1,11 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import path from "path";
import fs from "fs";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import path from 'path'
import fs from 'fs'
import TileRepository from '../../../repositories/TileRepository'
interface IPayload {
tile: string;
tile: string
}
/**
@ -14,32 +14,31 @@ interface IPayload {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:tile:remove', async (data: IPayload, callback: (response: boolean) => void) => {
socket.on('gm:tile:remove', async (data: IPayload, callback: (response: boolean) => void) => {
if (socket.character?.role !== 'gm') {
return
}
if (socket.character?.role !== 'gm') {
return;
try {
await TileRepository.delete(data.tile)
// get root path
const public_folder = path.join(process.cwd(), 'public', 'tiles')
// remove the tile from the disk
const finalFilePath = path.join(public_folder, data.tile + '.png')
fs.unlink(finalFilePath, (err) => {
if (err) {
console.log(err)
callback(false)
return
}
try {
await TileRepository.delete(data.tile);
// get root path
const public_folder = path.join(process.cwd(), 'public', 'tiles');
// remove the tile from the disk
const finalFilePath = path.join(public_folder, data.tile + '.png');
fs.unlink(finalFilePath, (err) => {
if (err) {
console.log(err);
callback(false);
return;
}
callback(true);
});
} catch (e) {
console.log(e);
callback(false);
}
});
callback(true)
})
} catch (e) {
console.log(e)
callback(false)
}
})
}

View File

@ -1,11 +1,11 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import TileRepository from '../../../repositories/TileRepository'
interface IPayload {
id: string;
name: string;
tags: string[];
id: string
name: string
tags: string[]
}
/**
@ -15,18 +15,17 @@ interface IPayload {
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:tile:update', async (data: IPayload, callback: (success: boolean) => void) => {
if (socket.character?.role !== 'gm') {
return;
return
}
try {
const Tile = await TileRepository.update(data.id, data.name, data.tags);
const Tile = await TileRepository.update(data.id, data.name, data.tags)
callback(true);
callback(true)
} catch (error) {
console.error(error);
callback(false);
console.error(error)
callback(false)
}
});
})
}

View File

@ -1,12 +1,12 @@
import { Server } from "socket.io";
import { TSocket } from "../../../utilities/Types";
import { writeFile } from "node:fs/promises";
import path from "path";
import fs from "fs/promises";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import { writeFile } from 'node:fs/promises'
import path from 'path'
import fs from 'fs/promises'
import tileRepository from '../../../repositories/TileRepository'
interface ITileData {
[key: string]: Buffer;
[key: string]: Buffer
}
/**
@ -15,32 +15,32 @@ interface ITileData {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:tile:upload', async (data: ITileData, callback: (response: boolean) => void) => {
try {
if (socket.character?.role !== 'gm') {
callback(false);
return;
}
socket.on('gm:tile:upload', async (data: ITileData, callback: (response: boolean) => void) => {
try {
if (socket.character?.role !== 'gm') {
callback(false)
return
}
const public_folder = path.join(process.cwd(), 'public', 'tiles');
const public_folder = path.join(process.cwd(), 'public', 'tiles')
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true });
// Ensure the folder exists
await fs.mkdir(public_folder, { recursive: true })
const uploadPromises = Object.entries(data).map(async ([key, tileData]) => {
const tile = await tileRepository.create('New tile');
const uuid = tile.id;
const filename = `${uuid}.png`;
const finalFilePath = path.join(public_folder, filename);
await writeFile(finalFilePath, tileData);
});
const uploadPromises = Object.entries(data).map(async ([key, tileData]) => {
const tile = await tileRepository.create('New tile')
const uuid = tile.id
const filename = `${uuid}.png`
const finalFilePath = path.join(public_folder, filename)
await writeFile(finalFilePath, tileData)
})
await Promise.all(uploadPromises);
await Promise.all(uploadPromises)
callback(true);
} catch (error) {
console.error('Error uploading tile:', error);
callback(false);
}
});
callback(true)
} catch (error) {
console.error('Error uploading tile:', error)
callback(false)
}
})
}

View File

@ -1,13 +1,13 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import ZoneRepository from "../../../repositories/ZoneRepository";
import ZoneManager from "../../../managers/ZoneManager";
import {Character, Zone} from "@prisma/client";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import ZoneRepository from '../../../repositories/ZoneRepository'
import ZoneManager from '../../../managers/ZoneManager'
import { Character, Zone } from '@prisma/client'
interface IPayload {
name: string;
width: number;
height: number;
name: string
width: number
height: number
}
/**
@ -16,30 +16,29 @@ interface IPayload {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:zone_editor:zone:create', async (data: IPayload, callback: (response: Zone[]) => void) => {
socket.on('gm:zone_editor:zone:create', async (data: IPayload, callback: (response: Zone[]) => void) => {
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`)
return
}
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`);
return;
}
console.log(`---GM ${socket.character?.id} has created a new zone via zone editor.`)
let zoneList: Zone[] = []
try {
const zone = await ZoneRepository.create(
data.name,
data.width,
data.height,
Array.from({ length: data.height }, () => Array.from({ length: data.width }, () => 'blank_tile'))
)
console.log(`---GM ${socket.character?.id} has created a new zone via zone editor.`);
let zoneList: Zone[] = [];
try {
const zone = await ZoneRepository.create(
data.name,
data.width,
data.height,
Array.from({length: data.height}, () => Array.from({length: data.width}, () => 'blank_tile')),
);
zoneList = await ZoneRepository.getAll();
callback(zoneList);
// send over zone and characters to socket
} catch (e) {
console.error(e);
socket.emit('notification', {message: 'Failed to create zone.'});
callback(zoneList);
}
});
zoneList = await ZoneRepository.getAll()
callback(zoneList)
// send over zone and characters to socket
} catch (e) {
console.error(e)
socket.emit('notification', { message: 'Failed to create zone.' })
callback(zoneList)
}
})
}

View File

@ -1,11 +1,11 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import ZoneRepository from "../../../repositories/ZoneRepository";
import ZoneManager from "../../../managers/ZoneManager";
import {Character, Zone} from "@prisma/client";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import ZoneRepository from '../../../repositories/ZoneRepository'
import ZoneManager from '../../../managers/ZoneManager'
import { Character, Zone } from '@prisma/client'
interface IPayload {
zoneId: number;
zoneId: number
}
/**
@ -14,29 +14,28 @@ interface IPayload {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:zone_editor:zone:delete', async (data: IPayload, callback: (response: boolean) => void) => {
socket.on('gm:zone_editor:zone:delete', async (data: IPayload, callback: (response: boolean) => void) => {
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`)
return
}
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`);
return;
}
console.log(`---GM ${socket.character?.id} has deleted a zone via zone editor.`)
console.log(`---GM ${socket.character?.id} has deleted a zone via zone editor.`);
try {
const zone = await ZoneRepository.getById(data.zoneId)
try {
const zone = await ZoneRepository.getById(data.zoneId);
if (!zone) {
console.log(`---Zone not found.`)
return
}
if (!zone) {
console.log(`---Zone not found.`);
return;
}
await ZoneRepository.delete(data.zoneId)
await ZoneRepository.delete(data.zoneId);
callback(true);
} catch (e) {
console.error(e);
callback(false);
}
});
callback(true)
} catch (e) {
console.error(e)
callback(false)
}
})
}

View File

@ -1,5 +1,5 @@
import { Server } from "socket.io";
import {TSocket} from '../../../utilities/Types'
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import { Zone } from '@prisma/client'
import ZoneRepository from '../../../repositories/ZoneRepository'
@ -11,21 +11,20 @@ interface IPayload {}
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:zone_editor:zone:list', async (data: IPayload, callback: (response: Zone[]) => void) => {
socket.on('gm:zone_editor:zone:list', async (data: IPayload, callback: (response: Zone[]) => void) => {
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`)
return
}
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`);
return;
}
console.log(`---GM ${socket.character?.id} has requested zone list via zone editor.`)
console.log(`---GM ${socket.character?.id} has requested zone list via zone editor.`);
try {
const zones = await ZoneRepository.getAll();
callback(zones);
} catch (e) {
console.error(e);
callback([]);
}
});
try {
const zones = await ZoneRepository.getAll()
callback(zones)
} catch (e) {
console.error(e)
callback([])
}
})
}

View File

@ -1,10 +1,10 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import ZoneRepository from "../../../repositories/ZoneRepository";
import {Zone} from "@prisma/client";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import ZoneRepository from '../../../repositories/ZoneRepository'
import { Zone } from '@prisma/client'
interface IPayload {
zoneId: number;
zoneId: number
}
/**
@ -13,30 +13,29 @@ interface IPayload {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:zone_editor:zone:request', async (data: IPayload, callback: (response: Zone) => void) => {
socket.on('gm:zone_editor:zone:request', async (data: IPayload, callback: (response: Zone) => void) => {
if (socket.character?.role !== 'gm') {
return
}
if (socket.character?.role !== 'gm') {
return;
}
console.log(`---GM ${socket.character?.id} has requested zone via zone editor.`)
console.log(`---GM ${socket.character?.id} has requested zone via zone editor.`);
if (!data.zoneId) {
console.log(`---Zone id not provided.`)
return
}
if (!data.zoneId) {
console.log(`---Zone id not provided.`);
return;
}
try {
const zone = await ZoneRepository.getById(data.zoneId)
try {
const zone = await ZoneRepository.getById(data.zoneId);
if (!zone) {
console.log(`---Zone not found.`)
return
}
if (!zone) {
console.log(`---Zone not found.`);
return;
}
callback(zone);
} catch (e) {
console.error(e);
}
});
callback(zone)
} catch (e) {
console.error(e)
}
})
}

View File

@ -1,17 +1,17 @@
import { Server } from "socket.io";
import {TSocket} from "../../../utilities/Types";
import ZoneRepository from "../../../repositories/ZoneRepository";
import ZoneManager from "../../../managers/ZoneManager";
import { Server } from 'socket.io'
import { TSocket } from '../../../utilities/Types'
import ZoneRepository from '../../../repositories/ZoneRepository'
import ZoneManager from '../../../managers/ZoneManager'
import { Character, Zone, ZoneEventTile, ZoneObject } from '@prisma/client'
interface IPayload {
zoneId: number;
name: string;
width: number;
height: number;
tiles: string[][];
zoneEventTiles: ZoneEventTile[];
zoneObjects: ZoneObject[];
zoneId: number
name: string
width: number
height: number
tiles: string[][]
zoneEventTiles: ZoneEventTile[]
zoneObjects: ZoneObject[]
}
/**
@ -20,44 +20,35 @@ interface IPayload {
* @param io
*/
export default function (socket: TSocket, io: Server) {
socket.on('gm:zone_editor:zone:update', async (data: IPayload) => {
socket.on('gm:zone_editor:zone:update', async (data: IPayload) => {
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`)
return
}
if (socket.character?.role !== 'gm') {
console.log(`---Character #${socket.character?.id} is not a game master.`);
return;
}
console.log(`---GM ${socket.character?.id} has updated zone via zone editor.`)
console.log(`---GM ${socket.character?.id} has updated zone via zone editor.`);
if (!data.zoneId) {
console.log(`---Zone id not provided.`)
return
}
if (!data.zoneId) {
console.log(`---Zone id not provided.`);
return;
}
try {
let zone = await ZoneRepository.getById(data.zoneId)
try {
let zone = await ZoneRepository.getById(data.zoneId);
if (!zone) {
console.log(`---Zone not found.`)
return
}
if (!zone) {
console.log(`---Zone not found.`);
return;
}
await ZoneRepository.update(data.zoneId, data.name, data.width, data.height, data.tiles, data.zoneEventTiles, data.zoneObjects)
await ZoneRepository.update(
data.zoneId,
data.name,
data.width,
data.height,
data.tiles,
data.zoneEventTiles,
data.zoneObjects
);
zone = await ZoneRepository.getById(data.zoneId)
zone = await ZoneRepository.getById(data.zoneId);
// send over zone and characters to socket
socket.emit('gm:zone_editor:zone:load', zone);
} catch (error: any) {
console.log(`---Error updating zone: ${error.message}`);
}
});
// send over zone and characters to socket
socket.emit('gm:zone_editor:zone:load', zone)
} catch (error: any) {
console.log(`---Error updating zone: ${error.message}`)
}
})
}

View File

@ -1,98 +1,95 @@
import * as readline from 'readline';
import * as fs from 'fs';
import * as path from 'path';
import { Server } from 'socket.io';
import * as readline from 'readline'
import * as fs from 'fs'
import * as path from 'path'
import { Server } from 'socket.io'
class CommandManager {
private commands: Map<string, Function> = new Map();
private rl: readline.Interface;
private io: Server | null = null;
private rlClosed: boolean = false;
private commands: Map<string, Function> = new Map()
private rl: readline.Interface
private io: Server | null = null
private rlClosed: boolean = false
constructor() {
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
constructor() {
this.rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
this.rl.on('close', () => {
this.rlClosed = true;
});
this.rl.on('close', () => {
this.rlClosed = true
})
}
public async boot(io: Server) {
this.io = io
await this.loadCommands()
console.log('[✅] Command manager loaded')
this.startPrompt()
}
private startPrompt() {
if (this.rlClosed) return
this.rl.question('> ', (command: string) => {
this.processCommand(command)
this.startPrompt()
})
}
private async processCommand(command: string): Promise<void> {
const [cmd, ...args] = command.trim().split(' ')
if (this.commands.has(cmd)) {
this.commands.get(cmd)?.(args, this.io as Server)
} else {
this.handleUnknownCommand(cmd)
}
}
public async boot(io: Server) {
this.io = io;
await this.loadCommands();
console.log('[✅] Command manager loaded');
this.startPrompt();
private handleUnknownCommand(command: string) {
switch (command) {
case 'exit':
console.log('Goodbye!')
this.rl.close()
break
default:
console.error(`Unknown command: ${command}`)
break
}
}
private startPrompt() {
if (this.rlClosed) return;
private async loadCommands() {
const commandsDir = path.resolve(__dirname, 'commands')
try {
const files: string[] = await fs.promises.readdir(commandsDir)
this.rl.question('> ', (command: string) => {
this.processCommand(command);
this.startPrompt();
});
for (const file of files) {
await this.loadCommand(commandsDir, file)
}
} catch (error) {
console.error('[❌] Failed to read commands directory:', error)
}
}
private async processCommand(command: string): Promise<void> {
const [cmd, ...args] = command.trim().split(' ');
if (this.commands.has(cmd)) {
this.commands.get(cmd)?.(args, this.io as Server);
} else {
this.handleUnknownCommand(cmd);
}
private async loadCommand(commandsDir: string, file: string) {
try {
const ext = path.extname(file)
const commandName = path.basename(file, ext)
const commandPath = path.join(commandsDir, file)
const module = await import(commandPath)
this.registerCommand(commandName, module.default)
} catch (error) {
console.error('[❌] Failed to load command:', file, error)
}
}
private handleUnknownCommand(command: string) {
switch (command) {
case 'exit':
console.log('Goodbye!');
this.rl.close();
break;
default:
console.error(`Unknown command: ${command}`);
break;
}
}
private async loadCommands() {
const commandsDir = path.resolve(__dirname, 'commands');
try {
const files: string[] = await fs.promises.readdir(commandsDir);
for (const file of files) {
await this.loadCommand(commandsDir, file);
}
} catch (error) {
console.error('[❌] Failed to read commands directory:', error);
}
}
private async loadCommand(commandsDir: string, file: string) {
try {
const ext = path.extname(file);
const commandName = path.basename(file, ext);
const commandPath = path.join(commandsDir, file);
const module = await import(commandPath);
this.registerCommand(commandName, module.default);
} catch (error) {
console.error('[❌] Failed to load command:', file, error);
}
}
private registerCommand(
name: string,
command: (args: string[], io: Server) => void
) {
if (this.commands.has(name)) {
console.warn(`Command '${name}' is already registered. Overwriting...`);
}
this.commands.set(name, command);
console.log(`Registered command: ${name}`);
private registerCommand(name: string, command: (args: string[], io: Server) => void) {
if (this.commands.has(name)) {
console.warn(`Command '${name}' is already registered. Overwriting...`)
}
this.commands.set(name, command)
console.log(`Registered command: ${name}`)
}
}
export default new CommandManager();
export default new CommandManager()

View File

@ -1,37 +1,37 @@
import {User} from "@prisma/client";
import { User } from '@prisma/client'
type TLoggedInUsers = {
users: User[];
users: User[]
}
class UserManager {
private loggedInUsers: TLoggedInUsers[] = [];
private loggedInUsers: TLoggedInUsers[] = []
// Method to initialize user manager
public async boot() {
console.log('[✅] User manager loaded');
}
// Method to initialize user manager
public async boot() {
console.log('[✅] User manager loaded')
}
// Function that adds user to logged in users
public loginUser(user: User) {
this.loggedInUsers.push({
users: [user]
});
}
// Function that adds user to logged in users
public loginUser(user: User) {
this.loggedInUsers.push({
users: [user]
})
}
// Function that checks if a user is already logged in
public findUser(user: User) {
return this.loggedInUsers.find((loggedInUser) => {
return loggedInUser.users.includes(user);
});
}
// Function that checks if a user is already logged in
public findUser(user: User) {
return this.loggedInUsers.find((loggedInUser) => {
return loggedInUser.users.includes(user)
})
}
// Function that lists all logged in users
public listUsers() {
return this.loggedInUsers.map((loggedInUser) => {
return loggedInUser.users;
});
}
// Function that lists all logged in users
public listUsers() {
return this.loggedInUsers.map((loggedInUser) => {
return loggedInUser.users
})
}
}
export default new UserManager();
export default new UserManager()

View File

@ -1,93 +1,93 @@
import {Character, Zone} from "@prisma/client";
import ZoneRepository from "../repositories/ZoneRepository";
import ZoneService from "../services/ZoneService";
import { Character, Zone } from '@prisma/client'
import ZoneRepository from '../repositories/ZoneRepository'
import ZoneService from '../services/ZoneService'
type TLoadedZone = {
zone: Zone;
characters: Character[];
zone: Zone
characters: Character[]
}
class ZoneManager {
private loadedZones: TLoadedZone[] = [];
private loadedZones: TLoadedZone[] = []
// Method to initialize zone manager
public async boot() {
if (!await ZoneRepository.getById(1)) {
const zoneService = new ZoneService();
await zoneService.createDemoZone();
}
const zones = await ZoneRepository.getAll();
for (const zone of zones) {
this.loadZone(zone);
}
console.log('[✅] Zone manager loaded');
// Method to initialize zone manager
public async boot() {
if (!(await ZoneRepository.getById(1))) {
const zoneService = new ZoneService()
await zoneService.createDemoZone()
}
// Method to handle individual zone loading
public loadZone(zone: Zone) {
this.loadedZones.push({
zone,
characters: []
});
console.log(`[✅] Zone ID ${zone.id} loaded`);
const zones = await ZoneRepository.getAll()
for (const zone of zones) {
this.loadZone(zone)
}
// Method to handle individual zone unloading
public unloadZone(zoneId: number) {
this.loadedZones = this.loadedZones.filter((loadedZone) => {
return loadedZone.zone.id !== zoneId;
});
console.log(`[❌] Zone ID ${zoneId} unloaded`);
}
console.log('[✅] Zone manager loaded')
}
// Getter for loaded zones
public getLoadedZones(): TLoadedZone[] {
return this.loadedZones;
}
// Method to handle individual zone loading
public loadZone(zone: Zone) {
this.loadedZones.push({
zone,
characters: []
})
console.log(`[✅] Zone ID ${zone.id} loaded`)
}
public addCharacterToZone(zoneId: number, character: Character) {
const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId;
});
if (loadedZone) {
loadedZone.characters.push(character);
}
}
// Method to handle individual zone unloading
public unloadZone(zoneId: number) {
this.loadedZones = this.loadedZones.filter((loadedZone) => {
return loadedZone.zone.id !== zoneId
})
console.log(`[❌] Zone ID ${zoneId} unloaded`)
}
public removeCharacterFromZone(zoneId: number, character: Character) {
const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId;
});
if (loadedZone) {
loadedZone.characters = loadedZone.characters.filter((loadedCharacter) => {
return loadedCharacter.id !== character.id;
});
}
}
// Getter for loaded zones
public getLoadedZones(): TLoadedZone[] {
return this.loadedZones
}
public updateCharacterInZone(zoneId: number, character: Character) {
const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId;
});
if (loadedZone) {
const characterIndex = loadedZone.characters.findIndex((loadedCharacter) => {
return loadedCharacter.id === character.id;
});
if (characterIndex !== -1) {
loadedZone.characters[characterIndex] = character;
}
}
public addCharacterToZone(zoneId: number, character: Character) {
const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId
})
if (loadedZone) {
loadedZone.characters.push(character)
}
}
public getCharactersInZone(zoneId: number): Character[] {
const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId;
});
return loadedZone ? loadedZone.characters : [];
public removeCharacterFromZone(zoneId: number, character: Character) {
const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId
})
if (loadedZone) {
loadedZone.characters = loadedZone.characters.filter((loadedCharacter) => {
return loadedCharacter.id !== character.id
})
}
}
public updateCharacterInZone(zoneId: number, character: Character) {
const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId
})
if (loadedZone) {
const characterIndex = loadedZone.characters.findIndex((loadedCharacter) => {
return loadedCharacter.id === character.id
})
if (characterIndex !== -1) {
loadedZone.characters[characterIndex] = character
}
}
}
public getCharactersInZone(zoneId: number): Character[] {
const loadedZone = this.loadedZones.find((loadedZone) => {
return loadedZone.zone.id === zoneId
})
return loadedZone ? loadedZone.characters : []
}
}
export default new ZoneManager;
export default new ZoneManager()

View File

@ -1,37 +1,36 @@
// socket io jwt auth middleware
import { verify } from 'jsonwebtoken';
import { TSocket } from '../utilities/Types';
import config from "../utilities/Config";
import UserRepository from "../repositories/UserRepository";
import {User} from "@prisma/client";
import { verify } from 'jsonwebtoken'
import { TSocket } from '../utilities/Types'
import config from '../utilities/Config'
import UserRepository from '../repositories/UserRepository'
import { User } from '@prisma/client'
export async function Authentication (socket: TSocket, next: any)
{
if (!socket.request.headers.cookie) {
console.log('No cookie provided');
return next(new Error('Authentication error'));
}
export async function Authentication(socket: TSocket, next: any) {
if (!socket.request.headers.cookie) {
console.log('No cookie provided')
return next(new Error('Authentication error'))
}
const cookies = socket.request.headers.cookie.split('; ').reduce((prev: any, current: any) => {
const [name, value] = current.split('=');
prev[name] = value;
return prev;
}, {});
const cookies = socket.request.headers.cookie.split('; ').reduce((prev: any, current: any) => {
const [name, value] = current.split('=')
prev[name] = value
return prev
}, {})
const token = cookies['token'];
const token = cookies['token']
if (token) {
verify(token, config.JWT_SECRET, async (err: any, decoded: any) => {
if (err) {
console.log('err');
return next(new Error('Authentication error'));
}
if (token) {
verify(token, config.JWT_SECRET, async (err: any, decoded: any) => {
if (err) {
console.log('err')
return next(new Error('Authentication error'))
}
socket.user = await UserRepository.getById(decoded.id) as User;
next();
});
} else {
console.log('No token provided');
next(new Error('Authentication error'));
}
socket.user = (await UserRepository.getById(decoded.id)) as User
next()
})
} else {
console.log('No token provided')
next(new Error('Authentication error'))
}
}

View File

@ -1,125 +1,125 @@
import prisma from '../utilities/Prisma'; // Import the global Prisma instance
import {Character} from '@prisma/client';
import prisma from '../utilities/Prisma' // Import the global Prisma instance
import { Character } from '@prisma/client'
class CharacterRepository {
async getByUserId(userId: number): Promise<Character[] | null> {
try {
return await prisma.character.findMany({
where: {
userId,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to get character by user ID: ${error.message}`);
async getByUserId(userId: number): Promise<Character[] | null> {
try {
return await prisma.character.findMany({
where: {
userId
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to get character by user ID: ${error.message}`)
}
}
async getByUserAndId(userId: number, characterId: number): Promise<Character | null> {
try {
return await prisma.character.findFirst({
where: {
userId,
id: characterId,
},
include: {
zone: true
}
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to get character by user ID and character ID: ${error.message}`);
async getByUserAndId(userId: number, characterId: number): Promise<Character | null> {
try {
return await prisma.character.findFirst({
where: {
userId,
id: characterId
},
include: {
zone: true
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to get character by user ID and character ID: ${error.message}`)
}
}
async getById(id: number): Promise<Character | null> {
try {
return await prisma.character.findUnique({
where: {
id,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to get character by ID: ${error.message}`);
async getById(id: number): Promise<Character | null> {
try {
return await prisma.character.findUnique({
where: {
id
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to get character by ID: ${error.message}`)
}
}
async create(userId: number, name: string, role: 'player'): Promise<Character | null> {
try {
return await prisma.character.create({
data: {
userId,
name,
role,
position_x: 0, // @TODO Set default registration values in the database
position_y: 0, // @TODO Set default registration values in the database
rotation: 0, // @TODO Set default registration values in the database
zoneId: 1, // @TODO Set default registration values in the database
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to create character: ${error.message}`);
async create(userId: number, name: string, role: 'player'): Promise<Character | null> {
try {
return await prisma.character.create({
data: {
userId,
name,
role,
position_x: 0, // @TODO Set default registration values in the database
position_y: 0, // @TODO Set default registration values in the database
rotation: 0, // @TODO Set default registration values in the database
zoneId: 1 // @TODO Set default registration values in the database
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to create character: ${error.message}`)
}
}
async updatePosition(id:number, position_x: number, position_y: number): Promise<Character | null> {
try {
return await prisma.character.update({
where: {
id: id,
},
data: {
position_x,
position_y,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to update character: ${error.message}`);
async updatePosition(id: number, position_x: number, position_y: number): Promise<Character | null> {
try {
return await prisma.character.update({
where: {
id: id
},
data: {
position_x,
position_y
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to update character: ${error.message}`)
}
}
async delete(id: number): Promise<Character | null> {
try {
return await prisma.character.delete({
where: {
id,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to delete character: ${error.message}`);
async delete(id: number): Promise<Character | null> {
try {
return await prisma.character.delete({
where: {
id
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to delete character: ${error.message}`)
}
}
async deleteByUserIdAndId(userId: number, characterId: number): Promise<Character | null> {
try {
return await prisma.character.delete({
where: {
userId,
id: characterId,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to delete character by user ID and character ID: ${error.message}`);
async deleteByUserIdAndId(userId: number, characterId: number): Promise<Character | null> {
try {
return await prisma.character.delete({
where: {
userId,
id: characterId
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to delete character by user ID and character ID: ${error.message}`)
}
}
async getByName(name: string): Promise<Character | null> {
try {
return await prisma.character.findFirst({
where: {
name,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to get character by name: ${error.message}`);
async getByName(name: string): Promise<Character | null> {
try {
return await prisma.character.findFirst({
where: {
name
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to get character by name: ${error.message}`)
}
}
}
export default new CharacterRepository;
export default new CharacterRepository()

View File

@ -1,44 +1,16 @@
import prisma from '../utilities/Prisma'; // Import the global Prisma instance
import prisma from '../utilities/Prisma' // Import the global Prisma instance
import { Object } from '@prisma/client'
class ObjectRepository {
async getById(id: string): Promise<Object | null> {
return prisma.object.findUnique({
where: { id },
});
}
async getById(id: string): Promise<Object | null> {
return prisma.object.findUnique({
where: { id }
})
}
async getAll(): Promise<Object[]> {
return prisma.object.findMany();
}
async create(name: string, origin_x: number, origin_y: number): Promise<Object> {
return prisma.object.create({
data: {
name,
origin_x,
origin_y
},
});
}
async update(id: string, name: string, tags: string[], origin_x: number, origin_y: number): Promise<Object> {
return prisma.object.update({
where: { id },
data: {
name,
tags,
origin_x,
origin_y
},
});
}
async delete(id: string): Promise<Object> {
return prisma.object.delete({
where: { id },
});
}
async getAll(): Promise<Object[]> {
return prisma.object.findMany()
}
}
export default new ObjectRepository();
export default new ObjectRepository()

View File

@ -1,43 +1,43 @@
import prisma from '../utilities/Prisma'; // Import the global Prisma instance
import prisma from '../utilities/Prisma' // Import the global Prisma instance
import { Sprite } from '@prisma/client'
class SpriteRepository {
async getById(id: string): Promise<Sprite | null> {
return prisma.sprite.findUnique({
where: { id },
});
}
async getById(id: string): Promise<Sprite | null> {
return prisma.sprite.findUnique({
where: { id }
})
}
async getAll(): Promise<Sprite[]> {
return prisma.sprite.findMany();
}
async getAll(): Promise<Sprite[]> {
return prisma.sprite.findMany()
}
async create(name: string, origin_x: number, origin_y: number): Promise<Sprite> {
return prisma.sprite.create({
data: {
name,
origin_x,
origin_y
},
});
}
async create(name: string, origin_x: number, origin_y: number): Promise<Sprite> {
return prisma.sprite.create({
data: {
name,
origin_x,
origin_y
}
})
}
async update(id: string, name: string, origin_x: number, origin_y: number): Promise<Sprite> {
return prisma.sprite.update({
where: { id },
data: {
name,
origin_x,
origin_y
},
});
}
async update(id: string, name: string, origin_x: number, origin_y: number): Promise<Sprite> {
return prisma.sprite.update({
where: { id },
data: {
name,
origin_x,
origin_y
}
})
}
async delete(id: string): Promise<Sprite> {
return prisma.sprite.delete({
where: { id },
});
}
async delete(id: string): Promise<Sprite> {
return prisma.sprite.delete({
where: { id }
})
}
}
export default new SpriteRepository();
export default new SpriteRepository()

View File

@ -1,47 +1,47 @@
import prisma from '../utilities/Prisma'; // Import the global Prisma instance
import prisma from '../utilities/Prisma' // Import the global Prisma instance
import { Tile } from '@prisma/client'
class TileRepository {
async getById(id: string): Promise<Tile | null> {
return prisma.tile.findUnique({
where: { id },
});
}
async getById(id: string): Promise<Tile | null> {
return prisma.tile.findUnique({
where: { id }
})
}
async getAll(): Promise<Tile[]> {
return prisma.tile.findMany();
}
async getAll(): Promise<Tile[]> {
return prisma.tile.findMany()
}
async create(name: string): Promise<Tile> {
return prisma.tile.create({
data: {
name,
tags: []
},
});
}
async create(name: string): Promise<Tile> {
return prisma.tile.create({
data: {
name,
tags: []
}
})
}
async update(id: string, name: string, tags: string[]): Promise<Tile> {
return prisma.tile.update({
where: { id },
data: {
name,
tags
},
});
}
async update(id: string, name: string, tags: string[]): Promise<Tile> {
return prisma.tile.update({
where: { id },
data: {
name,
tags
}
})
}
async delete(id: string): Promise<boolean> {
try {
await prisma.tile.delete({
where: { id },
});
return true;
} catch (error) {
console.log('Error deleting tile:', error)
return false;
}
async delete(id: string): Promise<boolean> {
try {
await prisma.tile.delete({
where: { id }
})
return true
} catch (error) {
console.log('Error deleting tile:', error)
return false
}
}
}
export default new TileRepository();
export default new TileRepository()

View File

@ -1,44 +1,44 @@
import prisma from '../utilities/Prisma'; // Import the global Prisma instance
import { User } from '@prisma/client';
import prisma from '../utilities/Prisma' // Import the global Prisma instance
import { User } from '@prisma/client'
class UserRepository {
async getById(id: number): Promise<User | null> {
try {
return await prisma.user.findUnique({
where: {
id,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to get user by ID: ${error.message}`);
async getById(id: number): Promise<User | null> {
try {
return await prisma.user.findUnique({
where: {
id
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to get user by ID: ${error.message}`)
}
async getByUsername(username: string): Promise<User | null> {
try {
return await prisma.user.findUnique({
where: {
username,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to get user by username: ${error.message}`);
}
async getByUsername(username: string): Promise<User | null> {
try {
return await prisma.user.findUnique({
where: {
username
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to get user by username: ${error.message}`)
}
async create(username: string, password: string): Promise<User> {
try {
return await prisma.user.create({
data: {
username,
password,
},
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to create user: ${error.message}`);
}
async create(username: string, password: string): Promise<User> {
try {
return await prisma.user.create({
data: {
username,
password
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to create user: ${error.message}`)
}
}
}
export default new UserRepository;
export default new UserRepository()

View File

@ -1,120 +1,120 @@
import { Zone, ZoneEventTile, ZoneObject } from '@prisma/client'
import prisma from '../utilities/Prisma'; // Import the global Prisma instance
import prisma from '../utilities/Prisma' // Import the global Prisma instance
class ZoneRepository {
async getFirst(): Promise<Zone | null> {
try {
return await prisma.zone.findFirst();
} catch (error: any) {
// Handle error
throw new Error(`Failed to get first zone: ${error.message}`);
}
async getFirst(): Promise<Zone | null> {
try {
return await prisma.zone.findFirst()
} catch (error: any) {
// Handle error
throw new Error(`Failed to get first zone: ${error.message}`)
}
}
async getAll(): Promise<Zone[]> {
try {
return await prisma.zone.findMany();
} catch (error: any) {
// Handle error
throw new Error(`Failed to get all zone: ${error.message}`);
}
async getAll(): Promise<Zone[]> {
try {
return await prisma.zone.findMany()
} catch (error: any) {
// Handle error
throw new Error(`Failed to get all zone: ${error.message}`)
}
}
async getById(id: number): Promise<Zone | null> {
try {
return await prisma.zone.findUnique({
where: {
id: id
},
include: {
zoneEventTiles: {
include: {
zone: true
}
},
zoneObjects: {
include: {
object: true
}
}
}
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to get zone by id: ${error.message}`);
async getById(id: number): Promise<Zone | null> {
try {
return await prisma.zone.findUnique({
where: {
id: id
},
include: {
zoneEventTiles: {
include: {
zone: true
}
},
zoneObjects: {
include: {
object: true
}
}
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to get zone by id: ${error.message}`)
}
}
async create(name: string, width: number, height: number, tiles: string[][]): Promise<Zone> {
try {
return await prisma.zone.create({
data: {
name: name,
width: width,
height: height,
tiles: tiles
}
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to create zone: ${error.message}`);
async create(name: string, width: number, height: number, tiles: string[][]): Promise<Zone> {
try {
return await prisma.zone.create({
data: {
name: name,
width: width,
height: height,
tiles: tiles
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to create zone: ${error.message}`)
}
}
async update(id: number, name: string, width: number, height: number, tiles: string[][], zoneEventTiles: ZoneEventTile[], zoneObjects: ZoneObject[]): Promise<Zone> {
try {
return await prisma.zone.update({
where: {
id: id
},
data: {
name,
width,
height,
tiles,
zoneEventTiles: {
deleteMany: {
zoneId: id // Ensure only event tiles related to the zone are deleted
},
// Save new zone event tiles
create: zoneEventTiles.map(zoneEventTile => ({
type: zoneEventTile.type,
position_x: zoneEventTile.position_x,
position_y: zoneEventTile.position_y
}))
},
zoneObjects: {
deleteMany: {
zoneId: id // Ensure only objects related to the zone are deleted
},
// Save new zone objects
create: zoneObjects.map(zoneObject => ({
objectId: zoneObject.objectId,
depth: zoneObject.depth,
position_x: zoneObject.position_x,
position_y: zoneObject.position_y
}))
}
}
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to update zone: ${error.message}`);
async update(id: number, name: string, width: number, height: number, tiles: string[][], zoneEventTiles: ZoneEventTile[], zoneObjects: ZoneObject[]): Promise<Zone> {
try {
return await prisma.zone.update({
where: {
id: id
},
data: {
name,
width,
height,
tiles,
zoneEventTiles: {
deleteMany: {
zoneId: id // Ensure only event tiles related to the zone are deleted
},
// Save new zone event tiles
create: zoneEventTiles.map((zoneEventTile) => ({
type: zoneEventTile.type,
position_x: zoneEventTile.position_x,
position_y: zoneEventTile.position_y
}))
},
zoneObjects: {
deleteMany: {
zoneId: id // Ensure only objects related to the zone are deleted
},
// Save new zone objects
create: zoneObjects.map((zoneObject) => ({
objectId: zoneObject.objectId,
depth: zoneObject.depth,
position_x: zoneObject.position_x,
position_y: zoneObject.position_y
}))
}
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to update zone: ${error.message}`)
}
}
async delete(id: number): Promise<Zone> {
try {
return await prisma.zone.delete({
where: {
id: id
}
});
} catch (error: any) {
// Handle error
throw new Error(`Failed to delete zone: ${error.message}`);
async delete(id: number): Promise<Zone> {
try {
return await prisma.zone.delete({
where: {
id: id
}
})
} catch (error: any) {
// Handle error
throw new Error(`Failed to delete zone: ${error.message}`)
}
}
}
export default new ZoneRepository;
export default new ZoneRepository()

View File

@ -1,104 +1,103 @@
import fs from "fs";
import path from "path";
import express, {Application} from 'express';
import {createServer as httpServer} from 'http';
import {addHttpRoutes} from './utilities/Http';
import cors from 'cors';
import {Server as SocketServer} from 'socket.io';
import {TSocket} from "./utilities/Types";
import config from './utilities/Config';
import prisma from './utilities/Prisma';
import ZoneManager from "./managers/ZoneManager";
import UserManager from "./managers/UserManager";
import {Authentication} from "./middleware/Authentication";
import CommandManager from "./managers/CommandManager";
import {Dirent} from "node:fs";
import fs from 'fs'
import path from 'path'
import express, { Application } from 'express'
import { createServer as httpServer } from 'http'
import { addHttpRoutes } from './utilities/Http'
import cors from 'cors'
import { Server as SocketServer } from 'socket.io'
import { TSocket } from './utilities/Types'
import config from './utilities/Config'
import prisma from './utilities/Prisma'
import ZoneManager from './managers/ZoneManager'
import UserManager from './managers/UserManager'
import { Authentication } from './middleware/Authentication'
import CommandManager from './managers/CommandManager'
import { Dirent } from 'node:fs'
export class Server
{
private readonly app: Application;
private readonly http: any;
private readonly io: SocketServer;
export class Server {
private readonly app: Application
private readonly http: any
private readonly io: SocketServer
/**
* Creates an instance of GameServer.
*/
constructor() {
this.app = express();
this.app.use(cors());
this.app.use(express.json());
this.app.use(express.urlencoded({ extended: true }));
this.http = httpServer(this.app)
this.io = new SocketServer(this.http);
this.io.use(Authentication)
/**
* Creates an instance of GameServer.
*/
constructor() {
this.app = express()
this.app.use(cors())
this.app.use(express.json())
this.app.use(express.urlencoded({ extended: true }))
this.http = httpServer(this.app)
this.io = new SocketServer(this.http)
this.io.use(Authentication)
}
/**
* Start the server
*/
public async start() {
// Check prisma connection
try {
await prisma.$connect()
console.log('[✅] Database connected')
} catch (error: any) {
throw new Error(`[❌] Database connection failed: ${error.message}`)
}
/**
* Start the server
*/
public async start() {
// Check prisma connection
try {
await prisma.$connect();
console.log('[✅] Database connected');
} catch (error: any) {
throw new Error(`[❌] Database connection failed: ${error.message}`);
}
// Start the server
try {
await this.http.listen(config.PORT, config.HOST);
console.log('[✅] Socket.IO running on port', config.PORT);
} catch (error: any) {
throw new Error(`[❌] Socket.IO failed to start: ${error.message}`);
}
// Add http API routes
await addHttpRoutes(this.app);
// Load user manager
await UserManager.boot();
// Load zone manager
await ZoneManager.boot();
// Load command manager - Disabled for now
// await CommandManager.boot(this.io);
// Listen for socket connections
this.io.on('connection', this.handleConnection.bind(this));
// Start the server
try {
await this.http.listen(config.PORT, config.HOST)
console.log('[✅] Socket.IO running on port', config.PORT)
} catch (error: any) {
throw new Error(`[❌] Socket.IO failed to start: ${error.message}`)
}
/**
* Handle socket connection
* @param socket
* @private
*/
private async handleConnection(socket: TSocket) {
const eventsPath = path.join(__dirname, 'events');
try {
await this.loadEventHandlers(eventsPath, socket);
} catch (error: any) {
throw new Error('[❌] Failed to load event handlers: ' + error.message);
}
// Add http API routes
await addHttpRoutes(this.app)
// Load user manager
await UserManager.boot()
// Load zone manager
await ZoneManager.boot()
// Load command manager - Disabled for now
// await CommandManager.boot(this.io);
// Listen for socket connections
this.io.on('connection', this.handleConnection.bind(this))
}
/**
* Handle socket connection
* @param socket
* @private
*/
private async handleConnection(socket: TSocket) {
const eventsPath = path.join(__dirname, 'events')
try {
await this.loadEventHandlers(eventsPath, socket)
} catch (error: any) {
throw new Error('[❌] Failed to load event handlers: ' + error.message)
}
}
private async loadEventHandlers(dir: string, socket: TSocket) {
const files: Dirent[] = await fs.promises.readdir(dir, { withFileTypes: true });
private async loadEventHandlers(dir: string, socket: TSocket) {
const files: Dirent[] = await fs.promises.readdir(dir, { withFileTypes: true })
for (const file of files) {
const fullPath = path.join(dir, file.name);
for (const file of files) {
const fullPath = path.join(dir, file.name)
if (file.isDirectory()) {
await this.loadEventHandlers(fullPath, socket);
} else if (file.isFile()) {
const module = await import(fullPath);
module.default(socket, this.io);
}
}
if (file.isDirectory()) {
await this.loadEventHandlers(fullPath, socket)
} else if (file.isFile()) {
const module = await import(fullPath)
module.default(socket, this.io)
}
}
}
}
// Start the server
const server = new Server();
server.start();
const server = new Server()
server.start()

View File

@ -1,6 +1,3 @@
class AssetService {}
class AssetService
{
}
export default AssetService;
export default AssetService

View File

@ -1,8 +1,5 @@
import {Character} from "@prisma/client";
import { Character } from '@prisma/client'
class CharacterService
{
class CharacterService {}
}
export default CharacterService;
export default CharacterService

View File

@ -1,31 +1,30 @@
import bcrypt from 'bcryptjs'
import UserRepository from '../repositories/UserRepository'
class UserService
{
async login(username: string, password: string): Promise<boolean | any> {
const user = await UserRepository.getByUsername(username);
if (!user) {
return false;
}
const passwordMatch = await bcrypt.compare(password, user.password);
if (!passwordMatch) {
return false;
}
return user;
class UserService {
async login(username: string, password: string): Promise<boolean | any> {
const user = await UserRepository.getByUsername(username)
if (!user) {
return false
}
async register(username: string, password: string): Promise<boolean | any> {
const user = await UserRepository.getByUsername(username);
if (user) {
return false;
}
const hashedPassword = await bcrypt.hash(password, 10);
return await UserRepository.create(username, hashedPassword)
const passwordMatch = await bcrypt.compare(password, user.password)
if (!passwordMatch) {
return false
}
return user
}
async register(username: string, password: string): Promise<boolean | any> {
const user = await UserRepository.getByUsername(username)
if (user) {
return false
}
const hashedPassword = await bcrypt.hash(password, 10)
return await UserRepository.create(username, hashedPassword)
}
}
export default UserService;
export default UserService

View File

@ -1,25 +1,23 @@
import {Zone} from "@prisma/client";
import ZoneRepository from "../repositories/ZoneRepository";
import { Zone } from '@prisma/client'
import ZoneRepository from '../repositories/ZoneRepository'
class ZoneService
{
async createDemoZone(): Promise<boolean>
{
await ZoneRepository.create("Demo Zone", 10, 10, [
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
])
console.log("Demo zone created.");
return true;
}
class ZoneService {
async createDemoZone(): Promise<boolean> {
await ZoneRepository.create('Demo Zone', 10, 10, [
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile'],
['blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile', 'blank_tile']
])
console.log('Demo zone created.')
return true
}
}
export default ZoneService;
export default ZoneService

View File

@ -1,17 +1,16 @@
import dotenv from "dotenv";
import dotenv from 'dotenv'
dotenv.config();
dotenv.config()
class config
{
static ENV: string = process.env.ENV || "prod";
static HOST: string = process.env.HOST || "0.0.0.0";
static PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 6969;
static JWT_SECRET: string = process.env.JWT_SECRET || "secret";
class config {
static ENV: string = process.env.ENV || 'prod'
static HOST: string = process.env.HOST || '0.0.0.0'
static PORT: number = process.env.PORT ? parseInt(process.env.PORT) : 6969
static JWT_SECRET: string = process.env.JWT_SECRET || 'secret'
static DEFAULT_CHARACTER_ZONE: number = parseInt(process.env.DEFAULT_CHARACTER_ZONE || "1");
static DEFAULT_CHARACTER_X: number = parseInt(process.env.DEFAULT_CHARACTER_POS_X || "0");
static DEFAULT_CHARACTER_Y: number = parseInt(process.env.DEFAULT_CHARACTER_POS_Y || "0");
static DEFAULT_CHARACTER_ZONE: number = parseInt(process.env.DEFAULT_CHARACTER_ZONE || '1')
static DEFAULT_CHARACTER_X: number = parseInt(process.env.DEFAULT_CHARACTER_POS_X || '0')
static DEFAULT_CHARACTER_Y: number = parseInt(process.env.DEFAULT_CHARACTER_POS_Y || '0')
}
export default config;
export default config

View File

@ -2,102 +2,103 @@
* Resources:
* https://stackoverflow.com/questions/76131891/what-is-the-best-method-for-socket-io-authentication
*/
import {Application, Request, Response} from 'express';
import UserService from '../services/UserService';
import jwt from "jsonwebtoken";
import config from "./Config";
import {loginAccountSchema, registerAccountSchema} from "./ZodTypes";
import path from "path";
import { Application, Request, Response } from 'express'
import UserService from '../services/UserService'
import jwt from 'jsonwebtoken'
import config from './Config'
import { loginAccountSchema, registerAccountSchema } from './ZodTypes'
import path from 'path'
import { TAsset } from './Types'
import tileRepository from '../repositories/TileRepository'
import objectRepository from '../repositories/ObjectRepository'
import spriteRepository from '../repositories/SpriteRepository'
async function addHttpRoutes(app: Application) {
app.get('/assets', async (req: Request, res: Response) => {
let assets: TAsset[] = [];
const tiles = await tileRepository.getAll();
tiles.forEach(tile => {
assets.push({
key: tile.id,
value: '/assets/tiles/' + tile.id + '.png',
group: 'tiles',
type: 'link'
});
});
app.get('/assets', async (req: Request, res: Response) => {
let assets: TAsset[] = []
const tiles = await tileRepository.getAll()
tiles.forEach((tile) => {
assets.push({
key: tile.id,
value: '/assets/tiles/' + tile.id + '.png',
group: 'tiles',
type: 'link'
})
})
const objects = await objectRepository.getAll();
objects.forEach(object => {
assets.push({
key: object.id,
value: '/assets/objects/' + object.id + '.png',
group: 'objects',
type: 'link'
});
});
const objects = await objectRepository.getAll()
objects.forEach((object) => {
assets.push({
key: object.id,
value: '/assets/objects/' + object.id + '.png',
group: 'objects',
type: 'link'
})
})
res.json(assets);
});
app.get('/assets/:type/:file', (req: Request, res: Response) => {
const assetName = req.params.file;
res.json(assets)
})
app.get('/assets/:type/:file', (req: Request, res: Response) => {
const assetName = req.params.file
// if (!isValidAsset(assetName)) {
// return res.status(400).send('Invalid asset name');
// }
// if (!isValidAsset(assetName)) {
// return res.status(400).send('Invalid asset name');
// }
const options = {
root: path.join(process.cwd(), 'public', req.params.type),
};
const options = {
root: path.join(process.cwd(), 'public', req.params.type)
}
res.sendFile(assetName, options, (err) => {
if (err) {
console.error('Error sending file:', err);
res.status(500).send('Error downloading the asset');
}
});
});
res.sendFile(assetName, options, (err) => {
if (err) {
console.error('Error sending file:', err)
res.status(500).send('Error downloading the asset')
}
})
})
app.post('/login', async (req: Request, res: Response) => {
const { username, password } = req.body;
app.post('/login', async (req: Request, res: Response) => {
const { username, password } = req.body
try {
loginAccountSchema.parse({ username, password });
} catch (error: any) {
return res.status(400).json({ message: error.errors[0]?.message });
}
try {
loginAccountSchema.parse({ username, password })
} catch (error: any) {
return res.status(400).json({ message: error.errors[0]?.message })
}
const userService = new UserService();
const user = await userService.login(username, password);
const userService = new UserService()
const user = await userService.login(username, password)
if (user) { //test
const token = jwt.sign({ id: user.id }, config.JWT_SECRET, { expiresIn: '4h' });
return res.status(200).json({ token });
}
if (user) {
//test
const token = jwt.sign({ id: user.id }, config.JWT_SECRET, { expiresIn: '4h' })
return res.status(200).json({ token })
}
return res.status(400).json({ message: 'Failed to login' });
});
return res.status(400).json({ message: 'Failed to login' })
})
app.post('/register', async (req: Request, res: Response) => {
const { username, password } = req.body;
app.post('/register', async (req: Request, res: Response) => {
const { username, password } = req.body
try {
registerAccountSchema.parse({ username, password });
} catch (error: any) {
return res.status(400).json({ message: error.errors[0]?.message });
}
try {
registerAccountSchema.parse({ username, password })
} catch (error: any) {
return res.status(400).json({ message: error.errors[0]?.message })
}
const userService = new UserService();
const user = await userService.register(username, password);
const userService = new UserService()
const user = await userService.register(username, password)
if (user) {
const token = jwt.sign({ id: user.id }, config.JWT_SECRET, { expiresIn: '4h' });
return res.status(200).json({ token });
}
if (user) {
const token = jwt.sign({ id: user.id }, config.JWT_SECRET, { expiresIn: '4h' })
return res.status(200).json({ token })
}
return res.status(400).json({ message: 'Failed to register user' });
});
return res.status(400).json({ message: 'Failed to register user' })
})
console.log('[✅] Web routes added');
console.log('[✅] Web routes added')
}
export { addHttpRoutes };
export { addHttpRoutes }

View File

@ -1,5 +1,5 @@
import { PrismaClient } from '@prisma/client';
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient();
const prisma = new PrismaClient()
export default prisma;
export default prisma

View File

@ -1,33 +1,31 @@
import { Socket } from 'socket.io';
import {Character, User} from "@prisma/client";
import { Socket } from 'socket.io'
import { Character, User } from '@prisma/client'
export type TSocket = Socket & {
user?: User
character?: Character
handshake?: {
query?: {
token?: any
}
user?: User
character?: Character
handshake?: {
query?: {
token?: any
}
request?: {
headers?: {
cookie?: any
}
}
request?: {
headers?: {
cookie?: any
}
}
}
export type TCharacter = Socket & {
user?: User,
character?: Character
user?: User
character?: Character
}
export type TZoneCharacter = Character & {
}
export type TZoneCharacter = Character & {}
export type TAsset = {
key: string
value: string
group: 'tiles' | 'objects' | 'sound' | 'music' | 'ui' | 'font' | 'other'
type: 'base64' | 'link'
key: string
value: string
group: 'tiles' | 'objects' | 'sound' | 'music' | 'ui' | 'font' | 'other'
type: 'base64' | 'link'
}

View File

@ -1,28 +1,37 @@
import { z } from 'zod';
import { z } from 'zod'
export const loginAccountSchema = z.object({
username: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' }),
password: z.string().min(8, {
message: 'Password must be at least 8 characters long'
}).max(255)
});
username: 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' }),
password: z
.string()
.min(8, {
message: 'Password must be at least 8 characters long'
})
.max(255)
})
export const registerAccountSchema = z.object({
username: 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' }),
password: z.string().min(8, {
message: 'Password must be at least 8 characters long'
}).max(255)
});
username: 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' }),
password: z
.string()
.min(8, {
message: 'Password must be at least 8 characters long'
})
.max(255)
})
export const ZCharacterCreate = z.object({
name: 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' })
});
name: 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' })
})