forked from noxious/server
Compare commits
21 Commits
feature/#2
...
feature/im
Author | SHA1 | Date | |
---|---|---|---|
5ac056bb8a | |||
664a74c973 | |||
0c77758351 | |||
e8ef160f2a | |||
2d6831b4ef | |||
0464538b1c | |||
e61aeed691 | |||
45e756fcd3 | |||
7c473de12b | |||
5982422e04 | |||
04e081c31a | |||
599264b362 | |||
6f84238503 | |||
586bb0ca83 | |||
465219276d | |||
11d30351ba | |||
85af73c079 | |||
9c28b10383 | |||
495e9f192e | |||
30b2028bd8 | |||
9d6a8730a9 |
@ -1,6 +1,6 @@
|
||||
import { Migration } from '@mikro-orm/migrations';
|
||||
|
||||
export class Migration20241229234130 extends Migration {
|
||||
export class Migration20250101224501 extends Migration {
|
||||
|
||||
override async up(): Promise<void> {
|
||||
this.addSql(`create table \`map_object\` (\`id\` varchar(255) not null, \`name\` varchar(255) not null, \`tags\` json null, \`origin_x\` int not null default 0, \`origin_y\` int not null default 0, \`is_animated\` tinyint(1) not null default false, \`frame_rate\` int not null default 0, \`frame_width\` int not null default 0, \`frame_height\` int not null default 0, \`created_at\` datetime not null, \`updated_at\` datetime not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
@ -10,10 +10,10 @@ export class Migration20241229234130 extends Migration {
|
||||
this.addSql(`create table \`item\` (\`id\` varchar(255) not null, \`name\` varchar(255) not null, \`description\` varchar(255) null, \`item_type\` enum('WEAPON', 'HELMET', 'CHEST', 'LEGS', 'BOOTS', 'GLOVES', 'RING', 'NECKLACE') not null, \`stackable\` tinyint(1) not null default false, \`rarity\` enum('COMMON', 'UNCOMMON', 'RARE', 'EPIC', 'LEGENDARY') not null default 'COMMON', \`sprite_id\` varchar(255) null, \`created_at\` datetime not null, \`updated_at\` datetime not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`item\` add index \`item_sprite_id_index\`(\`sprite_id\`);`);
|
||||
|
||||
this.addSql(`create table \`character_type\` (\`id\` int unsigned not null auto_increment primary key, \`name\` varchar(255) not null, \`gender\` enum('MALE', 'FEMALE') not null, \`race\` enum('HUMAN', 'ELF', 'DWARF', 'ORC', 'GOBLIN') not null, \`is_selectable\` tinyint(1) not null default false, \`sprite_id\` varchar(255) null, \`created_at\` datetime not null, \`updated_at\` datetime not null) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`character_type\` (\`id\` varchar(255) not null, \`name\` varchar(255) not null, \`gender\` enum('MALE', 'FEMALE') not null, \`race\` enum('HUMAN', 'ELF', 'DWARF', 'ORC', 'GOBLIN') not null, \`is_selectable\` tinyint(1) not null default false, \`sprite_id\` varchar(255) null, \`created_at\` datetime not null, \`updated_at\` datetime not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`character_type\` add index \`character_type_sprite_id_index\`(\`sprite_id\`);`);
|
||||
|
||||
this.addSql(`create table \`character_hair\` (\`id\` int unsigned not null auto_increment primary key, \`name\` varchar(255) not null, \`gender\` varchar(255) not null default 'MALE', \`is_selectable\` tinyint(1) not null default false, \`sprite_id\` varchar(255) null) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`character_hair\` (\`id\` varchar(255) not null, \`name\` varchar(255) not null, \`gender\` varchar(255) not null default 'MALE', \`is_selectable\` tinyint(1) not null default false, \`sprite_id\` varchar(255) null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`character_hair\` add index \`character_hair_sprite_id_index\`(\`sprite_id\`);`);
|
||||
|
||||
this.addSql(`create table \`sprite_action\` (\`id\` varchar(255) not null, \`sprite_id\` varchar(255) not null, \`action\` varchar(255) not null, \`sprites\` json null, \`origin_x\` int not null default 0, \`origin_y\` int not null default 0, \`is_animated\` tinyint(1) not null default false, \`is_looping\` tinyint(1) not null default false, \`frame_width\` int not null default 0, \`frame_height\` int not null default 0, \`frame_rate\` int not null default 0, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
@ -21,48 +21,48 @@ export class Migration20241229234130 extends Migration {
|
||||
|
||||
this.addSql(`create table \`tile\` (\`id\` varchar(255) not null, \`name\` varchar(255) not null, \`tags\` json null, \`created_at\` datetime not null, \`updated_at\` datetime not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
|
||||
this.addSql(`create table \`user\` (\`id\` int unsigned not null auto_increment primary key, \`username\` varchar(255) not null, \`email\` varchar(255) not null, \`password\` varchar(255) not null, \`online\` tinyint(1) not null default false) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`user\` (\`id\` varchar(255) not null, \`username\` varchar(255) not null, \`email\` varchar(255) not null, \`password\` varchar(255) not null, \`online\` tinyint(1) not null default false, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`user\` add unique \`user_username_unique\`(\`username\`);`);
|
||||
this.addSql(`alter table \`user\` add unique \`user_email_unique\`(\`email\`);`);
|
||||
|
||||
this.addSql(`create table \`password_reset_token\` (\`id\` int unsigned not null auto_increment primary key, \`user_id\` int unsigned not null, \`token\` varchar(255) not null, \`created_at\` datetime not null) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`password_reset_token\` (\`id\` varchar(255) not null, \`user_id\` varchar(255) not null, \`token\` varchar(255) not null, \`created_at\` datetime not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`password_reset_token\` add index \`password_reset_token_user_id_index\`(\`user_id\`);`);
|
||||
this.addSql(`alter table \`password_reset_token\` add unique \`password_reset_token_token_unique\`(\`token\`);`);
|
||||
|
||||
this.addSql(`create table \`world\` (\`date\` datetime not null, \`is_rain_enabled\` tinyint(1) not null default false, \`rain_percentage\` int not null default 0, \`is_fog_enabled\` tinyint(1) not null default false, \`fog_density\` int not null default 0, primary key (\`date\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
|
||||
this.addSql(`create table \`zone\` (\`id\` int unsigned not null auto_increment primary key, \`name\` varchar(255) not null, \`width\` int not null default 10, \`height\` int not null default 10, \`tiles\` json null, \`pvp\` tinyint(1) not null default false, \`created_at\` datetime not null, \`updated_at\` datetime not null) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`zone\` (\`id\` varchar(255) not null, \`name\` varchar(255) not null, \`width\` int not null default 10, \`height\` int not null default 10, \`tiles\` json null, \`pvp\` tinyint(1) not null default false, \`created_at\` datetime not null, \`updated_at\` datetime not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
|
||||
this.addSql(`create table \`character\` (\`id\` int unsigned not null auto_increment primary key, \`user_id\` int unsigned not null, \`name\` varchar(255) not null, \`online\` tinyint(1) not null default false, \`role\` varchar(255) not null default 'player', \`zone_id\` int unsigned not null, \`position_x\` int not null default 0, \`position_y\` int not null default 0, \`rotation\` int not null default 0, \`character_type_id\` int unsigned null, \`character_hair_id\` int unsigned null, \`alignment\` int not null default 50, \`hitpoints\` int not null default 100, \`mana\` int not null default 100, \`level\` int not null default 1, \`experience\` int not null default 0, \`strength\` int not null default 10, \`dexterity\` int not null default 10, \`intelligence\` int not null default 10, \`wisdom\` int not null default 10) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`character\` (\`id\` varchar(255) not null, \`user_id\` varchar(255) not null, \`name\` varchar(255) not null, \`online\` tinyint(1) not null default false, \`role\` varchar(255) not null default 'player', \`zone_id\` varchar(255) not null, \`position_x\` int not null default 0, \`position_y\` int not null default 0, \`rotation\` int not null default 0, \`character_type_id\` varchar(255) null, \`character_hair_id\` varchar(255) null, \`alignment\` int not null default 50, \`hitpoints\` int not null default 100, \`mana\` int not null default 100, \`level\` int not null default 1, \`experience\` int not null default 0, \`strength\` int not null default 10, \`dexterity\` int not null default 10, \`intelligence\` int not null default 10, \`wisdom\` int not null default 10, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`character\` add index \`character_user_id_index\`(\`user_id\`);`);
|
||||
this.addSql(`alter table \`character\` add unique \`character_name_unique\`(\`name\`);`);
|
||||
this.addSql(`alter table \`character\` add index \`character_zone_id_index\`(\`zone_id\`);`);
|
||||
this.addSql(`alter table \`character\` add index \`character_character_type_id_index\`(\`character_type_id\`);`);
|
||||
this.addSql(`alter table \`character\` add index \`character_character_hair_id_index\`(\`character_hair_id\`);`);
|
||||
|
||||
this.addSql(`create table \`chat\` (\`id\` int unsigned not null auto_increment primary key, \`character_id\` int unsigned not null, \`zone_id\` int unsigned not null, \`message\` varchar(255) not null, \`created_at\` datetime not null) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`chat\` (\`id\` varchar(255) not null, \`character_id\` varchar(255) not null, \`zone_id\` varchar(255) not null, \`message\` varchar(255) not null, \`created_at\` datetime not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`chat\` add index \`chat_character_id_index\`(\`character_id\`);`);
|
||||
this.addSql(`alter table \`chat\` add index \`chat_zone_id_index\`(\`zone_id\`);`);
|
||||
|
||||
this.addSql(`create table \`character_item\` (\`id\` int unsigned not null auto_increment primary key, \`character_id\` int unsigned not null, \`item_id\` varchar(255) not null, \`quantity\` int not null) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`character_item\` (\`id\` varchar(255) not null, \`character_id\` varchar(255) not null, \`item_id\` varchar(255) not null, \`quantity\` int not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`character_item\` add index \`character_item_character_id_index\`(\`character_id\`);`);
|
||||
this.addSql(`alter table \`character_item\` add index \`character_item_item_id_index\`(\`item_id\`);`);
|
||||
|
||||
this.addSql(`create table \`character_equipment\` (\`id\` int unsigned not null auto_increment primary key, \`slot\` enum('HEAD', 'BODY', 'ARMS', 'LEGS', 'NECK', 'RING') not null, \`character_id\` int unsigned not null, \`character_item_id\` int unsigned not null) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`character_equipment\` (\`id\` varchar(255) not null, \`slot\` enum('HEAD', 'BODY', 'ARMS', 'LEGS', 'NECK', 'RING') not null, \`character_id\` varchar(255) not null, \`character_item_id\` varchar(255) not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`character_equipment\` add index \`character_equipment_character_id_index\`(\`character_id\`);`);
|
||||
this.addSql(`alter table \`character_equipment\` add index \`character_equipment_character_item_id_index\`(\`character_item_id\`);`);
|
||||
|
||||
this.addSql(`create table \`zone_effect\` (\`id\` varchar(255) not null, \`zone_id\` int unsigned not null, \`effect\` varchar(255) not null, \`strength\` int not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`zone_effect\` (\`id\` varchar(255) not null, \`zone_id\` varchar(255) not null, \`effect\` varchar(255) not null, \`strength\` int not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`zone_effect\` add index \`zone_effect_zone_id_index\`(\`zone_id\`);`);
|
||||
|
||||
this.addSql(`create table \`zone_event_tile\` (\`id\` varchar(255) not null, \`zone_id\` int unsigned not null, \`type\` enum('BLOCK', 'TELEPORT', 'NPC', 'ITEM') not null, \`position_x\` int not null, \`position_y\` int not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`zone_event_tile\` (\`id\` varchar(255) not null, \`zone_id\` varchar(255) not null, \`type\` enum('BLOCK', 'TELEPORT', 'NPC', 'ITEM') not null, \`position_x\` int not null, \`position_y\` int not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`zone_event_tile\` add index \`zone_event_tile_zone_id_index\`(\`zone_id\`);`);
|
||||
|
||||
this.addSql(`create table \`zone_event_tile_teleport\` (\`id\` varchar(255) not null, \`zone_event_tile_id\` varchar(255) not null, \`to_zone_id\` int unsigned not null, \`to_rotation\` int not null, \`to_position_x\` int not null, \`to_position_y\` int not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`zone_event_tile_teleport\` (\`id\` varchar(255) not null, \`zone_event_tile_id\` varchar(255) not null, \`to_zone_id\` varchar(255) not null, \`to_rotation\` int not null, \`to_position_x\` int not null, \`to_position_y\` int not null, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`zone_event_tile_teleport\` add unique \`zone_event_tile_teleport_zone_event_tile_id_unique\`(\`zone_event_tile_id\`);`);
|
||||
this.addSql(`alter table \`zone_event_tile_teleport\` add index \`zone_event_tile_teleport_to_zone_id_index\`(\`to_zone_id\`);`);
|
||||
|
||||
this.addSql(`create table \`zone_object\` (\`id\` varchar(255) not null, \`zone_id\` int unsigned not null, \`map_object_id\` varchar(255) not null, \`depth\` int not null default 0, \`is_rotated\` tinyint(1) not null default false, \`position_x\` int not null default 0, \`position_y\` int not null default 0, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`create table \`zone_object\` (\`id\` varchar(255) not null, \`zone_id\` varchar(255) not null, \`map_object_id\` varchar(255) not null, \`depth\` int not null default 0, \`is_rotated\` tinyint(1) not null default false, \`position_x\` int not null default 0, \`position_y\` int not null default 0, primary key (\`id\`)) default character set utf8mb4 engine = InnoDB;`);
|
||||
this.addSql(`alter table \`zone_object\` add index \`zone_object_zone_id_index\`(\`zone_id\`);`);
|
||||
this.addSql(`alter table \`zone_object\` add index \`zone_object_map_object_id_index\`(\`map_object_id\`);`);
|
||||
|
||||
@ -72,33 +72,33 @@ export class Migration20241229234130 extends Migration {
|
||||
|
||||
this.addSql(`alter table \`character_hair\` add constraint \`character_hair_sprite_id_foreign\` foreign key (\`sprite_id\`) references \`sprite\` (\`id\`) on update cascade on delete set null;`);
|
||||
|
||||
this.addSql(`alter table \`sprite_action\` add constraint \`sprite_action_sprite_id_foreign\` foreign key (\`sprite_id\`) references \`sprite\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`sprite_action\` add constraint \`sprite_action_sprite_id_foreign\` foreign key (\`sprite_id\`) references \`sprite\` (\`id\`) on update cascade on delete cascade;`);
|
||||
|
||||
this.addSql(`alter table \`password_reset_token\` add constraint \`password_reset_token_user_id_foreign\` foreign key (\`user_id\`) references \`user\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`password_reset_token\` add constraint \`password_reset_token_user_id_foreign\` foreign key (\`user_id\`) references \`user\` (\`id\`) on update cascade on delete cascade;`);
|
||||
|
||||
this.addSql(`alter table \`character\` add constraint \`character_user_id_foreign\` foreign key (\`user_id\`) references \`user\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`character\` add constraint \`character_user_id_foreign\` foreign key (\`user_id\`) references \`user\` (\`id\`) on update cascade on delete cascade;`);
|
||||
this.addSql(`alter table \`character\` add constraint \`character_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`character\` add constraint \`character_character_type_id_foreign\` foreign key (\`character_type_id\`) references \`character_type\` (\`id\`) on update cascade on delete set null;`);
|
||||
this.addSql(`alter table \`character\` add constraint \`character_character_hair_id_foreign\` foreign key (\`character_hair_id\`) references \`character_hair\` (\`id\`) on update cascade on delete set null;`);
|
||||
|
||||
this.addSql(`alter table \`chat\` add constraint \`chat_character_id_foreign\` foreign key (\`character_id\`) references \`character\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`chat\` add constraint \`chat_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`chat\` add constraint \`chat_character_id_foreign\` foreign key (\`character_id\`) references \`character\` (\`id\`) on update cascade on delete cascade;`);
|
||||
this.addSql(`alter table \`chat\` add constraint \`chat_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade on delete cascade;`);
|
||||
|
||||
this.addSql(`alter table \`character_item\` add constraint \`character_item_character_id_foreign\` foreign key (\`character_id\`) references \`character\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`character_item\` add constraint \`character_item_item_id_foreign\` foreign key (\`item_id\`) references \`item\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`character_item\` add constraint \`character_item_character_id_foreign\` foreign key (\`character_id\`) references \`character\` (\`id\`) on update cascade on delete cascade;`);
|
||||
this.addSql(`alter table \`character_item\` add constraint \`character_item_item_id_foreign\` foreign key (\`item_id\`) references \`item\` (\`id\`) on update cascade on delete cascade;`);
|
||||
|
||||
this.addSql(`alter table \`character_equipment\` add constraint \`character_equipment_character_id_foreign\` foreign key (\`character_id\`) references \`character\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`character_equipment\` add constraint \`character_equipment_character_item_id_foreign\` foreign key (\`character_item_id\`) references \`character_item\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`character_equipment\` add constraint \`character_equipment_character_id_foreign\` foreign key (\`character_id\`) references \`character\` (\`id\`) on update cascade on delete cascade;`);
|
||||
this.addSql(`alter table \`character_equipment\` add constraint \`character_equipment_character_item_id_foreign\` foreign key (\`character_item_id\`) references \`character_item\` (\`id\`) on update cascade on delete cascade;`);
|
||||
|
||||
this.addSql(`alter table \`zone_effect\` add constraint \`zone_effect_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`zone_effect\` add constraint \`zone_effect_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade on delete cascade;`);
|
||||
|
||||
this.addSql(`alter table \`zone_event_tile\` add constraint \`zone_event_tile_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`zone_event_tile\` add constraint \`zone_event_tile_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade on delete cascade;`);
|
||||
|
||||
this.addSql(`alter table \`zone_event_tile_teleport\` add constraint \`zone_event_tile_teleport_zone_event_tile_id_foreign\` foreign key (\`zone_event_tile_id\`) references \`zone_event_tile\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`zone_event_tile_teleport\` add constraint \`zone_event_tile_teleport_to_zone_id_foreign\` foreign key (\`to_zone_id\`) references \`zone\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`zone_event_tile_teleport\` add constraint \`zone_event_tile_teleport_zone_event_tile_id_foreign\` foreign key (\`zone_event_tile_id\`) references \`zone_event_tile\` (\`id\`) on update cascade on delete cascade;`);
|
||||
this.addSql(`alter table \`zone_event_tile_teleport\` add constraint \`zone_event_tile_teleport_to_zone_id_foreign\` foreign key (\`to_zone_id\`) references \`zone\` (\`id\`) on update cascade on delete cascade;`);
|
||||
|
||||
this.addSql(`alter table \`zone_object\` add constraint \`zone_object_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`zone_object\` add constraint \`zone_object_map_object_id_foreign\` foreign key (\`map_object_id\`) references \`map_object\` (\`id\`) on update cascade;`);
|
||||
this.addSql(`alter table \`zone_object\` add constraint \`zone_object_zone_id_foreign\` foreign key (\`zone_id\`) references \`zone\` (\`id\`) on update cascade on delete cascade;`);
|
||||
this.addSql(`alter table \`zone_object\` add constraint \`zone_object_map_object_id_foreign\` foreign key (\`map_object_id\`) references \`map_object\` (\`id\`) on update cascade on delete cascade;`);
|
||||
}
|
||||
|
||||
}
|
@ -16,7 +16,7 @@ export default defineConfig({
|
||||
user: serverConfig.DB_USER,
|
||||
password: serverConfig.DB_PASS,
|
||||
dbName: serverConfig.DB_NAME,
|
||||
debug: serverConfig.ENV !== 'production',
|
||||
// debug: serverConfig.ENV !== 'production',
|
||||
driverOptions: {
|
||||
allowPublicKeyRetrieval: true
|
||||
},
|
||||
|
131
package-lock.json
generated
131
package-lock.json
generated
@ -11,8 +11,10 @@
|
||||
"@mikro-orm/mysql": "^6.4.2",
|
||||
"@mikro-orm/reflection": "^6.4.2",
|
||||
"@prisma/client": "^6.1.0",
|
||||
"@types/blessed": "^0.1.25",
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"blessed": "^0.1.81",
|
||||
"bullmq": "^5.13.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
@ -1661,6 +1663,15 @@
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/blessed": {
|
||||
"version": "0.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/blessed/-/blessed-0.1.25.tgz",
|
||||
"integrity": "sha512-kQsjBgtsbJLmG6CJA+Z6Nujj+tq1fcSE3UIowbDvzQI4wWmoTV7djUDhSo5lDjgwpIN0oRvks0SA5mMdKE5eFg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/body-parser": {
|
||||
"version": "1.19.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/body-parser/-/body-parser-1.19.5.tgz",
|
||||
@ -1784,9 +1795,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.17.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.10.tgz",
|
||||
"integrity": "sha512-/jrvh5h6NXhEauFFexRin69nA0uHJ5gwk4iDivp/DeoEua3uwCUto6PC86IpRITBOs4+6i2I56K5x5b6WYGXHA==",
|
||||
"version": "20.17.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.11.tgz",
|
||||
"integrity": "sha512-Ept5glCK35R8yeyIeYlRIZtX6SLRyqMhOFTgj5SOkMpLTdw3SEHI9fHx60xaUZ+V1aJxQJODE+7/j5ocZydYTg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.19.2"
|
||||
@ -1840,17 +1851,17 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.18.2.tgz",
|
||||
"integrity": "sha512-adig4SzPLjeQ0Tm+jvsozSGiCliI2ajeURDGHjZ2llnA+A67HihCQ+a3amtPhUakd1GlwHxSRvzOZktbEvhPPg==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.19.0.tgz",
|
||||
"integrity": "sha512-NggSaEZCdSrFddbctrVjkVZvFC6KGfKfNK0CU7mNK/iKHGKbzT4Wmgm08dKpcZECBu9f5FypndoMyRHkdqfT1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/regexpp": "^4.10.0",
|
||||
"@typescript-eslint/scope-manager": "8.18.2",
|
||||
"@typescript-eslint/type-utils": "8.18.2",
|
||||
"@typescript-eslint/utils": "8.18.2",
|
||||
"@typescript-eslint/visitor-keys": "8.18.2",
|
||||
"@typescript-eslint/scope-manager": "8.19.0",
|
||||
"@typescript-eslint/type-utils": "8.19.0",
|
||||
"@typescript-eslint/utils": "8.19.0",
|
||||
"@typescript-eslint/visitor-keys": "8.19.0",
|
||||
"graphemer": "^1.4.0",
|
||||
"ignore": "^5.3.1",
|
||||
"natural-compare": "^1.4.0",
|
||||
@ -1870,16 +1881,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/parser": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.18.2.tgz",
|
||||
"integrity": "sha512-y7tcq4StgxQD4mDr9+Jb26dZ+HTZ/SkfqpXSiqeUXZHxOUyjWDKsmwKhJ0/tApR08DgOhrFAoAhyB80/p3ViuA==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.19.0.tgz",
|
||||
"integrity": "sha512-6M8taKyOETY1TKHp0x8ndycipTVgmp4xtg5QpEZzXxDhNvvHOJi5rLRkLr8SK3jTgD5l4fTlvBiRdfsuWydxBw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/scope-manager": "8.18.2",
|
||||
"@typescript-eslint/types": "8.18.2",
|
||||
"@typescript-eslint/typescript-estree": "8.18.2",
|
||||
"@typescript-eslint/visitor-keys": "8.18.2",
|
||||
"@typescript-eslint/scope-manager": "8.19.0",
|
||||
"@typescript-eslint/types": "8.19.0",
|
||||
"@typescript-eslint/typescript-estree": "8.19.0",
|
||||
"@typescript-eslint/visitor-keys": "8.19.0",
|
||||
"debug": "^4.3.4"
|
||||
},
|
||||
"engines": {
|
||||
@ -1895,14 +1906,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/scope-manager": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.18.2.tgz",
|
||||
"integrity": "sha512-YJFSfbd0CJjy14r/EvWapYgV4R5CHzptssoag2M7y3Ra7XNta6GPAJPPP5KGB9j14viYXyrzRO5GkX7CRfo8/g==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.19.0.tgz",
|
||||
"integrity": "sha512-hkoJiKQS3GQ13TSMEiuNmSCvhz7ujyqD1x3ShbaETATHrck+9RaDdUbt+osXaUuns9OFwrDTTrjtwsU8gJyyRA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.18.2",
|
||||
"@typescript-eslint/visitor-keys": "8.18.2"
|
||||
"@typescript-eslint/types": "8.19.0",
|
||||
"@typescript-eslint/visitor-keys": "8.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -1913,14 +1924,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/type-utils": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.18.2.tgz",
|
||||
"integrity": "sha512-AB/Wr1Lz31bzHfGm/jgbFR0VB0SML/hd2P1yxzKDM48YmP7vbyJNHRExUE/wZsQj2wUCvbWH8poNHFuxLqCTnA==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.19.0.tgz",
|
||||
"integrity": "sha512-TZs0I0OSbd5Aza4qAMpp1cdCYVnER94IziudE3JU328YUHgWu9gwiwhag+fuLeJ2LkWLXI+F/182TbG+JaBdTg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/typescript-estree": "8.18.2",
|
||||
"@typescript-eslint/utils": "8.18.2",
|
||||
"@typescript-eslint/typescript-estree": "8.19.0",
|
||||
"@typescript-eslint/utils": "8.19.0",
|
||||
"debug": "^4.3.4",
|
||||
"ts-api-utils": "^1.3.0"
|
||||
},
|
||||
@ -1937,9 +1948,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/types": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.18.2.tgz",
|
||||
"integrity": "sha512-Z/zblEPp8cIvmEn6+tPDIHUbRu/0z5lqZ+NvolL5SvXWT5rQy7+Nch83M0++XzO0XrWRFWECgOAyE8bsJTl1GQ==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.19.0.tgz",
|
||||
"integrity": "sha512-8XQ4Ss7G9WX8oaYvD4OOLCjIQYgRQxO+qCiR2V2s2GxI9AUpo7riNwo6jDhKtTcaJjT8PY54j2Yb33kWtSJsmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@ -1951,14 +1962,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/typescript-estree": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.18.2.tgz",
|
||||
"integrity": "sha512-WXAVt595HjpmlfH4crSdM/1bcsqh+1weFRWIa9XMTx/XHZ9TCKMcr725tLYqWOgzKdeDrqVHxFotrvWcEsk2Tg==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.19.0.tgz",
|
||||
"integrity": "sha512-WW9PpDaLIFW9LCbucMSdYUuGeFUz1OkWYS/5fwZwTA+l2RwlWFdJvReQqMUMBw4yJWJOfqd7An9uwut2Oj8sLw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.18.2",
|
||||
"@typescript-eslint/visitor-keys": "8.18.2",
|
||||
"@typescript-eslint/types": "8.19.0",
|
||||
"@typescript-eslint/visitor-keys": "8.19.0",
|
||||
"debug": "^4.3.4",
|
||||
"fast-glob": "^3.3.2",
|
||||
"is-glob": "^4.0.3",
|
||||
@ -1978,16 +1989,16 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/utils": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.18.2.tgz",
|
||||
"integrity": "sha512-Cr4A0H7DtVIPkauj4sTSXVl+VBWewE9/o40KcF3TV9aqDEOWoXF3/+oRXNby3DYzZeCATvbdksYsGZzplwnK/Q==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.19.0.tgz",
|
||||
"integrity": "sha512-PTBG+0oEMPH9jCZlfg07LCB2nYI0I317yyvXGfxnvGvw4SHIOuRnQ3kadyyXY6tGdChusIHIbM5zfIbp4M6tCg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@eslint-community/eslint-utils": "^4.4.0",
|
||||
"@typescript-eslint/scope-manager": "8.18.2",
|
||||
"@typescript-eslint/types": "8.18.2",
|
||||
"@typescript-eslint/typescript-estree": "8.18.2"
|
||||
"@typescript-eslint/scope-manager": "8.19.0",
|
||||
"@typescript-eslint/types": "8.19.0",
|
||||
"@typescript-eslint/typescript-estree": "8.19.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
|
||||
@ -2002,13 +2013,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@typescript-eslint/visitor-keys": {
|
||||
"version": "8.18.2",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.18.2.tgz",
|
||||
"integrity": "sha512-zORcwn4C3trOWiCqFQP1x6G3xTRyZ1LYydnj51cRnJ6hxBlr/cKPckk+PKPUw/fXmvfKTcw7bwY3w9izgx5jZw==",
|
||||
"version": "8.19.0",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.19.0.tgz",
|
||||
"integrity": "sha512-mCFtBbFBJDCNCWUl5y6sZSCHXw1DEFEk3c/M3nRK2a4XUB8StGFtmcEMizdjKuBzB6e/smJAAWYug3VrdLMr1w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@typescript-eslint/types": "8.18.2",
|
||||
"@typescript-eslint/types": "8.19.0",
|
||||
"eslint-visitor-keys": "^4.2.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -2390,6 +2401,18 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/blessed": {
|
||||
"version": "0.1.81",
|
||||
"resolved": "https://registry.npmjs.org/blessed/-/blessed-0.1.81.tgz",
|
||||
"integrity": "sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"blessed": "bin/tput.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/body-parser": {
|
||||
"version": "1.20.3",
|
||||
"resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.3.tgz",
|
||||
@ -2457,9 +2480,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/bullmq": {
|
||||
"version": "5.34.5",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.5.tgz",
|
||||
"integrity": "sha512-MHho9EOhLCTY3ZF+dd0wHv0VlY2FtpBcopMRsvj0kPra4TAwBFh2pik/s4WbX56cIfCE+VzfHIHy4xvqp3g1+Q==",
|
||||
"version": "5.34.6",
|
||||
"resolved": "https://registry.npmjs.org/bullmq/-/bullmq-5.34.6.tgz",
|
||||
"integrity": "sha512-pRCYyO9RlkQWxdmKlrNnUthyFwurYXRYLVXD1YIx+nCCdhAOiHatD8FDHbsT/w2I31c0NWoMcfZiIGuipiF7Lg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cron-parser": "^4.9.0",
|
||||
@ -5214,9 +5237,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/mariadb/node_modules/@types/node": {
|
||||
"version": "22.10.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.2.tgz",
|
||||
"integrity": "sha512-Xxr6BBRCAOQixvonOye19wnzyDiUtTeqldOOmj3CkeblonbccA12PFwlufvRdrpjXxqnmUaeiU5EOA+7s5diUQ==",
|
||||
"version": "22.10.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.3.tgz",
|
||||
"integrity": "sha512-DifAyw4BkrufCILvD3ucnuN8eydUfc/C1GlyrnI+LK6543w5/L3VeVgf05o3B4fqSXP1dKYLOZsKfutpxPzZrw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~6.20.0"
|
||||
@ -5732,9 +5755,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/own-keys": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.0.tgz",
|
||||
"integrity": "sha512-HcuIjzpjrUbqZPGzWHVg95Bc2Y37KoY5n66QQyEGMzrIWVKHsgHcv8/Aq5Cu3qFUQJzMSPVP8MD3oaFoaME1lg==",
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz",
|
||||
"integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
|
@ -2,8 +2,8 @@
|
||||
"useTsNode": true,
|
||||
"alwaysAllowTs": true,
|
||||
"scripts": {
|
||||
"start": "npx prisma migrate deploy && node dist/server.js",
|
||||
"dev": "nodemon --ignore 'data/*' --exec tsx src/server.ts",
|
||||
"start": "node dist/server.js",
|
||||
"dev": "nodemon --exec tsx src/server.ts",
|
||||
"build": "tsc",
|
||||
"format": "prettier --write src/",
|
||||
"lint": "eslint .",
|
||||
@ -16,8 +16,10 @@
|
||||
"@mikro-orm/mysql": "^6.4.2",
|
||||
"@mikro-orm/reflection": "^6.4.2",
|
||||
"@prisma/client": "^6.1.0",
|
||||
"@types/blessed": "^0.1.25",
|
||||
"@types/ioredis": "^4.28.10",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"blessed": "^0.1.81",
|
||||
"bullmq": "^5.13.2",
|
||||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.5",
|
||||
|
@ -1,57 +0,0 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import { pathToFileURL } from 'url'
|
||||
|
||||
import Logger, { LoggerType } from '#application/logger'
|
||||
import { getAppPath } from '#application/storage'
|
||||
import { Command } from '#application/types'
|
||||
|
||||
export class CommandRegistry {
|
||||
private readonly commands: Map<string, Command> = new Map()
|
||||
private readonly logger = Logger.type(LoggerType.COMMAND)
|
||||
|
||||
public getCommand(name: string): Command | undefined {
|
||||
return this.commands.get(name)
|
||||
}
|
||||
|
||||
public async loadCommands(): Promise<void> {
|
||||
const directory = getAppPath('commands')
|
||||
this.logger.info(`Loading commands from: ${directory}`)
|
||||
|
||||
try {
|
||||
const files = await fs.promises.readdir(directory, { withFileTypes: true })
|
||||
await Promise.all(files.filter((file) => this.isValidCommandFile(file)).map((file) => this.loadCommandFile(file)))
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to read commands directory: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
private isValidCommandFile(file: fs.Dirent): boolean {
|
||||
return file.isFile() && (file.name.endsWith('.ts') || file.name.endsWith('.js'))
|
||||
}
|
||||
|
||||
private async loadCommandFile(file: fs.Dirent): Promise<void> {
|
||||
try {
|
||||
const filePath = getAppPath('commands', file.name)
|
||||
const commandName = path.basename(file.name, path.extname(file.name))
|
||||
|
||||
const module = await import(pathToFileURL(filePath).href)
|
||||
if (typeof module.default !== 'function') {
|
||||
this.logger.warn(`Unrecognized export in ${file.name}`)
|
||||
return
|
||||
}
|
||||
|
||||
this.registerCommand(commandName, module.default)
|
||||
} catch (error) {
|
||||
this.logger.error(`Error loading command ${file.name}: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
private registerCommand(name: string, CommandClass: Command): void {
|
||||
if (this.commands.has(name)) {
|
||||
this.logger.warn(`Command '${name}' is already registered. Overwriting...`)
|
||||
}
|
||||
this.commands.set(name, CommandClass)
|
||||
this.logger.info(`Registered command: ${name}`)
|
||||
}
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import * as readline from 'readline'
|
||||
|
||||
export class ConsolePrompt {
|
||||
private readonly rl: readline.Interface
|
||||
private isClosed: boolean = false
|
||||
|
||||
constructor(private readonly commandHandler: (command: string) => void) {
|
||||
this.rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
})
|
||||
|
||||
this.rl.on('close', () => {
|
||||
this.isClosed = true
|
||||
})
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
if (this.isClosed) return
|
||||
this.promptCommand()
|
||||
}
|
||||
|
||||
public close(): void {
|
||||
this.rl.close()
|
||||
}
|
||||
|
||||
private promptCommand(): void {
|
||||
this.rl.question('> ', (command: string) => {
|
||||
this.commandHandler(command)
|
||||
this.promptCommand()
|
||||
})
|
||||
}
|
||||
}
|
@ -1,77 +0,0 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
|
||||
import Logger, { LoggerType } from '#application/logger'
|
||||
|
||||
export class LogReader {
|
||||
private logger = Logger.type(LoggerType.CONSOLE)
|
||||
private watchers: fs.FSWatcher[] = []
|
||||
private readonly logsDirectory: string
|
||||
|
||||
constructor(rootPath: string) {
|
||||
this.logsDirectory = path.join(rootPath, 'logs')
|
||||
}
|
||||
|
||||
public start(): void {
|
||||
this.logger.info('Starting log reader...')
|
||||
this.watchLogs()
|
||||
}
|
||||
|
||||
public stop(): void {
|
||||
this.watchers.forEach((watcher) => watcher.close())
|
||||
this.watchers = []
|
||||
}
|
||||
|
||||
private watchLogs(): void {
|
||||
// Watch directory for new files
|
||||
const directoryWatcher = fs.watch(this.logsDirectory, (_, filename) => {
|
||||
if (filename?.endsWith('.log')) {
|
||||
this.watchLogFile(filename)
|
||||
}
|
||||
})
|
||||
this.watchers.push(directoryWatcher)
|
||||
|
||||
// Watch existing files
|
||||
try {
|
||||
fs.readdirSync(this.logsDirectory)
|
||||
.filter((file) => file.endsWith('.log'))
|
||||
.forEach((file) => this.watchLogFile(file))
|
||||
} catch (error) {
|
||||
this.logger.error(`Error reading logs directory: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
private watchLogFile(filename: string): void {
|
||||
const filePath = path.join(this.logsDirectory, filename)
|
||||
let currentPosition = fs.existsSync(filePath) ? fs.statSync(filePath).size : 0
|
||||
|
||||
const watcher = fs.watch(filePath, () => {
|
||||
try {
|
||||
const stat = fs.statSync(filePath)
|
||||
const newPosition = stat.size
|
||||
|
||||
if (newPosition < currentPosition) {
|
||||
currentPosition = 0
|
||||
}
|
||||
|
||||
if (newPosition > currentPosition) {
|
||||
const stream = fs.createReadStream(filePath, {
|
||||
start: currentPosition,
|
||||
end: newPosition
|
||||
})
|
||||
|
||||
stream.on('data', (data) => {
|
||||
process.stdout.write('\r' + `[${filename}]\n${data}`)
|
||||
process.stdout.write('\n> ')
|
||||
})
|
||||
|
||||
currentPosition = newPosition
|
||||
}
|
||||
} catch {
|
||||
watcher.close()
|
||||
}
|
||||
})
|
||||
|
||||
this.watchers.push(watcher)
|
||||
}
|
||||
}
|
@ -1,34 +1,71 @@
|
||||
import fs from 'fs'
|
||||
import path from 'path'
|
||||
|
||||
import config from './config'
|
||||
import config from '#application/config'
|
||||
|
||||
export function getRootPath(folder: string, ...additionalSegments: string[]) {
|
||||
return path.join(process.cwd(), folder, ...additionalSegments)
|
||||
}
|
||||
class Storage {
|
||||
private readonly baseDir: string
|
||||
private readonly rootDir: string
|
||||
|
||||
export function getAppPath(folder: string, ...additionalSegments: string[]) {
|
||||
const baseDir = config.ENV === 'development' ? 'src' : 'dist'
|
||||
return path.join(process.cwd(), baseDir, folder, ...additionalSegments)
|
||||
}
|
||||
constructor() {
|
||||
this.rootDir = process.cwd()
|
||||
this.baseDir = config.ENV === 'development' ? 'src' : 'dist'
|
||||
}
|
||||
|
||||
export function getPublicPath(folder: string, ...additionalSegments: string[]) {
|
||||
return path.join(process.cwd(), 'public', folder, ...additionalSegments)
|
||||
}
|
||||
/**
|
||||
* Gets path relative to project root
|
||||
*/
|
||||
public getRootPath(folder: string, ...additionalSegments: string[]): string {
|
||||
return path.join(this.rootDir, folder, ...additionalSegments)
|
||||
}
|
||||
|
||||
export function doesPathExist(path: string) {
|
||||
try {
|
||||
fs.accessSync(path, fs.constants.F_OK)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
/**
|
||||
* Gets path relative to app directory (src/dist)
|
||||
*/
|
||||
public getAppPath(folder: string, ...additionalSegments: string[]): string {
|
||||
return path.join(this.rootDir, this.baseDir, folder, ...additionalSegments)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets path relative to public directory
|
||||
*/
|
||||
public getPublicPath(folder: string, ...additionalSegments: string[]): string {
|
||||
return path.join(this.rootDir, 'public', folder, ...additionalSegments)
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a path exists
|
||||
* @throws Error if path is empty or invalid
|
||||
*/
|
||||
public doesPathExist(pathToCheck: string): boolean {
|
||||
if (!pathToCheck) {
|
||||
throw new Error('Path cannot be empty')
|
||||
}
|
||||
|
||||
try {
|
||||
fs.accessSync(pathToCheck, fs.constants.F_OK)
|
||||
return true
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a directory and any necessary parent directories
|
||||
* @throws Error if directory creation fails
|
||||
*/
|
||||
public createDir(dirPath: string): void {
|
||||
if (!dirPath) {
|
||||
throw new Error('Directory path cannot be empty')
|
||||
}
|
||||
|
||||
try {
|
||||
fs.mkdirSync(dirPath, { recursive: true })
|
||||
} catch (error) {
|
||||
const typedError = error as Error
|
||||
throw new Error(`Failed to create directory: ${typedError.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function createDir(path: string) {
|
||||
try {
|
||||
fs.mkdirSync(path, { recursive: true })
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
export default new Storage()
|
||||
|
@ -7,8 +7,8 @@ import { ZoneEventTileTeleport } from '#entities/zoneEventTileTeleport'
|
||||
export type UUID = `${string}-${string}-${string}-${string}-${string}`
|
||||
|
||||
export type TSocket = Socket & {
|
||||
userId?: number
|
||||
characterId?: number
|
||||
userId?: UUID
|
||||
characterId?: UUID
|
||||
handshake?: {
|
||||
query?: {
|
||||
token?: any
|
||||
|
@ -1,11 +1,10 @@
|
||||
import fs from 'fs'
|
||||
|
||||
import sharp from 'sharp'
|
||||
import { Server } from 'socket.io'
|
||||
|
||||
import { BaseCommand } from '#application/base/baseCommand'
|
||||
import { CharacterGender, CharacterRace } from '#application/enums'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
import { UUID } from '#application/types'
|
||||
import { Character } from '#entities/character'
|
||||
import { CharacterHair } from '#entities/characterHair'
|
||||
@ -44,7 +43,7 @@ export default class InitCommand extends BaseCommand {
|
||||
}
|
||||
|
||||
private async importTiles(): Promise<void> {
|
||||
for (const tile of fs.readdirSync(getPublicPath('tiles'))) {
|
||||
for (const tile of fs.readdirSync(Storage.getPublicPath('tiles'))) {
|
||||
const newTile = new Tile()
|
||||
newTile.setId(tile.split('.')[0] as UUID).setName('New tile')
|
||||
|
||||
@ -53,18 +52,18 @@ export default class InitCommand extends BaseCommand {
|
||||
}
|
||||
|
||||
private async importObjects(): Promise<void> {
|
||||
for (const object of fs.readdirSync(getPublicPath('objects'))) {
|
||||
for (const object of fs.readdirSync(Storage.getPublicPath('objects'))) {
|
||||
const newMapObject = new MapObject()
|
||||
newMapObject
|
||||
.setId(object.split('.')[0] as UUID)
|
||||
.setName('New object')
|
||||
.setFrameWidth(
|
||||
(await sharp(getPublicPath('objects', object))
|
||||
(await sharp(Storage.getPublicPath('objects', object))
|
||||
.metadata()
|
||||
.then((metadata) => metadata.height)) ?? 0
|
||||
)
|
||||
.setFrameHeight(
|
||||
(await sharp(getPublicPath('objects', object))
|
||||
(await sharp(Storage.getPublicPath('objects', object))
|
||||
.metadata()
|
||||
.then((metadata) => metadata.width)) ?? 0
|
||||
)
|
||||
@ -149,7 +148,7 @@ export default class InitCommand extends BaseCommand {
|
||||
.save()
|
||||
|
||||
const characterType = new CharacterType()
|
||||
await characterType.setId(1).setName('New character type').setGender(CharacterGender.MALE).setRace(CharacterRace.HUMAN).setIsSelectable(true).setSprite(characterSprite).save()
|
||||
await characterType.setId('75b70c78-17f0-44c0-a4fa-15043cb95be0').setName('New character type').setGender(CharacterGender.MALE).setRace(CharacterRace.HUMAN).setIsSelectable(true).setSprite(characterSprite).save()
|
||||
}
|
||||
|
||||
private async createCharacterHair(): Promise<void> {
|
||||
@ -190,7 +189,7 @@ export default class InitCommand extends BaseCommand {
|
||||
.save()
|
||||
|
||||
const characterHair = new CharacterHair()
|
||||
await characterHair.setId(1).setName('Hair 1').setGender(CharacterGender.MALE).setIsSelectable(true).setSprite(hairSprite).save()
|
||||
await characterHair.setId('a2471230-d238-4ffb-9eca-9eab869f1b67').setName('Hair 1').setGender(CharacterGender.MALE).setIsSelectable(true).setSprite(hairSprite).save()
|
||||
}
|
||||
|
||||
private async createCharacterEquipment(): Promise<void> {
|
||||
@ -240,15 +239,15 @@ export default class InitCommand extends BaseCommand {
|
||||
|
||||
private async createUser(): Promise<void> {
|
||||
const user = new User()
|
||||
await user.setId(1).setUsername('root').setEmail('local@host').setPassword('password').setOnline(false).save()
|
||||
await user.setId('6f9a58b4-172d-425e-b9ea-71e1d13d81ee').setUsername('root').setEmail('local@host').setPassword('password').setOnline(false).save()
|
||||
|
||||
const character = new Character()
|
||||
await character
|
||||
.setId(1)
|
||||
.setId('26850183-1757-4135-938f-aa1448c49654')
|
||||
.setUser(user)
|
||||
.setName('root')
|
||||
.setRole('gm')
|
||||
.setZone((await ZoneRepository.getFirst()) ?? undefined)
|
||||
.setZone((await ZoneRepository.getFirst())!)
|
||||
.setCharacterType((await CharacterTypeRepository.getFirst()) ?? undefined)
|
||||
.setCharacterHair((await CharacterHairRepository.getFirst()) ?? undefined)
|
||||
.save()
|
||||
|
@ -3,12 +3,12 @@ import fs from 'fs'
|
||||
import sharp from 'sharp'
|
||||
|
||||
import { BaseCommand } from '#application/base/baseCommand'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
|
||||
export default class TilesCommand extends BaseCommand {
|
||||
public async execute(): Promise<void> {
|
||||
// Get all tiles
|
||||
const tilesDir = getPublicPath('tiles')
|
||||
const tilesDir = Storage.getPublicPath('tiles')
|
||||
const tiles = fs.readdirSync(tilesDir).filter((file) => file.endsWith('.png'))
|
||||
|
||||
// Create output directory if it doesn't exist
|
||||
@ -18,14 +18,14 @@ export default class TilesCommand extends BaseCommand {
|
||||
|
||||
for (const tile of tiles) {
|
||||
// Check if tile is already 66x34
|
||||
const metadata = await sharp(getPublicPath('tiles', tile)).metadata()
|
||||
const metadata = await sharp(Storage.getPublicPath('tiles', tile)).metadata()
|
||||
if (metadata.width === 66 && metadata.height === 34) {
|
||||
this.logger.info(`Tile ${tile} already processed`)
|
||||
continue
|
||||
}
|
||||
|
||||
const inputPath = getPublicPath('tiles', tile)
|
||||
const tempPath = getPublicPath('tiles', `temp_${tile}`)
|
||||
const inputPath = Storage.getPublicPath('tiles', tile)
|
||||
const tempPath = Storage.getPublicPath('tiles', `temp_${tile}`)
|
||||
|
||||
try {
|
||||
await sharp(inputPath)
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
|
||||
|
||||
import { CharacterEquipment } from './characterEquipment'
|
||||
@ -9,13 +11,14 @@ import { User } from './user'
|
||||
import { Zone } from './zone'
|
||||
|
||||
import { BaseEntity } from '#application/base/baseEntity'
|
||||
import { UUID } from '#application/types'
|
||||
|
||||
@Entity()
|
||||
export class Character extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: number
|
||||
id = randomUUID()
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
user!: User
|
||||
|
||||
@Property({ unique: true })
|
||||
@ -32,7 +35,7 @@ export class Character extends BaseEntity {
|
||||
|
||||
// Position
|
||||
@ManyToOne()
|
||||
zone!: Zone
|
||||
zone!: Zone // @TODO: Update to spawn point when current zone is not found
|
||||
|
||||
@Property()
|
||||
positionX = 0
|
||||
@ -44,10 +47,10 @@ export class Character extends BaseEntity {
|
||||
rotation = 0
|
||||
|
||||
// Customization
|
||||
@ManyToOne()
|
||||
@ManyToOne({ deleteRule: 'set null' })
|
||||
characterType?: CharacterType | null | undefined
|
||||
|
||||
@ManyToOne()
|
||||
@ManyToOne({ deleteRule: 'set null' })
|
||||
characterHair?: CharacterHair | null | undefined
|
||||
|
||||
// Inventory
|
||||
@ -85,7 +88,7 @@ export class Character extends BaseEntity {
|
||||
@Property()
|
||||
wisdom = 10
|
||||
|
||||
setId(id: number) {
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import { Entity, Enum, ManyToOne, PrimaryKey } from '@mikro-orm/core'
|
||||
|
||||
import { Character } from './character'
|
||||
@ -5,22 +7,23 @@ import { CharacterItem } from './characterItem'
|
||||
|
||||
import { BaseEntity } from '#application/base/baseEntity'
|
||||
import { CharacterEquipmentSlotType } from '#application/enums'
|
||||
import { UUID } from '#application/types'
|
||||
|
||||
@Entity()
|
||||
export class CharacterEquipment extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: number
|
||||
id = randomUUID()
|
||||
|
||||
@Enum(() => CharacterEquipmentSlotType)
|
||||
slot!: CharacterEquipmentSlotType
|
||||
|
||||
@ManyToOne(() => Character)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
character!: Character
|
||||
|
||||
@ManyToOne(() => CharacterItem)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
characterItem!: CharacterItem
|
||||
|
||||
setId(id: number) {
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
|
||||
|
||||
import { Character } from './character'
|
||||
@ -5,11 +7,12 @@ import { Sprite } from './sprite'
|
||||
|
||||
import { BaseEntity } from '#application/base/baseEntity'
|
||||
import { CharacterGender } from '#application/enums'
|
||||
import { UUID } from '#application/types'
|
||||
|
||||
@Entity()
|
||||
export class CharacterHair extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: number
|
||||
id = randomUUID()
|
||||
|
||||
@Property()
|
||||
name!: string
|
||||
@ -20,13 +23,10 @@ export class CharacterHair extends BaseEntity {
|
||||
@Property()
|
||||
isSelectable = false
|
||||
|
||||
@ManyToOne(() => Sprite, { nullable: true })
|
||||
@ManyToOne({ nullable: true })
|
||||
sprite?: Sprite
|
||||
|
||||
@OneToMany(() => Character, (character) => character.characterHair)
|
||||
characters = new Collection<Character>(this)
|
||||
|
||||
setId(id: number) {
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
@ -70,13 +70,4 @@ export class CharacterHair extends BaseEntity {
|
||||
getSprite() {
|
||||
return this.sprite
|
||||
}
|
||||
|
||||
setCharacters(characters: Collection<Character>) {
|
||||
this.characters = characters
|
||||
return this
|
||||
}
|
||||
|
||||
getCharacters() {
|
||||
return this.characters
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import { Collection, Entity, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
|
||||
|
||||
import { Character } from './character'
|
||||
@ -5,25 +7,23 @@ import { CharacterEquipment } from './characterEquipment'
|
||||
import { Item } from './item'
|
||||
|
||||
import { BaseEntity } from '#application/base/baseEntity'
|
||||
import { UUID } from '#application/types'
|
||||
|
||||
@Entity()
|
||||
export class CharacterItem extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: number
|
||||
id = randomUUID()
|
||||
|
||||
@ManyToOne(() => Character)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
character!: Character
|
||||
|
||||
@ManyToOne(() => Item)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
item!: Item
|
||||
|
||||
@Property()
|
||||
quantity!: number
|
||||
|
||||
@OneToMany(() => CharacterEquipment, (equipment) => equipment.characterItem)
|
||||
characterEquipment = new Collection<CharacterEquipment>(this)
|
||||
|
||||
setId(id: number) {
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
@ -58,13 +58,4 @@ export class CharacterItem extends BaseEntity {
|
||||
getQuantity() {
|
||||
return this.quantity
|
||||
}
|
||||
|
||||
setCharacterEquipment(characterEquipment: Collection<CharacterEquipment>) {
|
||||
this.characterEquipment = characterEquipment
|
||||
return this
|
||||
}
|
||||
|
||||
getCharacterEquipment() {
|
||||
return this.characterEquipment
|
||||
}
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import { Collection, Entity, Enum, ManyToOne, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
|
||||
|
||||
import { Character } from './character'
|
||||
@ -5,11 +7,12 @@ import { Sprite } from './sprite'
|
||||
|
||||
import { BaseEntity } from '#application/base/baseEntity'
|
||||
import { CharacterGender, CharacterRace } from '#application/enums'
|
||||
import { UUID } from '#application/types'
|
||||
|
||||
@Entity()
|
||||
export class CharacterType extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: number
|
||||
id = randomUUID()
|
||||
|
||||
@Property()
|
||||
name!: string
|
||||
@ -35,7 +38,7 @@ export class CharacterType extends BaseEntity {
|
||||
@Property()
|
||||
updatedAt = new Date()
|
||||
|
||||
setId(id: number) {
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
@ -1,19 +1,22 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
|
||||
|
||||
import { Character } from './character'
|
||||
import { Zone } from './zone'
|
||||
|
||||
import { BaseEntity } from '#application/base/baseEntity'
|
||||
import { UUID } from '#application/types'
|
||||
|
||||
@Entity()
|
||||
export class Chat extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: number
|
||||
id = randomUUID()
|
||||
|
||||
@ManyToOne(() => Character)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
character!: Character
|
||||
|
||||
@ManyToOne(() => Zone)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
zone!: Zone
|
||||
|
||||
@Property()
|
||||
@ -22,7 +25,7 @@ export class Chat extends BaseEntity {
|
||||
@Property()
|
||||
createdAt = new Date()
|
||||
|
||||
setId(id: number) {
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
@ -38,9 +38,6 @@ export class Item extends BaseEntity {
|
||||
@Property()
|
||||
updatedAt = new Date()
|
||||
|
||||
@OneToMany(() => CharacterItem, (characterItem) => characterItem.item)
|
||||
characters = new Collection<CharacterItem>(this)
|
||||
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
@ -121,13 +118,4 @@ export class Item extends BaseEntity {
|
||||
getUpdatedAt() {
|
||||
return this.updatedAt
|
||||
}
|
||||
|
||||
setCharacters(characters: Collection<CharacterItem>) {
|
||||
this.characters = characters
|
||||
return this
|
||||
}
|
||||
|
||||
getCharacters() {
|
||||
return this.characters
|
||||
}
|
||||
}
|
||||
|
@ -42,9 +42,6 @@ export class MapObject extends BaseEntity {
|
||||
@Property()
|
||||
updatedAt = new Date()
|
||||
|
||||
@OneToMany(() => ZoneObject, (zoneObject) => zoneObject.mapObject)
|
||||
zoneObjects = new Collection<ZoneObject>(this)
|
||||
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
@ -143,13 +140,4 @@ export class MapObject extends BaseEntity {
|
||||
getUpdatedAt() {
|
||||
return this.updatedAt
|
||||
}
|
||||
|
||||
setZoneObjects(zoneObjects: Collection<ZoneObject>) {
|
||||
this.zoneObjects = zoneObjects
|
||||
return this
|
||||
}
|
||||
|
||||
getZoneObjects() {
|
||||
return this.zoneObjects
|
||||
}
|
||||
}
|
||||
|
@ -1,15 +1,18 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import { Entity, ManyToOne, PrimaryKey, Property } from '@mikro-orm/core'
|
||||
|
||||
import { User } from './user'
|
||||
|
||||
import { BaseEntity } from '#application/base/baseEntity'
|
||||
import { UUID } from '#application/types'
|
||||
|
||||
@Entity()
|
||||
export class PasswordResetToken extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: number
|
||||
id = randomUUID()
|
||||
|
||||
@ManyToOne(() => User)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
user!: User
|
||||
|
||||
@Property({ unique: true })
|
||||
@ -18,7 +21,7 @@ export class PasswordResetToken extends BaseEntity {
|
||||
@Property()
|
||||
createdAt = new Date()
|
||||
|
||||
setId(id: number) {
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export class SpriteAction extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id = randomUUID()
|
||||
|
||||
@ManyToOne(() => Sprite)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
sprite!: Sprite
|
||||
|
||||
@Property()
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
|
||||
import bcrypt from 'bcryptjs'
|
||||
|
||||
@ -5,11 +7,12 @@ import { Character } from './character'
|
||||
import { PasswordResetToken } from './passwordResetToken'
|
||||
|
||||
import { BaseEntity } from '#application/base/baseEntity'
|
||||
import { UUID } from '#application/types'
|
||||
|
||||
@Entity()
|
||||
export class User extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: number
|
||||
id = randomUUID()
|
||||
|
||||
@Property({ unique: true })
|
||||
username!: string
|
||||
@ -29,7 +32,7 @@ export class User extends BaseEntity {
|
||||
@OneToMany(() => PasswordResetToken, (token) => token.user)
|
||||
passwordResetTokens = new Collection<PasswordResetToken>(this)
|
||||
|
||||
setId(id: number) {
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
|
@ -1,3 +1,5 @@
|
||||
import { randomUUID } from 'node:crypto'
|
||||
|
||||
import { Collection, Entity, OneToMany, PrimaryKey, Property } from '@mikro-orm/core'
|
||||
|
||||
import { Character } from './character'
|
||||
@ -8,11 +10,12 @@ import { ZoneEventTileTeleport } from './zoneEventTileTeleport'
|
||||
import { ZoneObject } from './zoneObject'
|
||||
|
||||
import { BaseEntity } from '#application/base/baseEntity'
|
||||
import { UUID } from '#application/types'
|
||||
|
||||
@Entity()
|
||||
export class Zone extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id!: number
|
||||
id = randomUUID()
|
||||
|
||||
@Property()
|
||||
name!: string
|
||||
@ -29,6 +32,12 @@ export class Zone extends BaseEntity {
|
||||
@Property()
|
||||
pvp = false
|
||||
|
||||
@Property()
|
||||
createdAt = new Date()
|
||||
|
||||
@Property()
|
||||
updatedAt = new Date()
|
||||
|
||||
@OneToMany(() => ZoneEffect, (effect) => effect.zone)
|
||||
zoneEffects = new Collection<ZoneEffect>(this)
|
||||
|
||||
@ -47,13 +56,7 @@ export class Zone extends BaseEntity {
|
||||
@OneToMany(() => Chat, (chat) => chat.zone)
|
||||
chats = new Collection<Chat>(this)
|
||||
|
||||
@Property()
|
||||
createdAt = new Date()
|
||||
|
||||
@Property()
|
||||
updatedAt = new Date()
|
||||
|
||||
setId(id: number) {
|
||||
setId(id: UUID) {
|
||||
this.id = id
|
||||
return this
|
||||
}
|
||||
@ -107,6 +110,24 @@ export class Zone extends BaseEntity {
|
||||
return this.pvp
|
||||
}
|
||||
|
||||
setCreatedAt(createdAt: Date) {
|
||||
this.createdAt = createdAt
|
||||
return this
|
||||
}
|
||||
|
||||
getCreatedAt() {
|
||||
return this.createdAt
|
||||
}
|
||||
|
||||
setUpdatedAt(updatedAt: Date) {
|
||||
this.updatedAt = updatedAt
|
||||
return this
|
||||
}
|
||||
|
||||
getUpdatedAt() {
|
||||
return this.updatedAt
|
||||
}
|
||||
|
||||
setZoneEffects(zoneEffects: Collection<ZoneEffect>) {
|
||||
this.zoneEffects = zoneEffects
|
||||
return this
|
||||
@ -160,22 +181,4 @@ export class Zone extends BaseEntity {
|
||||
getChats() {
|
||||
return this.chats
|
||||
}
|
||||
|
||||
setCreatedAt(createdAt: Date) {
|
||||
this.createdAt = createdAt
|
||||
return this
|
||||
}
|
||||
|
||||
getCreatedAt() {
|
||||
return this.createdAt
|
||||
}
|
||||
|
||||
setUpdatedAt(updatedAt: Date) {
|
||||
this.updatedAt = updatedAt
|
||||
return this
|
||||
}
|
||||
|
||||
getUpdatedAt() {
|
||||
return this.updatedAt
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,7 @@ export class ZoneEffect extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id = randomUUID()
|
||||
|
||||
@ManyToOne(() => Zone)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
zone!: Zone
|
||||
|
||||
@Property()
|
||||
|
@ -14,7 +14,7 @@ export class ZoneEventTile extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id = randomUUID()
|
||||
|
||||
@ManyToOne(() => Zone)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
zone!: Zone
|
||||
|
||||
@Enum(() => ZoneEventTileType)
|
||||
|
@ -13,10 +13,10 @@ export class ZoneEventTileTeleport extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id = randomUUID()
|
||||
|
||||
@OneToOne(() => ZoneEventTile)
|
||||
@OneToOne({ deleteRule: 'cascade' })
|
||||
zoneEventTile!: ZoneEventTile
|
||||
|
||||
@ManyToOne(() => Zone)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
toZone!: Zone
|
||||
|
||||
@Property()
|
||||
|
@ -14,10 +14,10 @@ export class ZoneObject extends BaseEntity {
|
||||
@PrimaryKey()
|
||||
id = randomUUID()
|
||||
|
||||
@ManyToOne(() => Zone)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
zone!: Zone
|
||||
|
||||
@ManyToOne(() => MapObject)
|
||||
@ManyToOne({ deleteRule: 'cascade' })
|
||||
mapObject!: MapObject
|
||||
|
||||
@Property()
|
||||
|
@ -1,12 +1,13 @@
|
||||
import { BaseEvent } from '#application/base/baseEvent'
|
||||
import Database from '#application/database'
|
||||
import { UUID } from '#application/types'
|
||||
import ZoneManager from '#managers/zoneManager'
|
||||
import CharacterHairRepository from '#repositories/characterHairRepository'
|
||||
import CharacterRepository from '#repositories/characterRepository'
|
||||
import TeleportService from '#services/teleportService'
|
||||
|
||||
interface CharacterConnectPayload {
|
||||
characterId: number
|
||||
characterHairId?: number
|
||||
characterId: UUID
|
||||
characterHairId?: UUID
|
||||
}
|
||||
|
||||
export default class CharacterConnectEvent extends BaseEvent {
|
||||
@ -56,7 +57,17 @@ export default class CharacterConnectEvent extends BaseEvent {
|
||||
// Emit character connect event
|
||||
callback({ character })
|
||||
|
||||
// @TODO: Teleport character into zone
|
||||
// wait 300 ms, @TODO: Find a better way to do this
|
||||
await new Promise((resolve) => setTimeout(resolve, 100))
|
||||
|
||||
await TeleportService.teleportCharacter(character.id, {
|
||||
targetZoneId: character.zone.id,
|
||||
targetX: character.positionX,
|
||||
targetY: character.positionY,
|
||||
rotation: character.rotation,
|
||||
isInitialJoin: true,
|
||||
character
|
||||
})
|
||||
} catch (error) {
|
||||
this.handleError('Failed to connect character', error)
|
||||
}
|
||||
|
@ -5,6 +5,7 @@ import { ZCharacterCreate } from '#application/zodTypes'
|
||||
import { Character } from '#entities/character'
|
||||
import CharacterRepository from '#repositories/characterRepository'
|
||||
import UserRepository from '#repositories/userRepository'
|
||||
import ZoneRepository from '#repositories/zoneRepository'
|
||||
|
||||
export default class CharacterCreateEvent extends BaseEvent {
|
||||
public listen(): void {
|
||||
@ -35,10 +36,15 @@ export default class CharacterCreateEvent extends BaseEvent {
|
||||
return this.socket.emit('notification', { message: 'You can only have 4 characters' })
|
||||
}
|
||||
|
||||
const newCharacter = new Character()
|
||||
await newCharacter.setName(data.name).setUser(user).save()
|
||||
// @TODO: Change to default location
|
||||
const zone = await ZoneRepository.getFirst()
|
||||
|
||||
if (!newCharacter) return this.socket.emit('notification', { message: 'Failed to create character. Please try again (later).' })
|
||||
const newCharacter = new Character()
|
||||
await newCharacter.setName(data.name).setUser(user).setZone(zone!).save()
|
||||
|
||||
if (!newCharacter) {
|
||||
return this.socket.emit('notification', { message: 'Failed to create character. Please try again (later).' })
|
||||
}
|
||||
|
||||
characters = [...characters, newCharacter]
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
import { BaseEvent } from '#application/base/baseEvent'
|
||||
import { UUID } from '#application/types'
|
||||
import { Character } from '#entities/character'
|
||||
import { Zone } from '#entities/zone'
|
||||
import CharacterRepository from '#repositories/characterRepository'
|
||||
|
||||
type TypePayload = {
|
||||
characterId: number
|
||||
characterId: UUID
|
||||
}
|
||||
|
||||
type TypeResponse = {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { BaseEvent } from '#application/base/baseEvent'
|
||||
import { UUID } from '#application/types'
|
||||
import ZoneManager from '#managers/zoneManager'
|
||||
import zoneManager from '#managers/zoneManager'
|
||||
import ZoneCharacter from '#models/zoneCharacter'
|
||||
import ZoneRepository from '#repositories/zoneRepository'
|
||||
import ChatService from '#services/chatService'
|
||||
import TeleportService from '#services/teleportService'
|
||||
|
||||
type TypePayload = {
|
||||
message: string
|
||||
@ -14,9 +14,8 @@ export default class TeleportCommandEvent extends BaseEvent {
|
||||
this.socket.on('chat:message', this.handleEvent.bind(this))
|
||||
}
|
||||
|
||||
private async handleEvent(data: TypePayload, callback: (response: boolean) => void): Promise<void> {
|
||||
private async handleEvent(data: TypePayload, callback: (response: boolean) => void) {
|
||||
try {
|
||||
// Check if character exists
|
||||
const zoneCharacter = ZoneManager.getCharacterById(this.socket.characterId!)
|
||||
if (!zoneCharacter) {
|
||||
this.logger.error('chat:message error', 'Character not found')
|
||||
@ -25,7 +24,6 @@ export default class TeleportCommandEvent extends BaseEvent {
|
||||
|
||||
const character = zoneCharacter.character
|
||||
|
||||
// Check if the user is the GM
|
||||
if (character.role !== 'gm') {
|
||||
this.logger.info(`User ${character.id} tried to set time but is not a game master.`)
|
||||
return
|
||||
@ -35,54 +33,68 @@ export default class TeleportCommandEvent extends BaseEvent {
|
||||
|
||||
const args = ChatService.getArgs('teleport', data.message)
|
||||
|
||||
if (!args || args.length !== 1) {
|
||||
this.socket.emit('notification', { title: 'Server message', message: 'Usage: /teleport <zoneId>' })
|
||||
if (!args || args.length === 0 || args.length > 3) {
|
||||
this.socket.emit('notification', {
|
||||
title: 'Server message',
|
||||
message: 'Usage: /teleport <zoneId> [x] [y]'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const zoneId = parseInt(args[0], 10)
|
||||
if (isNaN(zoneId)) {
|
||||
this.socket.emit('notification', { title: 'Server message', message: 'Invalid zone ID' })
|
||||
const zoneId = args[0] as UUID
|
||||
const targetX = args[1] ? parseInt(args[1], 10) : 0
|
||||
const targetY = args[2] ? parseInt(args[2], 10) : 0
|
||||
|
||||
if (!zoneId || isNaN(targetX) || isNaN(targetY)) {
|
||||
this.socket.emit('notification', {
|
||||
title: 'Server message',
|
||||
message: 'Invalid parameters. X and Y coordinates must be numbers.'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const zone = await ZoneRepository.getById(zoneId)
|
||||
if (!zone) {
|
||||
this.socket.emit('notification', { title: 'Server message', message: 'Zone not found' })
|
||||
this.socket.emit('notification', {
|
||||
title: 'Server message',
|
||||
message: 'Zone not found'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
if (character.zoneId === zone.id) {
|
||||
this.socket.emit('notification', { title: 'Server message', message: 'You are already in that zone' })
|
||||
if (character.zone.id === zone.id && targetX === character.positionX && targetY === character.positionY) {
|
||||
this.socket.emit('notification', {
|
||||
title: 'Server message',
|
||||
message: 'You are already at that location'
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Remove character from current zone
|
||||
zoneManager.removeCharacter(character.id)
|
||||
this.io.to(character.zoneId.toString()).emit('zone:character:leave', character.id)
|
||||
this.socket.leave(character.zoneId.toString())
|
||||
|
||||
// Add character to new zone
|
||||
zoneManager.getZoneById(zone.id)?.addCharacter(character)
|
||||
this.io.to(zone.id.toString()).emit('zone:character:join', character)
|
||||
this.socket.join(zone.id.toString())
|
||||
|
||||
character.zoneId = zone.id
|
||||
character.positionX = 0
|
||||
character.positionY = 0
|
||||
|
||||
zoneCharacter.isMoving = false
|
||||
|
||||
this.socket.emit('zone:character:teleport', {
|
||||
zone,
|
||||
characters: ZoneManager.getZoneById(zone.id)?.getCharactersInZone()
|
||||
const success = await TeleportService.teleportCharacter(character.id, {
|
||||
targetZoneId: zone.id,
|
||||
targetX,
|
||||
targetY,
|
||||
rotation: character.rotation
|
||||
})
|
||||
|
||||
this.socket.emit('notification', { title: 'Server message', message: `You have been teleported to ${zone.name}` })
|
||||
this.logger.info('teleport', `Character ${character.id} teleported to zone ${zone.id}`)
|
||||
if (!success) {
|
||||
return this.socket.emit('notification', {
|
||||
title: 'Server message',
|
||||
message: 'Failed to teleport'
|
||||
})
|
||||
}
|
||||
|
||||
this.socket.emit('notification', {
|
||||
title: 'Server message',
|
||||
message: `Teleported to ${zone.name} (${targetX}, ${targetY})`
|
||||
})
|
||||
this.logger.info('teleport', `Character ${character.id} teleported to zone ${zone.id} at position (${targetX}, ${targetY})`)
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Error in teleport command: ${error.message}`)
|
||||
this.socket.emit('notification', { title: 'Server message', message: 'An error occurred while teleporting' })
|
||||
this.socket.emit('notification', {
|
||||
title: 'Server message',
|
||||
message: 'An error occurred while teleporting'
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -26,13 +26,13 @@ export default class ChatMessageEvent extends BaseEvent {
|
||||
|
||||
const character = zoneCharacter.character
|
||||
|
||||
const zone = await ZoneRepository.getById(character.zone?.id!)
|
||||
const zone = await ZoneRepository.getById(character.zone.id)
|
||||
if (!zone) {
|
||||
this.logger.error('chat:message error', 'Zone not found')
|
||||
return callback(false)
|
||||
}
|
||||
|
||||
if (await ChatService.sendZoneMessage(this.io, this.socket, data.message, character.id, zone.id)) {
|
||||
if (await ChatService.sendZoneMessage(character.getId(), zone.getId(), data.message)) {
|
||||
return callback(true)
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ export default class DisconnectEvent extends BaseEvent {
|
||||
this.socket.on('disconnect', this.handleEvent.bind(this))
|
||||
}
|
||||
|
||||
private async handleEvent(data: any): Promise<void> {
|
||||
private async handleEvent(): Promise<void> {
|
||||
try {
|
||||
if (!this.socket.userId) {
|
||||
this.logger.info('User disconnected but had no user set')
|
||||
@ -21,18 +21,8 @@ export default class DisconnectEvent extends BaseEvent {
|
||||
return
|
||||
}
|
||||
|
||||
const character = zoneCharacter.character
|
||||
|
||||
// Save character position and remove from zone
|
||||
zoneCharacter.isMoving = false
|
||||
await zoneCharacter.savePosition()
|
||||
ZoneManager.removeCharacter(this.socket.characterId!)
|
||||
|
||||
await zoneCharacter.disconnect(this.socket, this.io)
|
||||
this.logger.info('User disconnected along with their character')
|
||||
|
||||
// Inform other clients that the character has left
|
||||
this.io.in(character.zone!.id.toString()).emit('zone:character:leave', character.id)
|
||||
this.io.emit('character:disconnect', character.id)
|
||||
} catch (error: any) {
|
||||
this.logger.error('disconnect error: ' + error.message)
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ export default class CharacterHairCreateEvent extends BaseEvent {
|
||||
|
||||
private async handleEvent(data: undefined, callback: (response: boolean, characterType?: any) => void): Promise<void> {
|
||||
try {
|
||||
const character = await characterRepository.getById(this.socket.characterId as number)
|
||||
const character = await characterRepository.getById(this.socket.characterId!)
|
||||
if (!character) return callback(false)
|
||||
|
||||
if (character.role !== 'gm') {
|
||||
|
@ -20,7 +20,7 @@ export default class CharacterTypeDeleteEvent {
|
||||
}
|
||||
|
||||
private async handleEvent(data: IPayload, callback: (response: boolean) => void): Promise<void> {
|
||||
const character = await characterRepository.getById(this.socket.characterId as number)
|
||||
const character = await characterRepository.getById(this.socket.characterId!)
|
||||
if (!character) return callback(false)
|
||||
|
||||
if (character.role !== 'gm') {
|
||||
|
@ -25,7 +25,7 @@ export default class CharacterTypeUpdateEvent {
|
||||
}
|
||||
|
||||
private async handleEvent(data: Payload, callback: (success: boolean) => void): Promise<void> {
|
||||
const character = await characterRepository.getById(this.socket.characterId as number)
|
||||
const character = await characterRepository.getById(this.socket.characterId!)
|
||||
if (!character) return callback(false)
|
||||
|
||||
if (character.role !== 'gm') {
|
||||
|
@ -4,7 +4,7 @@ import { Server } from 'socket.io'
|
||||
|
||||
import { gameLogger, gameMasterLogger } from '#application/logger'
|
||||
import prisma from '#application/prisma'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
import { TSocket } from '#application/types'
|
||||
import characterRepository from '#repositories/characterRepository'
|
||||
|
||||
@ -38,10 +38,10 @@ export default class ObjectRemoveEvent {
|
||||
})
|
||||
|
||||
// get root path
|
||||
const public_folder = getPublicPath('objects')
|
||||
const public_folder = Storage.getPublicPath('objects')
|
||||
|
||||
// remove the tile from the disk
|
||||
const finalFilePath = getPublicPath('objects', data.object + '.png')
|
||||
const finalFilePath = Storage.getPublicPath('objects', data.object + '.png')
|
||||
fs.unlink(finalFilePath, (err) => {
|
||||
if (err) {
|
||||
gameMasterLogger.error(`Error deleting object ${data.object}: ${err.message}`)
|
||||
|
@ -6,7 +6,7 @@ import { Server } from 'socket.io'
|
||||
|
||||
import { gameMasterLogger } from '#application/logger'
|
||||
import prisma from '#application/prisma'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
import { TSocket } from '#application/types'
|
||||
import characterRepository from '#repositories/characterRepository'
|
||||
|
||||
@ -32,7 +32,7 @@ export default class ObjectUploadEvent {
|
||||
if (character.role !== 'gm') {
|
||||
return callback(false)
|
||||
}
|
||||
const public_folder = getPublicPath('objects')
|
||||
const public_folder = Storage.getPublicPath('objects')
|
||||
|
||||
// Ensure the folder exists
|
||||
await fs.mkdir(public_folder, { recursive: true })
|
||||
@ -56,7 +56,7 @@ export default class ObjectUploadEvent {
|
||||
|
||||
const uuid = object.id
|
||||
const filename = `${uuid}.png`
|
||||
const finalFilePath = getPublicPath('objects', filename)
|
||||
const finalFilePath = Storage.getPublicPath('objects', filename)
|
||||
await writeFile(finalFilePath, objectData)
|
||||
|
||||
gameMasterLogger.info('gm:object:upload', `Object ${key} uploaded with id ${uuid}`)
|
||||
|
@ -3,7 +3,7 @@ import fs from 'fs/promises'
|
||||
import { Server } from 'socket.io'
|
||||
|
||||
import prisma from '#application/prisma'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
import { TSocket } from '#application/types'
|
||||
import characterRepository from '#repositories/characterRepository'
|
||||
|
||||
@ -26,7 +26,7 @@ export default class SpriteCreateEvent {
|
||||
return callback(false)
|
||||
}
|
||||
|
||||
const public_folder = getPublicPath('sprites')
|
||||
const public_folder = Storage.getPublicPath('sprites')
|
||||
|
||||
// Ensure the folder exists
|
||||
await fs.mkdir(public_folder, { recursive: true })
|
||||
@ -39,7 +39,7 @@ export default class SpriteCreateEvent {
|
||||
const uuid = sprite.id
|
||||
|
||||
// Create folder with uuid
|
||||
const sprite_folder = getPublicPath('sprites', uuid)
|
||||
const sprite_folder = Storage.getPublicPath('sprites', uuid)
|
||||
await fs.mkdir(sprite_folder, { recursive: true })
|
||||
|
||||
callback(true)
|
||||
|
@ -4,7 +4,7 @@ import { Server } from 'socket.io'
|
||||
|
||||
import { gameMasterLogger } from '#application/logger'
|
||||
import prisma from '#application/prisma'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
import { TSocket } from '#application/types'
|
||||
import CharacterRepository from '#repositories/characterRepository'
|
||||
|
||||
@ -19,7 +19,7 @@ export default class GMSpriteDeleteEvent {
|
||||
private readonly io: Server,
|
||||
private readonly socket: TSocket
|
||||
) {
|
||||
this.public_folder = getPublicPath('sprites')
|
||||
this.public_folder = Storage.getPublicPath('sprites')
|
||||
}
|
||||
|
||||
public listen(): void {
|
||||
@ -45,7 +45,7 @@ export default class GMSpriteDeleteEvent {
|
||||
}
|
||||
|
||||
private async deleteSpriteFolder(spriteId: string): Promise<void> {
|
||||
const finalFilePath = getPublicPath('sprites', spriteId)
|
||||
const finalFilePath = Storage.getPublicPath('sprites', spriteId)
|
||||
|
||||
if (fs.existsSync(finalFilePath)) {
|
||||
await fs.promises.rmdir(finalFilePath, { recursive: true })
|
||||
|
@ -7,7 +7,7 @@ import type { Prisma, SpriteAction } from '@prisma/client'
|
||||
|
||||
import { gameMasterLogger } from '#application/logger'
|
||||
import prisma from '#application/prisma'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
import { TSocket } from '#application/types'
|
||||
import CharacterRepository from '#repositories/characterRepository'
|
||||
|
||||
@ -314,13 +314,13 @@ export default class SpriteUpdateEvent {
|
||||
}
|
||||
|
||||
private async saveSpritesToDisk(id: string, actions: ProcessedSpriteAction[]): Promise<void> {
|
||||
const publicFolder = getPublicPath('sprites', id)
|
||||
const publicFolder = Storage.getPublicPath('sprites', id)
|
||||
await mkdir(publicFolder, { recursive: true })
|
||||
|
||||
await Promise.all(
|
||||
actions.map(async (action) => {
|
||||
const spritesheet = await this.createSpritesheet(action.buffersWithDimensions)
|
||||
await writeFile(getPublicPath('sprites', id, `${action.action}.png`), spritesheet)
|
||||
await writeFile(Storage.getPublicPath('sprites', id, `${action.action}.png`), spritesheet)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import { Server } from 'socket.io'
|
||||
|
||||
import { gameMasterLogger } from '#application/logger'
|
||||
import prisma from '#application/prisma'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
import { TSocket } from '#application/types'
|
||||
import characterRepository from '#repositories/characterRepository'
|
||||
|
||||
@ -19,7 +19,7 @@ export default class GMTileDeleteEvent {
|
||||
private readonly io: Server,
|
||||
private readonly socket: TSocket
|
||||
) {
|
||||
this.public_folder = getPublicPath('tiles')
|
||||
this.public_folder = Storage.getPublicPath('tiles')
|
||||
}
|
||||
|
||||
public listen(): void {
|
||||
@ -56,7 +56,7 @@ export default class GMTileDeleteEvent {
|
||||
}
|
||||
|
||||
private async deleteTileFile(tileId: string): Promise<void> {
|
||||
const finalFilePath = getPublicPath('tiles', `${tileId}.png`)
|
||||
const finalFilePath = Storage.getPublicPath('tiles', `${tileId}.png`)
|
||||
try {
|
||||
await fs.unlink(finalFilePath)
|
||||
} catch (error: any) {
|
||||
|
@ -5,7 +5,7 @@ import { Server } from 'socket.io'
|
||||
|
||||
import { gameMasterLogger } from '#application/logger'
|
||||
import prisma from '#application/prisma'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
import { TSocket } from '#application/types'
|
||||
import characterRepository from '#repositories/characterRepository'
|
||||
|
||||
@ -32,7 +32,7 @@ export default class TileUploadEvent {
|
||||
return
|
||||
}
|
||||
|
||||
const public_folder = getPublicPath('tiles')
|
||||
const public_folder = Storage.getPublicPath('tiles')
|
||||
|
||||
// Ensure the folder exists
|
||||
await fs.mkdir(public_folder, { recursive: true })
|
||||
@ -45,7 +45,7 @@ export default class TileUploadEvent {
|
||||
})
|
||||
const uuid = tile.id
|
||||
const filename = `${uuid}.png`
|
||||
const finalFilePath = getPublicPath('tiles', filename)
|
||||
const finalFilePath = Storage.getPublicPath('tiles', filename)
|
||||
await writeFile(finalFilePath, tileData)
|
||||
})
|
||||
|
||||
|
@ -29,7 +29,7 @@ export default class CharacterMove extends BaseEvent {
|
||||
|
||||
const path = await this.characterService.calculatePath(zoneCharacter.character, positionX, positionY)
|
||||
if (!path) {
|
||||
this.io.in(zoneCharacter.character.zone!.id.toString()).emit('zone:character:moveError', 'No valid path found')
|
||||
this.io.in(zoneCharacter.character.zone.id).emit('zone:character:moveError', 'No valid path found')
|
||||
return
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ export default class CharacterMove extends BaseEvent {
|
||||
const [start, end] = [path[i], path[i + 1]]
|
||||
character.rotation = CharacterService.calculateRotation(start.x, start.y, end.x, end.y)
|
||||
|
||||
const zoneEventTile = await zoneEventTileRepository.getEventTileByZoneIdAndPosition(character.zone!.id, Math.floor(end.x), Math.floor(end.y))
|
||||
const zoneEventTile = await zoneEventTileRepository.getEventTileByZoneIdAndPosition(character.zone.id, Math.floor(end.x), Math.floor(end.y))
|
||||
|
||||
if (zoneEventTile?.type === 'BLOCK') break
|
||||
if (zoneEventTile?.type === 'TELEPORT' && zoneEventTile.teleport) {
|
||||
@ -63,8 +63,8 @@ export default class CharacterMove extends BaseEvent {
|
||||
character.positionY = end.y
|
||||
|
||||
// Then emit with the same properties
|
||||
this.io.in(character.zone!.id.toString()).emit('zone:character:move', {
|
||||
id: character.id,
|
||||
this.io.in(character.zone.id).emit('zone:character:move', {
|
||||
characterId: character.id,
|
||||
positionX: character.positionX,
|
||||
positionY: character.positionY,
|
||||
rotation: character.rotation,
|
||||
@ -93,8 +93,8 @@ export default class CharacterMove extends BaseEvent {
|
||||
|
||||
private finalizeMovement(zoneCharacter: ZoneCharacter): void {
|
||||
zoneCharacter.isMoving = false
|
||||
this.io.in(zoneCharacter.character.zone!.id.toString()).emit('zone:character:move', {
|
||||
id: zoneCharacter.character.id,
|
||||
this.io.in(zoneCharacter.character.zone.id).emit('zone:character:move', {
|
||||
characterId: zoneCharacter.character.id,
|
||||
positionX: zoneCharacter.character.positionX,
|
||||
positionY: zoneCharacter.character.positionY,
|
||||
rotation: zoneCharacter.character.rotation,
|
||||
|
@ -3,7 +3,8 @@ import fs from 'fs'
|
||||
import { Request, Response } from 'express'
|
||||
|
||||
import { BaseController } from '#application/base/baseController'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Database from '#application/database'
|
||||
import Storage from '#application/storage'
|
||||
import { AssetData, UUID } from '#application/types'
|
||||
import SpriteRepository from '#repositories/spriteRepository'
|
||||
import TileRepository from '#repositories/tileRepository'
|
||||
@ -32,9 +33,9 @@ export class AssetsController extends BaseController {
|
||||
* @param res
|
||||
*/
|
||||
public async listTilesByZone(req: Request, res: Response) {
|
||||
const zoneId = parseInt(req.params.zoneId)
|
||||
const zoneId = req.params.zoneId as UUID
|
||||
|
||||
if (!zoneId || zoneId === 0) {
|
||||
if (!zoneId) {
|
||||
return this.sendError(res, 'Invalid zone ID', 400)
|
||||
}
|
||||
|
||||
@ -70,6 +71,8 @@ export class AssetsController extends BaseController {
|
||||
return this.sendError(res, 'Sprite not found', 404)
|
||||
}
|
||||
|
||||
await Database.getEntityManager().populate(sprite, ['spriteActions'])
|
||||
|
||||
const assets: AssetData[] = sprite.spriteActions.getItems().map((spriteAction) => ({
|
||||
key: sprite.id + '-' + spriteAction.action,
|
||||
data: '/assets/sprites/' + sprite.getId() + '/' + spriteAction.getAction() + '.png',
|
||||
@ -95,7 +98,7 @@ export class AssetsController extends BaseController {
|
||||
public async downloadAsset(req: Request, res: Response) {
|
||||
const { type, spriteId, file } = req.params
|
||||
|
||||
const assetPath = type === 'sprites' && spriteId ? getPublicPath(type, spriteId, file) : getPublicPath(type, file)
|
||||
const assetPath = type === 'sprites' && spriteId ? Storage.getPublicPath(type, spriteId, file) : Storage.getPublicPath(type, file)
|
||||
|
||||
if (!fs.existsSync(assetPath)) {
|
||||
this.logger.error(`File not found: ${assetPath}`)
|
||||
|
@ -4,14 +4,15 @@ import { Request, Response } from 'express'
|
||||
import sharp from 'sharp'
|
||||
|
||||
import { BaseController } from '#application/base/baseController'
|
||||
import { getPublicPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
import { UUID } from '#application/types'
|
||||
import CharacterHairRepository from '#repositories/characterHairRepository'
|
||||
import CharacterRepository from '#repositories/characterRepository'
|
||||
import CharacterTypeRepository from '#repositories/characterTypeRepository'
|
||||
|
||||
interface AvatarOptions {
|
||||
characterTypeId: number
|
||||
characterHairId?: number
|
||||
characterTypeId: UUID
|
||||
characterHairId?: UUID
|
||||
}
|
||||
|
||||
export class AvatarController extends BaseController {
|
||||
@ -39,8 +40,8 @@ export class AvatarController extends BaseController {
|
||||
*/
|
||||
public async getByParams(req: Request, res: Response) {
|
||||
return this.generateAvatar(res, {
|
||||
characterTypeId: parseInt(req.params.characterTypeId),
|
||||
characterHairId: req.params.characterHairId ? parseInt(req.params.characterHairId) : undefined
|
||||
characterTypeId: req.params.characterTypeId as UUID,
|
||||
characterHairId: req.params.characterHairId ? (req.params.characterHairId as UUID) : undefined
|
||||
})
|
||||
}
|
||||
|
||||
@ -57,7 +58,7 @@ export class AvatarController extends BaseController {
|
||||
return this.sendError(res, 'Character type not found', 404)
|
||||
}
|
||||
|
||||
const bodySpritePath = getPublicPath('sprites', characterType.sprite.id, 'idle_right_down.png')
|
||||
const bodySpritePath = Storage.getPublicPath('sprites', characterType.sprite.id, 'idle_right_down.png')
|
||||
if (!fs.existsSync(bodySpritePath)) {
|
||||
return this.sendError(res, 'Body sprite file not found', 404)
|
||||
}
|
||||
@ -71,7 +72,7 @@ export class AvatarController extends BaseController {
|
||||
if (options.characterHairId) {
|
||||
const characterHair = await CharacterHairRepository.getById(options.characterHairId)
|
||||
if (characterHair?.sprite?.id) {
|
||||
const hairSpritePath = getPublicPath('sprites', characterHair.sprite.id, 'front.png')
|
||||
const hairSpritePath = Storage.getPublicPath('sprites', characterHair.sprite.id, 'front.png')
|
||||
if (fs.existsSync(hairSpritePath)) {
|
||||
avatar = avatar.composite([{ input: hairSpritePath, gravity: 'north' }])
|
||||
}
|
||||
|
@ -1,55 +1,7 @@
|
||||
import { Server } from 'socket.io'
|
||||
|
||||
import { CommandRegistry } from '#application/console/commandRegistry'
|
||||
import { ConsolePrompt } from '#application/console/consolePrompt'
|
||||
import { LogReader } from '#application/console/logReader'
|
||||
import Logger, { LoggerType } from '#application/logger'
|
||||
import SocketManager from '#managers/socketManager'
|
||||
|
||||
export class ConsoleManager {
|
||||
private readonly logger = Logger.type(LoggerType.COMMAND)
|
||||
private readonly registry: CommandRegistry
|
||||
private readonly prompt: ConsolePrompt
|
||||
private readonly logReader: LogReader
|
||||
private io: Server | null = null
|
||||
|
||||
constructor() {
|
||||
this.registry = new CommandRegistry()
|
||||
this.prompt = new ConsolePrompt((command: string) => this.processCommand(command))
|
||||
|
||||
this.logReader = new LogReader(process.cwd())
|
||||
}
|
||||
|
||||
public async boot(): Promise<void> {
|
||||
this.io = SocketManager.getIO()
|
||||
|
||||
await this.registry.loadCommands()
|
||||
this.logReader.start()
|
||||
this.prompt.start()
|
||||
|
||||
this.logger.info('Console manager loaded')
|
||||
}
|
||||
|
||||
private async processCommand(commandLine: string): Promise<void> {
|
||||
const [cmd, ...args] = commandLine.trim().split(' ')
|
||||
|
||||
if (cmd === 'exit') {
|
||||
this.prompt.close()
|
||||
return
|
||||
}
|
||||
|
||||
const CommandClass = this.registry.getCommand(cmd)
|
||||
if (!CommandClass) {
|
||||
console.error(`Unknown command: ${cmd}`)
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const commandInstance = new CommandClass(this.io as Server)
|
||||
await commandInstance.execute(args)
|
||||
} catch (error) {
|
||||
this.logger.error(`Error executing command ${cmd}: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
class ConsoleManager {
|
||||
async boot() {
|
||||
console.log('Console manager loaded')
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,75 +1,83 @@
|
||||
import { Server } from 'socket.io'
|
||||
|
||||
import Logger, { LoggerType } from '#application/logger'
|
||||
import worldRepository from '#repositories/worldRepository'
|
||||
import worldService from '#services/worldService'
|
||||
import SocketManager from '#managers/socketManager'
|
||||
|
||||
class DateManager {
|
||||
private static readonly GAME_SPEED = 8 // 24 game hours / 3 real hours
|
||||
private static readonly UPDATE_INTERVAL = 1000 // 1 second
|
||||
private static readonly CONFIG = {
|
||||
GAME_SPEED: 8, // 24 game hours / 3 real hours
|
||||
UPDATE_INTERVAL: 1000, // 1 second
|
||||
} as const
|
||||
|
||||
private io: Server | null = null
|
||||
private intervalId: NodeJS.Timeout | null = null
|
||||
private currentDate: Date = new Date()
|
||||
private logger = Logger.type(LoggerType.APP)
|
||||
private currentDate = new Date()
|
||||
private readonly logger = Logger.type(LoggerType.APP)
|
||||
|
||||
public async boot(io: Server): Promise<void> {
|
||||
this.io = io
|
||||
public async boot(): Promise<void> {
|
||||
this.io = SocketManager.getIO()
|
||||
await this.loadDate()
|
||||
this.startDateLoop()
|
||||
this.logger.info('Date manager loaded')
|
||||
}
|
||||
|
||||
public async setTime(time: string): Promise<void> {
|
||||
try {
|
||||
let newDate: Date
|
||||
public getCurrentDate(): Date {
|
||||
return this.currentDate
|
||||
}
|
||||
|
||||
// Check if it's just a time (HH:mm or HH:mm:ss format)
|
||||
if (/^\d{1,2}:\d{2}(:\d{2})?$/.test(time)) {
|
||||
const [hours, minutes] = time.split(':').map(Number)
|
||||
newDate = new Date(this.currentDate) // Clone current date
|
||||
newDate.setHours(hours, minutes)
|
||||
} else {
|
||||
// Treat as full datetime string
|
||||
newDate = new Date(time)
|
||||
if (isNaN(newDate.getTime())) return
|
||||
}
|
||||
public async setTime(timeString: string): Promise<void> {
|
||||
try {
|
||||
const newDate = this.parseTimeString(timeString)
|
||||
if (!newDate) return
|
||||
|
||||
this.currentDate = newDate
|
||||
this.emitDate()
|
||||
await this.saveDate()
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to set time: ${error instanceof Error ? error.message : String(error)}`)
|
||||
this.handleError('Failed to set time', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
public cleanup(): void {
|
||||
this.intervalId && clearInterval(this.intervalId)
|
||||
}
|
||||
|
||||
private async loadDate(): Promise<void> {
|
||||
try {
|
||||
const world = await worldRepository.getFirst()
|
||||
|
||||
if (world) {
|
||||
this.currentDate = world.date
|
||||
}
|
||||
this.currentDate = world?.date ?? new Date()
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to load date: ${error instanceof Error ? error.message : String(error)}`)
|
||||
this.currentDate = new Date() // Use current date as fallback
|
||||
this.handleError('Failed to load date', error)
|
||||
this.currentDate = new Date()
|
||||
}
|
||||
}
|
||||
|
||||
private parseTimeString(timeString: string): Date | null {
|
||||
const timeOnlyPattern = /^\d{1,2}:\d{2}(:\d{2})?$/
|
||||
|
||||
if (timeOnlyPattern.test(timeString)) {
|
||||
const [hours, minutes] = timeString.split(':').map(Number)
|
||||
const newDate = new Date(this.currentDate)
|
||||
newDate.setHours(hours, minutes)
|
||||
return newDate
|
||||
}
|
||||
|
||||
const fullDate = new Date(timeString)
|
||||
return isNaN(fullDate.getTime()) ? null : fullDate
|
||||
}
|
||||
|
||||
private startDateLoop(): void {
|
||||
this.intervalId = setInterval(() => {
|
||||
this.advanceGameTime()
|
||||
this.emitDate()
|
||||
void this.saveDate()
|
||||
}, DateManager.UPDATE_INTERVAL)
|
||||
}, DateManager.CONFIG.UPDATE_INTERVAL)
|
||||
}
|
||||
|
||||
private advanceGameTime(): void {
|
||||
if (!this.currentDate) {
|
||||
this.currentDate = new Date()
|
||||
}
|
||||
const advanceMilliseconds = DateManager.GAME_SPEED * DateManager.UPDATE_INTERVAL
|
||||
const advanceMilliseconds = DateManager.CONFIG.GAME_SPEED * DateManager.CONFIG.UPDATE_INTERVAL
|
||||
this.currentDate = new Date(this.currentDate.getTime() + advanceMilliseconds)
|
||||
}
|
||||
|
||||
@ -79,22 +87,14 @@ class DateManager {
|
||||
|
||||
private async saveDate(): Promise<void> {
|
||||
try {
|
||||
await worldService.update({
|
||||
date: this.currentDate
|
||||
})
|
||||
await worldService.update({ date: this.currentDate })
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to save date: ${error instanceof Error ? error.message : String(error)}`)
|
||||
this.handleError('Failed to save date', error)
|
||||
}
|
||||
}
|
||||
|
||||
public cleanup(): void {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
}
|
||||
}
|
||||
|
||||
public getCurrentDate(): Date {
|
||||
return this.currentDate
|
||||
private handleError(message: string, error: unknown): void {
|
||||
this.logger.error(`${message}: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,7 +6,7 @@ import { Server as SocketServer } from 'socket.io'
|
||||
|
||||
import config from '#application/config'
|
||||
import Logger, { LoggerType } from '#application/logger'
|
||||
import { getAppPath } from '#application/storage'
|
||||
import Storage from '#application/storage'
|
||||
import { TSocket } from '#application/types'
|
||||
import SocketManager from '#managers/socketManager'
|
||||
|
||||
@ -56,9 +56,9 @@ class QueueManager {
|
||||
const { jobName, params, socketId } = job.data
|
||||
|
||||
try {
|
||||
const jobsDir = getAppPath('jobs')
|
||||
const jobsDir = Storage.getAppPath('jobs')
|
||||
const extension = config.ENV === 'development' ? '.ts' : '.js'
|
||||
const jobPath = getAppPath('jobs', `${jobName}${extension}`)
|
||||
const jobPath = Storage.getAppPath('jobs', `${jobName}${extension}`)
|
||||
|
||||
if (!fs.existsSync(jobPath)) {
|
||||
this.logger.warn(`Job file not found: ${jobPath}`)
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { Server as SocketServer } from 'socket.io'
|
||||
import fs from 'fs'
|
||||
import { pathToFileURL } from 'url'
|
||||
import { Server as HTTPServer } from 'http'
|
||||
import { Application } from 'express'
|
||||
import { pathToFileURL } from 'url'
|
||||
|
||||
import { Application } from 'express'
|
||||
import { Server as SocketServer } from 'socket.io'
|
||||
|
||||
import Logger, { LoggerType } from '#application/logger'
|
||||
import { getAppPath } from '#application/storage'
|
||||
import { TSocket } from '#application/types'
|
||||
import config from '#application/config'
|
||||
import Logger, { LoggerType } from '#application/logger'
|
||||
import Storage from '#application/storage'
|
||||
import { TSocket, UUID } from '#application/types'
|
||||
import { Authentication } from '#middleware/authentication'
|
||||
|
||||
class SocketManager {
|
||||
@ -60,11 +61,11 @@ class SocketManager {
|
||||
*/
|
||||
private async loadEventHandlers(baseDir: string, subDir: string, socket: TSocket): Promise<void> {
|
||||
try {
|
||||
const fullDir = getAppPath(baseDir, subDir)
|
||||
const fullDir = Storage.getAppPath(baseDir, subDir)
|
||||
const files = await fs.promises.readdir(fullDir, { withFileTypes: true })
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = getAppPath(baseDir, subDir, file.name)
|
||||
const filePath = Storage.getAppPath(baseDir, subDir, file.name)
|
||||
|
||||
if (file.isDirectory()) {
|
||||
await this.loadEventHandlers(baseDir, `${subDir}/${file.name}`, socket)
|
||||
@ -105,7 +106,19 @@ class SocketManager {
|
||||
* Emit event to specific room
|
||||
*/
|
||||
public emitToRoom(room: string, event: string, ...args: any[]): void {
|
||||
this.getIO().to(room).emit(event, ...args)
|
||||
this.getIO()
|
||||
.to(room)
|
||||
.emit(event, ...args)
|
||||
}
|
||||
|
||||
public getSocketByUserId(userId: UUID): TSocket | undefined {
|
||||
const sockets = Array.from(this.getIO().sockets.sockets.values())
|
||||
return sockets.find((socket: TSocket) => socket.userId === userId)
|
||||
}
|
||||
|
||||
public getSocketByCharacterId(characterId: UUID): TSocket | undefined {
|
||||
const sockets = Array.from(this.getIO().sockets.sockets.values())
|
||||
return sockets.find((socket: TSocket) => socket.characterId === characterId)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Server } from 'socket.io'
|
||||
|
||||
import Logger, { LoggerType } from '#application/logger'
|
||||
import worldRepository from '#repositories/worldRepository'
|
||||
import worldService from '#services/worldService'
|
||||
import SocketManager from '#managers/socketManager'
|
||||
|
||||
interface WeatherState {
|
||||
type WeatherState = {
|
||||
isRainEnabled: boolean
|
||||
rainPercentage: number
|
||||
isFogEnabled: boolean
|
||||
@ -12,13 +12,18 @@ interface WeatherState {
|
||||
}
|
||||
|
||||
class WeatherManager {
|
||||
private static readonly UPDATE_INTERVAL = 60000 // Check weather every minute
|
||||
private static readonly RAIN_CHANCE = 0.2 // 20% chance of rain
|
||||
private static readonly FOG_CHANCE = 0.15 // 15% chance of fog
|
||||
private readonly logger = Logger.type(LoggerType.APP)
|
||||
private static readonly CONFIG = {
|
||||
UPDATE_INTERVAL_MS: 60_000, // Check weather every minute
|
||||
RAIN_CHANCE: 0.2, // 20% chance
|
||||
FOG_CHANCE: 0.15, // 15% chance
|
||||
RAIN_PERCENTAGE_RANGE: { min: 50, max: 100 },
|
||||
FOG_DENSITY_RANGE: { min: 30, max: 100 }
|
||||
} as const
|
||||
|
||||
private readonly logger = Logger.type(LoggerType.APP)
|
||||
private io: Server | null = null
|
||||
private intervalId: NodeJS.Timeout | null = null
|
||||
|
||||
private weatherState: WeatherState = {
|
||||
isRainEnabled: false,
|
||||
rainPercentage: 0,
|
||||
@ -26,37 +31,34 @@ class WeatherManager {
|
||||
fogDensity: 0
|
||||
}
|
||||
|
||||
public async boot(io: Server): Promise<void> {
|
||||
// this.io = io
|
||||
// await this.loadWeather()
|
||||
// this.startWeatherLoop()
|
||||
public async boot(): Promise<void> {
|
||||
this.io = SocketManager.getIO()
|
||||
await this.loadWeather()
|
||||
this.startWeatherLoop()
|
||||
this.logger.info('Weather manager loaded')
|
||||
}
|
||||
|
||||
public async toggleRain(): Promise<void> {
|
||||
this.weatherState.isRainEnabled = !this.weatherState.isRainEnabled
|
||||
this.weatherState.rainPercentage = this.weatherState.isRainEnabled
|
||||
? Math.floor(Math.random() * 50) + 50 // 50-100%
|
||||
: 0
|
||||
public getWeatherState(): WeatherState {
|
||||
return { ...this.weatherState }
|
||||
}
|
||||
|
||||
await this.saveWeather()
|
||||
this.emitWeather()
|
||||
public async toggleRain(): Promise<void> {
|
||||
this.updateWeatherProperty('rain')
|
||||
await this.saveAndEmitWeather()
|
||||
}
|
||||
|
||||
public async toggleFog(): Promise<void> {
|
||||
this.weatherState.isFogEnabled = !this.weatherState.isFogEnabled
|
||||
this.weatherState.fogDensity = this.weatherState.isFogEnabled
|
||||
? Math.floor((Math.random() * 0.7 + 0.3) * 100) // Convert 0.3-1.0 to 30-100
|
||||
: 0
|
||||
this.updateWeatherProperty('fog')
|
||||
await this.saveAndEmitWeather()
|
||||
}
|
||||
|
||||
await this.saveWeather()
|
||||
this.emitWeather()
|
||||
public cleanup(): void {
|
||||
this.intervalId && clearInterval(this.intervalId)
|
||||
}
|
||||
|
||||
private async loadWeather(): Promise<void> {
|
||||
try {
|
||||
const world = await worldRepository.getFirst()
|
||||
|
||||
if (world) {
|
||||
this.weatherState = {
|
||||
isRainEnabled: world.isRainEnabled,
|
||||
@ -66,63 +68,73 @@ class WeatherManager {
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to load weather: ${error instanceof Error ? error.message : String(error)}`)
|
||||
this.logError('load', error)
|
||||
}
|
||||
}
|
||||
|
||||
public getWeatherState(): WeatherState {
|
||||
return this.weatherState
|
||||
}
|
||||
|
||||
private startWeatherLoop(): void {
|
||||
this.intervalId = setInterval(async () => {
|
||||
this.updateWeather()
|
||||
this.emitWeather()
|
||||
await this.saveWeather().catch((error) => {
|
||||
this.logger.error(`Failed to save weather: ${error instanceof Error ? error.message : String(error)}`)
|
||||
})
|
||||
}, WeatherManager.UPDATE_INTERVAL)
|
||||
this.updateRandomWeather()
|
||||
await this.saveAndEmitWeather()
|
||||
}, WeatherManager.CONFIG.UPDATE_INTERVAL_MS)
|
||||
}
|
||||
|
||||
private updateWeather(): void {
|
||||
// Update rain
|
||||
if (Math.random() < WeatherManager.RAIN_CHANCE) {
|
||||
private updateRandomWeather(): void {
|
||||
if (Math.random() < WeatherManager.CONFIG.RAIN_CHANCE) {
|
||||
this.updateWeatherProperty('rain')
|
||||
}
|
||||
if (Math.random() < WeatherManager.CONFIG.FOG_CHANCE) {
|
||||
this.updateWeatherProperty('fog')
|
||||
}
|
||||
}
|
||||
|
||||
private updateWeatherProperty(type: 'rain' | 'fog'): void {
|
||||
if (type === 'rain') {
|
||||
this.weatherState.isRainEnabled = !this.weatherState.isRainEnabled
|
||||
this.weatherState.rainPercentage = this.weatherState.isRainEnabled
|
||||
? Math.floor(Math.random() * 50) + 50 // 50-100%
|
||||
? this.getRandomNumber(
|
||||
WeatherManager.CONFIG.RAIN_PERCENTAGE_RANGE.min,
|
||||
WeatherManager.CONFIG.RAIN_PERCENTAGE_RANGE.max
|
||||
)
|
||||
: 0
|
||||
}
|
||||
|
||||
// Update fog
|
||||
if (Math.random() < WeatherManager.FOG_CHANCE) {
|
||||
if (type === 'fog') {
|
||||
this.weatherState.isFogEnabled = !this.weatherState.isFogEnabled
|
||||
this.weatherState.fogDensity = this.weatherState.isFogEnabled
|
||||
? Math.floor((Math.random() * 0.7 + 0.3) * 100) // Convert 0.3-1.0 to 30-100
|
||||
? this.getRandomNumber(
|
||||
WeatherManager.CONFIG.FOG_DENSITY_RANGE.min,
|
||||
WeatherManager.CONFIG.FOG_DENSITY_RANGE.max
|
||||
)
|
||||
: 0
|
||||
}
|
||||
}
|
||||
|
||||
private async saveAndEmitWeather(): Promise<void> {
|
||||
await this.saveWeather()
|
||||
this.emitWeather()
|
||||
}
|
||||
|
||||
private emitWeather(): void {
|
||||
this.io?.emit('weather', this.weatherState)
|
||||
}
|
||||
|
||||
private async saveWeather(): Promise<void> {
|
||||
try {
|
||||
await worldService.update({
|
||||
isRainEnabled: this.weatherState.isRainEnabled,
|
||||
rainPercentage: this.weatherState.rainPercentage,
|
||||
isFogEnabled: this.weatherState.isFogEnabled,
|
||||
fogDensity: this.weatherState.fogDensity
|
||||
})
|
||||
await worldService.update(this.weatherState)
|
||||
} catch (error) {
|
||||
this.logger.error(`Failed to save weather: ${error instanceof Error ? error.message : String(error)}`)
|
||||
this.logError('save', error)
|
||||
}
|
||||
}
|
||||
|
||||
public cleanup(): void {
|
||||
if (this.intervalId) {
|
||||
clearInterval(this.intervalId)
|
||||
}
|
||||
private getRandomNumber(min: number, max: number): number {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min
|
||||
}
|
||||
|
||||
private logError(operation: string, error: unknown): void {
|
||||
this.logger.error(
|
||||
`Failed to ${operation} weather: ${error instanceof Error ? error.message : String(error)}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,11 +1,12 @@
|
||||
import Logger, { LoggerType } from '#application/logger'
|
||||
import { UUID } from '#application/types'
|
||||
import { Zone } from '#entities/zone'
|
||||
import LoadedZone from '#models/loadedZone'
|
||||
import ZoneCharacter from '#models/zoneCharacter'
|
||||
import ZoneRepository from '#repositories/zoneRepository'
|
||||
|
||||
class ZoneManager {
|
||||
private readonly zones = new Map<number, LoadedZone>()
|
||||
private readonly zones = new Map<UUID, LoadedZone>()
|
||||
private logger = Logger.type(LoggerType.GAME)
|
||||
|
||||
public async boot(): Promise<void> {
|
||||
@ -21,7 +22,7 @@ class ZoneManager {
|
||||
this.logger.info(`Zone ID ${zone.id} loaded`)
|
||||
}
|
||||
|
||||
public unloadZone(zoneId: number): void {
|
||||
public unloadZone(zoneId: UUID): void {
|
||||
this.zones.delete(zoneId)
|
||||
this.logger.info(`Zone ID ${zoneId} unloaded`)
|
||||
}
|
||||
@ -30,11 +31,11 @@ class ZoneManager {
|
||||
return Array.from(this.zones.values())
|
||||
}
|
||||
|
||||
public getZoneById(zoneId: number): LoadedZone | undefined {
|
||||
public getZoneById(zoneId: UUID): LoadedZone | undefined {
|
||||
return this.zones.get(zoneId)
|
||||
}
|
||||
|
||||
public getCharacterById(characterId: number): ZoneCharacter | undefined {
|
||||
public getCharacterById(characterId: UUID): ZoneCharacter | undefined {
|
||||
for (const zone of this.zones.values()) {
|
||||
const character = zone.getCharactersInZone().find((char) => char.character.id === characterId)
|
||||
if (character) return character
|
||||
@ -42,7 +43,7 @@ class ZoneManager {
|
||||
return undefined
|
||||
}
|
||||
|
||||
public removeCharacter(characterId: number): void {
|
||||
public removeCharacter(characterId: UUID): void {
|
||||
this.zones.forEach((zone) => zone.removeCharacter(characterId))
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
import ZoneCharacter from './zoneCharacter'
|
||||
|
||||
import { UUID } from '#application/types'
|
||||
import { Character } from '#entities/character'
|
||||
import { Zone } from '#entities/zone'
|
||||
import zoneEventTileRepository from '#repositories/zoneEventTileRepository'
|
||||
@ -21,7 +22,7 @@ class LoadedZone {
|
||||
this.characters.push(zoneCharacter)
|
||||
}
|
||||
|
||||
public async removeCharacter(id: number) {
|
||||
public async removeCharacter(id: UUID) {
|
||||
const zoneCharacter = this.getCharacterById(id)
|
||||
if (zoneCharacter) {
|
||||
await zoneCharacter.savePosition()
|
||||
@ -29,7 +30,7 @@ class LoadedZone {
|
||||
}
|
||||
}
|
||||
|
||||
public getCharacterById(id: number): ZoneCharacter | undefined {
|
||||
public getCharacterById(id: UUID): ZoneCharacter | undefined {
|
||||
return this.characters.find((c) => c.character.id === id)
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,10 @@
|
||||
import { Server } from 'socket.io'
|
||||
|
||||
import { TSocket } from '#application/types'
|
||||
import { Character } from '#entities/character'
|
||||
import SocketManager from '#managers/socketManager'
|
||||
import ZoneManager from '#managers/zoneManager'
|
||||
import TeleportService from '#services/teleportService'
|
||||
|
||||
class ZoneCharacter {
|
||||
public readonly character: Character
|
||||
@ -12,6 +18,37 @@ class ZoneCharacter {
|
||||
public async savePosition() {
|
||||
await this.character.setPositionX(this.character.positionX).setPositionY(this.character.positionY).setRotation(this.character.rotation).setZone(this.character.zone).update()
|
||||
}
|
||||
|
||||
public async teleport(zoneId: number, targetX: number, targetY: number): Promise<void> {
|
||||
await TeleportService.teleportCharacter(this.character.id, {
|
||||
targetZoneId: zoneId,
|
||||
targetX,
|
||||
targetY
|
||||
})
|
||||
}
|
||||
|
||||
public async disconnect(socket: TSocket, io: Server): Promise<void> {
|
||||
try {
|
||||
// Stop any movement and save final position
|
||||
this.isMoving = false
|
||||
this.currentPath = null
|
||||
await this.savePosition()
|
||||
|
||||
// Leave zone and remove from manager
|
||||
if (this.character.zone) {
|
||||
socket.leave(this.character.zone.id)
|
||||
ZoneManager.removeCharacter(this.character.id)
|
||||
|
||||
// Notify zone players
|
||||
io.in(this.character.zone.id).emit('zone:character:leave', this.character.id)
|
||||
}
|
||||
|
||||
// Notify all players
|
||||
io.emit('character:disconnect', this.character.id)
|
||||
} catch (error) {
|
||||
console.error(`Error disconnecting character ${this.character.id}:`, error)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default ZoneCharacter
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
import { CharacterHair } from '#entities/characterHair'
|
||||
|
||||
class CharacterHairRepository extends BaseRepository {
|
||||
@ -32,7 +33,7 @@ class CharacterHairRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getById(id: number): Promise<CharacterHair | null> {
|
||||
async getById(id: UUID): Promise<CharacterHair | null> {
|
||||
try {
|
||||
const repository = this.em.getRepository(CharacterHair)
|
||||
return await repository.findOne({ id })
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
import { Character } from '#entities/character'
|
||||
|
||||
class CharacterRepository extends BaseRepository {
|
||||
async getByUserId(userId: number): Promise<Character[]> {
|
||||
async getByUserId(userId: UUID): Promise<Character[]> {
|
||||
try {
|
||||
const repository = this.em.getRepository(Character)
|
||||
return await repository.find({ user: userId })
|
||||
@ -12,7 +13,7 @@ class CharacterRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getByUserAndId(userId: number, characterId: number): Promise<Character | null> {
|
||||
async getByUserAndId(userId: UUID, characterId: UUID): Promise<Character | null> {
|
||||
try {
|
||||
const repository = this.em.getRepository(Character)
|
||||
return await repository.findOne({ user: userId, id: characterId })
|
||||
@ -22,7 +23,7 @@ class CharacterRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getById(id: number, populate?: string[]): Promise<Character | null> {
|
||||
async getById(id: UUID, populate?: string[]): Promise<Character | null> {
|
||||
try {
|
||||
const repository = this.em.getRepository(Character)
|
||||
return await repository.findOne({ id })
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
import { CharacterType } from '#entities/characterType'
|
||||
|
||||
class CharacterTypeRepository extends BaseRepository {
|
||||
@ -22,7 +23,7 @@ class CharacterTypeRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getById(id: number) {
|
||||
async getById(id: UUID) {
|
||||
try {
|
||||
const repository = this.em.getRepository(CharacterType)
|
||||
return await repository.findOne({ id })
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
import { Chat } from '#entities/chat'
|
||||
|
||||
class ChatRepository extends BaseRepository {
|
||||
async getById(id: number): Promise<Chat[]> {
|
||||
async getById(id: UUID): Promise<Chat[]> {
|
||||
try {
|
||||
const repository = this.em.getRepository(Chat)
|
||||
return await repository.find({
|
||||
@ -24,7 +25,7 @@ class ChatRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getByCharacterId(characterId: number): Promise<Chat[]> {
|
||||
async getByCharacterId(characterId: UUID): Promise<Chat[]> {
|
||||
try {
|
||||
const repository = this.em.getRepository(Chat)
|
||||
return await repository.find({ character: characterId })
|
||||
@ -34,7 +35,7 @@ class ChatRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getByZoneId(zoneId: number): Promise<Chat[]> {
|
||||
async getByZoneId(zoneId: UUID): Promise<Chat[]> {
|
||||
try {
|
||||
const repository = this.em.getRepository(Chat)
|
||||
return await repository.find({ zone: zoneId })
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
import { Item } from '#entities/item'
|
||||
|
||||
class ItemRepository extends BaseRepository {
|
||||
async getById(id: string): Promise<any> {
|
||||
async getById(id: UUID): Promise<any> {
|
||||
try {
|
||||
const repository = this.em.getRepository(Item)
|
||||
return await repository.findOne({ id })
|
||||
@ -12,7 +13,7 @@ class ItemRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getByIds(ids: string[]): Promise<any> {
|
||||
async getByIds(ids: UUID[]): Promise<any> {
|
||||
try {
|
||||
const repository = this.em.getRepository(Item)
|
||||
return await repository.find({
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
|
||||
class ObjectRepository extends BaseRepository {
|
||||
async getById(id: string): Promise<any> {
|
||||
async getById(id: UUID): Promise<any> {
|
||||
try {
|
||||
const repository = this.em.getRepository(Object)
|
||||
return await repository.findOne({ id })
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { BaseRepository } from '#application/base/baseRepository' // Import the global Prisma instance
|
||||
import { UUID } from '#application/types'
|
||||
import { PasswordResetToken } from '#entities/passwordResetToken'
|
||||
|
||||
class PasswordResetTokenRepository extends BaseRepository {
|
||||
async getById(id: number): Promise<any> {
|
||||
async getById(id: UUID): Promise<any> {
|
||||
try {
|
||||
const repository = this.em.getRepository(PasswordResetToken)
|
||||
return await repository.findOne({ id })
|
||||
@ -12,7 +13,7 @@ class PasswordResetTokenRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getByUserId(userId: number): Promise<any> {
|
||||
async getByUserId(userId: UUID): Promise<any> {
|
||||
try {
|
||||
const repository = this.em.getRepository(PasswordResetToken)
|
||||
return await repository.findOne({
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { FilterValue } from '@mikro-orm/core'
|
||||
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
import { Sprite } from '#entities/sprite'
|
||||
|
||||
class SpriteRepository extends BaseRepository {
|
||||
async getById(id: FilterValue<`${string}-${string}-${string}-${string}-${string}`>) {
|
||||
async getById(id: UUID) {
|
||||
try {
|
||||
const repository = this.em.getRepository(Sprite)
|
||||
return await repository.findOne({ id })
|
||||
|
@ -1,13 +1,14 @@
|
||||
import { FilterValue } from '@mikro-orm/core'
|
||||
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
import { unduplicateArray } from '#application/utilities'
|
||||
import { Tile } from '#entities/tile'
|
||||
import { Zone } from '#entities/zone'
|
||||
import ZoneService from '#services/zoneService'
|
||||
|
||||
class TileRepository extends BaseRepository {
|
||||
async getById(id: FilterValue<`${string}-${string}-${string}-${string}-${string}`>): Promise<any> {
|
||||
async getById(id: UUID) {
|
||||
try {
|
||||
const repository = this.em.getRepository(Tile)
|
||||
return await repository.findOne({ id })
|
||||
@ -16,33 +17,33 @@ class TileRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getByIds(ids: FilterValue<`${string}-${string}-${string}-${string}-${string}`>): Promise<any> {
|
||||
async getByIds(ids: UUID[]) {
|
||||
try {
|
||||
const repository = this.em.getRepository(Tile)
|
||||
return await repository.find({
|
||||
id: ids
|
||||
})
|
||||
} catch (error: any) {
|
||||
return null
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async getAll(): Promise<any> {
|
||||
async getAll() {
|
||||
try {
|
||||
const repository = this.em.getRepository(Tile)
|
||||
return await repository.findAll()
|
||||
} catch (error: any) {
|
||||
return null
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
async getByZoneId(zoneId: number): Promise<any> {
|
||||
async getByZoneId(zoneId: UUID) {
|
||||
try {
|
||||
const repository = this.em.getRepository(Zone)
|
||||
const tileRepository = this.em.getRepository(Tile)
|
||||
|
||||
const zone = await repository.findOne({ id: zoneId })
|
||||
if (!zone) return null
|
||||
if (!zone) return []
|
||||
|
||||
const zoneTileArray = unduplicateArray(ZoneService.flattenZoneArray(JSON.parse(JSON.stringify(zone.tiles))))
|
||||
|
||||
@ -50,7 +51,7 @@ class TileRepository extends BaseRepository {
|
||||
id: zoneTileArray
|
||||
})
|
||||
} catch (error: any) {
|
||||
return null
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
import { User } from '#entities/user'
|
||||
|
||||
class UserRepository extends BaseRepository {
|
||||
async getById(id: number) {
|
||||
async getById(id: UUID) {
|
||||
try {
|
||||
const repository = this.em.getRepository(User)
|
||||
return await repository.findOne({ id })
|
||||
|
@ -1,8 +1,9 @@
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
import { ZoneEventTile } from '#entities/zoneEventTile'
|
||||
|
||||
class ZoneEventTileRepository extends BaseRepository {
|
||||
async getAll(id: number): Promise<ZoneEventTile[]> {
|
||||
async getAll(id: UUID): Promise<ZoneEventTile[]> {
|
||||
try {
|
||||
const repository = this.em.getRepository(ZoneEventTile)
|
||||
return await repository.find({
|
||||
@ -14,7 +15,7 @@ class ZoneEventTileRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getEventTileByZoneIdAndPosition(zoneId: number, positionX: number, positionY: number) {
|
||||
async getEventTileByZoneIdAndPosition(zoneId: UUID, positionX: number, positionY: number) {
|
||||
try {
|
||||
const repository = this.em.getRepository(ZoneEventTile)
|
||||
return await repository.findOne({
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { BaseRepository } from '#application/base/baseRepository'
|
||||
import { UUID } from '#application/types'
|
||||
import { Zone } from '#entities/zone'
|
||||
import { ZoneEventTile } from '#entities/zoneEventTile'
|
||||
import { ZoneObject } from '#entities/zoneObject'
|
||||
@ -24,7 +25,7 @@ class ZoneRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getById(id: number) {
|
||||
async getById(id: UUID) {
|
||||
try {
|
||||
const repository = this.em.getRepository(Zone)
|
||||
return await repository.findOne({ id })
|
||||
@ -34,7 +35,7 @@ class ZoneRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getEventTiles(id: number): Promise<ZoneEventTile[]> {
|
||||
async getEventTiles(id: UUID): Promise<ZoneEventTile[]> {
|
||||
try {
|
||||
const repository = this.em.getRepository(ZoneEventTile)
|
||||
return await repository.find({ zone: id })
|
||||
@ -44,7 +45,7 @@ class ZoneRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getFirstEventTile(zoneId: number, positionX: number, positionY: number): Promise<ZoneEventTile | null> {
|
||||
async getFirstEventTile(zoneId: UUID, positionX: number, positionY: number): Promise<ZoneEventTile | null> {
|
||||
try {
|
||||
const repository = this.em.getRepository(ZoneEventTile)
|
||||
return await repository.findOne({
|
||||
@ -58,7 +59,7 @@ class ZoneRepository extends BaseRepository {
|
||||
}
|
||||
}
|
||||
|
||||
async getZoneObjects(id: number): Promise<ZoneObject[]> {
|
||||
async getZoneObjects(id: UUID): Promise<ZoneObject[]> {
|
||||
try {
|
||||
const repository = this.em.getRepository(ZoneObject)
|
||||
return await repository.find({ zone: id })
|
||||
|
@ -1,6 +1,7 @@
|
||||
import { createServer as httpServer, Server as HTTPServer } from 'http'
|
||||
import express, { Application } from 'express'
|
||||
|
||||
import cors from 'cors'
|
||||
import express, { Application } from 'express'
|
||||
|
||||
import config from '#application/config'
|
||||
import Database from '#application/database'
|
||||
@ -42,12 +43,11 @@ export class Server {
|
||||
SocketManager.boot(this.app, this.http),
|
||||
QueueManager.boot(),
|
||||
UserManager.boot(),
|
||||
// DateManager.boot(SocketManager.getIO()),
|
||||
// WeatherManager.boot(SocketManager.getIO()),
|
||||
DateManager.boot(),
|
||||
WeatherManager.boot(),
|
||||
ZoneManager.boot(),
|
||||
ConsoleManager.boot()
|
||||
])
|
||||
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Server failed to start: ${error.message}`)
|
||||
process.exit(1)
|
||||
|
@ -2,6 +2,7 @@ import { BaseService } from '#application/base/baseService'
|
||||
import config from '#application/config'
|
||||
import { Character } from '#entities/character'
|
||||
import { Zone } from '#entities/zone'
|
||||
import SocketManager from '#managers/socketManager'
|
||||
import ZoneManager from '#managers/zoneManager'
|
||||
import CharacterRepository from '#repositories/characterRepository'
|
||||
import ZoneRepository from '#repositories/zoneRepository'
|
||||
@ -23,7 +24,7 @@ class CharacterService extends BaseService {
|
||||
]
|
||||
|
||||
public async calculatePath(character: Character, targetX: number, targetY: number): Promise<Position[] | null> {
|
||||
const zone = ZoneManager.getZoneById(character.zone!.id)
|
||||
const zone = ZoneManager.getZoneById(character.zone.id)
|
||||
const grid = await zone?.getGrid()
|
||||
|
||||
if (!grid?.length) {
|
||||
|
@ -1,14 +1,15 @@
|
||||
import { Server } from 'socket.io'
|
||||
|
||||
import { BaseService } from '#application/base/baseService'
|
||||
import { TSocket } from '#application/types'
|
||||
import { TSocket, UUID } from '#application/types'
|
||||
import { Chat } from '#entities/chat'
|
||||
import SocketManager from '#managers/socketManager'
|
||||
import CharacterRepository from '#repositories/characterRepository'
|
||||
import ChatRepository from '#repositories/chatRepository'
|
||||
import ZoneRepository from '#repositories/zoneRepository'
|
||||
|
||||
class ChatService extends BaseService {
|
||||
async sendZoneMessage(io: Server, socket: TSocket, message: string, characterId: number, zoneId: number): Promise<boolean> {
|
||||
async sendZoneMessage(characterId: UUID, zoneId: UUID, message: string): Promise<boolean> {
|
||||
try {
|
||||
const character = await CharacterRepository.getById(characterId)
|
||||
if (!character) return false
|
||||
@ -16,16 +17,11 @@ class ChatService extends BaseService {
|
||||
const zone = await ZoneRepository.getById(zoneId)
|
||||
if (!zone) return false
|
||||
|
||||
const newChat = new Chat()
|
||||
const chat = new Chat()
|
||||
await chat.setCharacter(character).setZone(zone).setMessage(message).save()
|
||||
|
||||
newChat.setCharacter(character).setZone(zone).setMessage(message)
|
||||
|
||||
await newChat.save()
|
||||
|
||||
const chat = await ChatRepository.getById(newChat.id)
|
||||
if (!chat) return false
|
||||
|
||||
io.to(zoneId.toString()).emit('chat:message', chat)
|
||||
const io = SocketManager.getIO()
|
||||
io.to(zoneId).emit('chat:message', chat)
|
||||
return true
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Failed to save chat message: ${error instanceof Error ? error.message : String(error)}`)
|
||||
|
80
src/services/teleportService.ts
Normal file
80
src/services/teleportService.ts
Normal file
@ -0,0 +1,80 @@
|
||||
import Logger, { LoggerType } from '#application/logger'
|
||||
import { UUID } from '#application/types'
|
||||
import { Character } from '#entities/character'
|
||||
import SocketManager from '#managers/socketManager'
|
||||
import ZoneManager from '#managers/zoneManager'
|
||||
import ZoneCharacter from '#models/zoneCharacter'
|
||||
|
||||
interface TeleportOptions {
|
||||
targetZoneId: UUID
|
||||
targetX: number
|
||||
targetY: number
|
||||
rotation?: number
|
||||
isInitialJoin?: boolean
|
||||
character?: Character
|
||||
}
|
||||
|
||||
class TeleportService {
|
||||
private readonly logger = Logger.type(LoggerType.GAME)
|
||||
|
||||
public async teleportCharacter(characterId: UUID, options: TeleportOptions): Promise<boolean> {
|
||||
const { targetZoneId, targetX, targetY, rotation = 0, isInitialJoin = false, character } = options
|
||||
|
||||
const socket = SocketManager.getSocketByCharacterId(characterId)
|
||||
const targetZone = ZoneManager.getZoneById(targetZoneId)
|
||||
|
||||
if (!socket || !targetZone) {
|
||||
this.logger.error(`Teleport failed - Missing socket or target zone for character ${characterId}`)
|
||||
return false
|
||||
}
|
||||
|
||||
if (isInitialJoin && !character) {
|
||||
this.logger.error('Initial join requires character data')
|
||||
return false
|
||||
}
|
||||
|
||||
const existingCharacter = !isInitialJoin && ZoneManager.getCharacterById(characterId)
|
||||
const zoneCharacter = isInitialJoin
|
||||
? new ZoneCharacter(character!)
|
||||
: existingCharacter ||
|
||||
(() => {
|
||||
this.logger.error(`Teleport failed - Character ${characterId} not found in ZoneManager`)
|
||||
return null
|
||||
})()
|
||||
|
||||
if (!zoneCharacter) return false
|
||||
|
||||
try {
|
||||
const currentZoneId = zoneCharacter.character.zone?.id
|
||||
const io = SocketManager.getIO()
|
||||
|
||||
// Handle current zone cleanup
|
||||
if (currentZoneId) {
|
||||
socket.leave(currentZoneId)
|
||||
ZoneManager.removeCharacter(characterId)
|
||||
io.in(currentZoneId).emit('zone:character:leave', characterId)
|
||||
}
|
||||
|
||||
// Update character position and zone
|
||||
await zoneCharacter.character.setPositionX(targetX).setPositionY(targetY).setRotation(rotation).setZone(targetZone.getZone()).update()
|
||||
|
||||
// Join new zone
|
||||
socket.join(targetZoneId)
|
||||
targetZone.addCharacter(zoneCharacter.character)
|
||||
|
||||
// Notify clients
|
||||
io.in(targetZoneId).emit('zone:character:join', zoneCharacter)
|
||||
socket.emit('zone:character:teleport', {
|
||||
zone: targetZone.getZone(),
|
||||
characters: targetZone.getCharactersInZone()
|
||||
})
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
this.logger.error(`Teleport error for character ${characterId}: ${error}`)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default new TeleportService()
|
@ -7,7 +7,7 @@ import ZoneManager from '#managers/zoneManager'
|
||||
|
||||
class ZoneEventTileService extends BaseService {
|
||||
public async handleTeleport(io: Server, socket: TSocket, character: ExtendedCharacter, teleport: ZoneEventTileTeleport): Promise<void> {
|
||||
if (teleport.toZone.id === character.zone!.id) return
|
||||
if (teleport.toZone.id === character.zone.id) return
|
||||
|
||||
const loadedZone = ZoneManager.getZoneById(teleport.toZone.id)
|
||||
if (!loadedZone) {
|
||||
@ -17,7 +17,7 @@ class ZoneEventTileService extends BaseService {
|
||||
|
||||
const zone = loadedZone.getZone()
|
||||
|
||||
const oldZoneId = character.zone!.id
|
||||
const oldZoneId = character.zone.id
|
||||
const newZoneId = teleport.toZone.id
|
||||
|
||||
character.isMoving = false
|
||||
@ -31,12 +31,12 @@ class ZoneEventTileService extends BaseService {
|
||||
loadedZone.addCharacter(character)
|
||||
|
||||
// Emit events
|
||||
io.to(oldZoneId.toString()).emit('zone:character:leave', character.id)
|
||||
io.to(newZoneId.toString()).emit('zone:character:join', character)
|
||||
io.to(oldZoneId).emit('zone:character:leave', character.id)
|
||||
io.to(newZoneId).emit('zone:character:join', character)
|
||||
|
||||
// Update socket rooms
|
||||
socket.leave(oldZoneId.toString())
|
||||
socket.join(newZoneId.toString())
|
||||
socket.leave(oldZoneId)
|
||||
socket.join(newZoneId)
|
||||
|
||||
// Send teleport information to the client
|
||||
socket.emit('zone:character:teleport', {
|
||||
|
Reference in New Issue
Block a user