From 898069140987b5fdd66eb6b7d319ef77cf79085b Mon Sep 17 00:00:00 2001
From: Dennis Postma <dennis@directonline.io>
Date: Tue, 24 Dec 2024 22:08:08 +0100
Subject: [PATCH] Added ORM entities

---
 src/entities/character.ts          | 84 ++++++++++++++++++++++++++++++
 src/entities/characterEquipment.ts | 19 +++++++
 src/entities/characterHair.ts      | 25 +++++++++
 src/entities/characterItem.ts      | 22 ++++++++
 src/entities/characterType.ts      | 34 ++++++++++++
 src/entities/chat.ts               | 21 ++++++++
 src/entities/item.ts               | 37 +++++++++++++
 src/entities/passwordResetToken.ts | 17 ++++++
 src/entities/sprite.ts             | 33 ++++++++++++
 src/entities/spriteAction.ts       | 39 ++++++++++++++
 src/entities/user.ts               | 27 ++++++++++
 src/entities/world.ts              | 19 +++++++
 src/entities/zone.ts               | 15 ++++++
 src/server.ts                      |  9 ++++
 tsconfig.json                      |  3 +-
 15 files changed, 403 insertions(+), 1 deletion(-)
 create mode 100644 src/entities/character.ts
 create mode 100644 src/entities/characterEquipment.ts
 create mode 100644 src/entities/characterHair.ts
 create mode 100644 src/entities/characterItem.ts
 create mode 100644 src/entities/characterType.ts
 create mode 100644 src/entities/chat.ts
 create mode 100644 src/entities/item.ts
 create mode 100644 src/entities/passwordResetToken.ts
 create mode 100644 src/entities/sprite.ts
 create mode 100644 src/entities/spriteAction.ts
 create mode 100644 src/entities/user.ts
 create mode 100644 src/entities/world.ts
 create mode 100644 src/entities/zone.ts

diff --git a/src/entities/character.ts b/src/entities/character.ts
new file mode 100644
index 0000000..fc0e75a
--- /dev/null
+++ b/src/entities/character.ts
@@ -0,0 +1,84 @@
+import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
+import { User } from './user';
+import { Zone } from './zone';
+import { CharacterType } from './characterType';
+import { CharacterHair } from './characterHair';
+import { CharacterItem } from './characterItem';
+import { CharacterEquipment } from './characterEquipment';
+import { Chat } from './chat';
+
+@Entity()
+export class Character {
+  @PrimaryKey()
+  id!: number;
+
+  @ManyToOne(() => User)
+  user!: User;
+
+  @Property({ unique: true })
+  name!: string;
+
+  @Property()
+  online = false;
+
+  @Property()
+  role = 'player';
+
+  @OneToMany(() => Chat, chat => chat.character)
+  chats = new Collection<Chat>(this);
+
+  // Position
+  @ManyToOne(() => Zone)
+  zone!: Zone;
+
+  @Property()
+  positionX = 0;
+
+  @Property()
+  positionY = 0;
+
+  @Property()
+  rotation = 0;
+
+  // Customization
+  @ManyToOne(() => CharacterType, { nullable: true })
+  characterType?: CharacterType;
+
+  @ManyToOne(() => CharacterHair, { nullable: true })
+  characterHair?: CharacterHair;
+
+  // Inventory
+  @OneToMany(() => CharacterItem, item => item.character)
+  items = new Collection<CharacterItem>(this);
+
+  @OneToMany(() => CharacterEquipment, equipment => equipment.character)
+  equipment = new Collection<CharacterEquipment>(this);
+
+  // Stats
+  @Property()
+  alignment = 50;
+
+  @Property()
+  hitpoints = 100;
+
+  @Property()
+  mana = 100;
+
+  @Property()
+  level = 1;
+
+  @Property()
+  experience = 0;
+
+  @Property()
+  strength = 10;
+
+  @Property()
+  dexterity = 10;
+
+  @Property()
+  intelligence = 10;
+
+  @Property()
+  wisdom = 10;
+}
\ No newline at end of file
diff --git a/src/entities/characterEquipment.ts b/src/entities/characterEquipment.ts
new file mode 100644
index 0000000..7dd3e58
--- /dev/null
+++ b/src/entities/characterEquipment.ts
@@ -0,0 +1,19 @@
+import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
+import { Character } from './character';
+import { CharacterItem } from './characterItem';
+import { CharacterEquipmentSlotType } from '../utilities/enums';
+
+@Entity()
+export class CharacterEquipment {
+  @PrimaryKey()
+  id!: number;
+
+  @Property()
+  slot!: CharacterEquipmentSlotType;
+
+  @ManyToOne(() => Character)
+  character!: Character;
+
+  @ManyToOne(() => CharacterItem)
+  characterItem!: CharacterItem;
+}
\ No newline at end of file
diff --git a/src/entities/characterHair.ts b/src/entities/characterHair.ts
new file mode 100644
index 0000000..3a45aca
--- /dev/null
+++ b/src/entities/characterHair.ts
@@ -0,0 +1,25 @@
+import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
+import { Character } from './character';
+import { Sprite } from './sprite';
+import { CharacterGender } from '../utilities/enums';
+
+@Entity()
+export class CharacterHair {
+  @PrimaryKey()
+  id!: number;
+
+  @Property()
+  name!: string;
+
+  @Property()
+  gender: CharacterGender = CharacterGender.MALE;
+
+  @Property()
+  isSelectable = false;
+
+  @ManyToOne(() => Sprite, { nullable: true })
+  sprite?: Sprite;
+
+  @OneToMany(() => Character, character => character.characterHair)
+  characters = new Collection<Character>(this);
+}
\ No newline at end of file
diff --git a/src/entities/characterItem.ts b/src/entities/characterItem.ts
new file mode 100644
index 0000000..7c772ba
--- /dev/null
+++ b/src/entities/characterItem.ts
@@ -0,0 +1,22 @@
+import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
+import { Character } from './character';
+import { Item } from './item';
+import { CharacterEquipment } from './characterEquipment';
+
+@Entity()
+export class CharacterItem {
+  @PrimaryKey()
+  id!: number;
+
+  @ManyToOne(() => Character)
+  character!: Character;
+
+  @ManyToOne(() => Item)
+  item!: Item;
+
+  @Property()
+  quantity!: number;
+
+  @OneToMany(() => CharacterEquipment, equipment => equipment.characterItem)
+  characterEquipment = new Collection<CharacterEquipment>(this);
+}
\ No newline at end of file
diff --git a/src/entities/characterType.ts b/src/entities/characterType.ts
new file mode 100644
index 0000000..ab89ddc
--- /dev/null
+++ b/src/entities/characterType.ts
@@ -0,0 +1,34 @@
+import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
+import { Character } from './character';
+import { Sprite } from './sprite';
+import { CharacterGender, CharacterRace } from '../utilities/enums';
+
+@Entity()
+export class CharacterType {
+  @PrimaryKey()
+  id!: number;
+
+  @Property()
+  name!: string;
+
+  @Property()
+  gender!: CharacterGender;
+
+  @Property()
+  race!: CharacterRace;
+
+  @Property()
+  isSelectable = false;
+
+  @OneToMany(() => Character, character => character.characterType)
+  characters = new Collection<Character>(this);
+
+  @ManyToOne(() => Sprite, { nullable: true })
+  sprite?: Sprite;
+
+  @Property()
+  createdAt = new Date();
+
+  @Property()
+  updatedAt = new Date();
+}
\ No newline at end of file
diff --git a/src/entities/chat.ts b/src/entities/chat.ts
new file mode 100644
index 0000000..58e9289
--- /dev/null
+++ b/src/entities/chat.ts
@@ -0,0 +1,21 @@
+import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
+import { Character } from './character';
+import { Zone } from './zone';
+
+@Entity()
+export class Chat {
+  @PrimaryKey()
+  id!: number;
+
+  @ManyToOne(() => Character)
+  character!: Character;
+
+  @ManyToOne(() => Zone)
+  zone!: Zone;
+
+  @Property()
+  message!: string;
+
+  @Property()
+  createdAt = new Date();
+}
\ No newline at end of file
diff --git a/src/entities/item.ts b/src/entities/item.ts
new file mode 100644
index 0000000..324400f
--- /dev/null
+++ b/src/entities/item.ts
@@ -0,0 +1,37 @@
+import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
+import { Sprite } from './sprite';
+import { CharacterItem } from './characterItem';
+import { ItemType, ItemRarity } from '../utilities/enums';
+
+@Entity()
+export class Item {
+  @PrimaryKey()
+  id!: string;
+
+  @Property()
+  name!: string;
+
+  @Property({ nullable: true })
+  description?: string;
+
+  @Property()
+  itemType!: ItemType;
+
+  @Property()
+  stackable = false;
+
+  @Property()
+  rarity: ItemRarity = ItemRarity.COMMON;
+
+  @ManyToOne(() => Sprite, { nullable: true })
+  sprite?: Sprite;
+
+  @Property()
+  createdAt = new Date();
+
+  @Property()
+  updatedAt = new Date();
+
+  @OneToMany(() => CharacterItem, characterItem => characterItem.item)
+  characters = new Collection<CharacterItem>(this);
+}
\ No newline at end of file
diff --git a/src/entities/passwordResetToken.ts b/src/entities/passwordResetToken.ts
new file mode 100644
index 0000000..1149d0a
--- /dev/null
+++ b/src/entities/passwordResetToken.ts
@@ -0,0 +1,17 @@
+import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
+import { User } from './user';
+
+@Entity()
+export class PasswordResetToken {
+  @PrimaryKey()
+  id!: number;
+
+  @ManyToOne(() => User)
+  user!: User;
+
+  @Property({ unique: true })
+  token!: string;
+
+  @Property()
+  createdAt = new Date();
+}
\ No newline at end of file
diff --git a/src/entities/sprite.ts b/src/entities/sprite.ts
new file mode 100644
index 0000000..0d1988c
--- /dev/null
+++ b/src/entities/sprite.ts
@@ -0,0 +1,33 @@
+import { randomUUID } from 'node:crypto';
+import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
+import { SpriteAction } from './spriteAction';
+import { CharacterType } from './characterType';
+import { CharacterHair } from './characterHair';
+import { Item } from './item';
+
+@Entity()
+export class Sprite {
+  @PrimaryKey()
+  id = randomUUID();
+
+  @Property()
+  name!: string;
+
+  @Property()
+  createdAt = new Date();
+
+  @Property()
+  updatedAt = new Date();
+
+  @OneToMany(() => SpriteAction, action => action.sprite)
+  spriteActions = new Collection<SpriteAction>(this);
+
+  @OneToMany(() => CharacterType, type => type.sprite)
+  characterTypes = new Collection<CharacterType>(this);
+
+  @OneToMany(() => CharacterHair, hair => hair.sprite)
+  characterHairs = new Collection<CharacterHair>(this);
+
+  @OneToMany(() => Item, item => item.sprite)
+  items = new Collection<Item>(this);
+}
\ No newline at end of file
diff --git a/src/entities/spriteAction.ts b/src/entities/spriteAction.ts
new file mode 100644
index 0000000..5c8c86b
--- /dev/null
+++ b/src/entities/spriteAction.ts
@@ -0,0 +1,39 @@
+import { randomUUID } from 'node:crypto'
+import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core';
+import { Sprite } from './sprite';
+
+@Entity()
+export class SpriteAction {
+  @PrimaryKey()
+  id = randomUUID();
+
+  @ManyToOne(() => Sprite)
+  sprite!: Sprite;
+
+  @Property()
+  action!: string;
+
+  @Property({ type: 'json', nullable: true })
+  sprites?: any;
+
+  @Property()
+  originX = 0;
+
+  @Property()
+  originY = 0;
+
+  @Property()
+  isAnimated = false;
+
+  @Property()
+  isLooping = false;
+
+  @Property()
+  frameWidth = 0;
+
+  @Property()
+  frameHeight = 0;
+
+  @Property()
+  frameRate = 0;
+}
\ No newline at end of file
diff --git a/src/entities/user.ts b/src/entities/user.ts
new file mode 100644
index 0000000..9691400
--- /dev/null
+++ b/src/entities/user.ts
@@ -0,0 +1,27 @@
+import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
+import { Character } from './character';
+import { PasswordResetToken } from './passwordResetToken';
+
+@Entity()
+export class User {
+  @PrimaryKey()
+  id!: number;
+
+  @Property({ unique: true })
+  username!: string;
+
+  @Property({ unique: true })
+  email!: string;
+
+  @Property()
+  password!: string;
+
+  @Property()
+  online = false;
+
+  @OneToMany(() => Character, character => character.user)
+  characters = new Collection<Character>(this);
+
+  @OneToMany(() => PasswordResetToken, token => token.user)
+  passwordResetTokens = new Collection<PasswordResetToken>(this);
+}
\ No newline at end of file
diff --git a/src/entities/world.ts b/src/entities/world.ts
new file mode 100644
index 0000000..265b08d
--- /dev/null
+++ b/src/entities/world.ts
@@ -0,0 +1,19 @@
+import { Entity, PrimaryKey, Property } from '@mikro-orm/core';
+
+@Entity()
+export class World {
+  @PrimaryKey()
+  date = new Date();
+
+  @Property()
+  isRainEnabled = false;
+
+  @Property()
+  rainPercentage = 0;
+
+  @Property()
+  isFogEnabled = false;
+
+  @Property()
+  fogDensity = 0;
+}
\ No newline at end of file
diff --git a/src/entities/zone.ts b/src/entities/zone.ts
new file mode 100644
index 0000000..7a43385
--- /dev/null
+++ b/src/entities/zone.ts
@@ -0,0 +1,15 @@
+import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core';
+import { Character } from './character';
+import { Chat } from './chat';
+
+@Entity()
+export class Zone {
+  @PrimaryKey()
+  id!: number;
+
+  @OneToMany(() => Character, character => character.zone)
+  characters = new Collection<Character>(this);
+
+  @OneToMany(() => Chat, chat => chat.zone)
+  chats = new Collection<Chat>(this);
+}
\ No newline at end of file
diff --git a/src/server.ts b/src/server.ts
index 8354301..db95433 100644
--- a/src/server.ts
+++ b/src/server.ts
@@ -8,6 +8,7 @@ import cors from 'cors'
 import { Server as SocketServer } from 'socket.io'
 import { Authentication } from './middleware/authentication'
 import { TSocket } from './utilities/types'
+import { MikroORM } from '@mikro-orm/mariadb'
 import prisma from './utilities/prisma'
 import { appLogger, watchLogs } from './utilities/logger'
 import ZoneManager from './managers/zoneManager'
@@ -57,6 +58,14 @@ export class Server {
       appLogger.error(`Database connection failed: ${error.message}`)
     }
 
+    // MikroORM
+    try {
+      const orm = await MikroORM.init();
+      appLogger.info('Database 2 connected')
+    } catch (error: any) {
+      appLogger.error(`Database 2 connection failed: ${error.message}`)
+    }
+
     // Start the server
     try {
       this.http.listen(config.PORT, config.HOST)
diff --git a/tsconfig.json b/tsconfig.json
index 6baea3d..e022d97 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -3,7 +3,8 @@
     // Enable latest features
     "lib": ["ESNext"],
     "target": "ESNext",
-    "module": "ESNext",
+    "module": "NodeNext",
+    "moduleResolution": "NodeNext",
     "moduleDetection": "force",
     "allowJs": true,
     "declaration": true,