├── .eslintrc ├── .gitignore ├── .prettierignore ├── .prettierrc ├── Jenkinsfile ├── LICENSE.md ├── README.md ├── config └── default.json ├── docs └── Commands.md ├── package-lock.json ├── package.json ├── plugins └── .gitingore ├── res ├── logo.ico ├── logo.png └── logo.svg ├── src ├── DB │ ├── CDClient.ts │ ├── CDClient │ │ ├── ComponentsRegistry.ts │ │ ├── ItemComponent.ts │ │ └── ZoneTable.ts │ ├── LUJS.ts │ └── Models │ │ ├── Ban.ts │ │ ├── Character.ts │ │ ├── HardwareSurvey.ts │ │ ├── InventoryItem.ts │ │ ├── Session.ts │ │ └── User.ts ├── LU │ ├── CharData.ts │ ├── GameMessageFactory.ts │ ├── InventoryTypes.ts │ ├── LDF.ts │ ├── Level │ │ └── luz.ts │ ├── Managers │ │ ├── ChatManager.ts │ │ ├── GenericManager.ts │ │ ├── LWOOBJIDManager.ts │ │ ├── Manager.ts │ │ ├── ReplicaManager.ts │ │ └── ReplicaManagers │ │ │ ├── CharacterManager.ts │ │ │ ├── ControllablePhysicsManager.ts │ │ │ ├── DestructibleManager.ts │ │ │ ├── InventoryManager.ts │ │ │ ├── RenderManager.ts │ │ │ ├── RocketLandingManager.ts │ │ │ ├── SkillManager.ts │ │ │ ├── SoundAmbient2DManager.ts │ │ │ └── Unknown107Manager.ts │ ├── Message Types │ │ ├── LUAuthenticationMessageType.ts │ │ ├── LUChatMessageType.ts │ │ ├── LUClientMessageType.ts │ │ ├── LUGeneralMessageType.ts │ │ ├── LURemoteConnectionType.ts │ │ └── LUServerMessageType.ts │ ├── Message.ts │ ├── Messages │ │ ├── DisconnectNotify.ts │ │ ├── LoadStaticZone.ts │ │ ├── LoginInfo.ts │ │ ├── MinifigCreateRequest.ts │ │ ├── MinifigCreateResponse.ts │ │ ├── MinifigDeleteResponse.ts │ │ ├── MinifigList.ts │ │ ├── TransferToWorld.ts │ │ ├── UserSessionInfo.ts │ │ └── VersionConfirm.ts │ └── Replica │ │ ├── ComponentMask.ts │ │ ├── Components.ts │ │ ├── Object.ts │ │ ├── SerializationOrder.ts │ │ └── SerializationType.ts ├── geometry │ ├── Vector3f.ts │ └── Vector4f.ts └── server │ ├── Commands.ts │ ├── Handles │ ├── MessageHandles │ │ ├── ID_CONNECTION_REQUEST.ts │ │ ├── ID_DISCONNECTION_NOTIFICATION.ts │ │ ├── ID_INTERNAL_PING.ts │ │ ├── ID_NEW_INCOMING_CONNECTION.ts │ │ └── ID_USER_PACKET_ENUM.ts │ └── UserHandles │ │ ├── LOAD_STATIC_ZONE.js │ │ ├── MSG_AUTH_LOGIN_REQUEST.js │ │ ├── MSG_SERVER_VERSION_CONFIRM.js │ │ ├── MSG_WORLD_CLIENT_CHARACTER_CREATE_REQUEST.js │ │ ├── MSG_WORLD_CLIENT_CHARACTER_DELETE_REQUEST.js │ │ ├── MSG_WORLD_CLIENT_CHARACTER_LIST_REQUEST.js │ │ ├── MSG_WORLD_CLIENT_GAME_MSG.js │ │ ├── MSG_WORLD_CLIENT_LEVEL_LOAD_COMPLETE.js │ │ ├── MSG_WORLD_CLIENT_LOGIN_REQUEST.js │ │ ├── MSG_WORLD_CLIENT_POSITION_UPDATE.js │ │ ├── MSG_WORLD_CLIENT_ROUTE_PACKET.js │ │ └── MSG_WORLD_CLIENT_VALIDATION.js │ ├── Loader.ts │ ├── Plugin.ts │ ├── PluginLoader.ts │ ├── Server.ts │ ├── ServerManager.ts │ └── index.ts └── tsconfig.json /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "globals": { 3 | "BigInt": true 4 | }, 5 | "root": true, 6 | "parser": "@typescript-eslint/parser", 7 | "plugins": [ 8 | "@typescript-eslint", 9 | "prettier" 10 | ], 11 | "extends": [ 12 | "eslint:recommended", 13 | "plugin:@typescript-eslint/eslint-recommended", 14 | "plugin:@typescript-eslint/recommended", 15 | "prettier" 16 | ], 17 | "rules": { 18 | "no-console": 1, // Means warning 19 | "consistent-this": [2, "self"], 20 | "prettier/prettier": 2 // Means error 21 | } 22 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | 30 | \.idea/ 31 | 32 | config/local.json 33 | 34 | *.sqlite 35 | 36 | *.bin 37 | 38 | plugins/* 39 | !plugins/.gitignore 40 | 41 | *.psd 42 | *.ai 43 | res/logo_full_res.png 44 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uwainium/lujs-server/de748fd4bb36f70ee91bcffd1b85e740400d4a90/.prettierignore -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "none", 4 | "singleQuote": true 5 | } -------------------------------------------------------------------------------- /Jenkinsfile: -------------------------------------------------------------------------------- 1 | pipeline { 2 | agent any 3 | 4 | tools {nodejs "NodeJS"} 5 | 6 | stages { 7 | stage('Download Dependencies') { 8 | steps { 9 | sh 'npm update' 10 | } 11 | } 12 | stage('Format and Lint Code') { 13 | steps { 14 | sh 'npm run format' 15 | } 16 | } 17 | stage('Build') { 18 | steps { 19 | sh 'npm run build' 20 | } 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 - 2020 Raine Bannister 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LUJS 2 | 3 | ### A simple Node server project for emulating LEGO Universe servers 4 | 5 | [![Build Status](http://jenkins.rainebannister.me:8080/job/LUJS/job/master/badge/icon?style=flat-square)](http://jenkins.rainebannister.me:8080/job/LUJS/job/master) 6 | 7 | ## Easy 8 | 9 | This server is supposed to be the easiest one to set up, at least, that is my goal. I would love to make it to where you could download one thing, run a command, and this just work. I will always keep the setup process as clean and easy as possible. 10 | 11 | ## Disclaimer 12 | 13 | I am a busy person with at least one other LEGO Universe project going on, and this is not the priority. 14 | 15 | ## Setup 16 | 17 | You will need the CDClient.sqlite, which can be found in a couple of places (I will put it up somewhere eventually so it can be included in the project, maybe) 18 | 19 | To get it running, download the repository, initialize Node, and it should be almost ready. You will need a maps folder from an extracted client, and to set the mapsFolder config to the path of that folder. The database will be rebuilt on the first run as well. 20 | 21 | ## Contributing 22 | 23 | I don't plan on anyone else taking interest in this, but if you do, feel free to fork this repository and give it a pull request. I will be happy to look at another person's code if they want to add to this. 24 | 25 | ## Other notes 26 | 27 | I might add more documentation later 28 | -------------------------------------------------------------------------------- /config/default.json: -------------------------------------------------------------------------------- 1 | { 2 | "servers": [ 3 | { 4 | "ip": "127.0.0.1", 5 | "port": 1001, 6 | "password": "3.25 ND", 7 | "zone": -1 8 | }, 9 | { 10 | "ip": "127.0.0.1", 11 | "port": 2001, 12 | "password": "3.25 ND", 13 | "zone": 0 14 | }, 15 | { 16 | "ip": "127.0.0.1", 17 | "port": 2002, 18 | "password": "3.25 ND", 19 | "zone": 1000 20 | } 21 | ], 22 | "database": { 23 | "type": "sqlite", 24 | "connection": "./lujs.sqlite", 25 | "rebuild": true, 26 | "models": "./DB/Models/" 27 | }, 28 | "cdclient": { 29 | "type": "sqlite", 30 | "connection": "./cdclient.sqlite", 31 | "models": "./DB/CDClient/" 32 | }, 33 | "handlers": "./Handles/MessageHandles/", 34 | "logLevel": 5, 35 | "mapsFolder": "You need to edit the config file to point to your maps folder", 36 | "globalIP": "127.0.0.1", 37 | "globalPassword": "3.25 ND", 38 | "pluginPath": "./plugins/", 39 | "timeout": 300000 40 | } 41 | -------------------------------------------------------------------------------- /docs/Commands.md: -------------------------------------------------------------------------------- 1 | # Commands 2 | 3 | ## In game 4 | 5 | ### Fly 6 | 7 | ``` 8 | /fly 9 | ``` 10 | 11 | Toggles flying on the server 12 | 13 | ## In console 14 | 15 | ### Create Account 16 | 17 | ``` 18 | /create-account 19 | ``` 20 | 21 | Creates an account in the database to log in to the server through the client 22 | 23 | ### List Servers 24 | 25 | ``` 26 | /list-servers 27 | ``` 28 | 29 | List all the servers running in this instance of Node 30 | 31 | ### Rebuild Database 32 | 33 | ``` 34 | /rebuild-database 35 | ``` 36 | 37 | Rebuilds the Database 38 | 39 | > :warning: **This will remove all data in the database**: Be very careful here! 40 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lujs", 3 | "version": "0.2.0", 4 | "description": "A simple LEGO Universe server written in Node", 5 | "main": "src/server/index.ts", 6 | "bin": "src/index.js", 7 | "scripts": { 8 | "test": "echo \"Error: no test specified\" && exit 1", 9 | "format": "prettier --config .prettierrc \"src/**/*.ts\" --write && npm run lint", 10 | "build": "tsc", 11 | "lint": "eslint . --ext .ts" 12 | }, 13 | "lint-staged": { 14 | "linters": { 15 | "**/*": [ 16 | "prettier-standard", 17 | "git add" 18 | ] 19 | } 20 | }, 21 | "repository": { 22 | "type": "git", 23 | "url": "https://github.com/RaineBannister/LUJS" 24 | }, 25 | "dependencies": { 26 | "bcryptjs": "^2.4.3", 27 | "config": "^3.3.1", 28 | "extract-zip": "^2.0.1", 29 | "fs-extra": "^8.1.0", 30 | "inet-aton": "git+https://git@github.com/RaineBannister/inet-aton.git", 31 | "lugamemessages": "git+https://git@github.com/RaineBannister/lu-game-messages.git", 32 | "md5-file": "^4.0.0", 33 | "node-raknet": "git+https://git@github.com/RaineBannister/node-raknet.git", 34 | "sequelize": "^5.22.3", 35 | "sqlite": "^3.0.6", 36 | "sqlite3": "^4.2.0", 37 | "typescript": "^3.9.7", 38 | "xmlbuilder": "^13.0.2" 39 | }, 40 | "author": "Raine Bannister", 41 | "license": "MIT", 42 | "bugs": { 43 | "url": "https://github.com/RaineBannister/lujs/issues" 44 | }, 45 | "homepage": "https://github.com/RaineBannister/lujs#readme", 46 | "pkg": { 47 | "scripts": "Handles/**/*.js", 48 | "assets": "res/*" 49 | }, 50 | "devDependencies": { 51 | "@typescript-eslint/eslint-plugin": "^3.7.1", 52 | "@typescript-eslint/parser": "^3.7.1", 53 | "eslint": "^6.8.0", 54 | "eslint-config-prettier": "^6.11.0", 55 | "eslint-config-standard": "^14.1.1", 56 | "eslint-config-standard-with-typescript": "^18.0.2", 57 | "eslint-plugin-import": "^2.22.0", 58 | "eslint-plugin-node": "^11.1.0", 59 | "eslint-plugin-prettier": "^3.1.4", 60 | "eslint-plugin-promise": "^4.2.1", 61 | "eslint-plugin-standard": "^4.0.1", 62 | "husky": "^4.2.5", 63 | "lint-staged": "^10.2.11", 64 | "pkg": "^4.4.9", 65 | "prettier": "^2.0.5", 66 | "prettier-standard": "^16.4.1" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /plugins/.gitingore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uwainium/lujs-server/de748fd4bb36f70ee91bcffd1b85e740400d4a90/plugins/.gitingore -------------------------------------------------------------------------------- /res/logo.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uwainium/lujs-server/de748fd4bb36f70ee91bcffd1b85e740400d4a90/res/logo.ico -------------------------------------------------------------------------------- /res/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/uwainium/lujs-server/de748fd4bb36f70ee91bcffd1b85e740400d4a90/res/logo.png -------------------------------------------------------------------------------- /src/DB/CDClient.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from 'sequelize'; 2 | import * as config from 'config'; 3 | 4 | import ComponentsRegistryClass from './CDClient/ComponentsRegistry'; 5 | import ItemComponentClass from './CDClient/ItemComponent'; 6 | import ZoneTableClass from './CDClient/ZoneTable'; 7 | 8 | // Set up connection information 9 | export const sequelize = new Sequelize('cdclient', null, null, { 10 | dialect: config.get('cdclient.type'), 11 | storage: config.get('cdclient.connection'), 12 | logging: false 13 | }); 14 | 15 | // Test connection 16 | sequelize.authenticate().then(function (err) { 17 | if (err) throw new Error('Unable to connect to the database:' + err); 18 | console.log('Connected to the CDClient database'); 19 | }); 20 | 21 | export const ComponentsRegistry = ComponentsRegistryClass(sequelize); 22 | export const ItemComponent = ItemComponentClass(sequelize); 23 | export const ZoneTable = ZoneTableClass(sequelize); 24 | -------------------------------------------------------------------------------- /src/DB/CDClient/ComponentsRegistry.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, INTEGER, Model } from 'sequelize'; 2 | 3 | export default (sequelize) => { 4 | return sequelize.define( 5 | 'ComponentsRegistry', 6 | { 7 | id: { 8 | type: INTEGER, 9 | primaryKey: true 10 | }, 11 | component_type: { 12 | type: INTEGER 13 | }, 14 | component_id: { 15 | type: INTEGER 16 | } 17 | }, 18 | { 19 | timestamps: false, 20 | tableName: 'ComponentsRegistry' 21 | } 22 | ); 23 | }; 24 | -------------------------------------------------------------------------------- /src/DB/CDClient/ItemComponent.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | 3 | export default (sequelize) => { 4 | return sequelize.define( 5 | 'ItemComponent', 6 | { 7 | id: { 8 | type: Sequelize.INTEGER, 9 | primaryKey: true 10 | }, 11 | equipLocation: { 12 | type: Sequelize.TEXT 13 | }, 14 | baseValue: { 15 | type: Sequelize.INTEGER 16 | }, 17 | isKitPiece: { 18 | type: Sequelize.BOOLEAN 19 | }, 20 | rarity: { 21 | type: Sequelize.INTEGER 22 | }, 23 | itemType: { 24 | type: Sequelize.INTEGER 25 | }, 26 | itemInfo: { 27 | type: Sequelize.INTEGER 28 | }, 29 | inLootTable: { 30 | type: Sequelize.BOOLEAN 31 | }, 32 | inVendor: { 33 | type: Sequelize.BOOLEAN 34 | }, 35 | isUnique: { 36 | type: Sequelize.BOOLEAN 37 | }, 38 | isBOP: { 39 | type: Sequelize.BOOLEAN 40 | }, 41 | isBOE: { 42 | type: Sequelize.BOOLEAN 43 | }, 44 | reqFlagID: { 45 | type: Sequelize.INTEGER 46 | }, 47 | reqSpecialtyID: { 48 | type: Sequelize.INTEGER 49 | }, 50 | reqSpecRank: { 51 | type: Sequelize.INTEGER 52 | }, 53 | reqAchievementID: { 54 | type: Sequelize.INTEGER 55 | }, 56 | stackSize: { 57 | type: Sequelize.INTEGER 58 | }, 59 | color1: { 60 | type: Sequelize.INTEGER 61 | }, 62 | decal: { 63 | type: Sequelize.INTEGER 64 | }, 65 | offsetGroupID: { 66 | type: Sequelize.INTEGER 67 | }, 68 | buildTypes: { 69 | type: Sequelize.INTEGER 70 | }, 71 | reqPrecondition: { 72 | type: Sequelize.TEXT 73 | }, 74 | animationFlag: { 75 | type: Sequelize.INTEGER 76 | }, 77 | equipEffects: { 78 | type: Sequelize.INTEGER 79 | }, 80 | readyForQA: { 81 | type: Sequelize.BOOLEAN 82 | }, 83 | itemRating: { 84 | type: Sequelize.INTEGER 85 | }, 86 | isTwoHanded: { 87 | type: Sequelize.BOOLEAN 88 | }, 89 | minNumRequired: { 90 | type: Sequelize.INTEGER 91 | }, 92 | delResIndex: { 93 | type: Sequelize.INTEGER 94 | }, 95 | currencyLOT: { 96 | type: Sequelize.INTEGER 97 | }, 98 | altCurrencyCost: { 99 | type: Sequelize.INTEGER 100 | }, 101 | subItems: { 102 | type: Sequelize.TEXT 103 | }, 104 | audioEventUse: { 105 | type: Sequelize.TEXT 106 | }, 107 | noEquipAnimation: { 108 | type: Sequelize.BOOLEAN 109 | }, 110 | commendationLOT: { 111 | type: Sequelize.INTEGER 112 | }, 113 | commendationCost: { 114 | type: Sequelize.INTEGER 115 | }, 116 | audioEquipMetaEventSet: { 117 | type: Sequelize.TEXT 118 | }, 119 | currencyCosts: { 120 | type: Sequelize.TEXT 121 | }, 122 | ingredientInfo: { 123 | type: Sequelize.TEXT 124 | }, 125 | locStatus: { 126 | type: Sequelize.INTEGER 127 | }, 128 | forgeType: { 129 | type: Sequelize.INTEGER 130 | }, 131 | SellMultiplier: { 132 | type: Sequelize.FLOAT 133 | } 134 | }, 135 | { 136 | timestamps: false, 137 | tableName: 'ItemComponent' 138 | } 139 | ); 140 | }; 141 | -------------------------------------------------------------------------------- /src/DB/CDClient/ZoneTable.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | 3 | export default (sequelize) => { 4 | return sequelize.define( 5 | 'ZoneTable', 6 | { 7 | zoneID: { 8 | type: Sequelize.INTEGER, 9 | primaryKey: true 10 | }, 11 | locStatus: { 12 | type: Sequelize.INTEGER 13 | }, 14 | zoneName: { 15 | type: Sequelize.TEXT 16 | }, 17 | scriptID: { 18 | type: Sequelize.INTEGER 19 | }, 20 | ghostdistance_min: { 21 | type: Sequelize.FLOAT 22 | }, 23 | ghostdistance: { 24 | type: Sequelize.FLOAT 25 | }, 26 | population_soft_cap: { 27 | type: Sequelize.INTEGER 28 | }, 29 | population_hard_cap: { 30 | type: Sequelize.INTEGER 31 | }, 32 | DisplayDescription: { 33 | type: Sequelize.TEXT 34 | }, 35 | mapFolder: { 36 | type: Sequelize.TEXT 37 | }, 38 | smashableMinDistance: { 39 | type: Sequelize.FLOAT 40 | }, 41 | smashableMaxDistance: { 42 | type: Sequelize.FLOAT 43 | }, 44 | mixerProgram: { 45 | type: Sequelize.TEXT 46 | }, 47 | clientPhysicsFramerate: { 48 | type: Sequelize.TEXT 49 | }, 50 | serverPhysicsFramerate: { 51 | type: Sequelize.TEXT 52 | }, 53 | zoneControlTemplate: { 54 | type: Sequelize.INTEGER 55 | }, 56 | widthInChunks: { 57 | type: Sequelize.INTEGER 58 | }, 59 | heightInChunks: { 60 | type: Sequelize.INTEGER 61 | }, 62 | petsAllowed: { 63 | type: Sequelize.BOOLEAN 64 | }, 65 | localize: { 66 | type: Sequelize.BOOLEAN 67 | }, 68 | fZoneWeight: { 69 | type: Sequelize.FLOAT 70 | }, 71 | thumbnail: { 72 | type: Sequelize.TEXT 73 | }, 74 | PlayerLoseCoinsOnDeath: { 75 | type: Sequelize.BOOLEAN 76 | }, 77 | disableSaveLoc: { 78 | type: Sequelize.BOOLEAN 79 | }, 80 | teamRadius: { 81 | type: Sequelize.FLOAT 82 | }, 83 | gate_version: { 84 | type: Sequelize.TEXT 85 | }, 86 | mountsAllowed: { 87 | type: Sequelize.BOOLEAN 88 | } 89 | }, 90 | { 91 | timestamps: false, 92 | tableName: 'ZoneTable' 93 | } 94 | ); 95 | }; 96 | -------------------------------------------------------------------------------- /src/DB/LUJS.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize } from 'sequelize'; 2 | import * as config from 'config'; 3 | 4 | import BanClass from './Models/Ban'; 5 | import CharacterClass from './Models/Character'; 6 | import HardwareSurveyClass from './Models/HardwareSurvey'; 7 | import InventoryItemClass from './Models/InventoryItem'; 8 | import SessionClass from './Models/Session'; 9 | import UserClass from './Models/User'; 10 | 11 | // Set up connection information 12 | export const sequelize = new Sequelize('lujs', null, null, { 13 | dialect: config.get('database.type'), 14 | storage: config.get('database.connection'), 15 | logging: false 16 | }); 17 | 18 | // Test connection 19 | sequelize.authenticate().then(function (err) { 20 | if (err) throw new Error('Unable to connect to the database:' + err); 21 | console.log('Connected to the LUJS database'); 22 | }); 23 | 24 | export const Ban = BanClass(sequelize); 25 | export const Character = CharacterClass(sequelize); 26 | export const HardwareSurvey = HardwareSurveyClass(sequelize); 27 | export const InventoryItem = InventoryItemClass(sequelize); 28 | export const Session = SessionClass(sequelize); 29 | export const User = UserClass(sequelize); 30 | 31 | // relationships 32 | Character.hasMany(InventoryItem, { 33 | as: 'Items', 34 | foreignKey: 'character_id', 35 | sourceKey: 'id' 36 | }); 37 | -------------------------------------------------------------------------------- /src/DB/Models/Ban.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | 3 | export default (sequelize) => { 4 | return sequelize.define('ban', { 5 | id: { 6 | type: Sequelize.INTEGER, 7 | primaryKey: true, 8 | autoIncrement: true 9 | }, 10 | reason: { 11 | type: Sequelize.TEXT, 12 | allowNull: false 13 | }, 14 | start_time: { 15 | type: Sequelize.DATE, 16 | allowNull: false 17 | }, 18 | end_time: { 19 | type: Sequelize.DATE, 20 | allowNull: false 21 | }, 22 | user_id: { 23 | type: Sequelize.INTEGER, 24 | allowNull: false 25 | } 26 | }); 27 | }; 28 | -------------------------------------------------------------------------------- /src/DB/Models/Character.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | 3 | export default (sequelize) => { 4 | return sequelize.define('character', { 5 | id: { 6 | type: Sequelize.INTEGER, 7 | primaryKey: true, 8 | autoIncrement: true 9 | }, 10 | name: { 11 | type: Sequelize.TEXT, 12 | allowNull: false 13 | }, 14 | unapproved_name: { 15 | type: Sequelize.TEXT, 16 | allowNull: false 17 | }, 18 | shirt_color: { 19 | type: Sequelize.INTEGER, 20 | allowNull: false 21 | }, 22 | shirt_style: { 23 | type: Sequelize.INTEGER, 24 | allowNull: false 25 | }, 26 | pants_color: { 27 | type: Sequelize.INTEGER, 28 | allowNull: false 29 | }, 30 | hair_style: { 31 | type: Sequelize.INTEGER, 32 | allowNull: false 33 | }, 34 | hair_color: { 35 | type: Sequelize.INTEGER, 36 | allowNull: false 37 | }, 38 | lh: { 39 | type: Sequelize.INTEGER, 40 | allowNull: false 41 | }, 42 | rh: { 43 | type: Sequelize.INTEGER, 44 | allowNull: false 45 | }, 46 | eyebrows: { 47 | type: Sequelize.INTEGER, 48 | allowNull: false 49 | }, 50 | eyes: { 51 | type: Sequelize.INTEGER, 52 | allowNull: false 53 | }, 54 | mouth: { 55 | type: Sequelize.INTEGER, 56 | allowNull: false 57 | }, 58 | zone: { 59 | type: Sequelize.INTEGER, 60 | allowNull: false, 61 | defaultValue: 0 62 | }, 63 | instance: { 64 | type: Sequelize.INTEGER, 65 | allowNull: false, 66 | defaultValue: 0 67 | }, 68 | clone: { 69 | type: Sequelize.INTEGER, 70 | allowNull: false, 71 | defaultValue: 0 72 | }, 73 | last_log: { 74 | type: Sequelize.DATE, 75 | allowNull: false, 76 | defaultValue: 0 77 | }, 78 | user_id: { 79 | type: Sequelize.INTEGER, 80 | allowNull: false 81 | }, 82 | x: { 83 | type: Sequelize.DOUBLE, 84 | allowNull: false, 85 | defaultValue: -626.5847 86 | }, 87 | y: { 88 | type: Sequelize.DOUBLE, 89 | allowNull: false, 90 | defaultValue: 613.3515 91 | }, 92 | z: { 93 | type: Sequelize.DOUBLE, 94 | allowNull: false, 95 | defaultValue: -28.6374 96 | }, 97 | rotation_x: { 98 | type: Sequelize.DOUBLE, 99 | allowNull: false, 100 | defaultValue: 0.7015 101 | }, 102 | rotation_y: { 103 | type: Sequelize.DOUBLE, 104 | allowNull: false, 105 | defaultValue: 0.0 106 | }, 107 | rotation_z: { 108 | type: Sequelize.DOUBLE, 109 | allowNull: false, 110 | defaultValue: 0.7126 111 | }, 112 | rotation_w: { 113 | type: Sequelize.DOUBLE, 114 | allowNull: false, 115 | defaultValue: 0.0 116 | } 117 | }); 118 | }; 119 | -------------------------------------------------------------------------------- /src/DB/Models/HardwareSurvey.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | 3 | export default (sequelize) => { 4 | return sequelize.define('hardware_survey', { 5 | id: { 6 | type: Sequelize.INTEGER, 7 | primaryKey: true, 8 | autoIncrement: true 9 | }, 10 | process_information: { 11 | type: Sequelize.TEXT, 12 | allowNull: false 13 | }, 14 | graphics_information: { 15 | type: Sequelize.TEXT, 16 | allowNull: false 17 | }, 18 | number_of_processors: { 19 | type: Sequelize.INTEGER, 20 | allowNull: false 21 | }, 22 | processor_type: { 23 | type: Sequelize.INTEGER, 24 | allowNull: false 25 | }, 26 | processor_level: { 27 | type: Sequelize.INTEGER, 28 | allowNull: false 29 | }, 30 | user_id: { 31 | type: Sequelize.INTEGER, 32 | allowNull: true 33 | }, 34 | createdAt: { 35 | type: Sequelize.DATE, 36 | defaultValue: Sequelize.NOW 37 | }, 38 | updatedAt: { 39 | type: Sequelize.DATE, 40 | defaultValue: Sequelize.NOW 41 | } 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /src/DB/Models/InventoryItem.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | 3 | export default (sequelize) => { 4 | return sequelize.define('inventory_item', { 5 | id: { 6 | type: Sequelize.INTEGER, 7 | primaryKey: true, 8 | autoIncrement: true 9 | }, 10 | character_id: { 11 | type: Sequelize.INTEGER, 12 | allowNull: false 13 | }, 14 | lot: { 15 | type: Sequelize.INTEGER, 16 | allowNull: false 17 | }, 18 | slot: { 19 | type: Sequelize.INTEGER, 20 | allowNull: false 21 | }, 22 | count: { 23 | type: Sequelize.INTEGER, 24 | allowNull: false 25 | }, 26 | type: { 27 | type: Sequelize.INTEGER, 28 | allowNull: false 29 | }, 30 | is_equipped: { 31 | type: Sequelize.BOOLEAN, 32 | allowNull: false, 33 | defaultValue: false 34 | }, 35 | is_linked: { 36 | type: Sequelize.BOOLEAN, 37 | allowNull: false, 38 | defaultValue: false 39 | } 40 | }); 41 | }; 42 | -------------------------------------------------------------------------------- /src/DB/Models/Session.ts: -------------------------------------------------------------------------------- 1 | import * as Sequelize from 'sequelize'; 2 | 3 | export default (sequelize) => { 4 | return sequelize.define('session', { 5 | id: { 6 | type: Sequelize.INTEGER, 7 | primaryKey: true, 8 | autoIncrement: true 9 | }, 10 | key: { 11 | type: Sequelize.TEXT, 12 | allowNull: false 13 | }, 14 | start_time: { 15 | type: Sequelize.DATE, 16 | allowNull: false 17 | }, 18 | end_time: { 19 | type: Sequelize.DATE, 20 | allowNull: false 21 | }, 22 | ip: { 23 | type: Sequelize.TEXT, 24 | allowNull: false 25 | }, 26 | user_id: { 27 | type: Sequelize.INTEGER, 28 | allowNull: false 29 | }, 30 | character_id: { 31 | type: Sequelize.INTEGER, 32 | allowNull: false, 33 | defaultValue: 0 34 | } 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /src/DB/Models/User.ts: -------------------------------------------------------------------------------- 1 | import { Sequelize, Model, INTEGER, TEXT, DATE, NOW } from 'sequelize'; 2 | 3 | export default (sequelize) => { 4 | return sequelize.define('user', { 5 | id: { 6 | type: INTEGER, 7 | primaryKey: true, 8 | autoIncrement: true 9 | }, 10 | username: { 11 | type: TEXT, 12 | allowNull: false 13 | }, 14 | password: { 15 | type: TEXT, 16 | allowNull: false 17 | }, 18 | email: { 19 | type: TEXT, 20 | allowNull: false 21 | }, 22 | first_name: { 23 | type: TEXT, 24 | allowNull: false 25 | }, 26 | last_name: { 27 | type: TEXT, 28 | allowNull: false 29 | }, 30 | birthdate: { 31 | type: TEXT, 32 | allowNull: false 33 | }, 34 | createdAt: { 35 | type: DATE, 36 | defaultValue: NOW 37 | }, 38 | updatedAt: { 39 | type: DATE, 40 | defaultValue: NOW 41 | } 42 | }); 43 | }; 44 | -------------------------------------------------------------------------------- /src/LU/CharData.ts: -------------------------------------------------------------------------------- 1 | import * as xmlbuilder from 'xmlbuilder'; 2 | import { InventoryTypes } from './InventoryTypes'; 3 | 4 | class CharData { 5 | #xml: xmlbuilder.XMLElement; 6 | #items; 7 | #char; 8 | #mf; 9 | #level; 10 | #inv; 11 | #container; 12 | 13 | constructor() { 14 | this.#xml = xmlbuilder.create('obj'); 15 | this.#xml.att('v', 1); 16 | 17 | // character 18 | this.#char = this.#xml.ele('char'); 19 | this.#mf = this.#xml.ele('mf'); 20 | this.#level = this.#xml.ele('lvl'); 21 | 22 | // inventory 23 | this.#inv = this.#xml.ele('inv'); 24 | 25 | this.#items = this.#inv.ele('items'); 26 | 27 | this.#container = []; 28 | this.#container[InventoryTypes.items] = this.#items.ele('in'); 29 | this.#container[InventoryTypes.items].att('t', InventoryTypes.items); 30 | } 31 | 32 | /** 33 | * 34 | * @param lot 35 | * @param id 36 | * @param slot 37 | * @param count 38 | * @param equipped 39 | * @param bound 40 | */ 41 | addItem(lot, id, slot, count, equipped, bound) { 42 | const item = this.#container[InventoryTypes.items].ele('i'); 43 | item.att('l', lot); 44 | item.att('id', id); 45 | item.att('s', slot); 46 | // item.att('c', count); 47 | 48 | if (equipped) { 49 | item.att('eq', 1); 50 | } 51 | 52 | if (bound) { 53 | // item.att('b', 1); 54 | } 55 | } 56 | 57 | /** 58 | * 59 | * @param hairColor 60 | * @param hairStyle 61 | * @param shirtColor 62 | * @param pantsColor 63 | * @param leftHand 64 | * @param rightHand 65 | * @param eyebrowStyle 66 | * @param eyeStyle 67 | * @param mouthStyle 68 | */ 69 | setMinifigureData( 70 | hairColor, 71 | hairStyle, 72 | shirtColor, 73 | pantsColor, 74 | leftHand, 75 | rightHand, 76 | eyebrowStyle, 77 | eyeStyle, 78 | mouthStyle 79 | ) { 80 | this.#mf.att('hc', hairColor); 81 | this.#mf.att('hs', hairStyle); 82 | this.#mf.att('hd', 0); 83 | this.#mf.att('t', shirtColor); 84 | this.#mf.att('l', pantsColor); 85 | this.#mf.att('hdc', 0); 86 | this.#mf.att('cd', 21); // no clue why this is 21, retrieved from other xml 87 | this.#mf.att('lh', leftHand); 88 | this.#mf.att('rh', rightHand); 89 | this.#mf.att('es', eyebrowStyle); 90 | this.#mf.att('ess', eyeStyle); 91 | this.#mf.att('ms', mouthStyle); 92 | } 93 | 94 | /** 95 | * 96 | * @param accountID 97 | * @param currency 98 | * @param gm 99 | * @param ftp 100 | */ 101 | setCharacterData(accountID, currency, gm, ftp) { 102 | this.#char.att('acct', accountID); 103 | this.#char.att('cc', currency); 104 | this.#char.att('gm', gm); 105 | this.#char.att('ft', ftp); 106 | } 107 | 108 | setDestructibleInformation( 109 | healthMaximum, 110 | healthCurrent, 111 | imaginationMaximum, 112 | imaginationCurrent, 113 | armorMaximum, 114 | armorCurrent 115 | ) { 116 | // TODO 117 | } 118 | 119 | /** 120 | * 121 | * @param level 122 | */ 123 | setLevelInformation(level) { 124 | this.#level.att('l', level); 125 | this.#level.att('cv', 1); 126 | this.#level.att('sb', 500); 127 | } 128 | 129 | get xml() { 130 | return this.#xml; 131 | } 132 | } 133 | 134 | module.exports = CharData; 135 | -------------------------------------------------------------------------------- /src/LU/GameMessageFactory.ts: -------------------------------------------------------------------------------- 1 | import { GameMessage, GameMessageKey } from 'lugamemessages/GameMessages'; 2 | import BitStream from 'node-raknet/structures/BitStream'; 3 | 4 | export default class GameMessageFactory { 5 | /** 6 | * 7 | * @param {number} ID 8 | * @param {object} properties 9 | */ 10 | static makeMessage(ID: number, properties = {}): LUGameMessage { 11 | const gm = GameMessage[ID]; 12 | if (gm !== undefined) { 13 | const toRet = new LUGameMessage(ID); 14 | toRet.properties = properties; 15 | return toRet; 16 | } 17 | } 18 | 19 | /** 20 | * Create a GM from an ID and the stream containing the data 21 | * @param id 22 | * @param stream 23 | */ 24 | static generateMessageFromBitStream( 25 | id: number, 26 | stream: BitStream 27 | ): LUGameMessage { 28 | const toRet = new LUGameMessage(id); 29 | toRet.deserialize(stream); 30 | return toRet; 31 | } 32 | } 33 | 34 | class LUGameMessage { 35 | #id: number; 36 | #data: { [characterName: string]: any }; 37 | 38 | /** 39 | * 40 | * @param {number} id 41 | */ 42 | constructor(id: number) { 43 | this.#id = id; 44 | this.#data = {}; 45 | } 46 | 47 | /** 48 | * 49 | * @param {BitStream} stream 50 | */ 51 | serialize(stream: BitStream): void { 52 | stream.writeShort(this.#id); 53 | 54 | const structure = GameMessage[this.#id]; 55 | 56 | for (const name in structure) { 57 | if (!structure.hasOwnProperty.call(name)) continue; 58 | 59 | if (structure[name].default !== undefined) { 60 | if (this.#data[name] === undefined) { 61 | // if the data is omitted then we just use default values 62 | if (structure[name].type !== 'bit') { 63 | stream.writeBit(false); 64 | } else { 65 | stream.writeBit(structure[name].default); 66 | } 67 | continue; 68 | } else { 69 | if (structure[name].type !== 'bit') { 70 | stream.writeBit(true); 71 | } 72 | } 73 | } else { 74 | // if there is no default then if we don't have data defined for this field we need to throw an error 75 | if (this.#data[name] === undefined) { 76 | throw Error( 77 | name + 78 | ' was not provided when creating game message ' + 79 | GameMessageKey.key(this.#id) 80 | ); 81 | } 82 | } 83 | 84 | switch (structure[name].type) { 85 | case 'bit': 86 | stream.writeBit(this.#data[name]); 87 | break; 88 | case 'int': 89 | stream.writeLong(this.#data[name]); 90 | break; 91 | case 'float': 92 | stream.writeFloat(this.#data[name]); 93 | break; 94 | } 95 | } 96 | } 97 | 98 | /** 99 | * 100 | * @param {BitStream} stream 101 | */ 102 | deserialize(stream: BitStream): void { 103 | const structure = GameMessage[this.#id]; 104 | 105 | for (const name in structure) { 106 | if (!structure.hasOwnProperty.call(name)) continue; 107 | 108 | switch (structure[name].type) { 109 | case 'int': 110 | this.#data[name] = stream.readLong(); 111 | break; 112 | case 'wstring': { 113 | let temp = ''; 114 | const length = stream.readLong(); 115 | for (let i = 0; i < length; i++) { 116 | temp += String.fromCharCode(stream.readShort()); 117 | } 118 | this.#data[name] = temp; 119 | break; 120 | } 121 | } 122 | } 123 | } 124 | 125 | get properties(): { [characterName: string]: any } { 126 | return this.#data; 127 | } 128 | 129 | /** 130 | * 131 | * @param properties 132 | */ 133 | set properties(properties: { [characterName: string]: any }) { 134 | this.#data = properties; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /src/LU/InventoryTypes.ts: -------------------------------------------------------------------------------- 1 | export const InventoryTypes = { 2 | items: 0, 3 | vault: 1, 4 | bricks: 2, 5 | temporary: 4, 6 | models: 5, 7 | temporaryModels: 6, 8 | behaviors: 7, 9 | deeds: 8, 10 | hidden: 12, 11 | vaultModels: 14 12 | }; 13 | -------------------------------------------------------------------------------- /src/LU/LDF.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | 3 | class LDFStruct { 4 | key: string; 5 | type: number; 6 | value: any; 7 | 8 | constructor(key: string, type: number, value: any) { 9 | this.key = key; 10 | this.type = type; 11 | this.value = value; 12 | } 13 | } 14 | 15 | export default class LDF { 16 | #ldf: Array; 17 | 18 | constructor() { 19 | this.#ldf = []; 20 | } 21 | 22 | addWString(key: string, value: string) { 23 | this.#ldf.push(new LDFStruct(key, 0, value)); 24 | } 25 | 26 | addSignedLong(key: string, value: number) { 27 | this.#ldf.push(new LDFStruct(key, 1, value)); 28 | } 29 | 30 | addFloat(key: string, value: number) { 31 | this.#ldf.push(new LDFStruct(key, 3, value)); 32 | } 33 | 34 | addDouble(key: string, value: number) { 35 | this.#ldf.push(new LDFStruct(key, 4, value)); 36 | } 37 | 38 | addLong(key: string, value: number) { 39 | this.#ldf.push(new LDFStruct(key, 5, value)); 40 | } 41 | 42 | addBoolean(key: string, value: boolean) { 43 | this.#ldf.push(new LDFStruct(key, 7, value)); 44 | } 45 | 46 | addSignedLongLong(key: string, value: bigint) { 47 | this.#ldf.push(new LDFStruct(key, 8, value)); 48 | } 49 | 50 | addLWOOBJID(key: string, value: bigint) { 51 | this.#ldf.push(new LDFStruct(key, 9, value)); 52 | } 53 | 54 | addByteString(key: string, value: string) { 55 | this.#ldf.push(new LDFStruct(key, 13, value)); 56 | } 57 | 58 | serialize(stream: BitStream) { 59 | stream.writeLong(this.#ldf.length); 60 | for (let i = 0; i < this.#ldf.length; i++) { 61 | const ldf = this.#ldf[i]; 62 | stream.writeByte(ldf.key.length * 2); 63 | for (let k = 0; k < ldf.key.length; k++) { 64 | stream.writeChar(ldf.key.charCodeAt(k)); 65 | stream.writeByte(0); 66 | } 67 | stream.writeByte(ldf.type); 68 | switch (ldf.type) { 69 | case 0: 70 | stream.writeLong(ldf.value.length); 71 | for (let k = 0; k < ldf.value.length; k++) { 72 | stream.writeShort(ldf.value.charCodeAt[k]); 73 | } 74 | break; 75 | case 1: 76 | stream.writeSignedLong(ldf.value); 77 | break; 78 | case 3: 79 | stream.writeFloat(ldf.value); 80 | break; 81 | case 4: 82 | // TODO: Not implemented yet 83 | // stream.writeDouble(ldf.value); 84 | break; 85 | case 5: 86 | stream.writeLong(ldf.value); 87 | break; 88 | case 7: 89 | stream.writeBoolean(ldf.value); 90 | break; 91 | case 8: 92 | stream.writeLongLong(ldf.value); 93 | break; 94 | case 9: 95 | stream.writeLongLong(ldf.value); 96 | break; 97 | case 13: 98 | stream.writeLong(ldf.value.length); 99 | for (let k = 0; k < ldf.value.length; k++) { 100 | stream.writeByte(ldf.value.charCodeAt[k]); 101 | } 102 | break; 103 | } 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/LU/Level/luz.ts: -------------------------------------------------------------------------------- 1 | import Vector3f from '../../geometry/Vector3f'; 2 | import Vector4f from '../../geometry/Vector4f'; 3 | 4 | const PathType = { 5 | MOVEMENT: 0, 6 | MOVING_PLATFORM: 1, 7 | PROPERTY: 2, 8 | CAMERA: 3, 9 | SPAWNER: 4, 10 | SHOWCASE: 5, 11 | RACE: 6, 12 | RAIL: 7 13 | }; 14 | 15 | /* 16 | const PathBehavior = { 17 | LOOP: 1, 18 | BOUNCE: 2, 19 | ONCE: 3 20 | }; 21 | */ 22 | 23 | class Scene { 24 | id: number; 25 | fileName: string; 26 | isAudioScene: number; 27 | name: string; 28 | 29 | constructor( 30 | id: number, 31 | fileName: string, 32 | isAudioScene: number, 33 | name: string 34 | ) { 35 | this.id = id; 36 | this.fileName = fileName; 37 | this.isAudioScene = isAudioScene; 38 | this.name = name; 39 | } 40 | } 41 | 42 | class Path { 43 | version; 44 | name; 45 | type; 46 | behavior; 47 | waypoints; 48 | } 49 | 50 | /** 51 | * Opens LUZ files 52 | */ 53 | export default class LUZ { 54 | #version: number; 55 | #zoneID: number; 56 | #unknown1: number; 57 | #scenes: Array; 58 | #spawnLoc: Vector3f; 59 | #spawnRot: Vector4f; 60 | #paths: Array; 61 | 62 | /** 63 | * 64 | * @param {BitStream} stream 65 | */ 66 | constructor(stream) { 67 | this.#version = stream.readLong(); 68 | if (this.#version >= 0x24) { 69 | this.#unknown1 = stream.readLong(); 70 | } 71 | this.#zoneID = stream.readLong(); 72 | if (this.#version >= 0x26) { 73 | this.#spawnLoc = new Vector3f( 74 | stream.readFloat(), 75 | stream.readFloat(), 76 | stream.readFloat() 77 | ); 78 | this.#spawnRot = new Vector4f( 79 | stream.readFloat(), 80 | stream.readFloat(), 81 | stream.readFloat(), 82 | stream.readFloat() 83 | ); 84 | } 85 | 86 | let sceneCount = 0; 87 | if (this.#version < 0x25) { 88 | sceneCount = stream.readByte(); 89 | } else { 90 | sceneCount = stream.readLong(); 91 | } 92 | 93 | this.#scenes = []; 94 | for (let i = 0; i < sceneCount; i++) { 95 | const filename = stream.readString(stream.readByte()); 96 | const id = stream.readByte(); 97 | stream.readString(3); 98 | const isAudioScene = stream.readByte(); 99 | stream.readString(3); 100 | const sceneName = stream.readString(stream.readByte()); 101 | stream.readString(3); 102 | 103 | this.#scenes.push(new Scene(id, filename, isAudioScene, sceneName)); 104 | } 105 | 106 | stream.readByte(); // unknown 2 107 | stream.readString(stream.readByte()); // map file name 108 | stream.readString(stream.readByte()); // map name 109 | stream.readString(stream.readByte()); // map desc 110 | 111 | if (this.#version >= 0x20) { 112 | const countOfSceneTransitions = stream.readLong(); 113 | for (let i = 0; i < countOfSceneTransitions; i++) { 114 | let sceneTransitionName = ''; 115 | if (this.#version < 0x25) { 116 | sceneTransitionName = stream.readString(stream.readByte()); 117 | } 118 | 119 | let loopTimes = 5; 120 | if (this.#version <= 0x21 || this.#version >= 0x27) { 121 | loopTimes = 2; 122 | } 123 | 124 | const sceneTransition = { 125 | name: sceneTransitionName, 126 | transitionPoints: [] 127 | }; 128 | 129 | for (let j = 0; j < loopTimes; j++) { 130 | sceneTransition.transitionPoints.push({ 131 | sceneId: stream.readLongLong(), 132 | x: stream.readFloat(), 133 | y: stream.readFloat(), 134 | z: stream.readFloat() 135 | }); 136 | } 137 | } 138 | } 139 | 140 | if (this.#version >= 0x23) { 141 | this.#paths = []; 142 | 143 | stream.readLong(); 144 | stream.readLong(); 145 | const pathsCount = stream.readLong(); 146 | for (let i = 0; i < pathsCount; i++) { 147 | const path = new Path(); 148 | path.version = stream.readLong(); 149 | path.name = ''; 150 | const pathNameLength = stream.readByte(); 151 | for (let j = 0; j < pathNameLength; j++) { 152 | path.name += String.fromCharCode(stream.readShort()); 153 | } 154 | path.type = stream.readLong(); 155 | stream.readLong(); 156 | path.behavior = stream.readLong(); 157 | 158 | switch (path.type) { 159 | case PathType.MOVING_PLATFORM: { 160 | if (path.version >= 18) { 161 | stream.readByte(); 162 | } else if (path.version >= 13) { 163 | const temp = stream.readByte(); 164 | for (let k = 0; k < temp; k++) { 165 | stream.readShort(); 166 | } 167 | } 168 | break; 169 | } 170 | case PathType.PROPERTY: { 171 | stream.readLong(); 172 | stream.readLong(); 173 | stream.readLong(); 174 | stream.readLongLong(); 175 | const temp2 = stream.readByte(); 176 | for (let j = 0; j < temp2; j++) { 177 | stream.readShort(); 178 | } 179 | const temp3 = stream.readLong(); 180 | for (let j = 0; j < temp3; j++) { 181 | stream.readShort(); 182 | } 183 | stream.readLong(); 184 | stream.readLong(); 185 | stream.readFloat(); 186 | stream.readLong(); 187 | stream.readLong(); 188 | stream.readFloat(); 189 | stream.readFloat(); 190 | stream.readFloat(); 191 | stream.readFloat(); 192 | 193 | break; 194 | } 195 | case PathType.CAMERA: { 196 | const temp4 = stream.readByte(); 197 | for (let j = 0; j < temp4; j++) { 198 | stream.readShort(); 199 | } 200 | if (path.version >= 14) stream.readByte(); 201 | break; 202 | } 203 | case PathType.SPAWNER: { 204 | stream.readLong(); // spawned LOT 205 | stream.readLong(); // respawn time 206 | stream.readLong(); // max 207 | stream.readLong(); // min 208 | stream.readLongLong(); // id 209 | stream.readBoolean(); // activate on load 210 | break; 211 | } 212 | } 213 | path.waypoints = []; 214 | const waypointCount = stream.readLong(); 215 | for (let j = 0; j < waypointCount; j++) { 216 | /*const waypoint = {}; 217 | waypoint.x = stream.readFloat(); 218 | waypoint.y = stream.readFloat(); 219 | waypoint.z = stream.readFloat(); 220 | switch (path.type) { 221 | case PathType.MOVING_PLATFORM: 222 | waypoint.w = stream.readFloat(); 223 | waypoint.x = stream.readFloat(); 224 | waypoint.y = stream.readFloat(); 225 | waypoint.z = stream.readFloat(); 226 | waypoint.lock = stream.readBoolean(); 227 | waypoint.movementSpeed = stream.readFloat(); 228 | waypoint.waitTime = stream.readFloat(); 229 | if (path.version >= 13) { 230 | const waypointArriveLength = stream.readByte(); 231 | waypoint.arrive = ''; 232 | for (let k = 0; k < waypointArriveLength; k++) { 233 | waypoint.arrive += String.fromCharCode(stream.readShort()); 234 | } 235 | const waypointDepartLength = stream.readByte(); 236 | waypoint.depart = ''; 237 | for (let k = 0; k < waypointDepartLength; k++) { 238 | waypoint.depart += String.fromCharCode(stream.readShort()); 239 | } 240 | } 241 | break; 242 | case PathType.CAMERA: 243 | stream.readFloat(); 244 | stream.readFloat(); 245 | stream.readFloat(); 246 | stream.readFloat(); 247 | stream.readFloat(); 248 | stream.readFloat(); 249 | stream.readFloat(); 250 | stream.readFloat(); 251 | stream.readFloat(); 252 | break; 253 | case PathType.SPAWNER: 254 | waypoint.w = stream.readFloat(); 255 | waypoint.x = stream.readFloat(); 256 | waypoint.y = stream.readFloat(); 257 | waypoint.z = stream.readFloat(); 258 | break; 259 | case PathType.RACE: 260 | waypoint.w = stream.readFloat(); 261 | waypoint.x = stream.readFloat(); 262 | waypoint.y = stream.readFloat(); 263 | waypoint.z = stream.readFloat(); 264 | stream.readByte(); 265 | stream.readByte(); 266 | stream.readFloat(); 267 | stream.readFloat(); 268 | stream.readFloat(); 269 | break; 270 | case PathType.RAIL: 271 | stream.readFloat(); 272 | stream.readFloat(); 273 | stream.readFloat(); 274 | stream.readFloat(); 275 | if (path.version >= 17) stream.readFloat(); 276 | break; 277 | } 278 | 279 | if ( 280 | path.type === PathType.MOVEMENT || 281 | path.type === PathType.SPAWNER || 282 | path.type === PathType.RAIL 283 | ) { 284 | waypoint.config = []; 285 | const configCount = stream.readLong(); 286 | for (let k = 0; k < configCount; k++) { 287 | const item = { 288 | name: stream.readWString(stream.readByte()), 289 | value: stream.readWString(stream.readByte()) 290 | }; 291 | waypoint.config.push(item); 292 | } 293 | } 294 | path.waypoints.push(waypoint);*/ 295 | } 296 | this.#paths.push(path); 297 | } 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /src/LU/Managers/ChatManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from './GenericManager'; 2 | import TransferToWorld from '../Messages/TransferToWorld'; 3 | import { LUClientMessageType } from '../Message Types/LUClientMessageType'; 4 | import { LURemoteConnectionType } from '../Message Types/LURemoteConnectionType'; 5 | import { Reliability } from 'node-raknet/ReliabilityLayer'; 6 | import BitStream from 'node-raknet/structures/BitStream'; 7 | import RakMessages from 'node-raknet/RakMessages'; 8 | import GameMessageFactory from '../GameMessageFactory'; 9 | import { GameMessageKey } from 'lugamemessages/GameMessages'; 10 | import { ServerManager } from '../../server/ServerManager'; 11 | import { Character } from '../../DB/LUJS'; 12 | import { Server } from '../../server/Server'; 13 | 14 | /** 15 | * A manager for basic chat functionality 16 | */ 17 | export default class ChatManager extends GenericManager { 18 | #commands; 19 | 20 | constructor(server: Server) { 21 | super(server); 22 | 23 | this.#commands = {}; 24 | 25 | this.eventBus.on('chat', (message, client, sender) => { 26 | const text = message.properties.string; 27 | // if it is a command 28 | if (text.charAt(0) === '/') { 29 | // yay we get to parse it -_- 30 | const args = text.substr(1).split(' '); 31 | const command = args[0]; 32 | 33 | if (this.#commands[command] === undefined) { 34 | // command doesn't exist 35 | console.log(`Command doesn't exist: ${command}`); 36 | } else { 37 | // TODO: permissions at some point 38 | this.#commands[command](sender, client, args); 39 | } 40 | } else { 41 | // TODO: Logic to broadcast to whole server 42 | } 43 | }); 44 | 45 | // start adding basic commands? 46 | 47 | this.#commands.loc = () => { 48 | // loc doesn't do anything on server so we can just ignore it 49 | }; 50 | 51 | // this is to change worlds 52 | this.#commands.testmap = (sender, client, args) => { 53 | Character.findByPk(client.session.character_id).then((character) => { 54 | ServerManager.request(parseInt(args[1])).then((server) => { 55 | const zone = server; 56 | 57 | character.x = zone.luz.spawnX; 58 | character.y = zone.luz.spawnY; 59 | character.z = zone.luz.spawnZ; 60 | character.rotation_x = zone.luz.spawnrX; 61 | character.rotation_y = zone.luz.spawnrY; 62 | character.rotation_z = zone.luz.spawnrZ; 63 | character.rotation_w = zone.luz.spawnrW; 64 | character.save(); 65 | 66 | const response = new TransferToWorld(); 67 | response.ip = zone.ip; 68 | response.port = zone.port; 69 | response.mythranShift = false; 70 | 71 | const send = new BitStream(); 72 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 73 | send.writeShort(LURemoteConnectionType.client); 74 | send.writeLong(LUClientMessageType.TRANSFER_TO_WORLD); 75 | send.writeByte(0); 76 | response.serialize(send); 77 | client.send(send, Reliability.RELIABLE_ORDERED); 78 | }); 79 | }); 80 | }; 81 | 82 | this.#commands.fly = (sender, client, args) => { 83 | if (client.fly === undefined) client.fly = false; 84 | client.fly = !client.fly; 85 | 86 | const stream = new BitStream(); 87 | stream.writeByte(RakMessages.ID_USER_PACKET_ENUM); 88 | stream.writeShort(LURemoteConnectionType.client); 89 | stream.writeLong(LUClientMessageType.GAME_MSG); 90 | stream.writeByte(0); 91 | stream.writeLongLong( 92 | 0x1de0b6b500000000n + BigInt(client.session.character_id) 93 | ); 94 | GameMessageFactory.makeMessage(GameMessageKey.setJetPackMode, { 95 | bypassChecks: true, 96 | use: client.fly, 97 | effectID: 0xa7 98 | }).serialize(stream); 99 | 100 | client.send(stream, Reliability.RELIABLE); 101 | }; 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/LU/Managers/GenericManager.ts: -------------------------------------------------------------------------------- 1 | import { Server } from '../../server/Server'; 2 | 3 | export default class GenericManager { 4 | #server: Server; 5 | 6 | constructor(server) { 7 | this.#server = server; 8 | } 9 | 10 | get eventBus() { 11 | return this.#server.eventBus; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/LU/Managers/LWOOBJIDManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from './GenericManager'; 2 | 3 | export default class LWOOBJIDManager extends GenericManager { 4 | #start: number; 5 | 6 | constructor(server) { 7 | super(server); 8 | 9 | this.#start = 0xffff; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/LU/Managers/Manager.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A class to manage managers 3 | */ 4 | import GenericManager from './GenericManager'; 5 | 6 | export default class Manager { 7 | #managers: { [key: string]: GenericManager }; 8 | 9 | constructor() { 10 | this.#managers = {}; 11 | } 12 | 13 | /** 14 | * Attaches a manager to the root manager 15 | * @param {string} name 16 | * @param {GenericManager} manager 17 | */ 18 | attachManager(name: string, manager: GenericManager): void { 19 | this.#managers[name] = manager; 20 | } 21 | 22 | /** 23 | * Retrieves a manager by name 24 | * @param name 25 | * @return {GenericManager} 26 | */ 27 | getManager(name: string): GenericManager { 28 | return this.#managers[name]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/LU/Managers/ReplicaManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from './GenericManager'; 2 | import GameObject from '../Replica/Object'; 3 | import { Server } from '../../server/Server'; 4 | import Vector3f from '../../geometry/Vector3f'; 5 | import Vector4f from '../../geometry/Vector4f'; 6 | 7 | /** 8 | * A manager for objects in game 9 | */ 10 | export default class ReplicaManager extends GenericManager { 11 | #objects: { [index: number]: GameObject }; 12 | #callbacks: { [index: number]: (value?) => void }; 13 | #count: number; 14 | 15 | constructor(server: Server) { 16 | super(server); 17 | /** 18 | * 19 | * @type {{Object}} 20 | * @private 21 | */ 22 | this.#objects = {}; 23 | 24 | /** 25 | * 26 | * @type {{Function}} 27 | * @private 28 | */ 29 | this.#callbacks = {}; 30 | 31 | this.#count = 0; 32 | 33 | // now time to wait for objects to load... 34 | this.eventBus.on('new-object-loaded', (object) => { 35 | /* let stream = new BitStream(); 36 | stream.writeByte(RakMessages.ID_REPLICA_MANAGER_CONSTRUCTION); 37 | stream.writeBit(true); 38 | stream.writeShort(this._count); 39 | object.serialize(SerializationType.CREATION, stream); */ 40 | // we need to broadcast this creation to all users... 41 | 42 | // server.broadcast(stream, Reliability.RELIABLE); 43 | this.#callbacks[object.ID.low](object); 44 | // stream.toFile((Date.now() / 1000 | 0) + "_[24]_[01-00]_(1).bin"); 45 | }); 46 | } 47 | 48 | /** 49 | * 50 | * @param objectTemplate 51 | * @param pos 52 | * @param rot 53 | * @param scale 54 | * @param owner 55 | * @param data 56 | * @param {bigint} [lwoobjid] 57 | */ 58 | loadObject( 59 | objectTemplate: number, 60 | pos: Vector3f, 61 | rot: Vector4f, 62 | scale: number, 63 | owner: bigint, 64 | data, 65 | lwoobjid: bigint 66 | ) { 67 | this.#count++; 68 | if (lwoobjid === undefined) { 69 | // TODO: Need to set up the LWOOBJID Manager to increment object ID's '.nextID()'? 70 | console.log('Needs LWOOBJID'); 71 | } 72 | 73 | const replicaManagerCount = this.#count; 74 | 75 | const obj = new GameObject( 76 | this, 77 | replicaManagerCount, 78 | lwoobjid, 79 | objectTemplate, 80 | pos, 81 | rot, 82 | scale, 83 | owner, 84 | data 85 | ); 86 | 87 | const promise = new Promise((resolve, reject) => { 88 | this.#callbacks[Number(obj.ID)] = resolve; 89 | }); 90 | this.#objects[Number(obj.ID)] = obj; 91 | 92 | return promise; 93 | } 94 | 95 | constructObject(id: bigint) { 96 | //TODO 97 | } 98 | 99 | constructAllObjects(user: bigint) { 100 | //TODO 101 | } 102 | 103 | /** 104 | * 105 | * @param {bigint} id 106 | * @returns {GameObject} 107 | */ 108 | getObject(id: bigint) { 109 | return this.#objects[Number(id)]; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/LU/Managers/ReplicaManagers/CharacterManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from '../GenericManager'; 2 | import { Components } from '../../Replica/Components'; 3 | import { SerializationOrder } from '../../Replica/SerializationOrder'; 4 | import { SerializationType } from '../../Replica/SerializationType'; 5 | import GameObject from '../../Replica/Object'; 6 | 7 | export default class CharacterManager extends GenericManager { 8 | #data; 9 | 10 | constructor(server) { 11 | super(server); 12 | 13 | this.#data = {}; 14 | 15 | server.eventBus.on('new-object-created', (object: GameObject) => { 16 | if (object.components.hasComponent(Components.CHARACTER_COMPONENT)) { 17 | this.#data[Number(object.ID)] = { 18 | unknown1: undefined, 19 | hasLevel: { 20 | level: 1 21 | }, 22 | unknown2: undefined, 23 | co: undefined, 24 | unknown3: undefined, 25 | unknown4: undefined, 26 | unknown5: undefined, 27 | hairColor: 0, 28 | hairStyle: 0, 29 | hd: 0, 30 | shirtColor: 0, 31 | pantsColor: 0, 32 | cd: 0, 33 | hdc: 0, 34 | eyebrows: 0, 35 | eyes: 0, 36 | mouth: 0, 37 | accountID: 0, 38 | llog: 0, 39 | unknown6: 0, 40 | legoScore: 0, 41 | freeToPlay: false, 42 | totalCurrency: 0, 43 | totalBricks: 0, 44 | totalSmashables: 0, 45 | totalQuickBuilds: 0, 46 | totalEnemies: 0, 47 | totalRockets: 0, 48 | totalMissions: 0, 49 | totalPets: 0, 50 | totalImagination: 0, 51 | totalLife: 0, 52 | totalArmor: 0, 53 | totalDistanceTravelled: 0, 54 | totalTimesSmashed: 0, 55 | totalDamageTaken: 0, 56 | totalDamageHealed: 0, 57 | totalArmorRepaired: 0, 58 | totalImaginationRestored: 0, 59 | totalImaginationUsed: 0, 60 | totalDistanceDriven: 0, 61 | totalTimeAirbornDriving: 0, 62 | totalRacingImaginationPowerups: 0, 63 | totalRacingImaginationCrates: 0, 64 | totalRacingBoostUsed: 0, 65 | totalRacingWrecks: 0, 66 | totalRacingSmashables: 0, 67 | totalRacingFinished: 0, 68 | totalRacingWins: 0, 69 | unknown7: undefined, 70 | rocketLanding: undefined, 71 | unknown8: { 72 | pvp: false, 73 | gmlevel: 0, 74 | unknown1: false, 75 | unknown2: 0 76 | }, 77 | effect: undefined, 78 | guild: undefined 79 | }; 80 | 81 | object.addSerializer( 82 | SerializationOrder.indexOf(Components.CHARACTER_COMPONENT), 83 | (type, stream) => { 84 | const data = this.#data[Number(object.ID)]; 85 | 86 | stream.writeBit(data.unknown1 !== undefined); 87 | stream.writeBit(data.hasLevel !== undefined); 88 | if (data.hasLevel !== undefined) { 89 | stream.writeLong(data.hasLevel.level); 90 | } 91 | stream.writeBit(data.unknown2 !== undefined); 92 | 93 | if (type === SerializationType.CREATION) { 94 | stream.writeBit(data.co !== undefined); 95 | stream.writeBit(data.unknown3 !== undefined); 96 | stream.writeBit(data.unknown4 !== undefined); 97 | stream.writeBit(data.unknown5 !== undefined); 98 | stream.writeLong(data.hairColor); 99 | stream.writeLong(data.hairStyle); 100 | stream.writeLong(data.hd); 101 | stream.writeLong(data.shirtColor); 102 | stream.writeLong(data.pantsColor); 103 | stream.writeLong(data.cd); 104 | stream.writeLong(data.hdc); 105 | stream.writeLong(data.eyebrows); 106 | stream.writeLong(data.eyes); 107 | stream.writeLong(data.mouth); 108 | stream.writeLongLong(0, data.accountID); 109 | stream.writeLongLong(data.llog); 110 | stream.writeLongLong(data.unknown6); 111 | stream.writeLongLong(data.legoScore); 112 | stream.writeBit(data.freeToPlay); 113 | stream.writeLongLong(data.totalCurrency); 114 | stream.writeLongLong(data.totalBricks); 115 | stream.writeLongLong(data.totalSmashables); 116 | stream.writeLongLong(data.totalQuickBuilds); 117 | stream.writeLongLong(data.totalEnemies); 118 | stream.writeLongLong(data.totalRockets); 119 | stream.writeLongLong(data.totalMissions); 120 | stream.writeLongLong(data.totalPets); 121 | stream.writeLongLong(data.totalImagination); 122 | stream.writeLongLong(data.totalLife); 123 | stream.writeLongLong(data.totalArmor); 124 | stream.writeLongLong(data.totalDistanceTravelled); 125 | stream.writeLongLong(data.totalTimesSmashed); 126 | stream.writeLongLong(data.totalDamageTaken); 127 | stream.writeLongLong(data.totalDamageHealed); 128 | stream.writeLongLong(data.totalArmorRepaired); 129 | stream.writeLongLong(data.totalImaginationRestored); 130 | stream.writeLongLong(data.totalImaginationUsed); 131 | stream.writeLongLong(data.totalDistanceDriven); 132 | stream.writeLongLong(data.totalTimeAirbornDriving); 133 | stream.writeLongLong(data.totalRacingImaginationPowerups); 134 | stream.writeLongLong(data.totalRacingImaginationCrates); 135 | stream.writeLongLong(data.totalRacingBoostUsed); 136 | stream.writeLongLong(data.totalRacingWrecks); 137 | stream.writeLongLong(data.totalRacingSmashables); 138 | stream.writeLongLong(data.totalRacingFinished); 139 | stream.writeLongLong(data.totalRacingWins); 140 | stream.writeBit(data.unknown7 !== undefined); 141 | stream.writeBit(data.rocketLanding !== undefined); 142 | } 143 | 144 | stream.writeBit(data.unknown8 !== undefined); 145 | if (data.unknown8 !== undefined) { 146 | stream.writeBit(data.unknown8.pvp); 147 | stream.writeBit(data.unknown8.gmlevel > 0); 148 | stream.writeByte(data.unknown8.gmlevel); 149 | stream.writeBit(data.unknown8.unknown1); 150 | stream.writeByte(data.unknown8.unknown2); 151 | } 152 | stream.writeBit(data.effect !== undefined); 153 | if (data.effect !== undefined) { 154 | stream.writeLong(data.effect.id); 155 | } 156 | stream.writeBit(data.guild !== undefined); 157 | } 158 | ); 159 | } 160 | }); 161 | } 162 | 163 | /** 164 | * 165 | * @param {LWOOBJID} objectID 166 | * @returns {Object} the data 167 | */ 168 | getObjectData(objectID: bigint) { 169 | return this.#data[Number(objectID)]; 170 | } 171 | 172 | /** 173 | * 174 | * @param {LWOOBJID} objectID 175 | * @param {Object} data 176 | */ 177 | setObjectData(objectID: bigint, data) { 178 | this.#data[Number(objectID)] = data; 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /src/LU/Managers/ReplicaManagers/ControllablePhysicsManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from '../GenericManager'; 2 | import { Components } from '../../Replica/Components'; 3 | import { SerializationOrder } from '../../Replica/SerializationOrder'; 4 | import { SerializationType } from '../../Replica/SerializationType'; 5 | import GameObject from '../../Replica/Object'; 6 | 7 | export default class ControllablePhysicsManager extends GenericManager { 8 | #data; 9 | 10 | constructor(server) { 11 | super(server); 12 | 13 | this.#data = {}; 14 | 15 | server.eventBus.on('new-object-created', (object: GameObject) => { 16 | if ( 17 | object.components.hasComponent(Components.CONTROLABLE_PHYSICS_COMPONENT) 18 | ) { 19 | this.#data[Number(object.ID)] = { 20 | unknown1: undefined, 21 | unknown2: undefined, 22 | unknown3: undefined, 23 | unknown4: undefined, 24 | unknown5: undefined, 25 | positionData: { 26 | x: object.position.x, 27 | y: object.position.y, 28 | z: object.position.z, 29 | rx: object.rotation.x, 30 | ry: object.rotation.y, 31 | rz: object.rotation.z, 32 | rw: object.rotation.w, 33 | isPlayerOnGround: true, 34 | unknown1: false, 35 | velocity: { 36 | x: 0, 37 | y: 0, 38 | z: 0 39 | }, 40 | angularVelocity: { 41 | x: 0, 42 | y: 0, 43 | z: 0 44 | }, 45 | movingPlatformData: undefined 46 | }, 47 | moreData: undefined 48 | }; 49 | 50 | object.addSerializer( 51 | SerializationOrder.indexOf(Components.CONTROLABLE_PHYSICS_COMPONENT), 52 | (type, stream) => { 53 | const data = this.#data[Number(object.ID)]; 54 | if (type === SerializationType.CREATION) { 55 | stream.writeBit(data.unknown1 !== undefined); 56 | stream.writeBit(data.unknown2 !== undefined); 57 | } 58 | stream.writeBit(data.unknown3 !== undefined); 59 | stream.writeBit(data.unknown4 !== undefined); 60 | stream.writeBit(data.unknown5 !== undefined); 61 | stream.writeBit(data.positionData !== undefined); 62 | if (data.positionData !== undefined) { 63 | stream.writeFloat(data.positionData.x); 64 | stream.writeFloat(data.positionData.y); 65 | stream.writeFloat(data.positionData.z); 66 | stream.writeFloat(data.positionData.rx); 67 | stream.writeFloat(data.positionData.ry); 68 | stream.writeFloat(data.positionData.rz); 69 | stream.writeFloat(data.positionData.rw); 70 | stream.writeBit(data.positionData.isPlayerOnGround); 71 | stream.writeBit(data.positionData.unknown1); 72 | stream.writeBit(data.positionData.velocity !== undefined); 73 | if (data.positionData.velocity !== undefined) { 74 | stream.writeFloat(data.positionData.velocity.x); 75 | stream.writeFloat(data.positionData.velocity.y); 76 | stream.writeFloat(data.positionData.velocity.z); 77 | } 78 | stream.writeBit(data.positionData.angularVelocity !== undefined); 79 | if (data.positionData.angularVelocity !== undefined) { 80 | stream.writeFloat(data.positionData.angularVelocity.x); 81 | stream.writeFloat(data.positionData.angularVelocity.y); 82 | stream.writeFloat(data.positionData.angularVelocity.z); 83 | } 84 | stream.writeBit( 85 | data.positionData.movingPlatformData !== undefined 86 | ); 87 | if (data.positionData.movingPlatformData !== undefined) { 88 | stream.writeLongLong( 89 | data.positionData.movingPlatformData.objectID 90 | ); 91 | stream.writeFloat( 92 | data.positionData.movingPlatformData.unknown1 93 | ); 94 | stream.writeFloat( 95 | data.positionData.movingPlatformData.unknown2 96 | ); 97 | stream.writeFloat( 98 | data.positionData.movingPlatformData.unknown3 99 | ); 100 | stream.writeBit( 101 | data.positionData.movingPlatformData.moreUnknown !== undefined 102 | ); 103 | if ( 104 | data.positionData.movingPlatformData.moreUnknown !== undefined 105 | ) { 106 | stream.writeFloat( 107 | data.positionData.movingPlatformData.moreUnknown.unknown1 108 | ); 109 | stream.writeFloat( 110 | data.positionData.movingPlatformData.moreUnknown.unknown2 111 | ); 112 | stream.writeFloat( 113 | data.positionData.movingPlatformData.moreUnknown.unknown3 114 | ); 115 | } 116 | } 117 | } 118 | 119 | if (type === SerializationType.SERIALIZATION) { 120 | stream.write(data.moreData !== undefined); 121 | } 122 | } 123 | ); 124 | } 125 | }); 126 | } 127 | } 128 | -------------------------------------------------------------------------------- /src/LU/Managers/ReplicaManagers/DestructibleManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from '../GenericManager'; 2 | import { Components } from '../../Replica/Components'; 3 | import { SerializationOrder } from '../../Replica/SerializationOrder'; 4 | import { SerializationType } from '../../Replica/SerializationType'; 5 | import GameObject from '../../Replica/Object'; 6 | 7 | export default class DestructibleManager extends GenericManager { 8 | #data; 9 | 10 | constructor(server) { 11 | super(server); 12 | 13 | this.#data = {}; 14 | 15 | server.eventBus.on('new-object-created', (object: GameObject) => { 16 | if (object.components.hasComponent(Components.DESTRUCTABLE_COMPONENT)) { 17 | this.#data[Number(object.ID)] = {}; 18 | 19 | object.addSerializer( 20 | SerializationOrder.indexOf(Components.DESTRUCTABLE_COMPONENT), 21 | (type, stream) => { 22 | // const data = manager._data[object.ID.low]; 23 | if (type === SerializationType.CREATION) { 24 | stream.writeBit(false); 25 | stream.writeBit(false); 26 | } 27 | 28 | // Stats component 29 | stream.writeBit(false); 30 | if (type === SerializationType.CREATION) { 31 | stream.writeBit(false); 32 | } 33 | stream.writeBit(false); 34 | } 35 | ); 36 | } 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/LU/Managers/ReplicaManagers/InventoryManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from '../GenericManager'; 2 | import { Components } from '../../Replica/Components'; 3 | import { SerializationOrder } from '../../Replica/SerializationOrder'; 4 | import GameObject from '../../Replica/Object'; 5 | 6 | export default class InventoryManager extends GenericManager { 7 | #data; 8 | 9 | constructor(server) { 10 | super(server); 11 | 12 | this.#data = {}; 13 | 14 | /** 15 | * @param {Object} object 16 | */ 17 | server.eventBus.on('new-object-created', (object: GameObject) => { 18 | if (object.components.hasComponent(Components.INVENTORY_COMPONENT)) { 19 | this.#data[Number(object.ID)] = { 20 | inventory: undefined 21 | }; 22 | 23 | object.addSerializer( 24 | SerializationOrder.indexOf(Components.INVENTORY_COMPONENT), 25 | (type, stream) => { 26 | const data = this.#data[Number(object.ID)]; 27 | stream.writeBit(data.inventory !== undefined); 28 | if (data.inventory !== undefined) { 29 | stream.writeLong(data.inventory.length); 30 | for (let i = 0; i < data.inventory.length; i++) { 31 | data.inventory[i].id.serialize(stream); 32 | stream.writeLong(data.inventory[i].lot); 33 | stream.writeBit(false); 34 | stream.writeBit(data.inventory[i].count > 1); 35 | if (data.inventory[i].count > 1) { 36 | stream.writeLong(data.inventory[i].count); 37 | } 38 | stream.writeBit(data.inventory[i].slot !== -1); 39 | if (data.inventory[i].slot !== -1) { 40 | stream.writeShort(data.inventory[i].slot); 41 | } 42 | stream.writeBit(false); 43 | stream.writeBit(false); 44 | stream.writeBit(true); 45 | } 46 | } 47 | stream.writeBit(false); 48 | } 49 | ); 50 | } 51 | }); 52 | } 53 | 54 | /** 55 | * 56 | * @param {bigint} objectID 57 | * @returns {Object} 58 | */ 59 | getObjectData(objectID: bigint) { 60 | return this.#data[Number(objectID)]; 61 | } 62 | 63 | /** 64 | * 65 | * @param {bigint} objectID 66 | * @param {Object} data 67 | */ 68 | setObjectData(objectID: bigint, data) { 69 | this.#data[Number(objectID)] = data; 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/LU/Managers/ReplicaManagers/RenderManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from '../GenericManager'; 2 | import { Components } from '../../Replica/Components'; 3 | import { SerializationOrder } from '../../Replica/SerializationOrder'; 4 | import { SerializationType } from '../../Replica/SerializationType'; 5 | import GameObject from '../../Replica/Object'; 6 | 7 | export default class RenderManager extends GenericManager { 8 | #data; 9 | 10 | constructor(server) { 11 | super(server); 12 | 13 | this.#data = {}; 14 | 15 | server.eventBus.on('new-object-created', (object: GameObject) => { 16 | if (object.components.hasComponent(Components.RENDER_COMPONENT)) { 17 | this.#data[Number(object.ID)] = { 18 | effects: [] 19 | }; 20 | 21 | object.addSerializer( 22 | SerializationOrder.indexOf(Components.RENDER_COMPONENT), 23 | (type, stream) => { 24 | const data = this.#data[Number(object.ID)]; 25 | 26 | if (type === SerializationType.CREATION) { 27 | stream.writeLong(data.effects.length); 28 | for (let i = 0; i < data.effects.length; i++) { 29 | stream.writeByte(data.effects[i].name.length); 30 | for (let j = 0; j < data.effects[i].name.length; j++) { 31 | stream.writeChar(data.effects[i].name.charCodeAt(j)); 32 | } 33 | stream.writeLong(data.effects[i].effectID); 34 | stream.writeByte(data.effects[i].type.length); 35 | for (let j = 0; j < data.effects[i].type.length; j++) { 36 | stream.writeShort(data.effects[i].type.charCodeAt(j)); 37 | } 38 | stream.writeFloat(data.effects[i].scale); 39 | stream.writeLongLong(data.effects[i].secondary); 40 | } 41 | } 42 | } 43 | ); 44 | } 45 | }); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/LU/Managers/ReplicaManagers/RocketLandingManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from '../GenericManager'; 2 | import { Components } from '../../Replica/Components'; 3 | import { SerializationOrder } from '../../Replica/SerializationOrder'; 4 | 5 | export default class RocketLandingManager extends GenericManager { 6 | #data; 7 | 8 | constructor(server) { 9 | super(server); 10 | 11 | this.#data = {}; 12 | 13 | server.eventBus.on('new-object-created', (object) => { 14 | if (object.components.hasComponent(Components.ROCKET_LANDING_COMPONENT)) { 15 | object.addSerializer( 16 | SerializationOrder.indexOf(Components.ROCKET_LANDING_COMPONENT), 17 | (type, stream) => { 18 | // No serialization for this object, but add a empty one 19 | } 20 | ); 21 | } 22 | }); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/LU/Managers/ReplicaManagers/SkillManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from '../GenericManager'; 2 | import { Components } from '../../Replica/Components'; 3 | import { SerializationOrder } from '../../Replica/SerializationOrder'; 4 | import { SerializationType } from '../../Replica/SerializationType'; 5 | import GameObject from '../../Replica/Object'; 6 | 7 | export default class SkillManager extends GenericManager { 8 | #data; 9 | 10 | constructor(server) { 11 | super(server); 12 | 13 | this.#data = {}; 14 | 15 | server.eventBus.on('new-object-created', (object: GameObject) => { 16 | if (object.components.hasComponent(Components.SKILL_COMPONENT)) { 17 | this.#data[Number(object.ID)] = {}; 18 | 19 | object.addSerializer( 20 | SerializationOrder.indexOf(Components.SKILL_COMPONENT), 21 | (type, stream) => { 22 | // const data = manager._data[object.ID.low]; 23 | if (type === SerializationType.CREATION) { 24 | stream.writeBit(false); 25 | } 26 | } 27 | ); 28 | } 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/LU/Managers/ReplicaManagers/SoundAmbient2DManager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from '../GenericManager'; 2 | import { Components } from '../../Replica/Components'; 3 | import { SerializationOrder } from '../../Replica/SerializationOrder'; 4 | 5 | export default class SoundAmbient2DManager extends GenericManager { 6 | #data; 7 | 8 | constructor(server) { 9 | super(server); 10 | 11 | this.#data = {}; 12 | 13 | server.eventBus.on('new-object-created', (object) => { 14 | if ( 15 | object.components.hasComponent(Components.SOUND_AMBIENT_2D_COMPONENT) 16 | ) { 17 | object.addSerializer( 18 | SerializationOrder.indexOf(Components.SOUND_AMBIENT_2D_COMPONENT), 19 | (type, stream) => { 20 | // This component doesn't have a serialization, so we add a blank one. 21 | } 22 | ); 23 | } 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/LU/Managers/ReplicaManagers/Unknown107Manager.ts: -------------------------------------------------------------------------------- 1 | import GenericManager from '../GenericManager'; 2 | import { Components } from '../../Replica/Components'; 3 | import { SerializationOrder } from '../../Replica/SerializationOrder'; 4 | import GameObject from '../../Replica/Object'; 5 | 6 | export default class Unknown107Manager extends GenericManager { 7 | #data; 8 | 9 | constructor(server) { 10 | super(server); 11 | 12 | this.#data = {}; 13 | 14 | server.eventBus.on('new-object-created', (object: GameObject) => { 15 | if (object.components.hasComponent(Components.UNKNOWN_107_COMPONENT)) { 16 | this.#data[Number(object.ID)] = {}; 17 | 18 | object.addSerializer( 19 | SerializationOrder.indexOf(Components.UNKNOWN_107_COMPONENT), 20 | (type, stream) => { 21 | // const data = manager._data[object.ID.low]; 22 | stream.writeBit(false); 23 | } 24 | ); 25 | } 26 | }); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/LU/Message Types/LUAuthenticationMessageType.ts: -------------------------------------------------------------------------------- 1 | export const LUAuthenticationMessageType = { 2 | MSG_AUTH_LOGIN_REQUEST: 0, 3 | MSG_AUTH_LOGOUT_REQUEST: 1, 4 | MSG_AUTH_CREATE_NEW_ACCOUNT_REQUEST: 2, 5 | MSG_AUTH_LEGOINTERFACE_AUTH_RESPONSE: 3, 6 | MSG_AUTH_SESSIONKEY_RECEIVED_CONFIRM: 4, 7 | MSG_AUTH_RUNTIME_CONFIG: 5, 8 | key: function (value) { 9 | for (const prop in this) { 10 | if (this.hasOwnProperty.call(prop)) { 11 | if (this[prop] === value) return prop; 12 | } 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/LU/Message Types/LUChatMessageType.ts: -------------------------------------------------------------------------------- 1 | export const LUChatMessageType = { 2 | MSG_CHAT_LOGIN_SESSION_NOTIFY: 0x00, 3 | MSG_CHAT_GENERAL_CHAT_MESSAGE: 0x01, 4 | MSG_CHAT_PRIVATE_CHAT_MESSAGE: 0x02, 5 | MSG_CHAT_USER_CHANNEL_CHAT_MESSAGE: 0x03, 6 | MSG_CHAT_WORLD_DISCONNECT_REQUEST: 0x04, 7 | MSG_CHAT_WORLD_PROXIMITY_RESPONSE: 0x05, 8 | MSG_CHAT_WORLD_PARCEL_RESPONSE: 0x06, 9 | MSG_CHAT_ADD_FRIEND_REQUEST: 0x07, 10 | MSG_CHAT_ADD_FRIEND_RESPONSE: 0x08, 11 | MSG_CHAT_REMOVE_FRIEND: 0x09, 12 | MSG_CHAT_GET_FRIENDS_LIST: 0x0a, 13 | MSG_CHAT_ADD_IGNORE: 0x0b, 14 | MSG_CHAT_REMOVE_IGNORE: 0x0c, 15 | MSG_CHAT_GET_IGNORE_LIST: 0x0d, 16 | MSG_CHAT_TEAM_MISSED_INVITE_CHECK: 0x0e, 17 | MSG_CHAT_TEAM_INVITE: 0x0f, 18 | MSG_CHAT_TEAM_INVITE_RESPONSE: 0x10, 19 | MSG_CHAT_TEAM_KICK: 0x11, 20 | MSG_CHAT_TEAM_LEAVE: 0x12, 21 | MSG_CHAT_TEAM_SET_LOOT: 0x13, 22 | MSG_CHAT_TEAM_SET_LEADER: 0x14, 23 | MSG_CHAT_TEAM_GET_STATUS: 0x15, 24 | MSG_CHAT_GUILD_CREATE: 0x16, 25 | MSG_CHAT_GUILD_INVITE: 0x17, 26 | MSG_CHAT_GUILD_INVITE_RESPONSE: 0x18, 27 | MSG_CHAT_GUILD_LEAVE: 0x19, 28 | MSG_CHAT_GUILD_KICK: 0x1a, 29 | MSG_CHAT_GUILD_GET_STATUS: 0x1b, 30 | MSG_CHAT_GUILD_GET_ALL: 0x1c, 31 | MSG_CHAT_SHOW_ALL: 0x1d, 32 | MSG_CHAT_BLUEPRINT_MODERATED: 0x1e, 33 | MSG_CHAT_BLUEPRINT_MODEL_READY: 0x1f, 34 | MSG_CHAT_PROPERTY_READY_FOR_APPROVAL: 0x20, 35 | MSG_CHAT_PROPERTY_MODERATION_CHANGED: 0x21, 36 | MSG_CHAT_PROPERTY_BUILDMODE_CHANGEd: 0x22, 37 | MSG_CHAT_PROPERTY_BUILDMODE_CHANGED_REPORT: 0x23, 38 | MSG_CHAT_MAIL: 0x24, 39 | MSG_CHAT_WORLD_INSTANCE_LOCATION_REQUEST: 0x25, 40 | MSG_CHAT_REPUTATION_UPDATE: 0x26, 41 | MSG_CHAT_SEND_CANNED_TEXT: 0x27, 42 | MSG_CHAT_GMLEVEL_UPDATE: 0x28, 43 | MSG_CHAT_CHARACTER_NAME_CHANGE_REQUEST: 0x29, 44 | MSG_CHAT_CSR_REQUEST: 0x2a, 45 | MSG_CHAT_CSR_REPLY: 0x2b, 46 | MSG_CHAT_GM_KICK: 0x2c, 47 | MSG_CHAT_GM_ANNOUNCE: 0x2d, 48 | MSG_CHAT_GM_MUTE: 0x2e, 49 | MSG_CHAT_ACTIVITY_UPDATE: 0x2f, 50 | MSG_CHAT_WORLD_ROUTE_PACKET: 0x30, 51 | MSG_CHAT_GET_ZONE_POPULATIONS: 0x31, 52 | MSG_CHAT_REQUEST_MINIMUM_CHAT_MODE: 0x32, 53 | MSG_CHAT_REQUEST_MINIMUM_CHAT_MODE_PRIVATE: 0x33, 54 | MSG_CHAT_MATCH_REQUEST: 0x34, 55 | MSG_CHAT_UGCMANIFEST_REPORT_MISSING_FILE: 0x35, 56 | MSG_CHAT_UGCMANIFEST_REPORT_DONE_FILE: 0x36, 57 | MSG_CHAT_UGCMANIFEST_REPORT_DONE_BLUEPRINT: 0x37, 58 | MSG_CHAT_UGCC_REQUEST: 0x38, 59 | MSG_CHAT_WHO: 0x39, 60 | MSG_CHAT_WORLD_PLAYERS_PET_MODERATED_ACKNOWLEDGE: 0x3a, 61 | MSG_CHAT_ACHIEVEMENT_NOTIFY: 0x3b, 62 | MSG_CHAT_GM_CLOSE_PRIVATE_CHAT_WINDOW: 0x3c, 63 | MSG_CHAT_UNEXPECTED_DISCONNECT: 0x3d, 64 | MSG_CHAT_PLAYER_READY: 0x3e, 65 | MSG_CHAT_GET_DONATION_TOTAL: 0x3f, 66 | MSG_CHAT_UPDATE_DONATION: 0x40, 67 | MSG_CHAT_PRG_CSR_COMMAND: 0x41, 68 | MSG_CHAT_HEARTBEAT_REQUEST_FROM_WORLD: 0x42, 69 | MSG_CHAT_UPDATE_FREE_TRIAL_STATUS: 0x43, 70 | key: function (value) { 71 | for (const prop in this) { 72 | if (this.hasOwnProperty.call(prop)) { 73 | if (this[prop] === value) return prop; 74 | } 75 | } 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /src/LU/Message Types/LUClientMessageType.ts: -------------------------------------------------------------------------------- 1 | export const LUClientMessageType = { 2 | LOGIN_RESPONSE: 0x00, 3 | LOGOUT_RESPONSE: 0x01, 4 | LOAD_STATIC_ZONE: 0x02, 5 | CREATE_OBJECT: 0x03, 6 | CREATE_CHARACTER: 0x04, 7 | CREATE_CHARACTER_EXTENDED: 0x05, 8 | CHARACTER_LIST_RESPONSE: 0x06, 9 | CHARACTER_CREATE_RESPONSE: 0x07, 10 | CHARACTER_RENAME_RESPONSE: 0x08, 11 | CHAT_CONNECT_RESPONSE: 0x09, 12 | AUTH_ACCOUNT_CREATE_RESPONSE: 0x0a, 13 | DELETE_CHARACTER_RESPONSE: 0x0b, 14 | GAME_MSG: 0x0c, 15 | CONNECT_CHAT: 0x0d, 16 | TRANSFER_TO_WORLD: 0x0e, 17 | IMPENDING_RELOAD_NOTIFY: 0x0f, 18 | MAKE_GM_RESPONSE: 0x10, 19 | HTTP_MONITOR_INFO_RESPONSE: 0x11, 20 | SLASH_PUSH_MAP_RESPONSE: 0x12, 21 | SLASH_PULL_MAP_RESPONSE: 0x13, 22 | SLASH_LOCK_MAP_RESPONSE: 0x14, 23 | BLUEPRINT_SAVE_RESPONSE: 0x15, 24 | BLUEPRINT_LUP_SAVE_RESPONSE: 0x16, 25 | BLUEPRINT_LOAD_RESPONSE_ITEMID: 0x17, 26 | BLUEPRINT_GET_ALL_DATA_RESPONSE: 0x18, 27 | MODEL_INSTANTIATE_RESPONSE: 0x19, 28 | DEBUG_OUTPUT: 0x1a, 29 | ADD_FRIEND_REQUEST: 0x1b, 30 | ADD_FRIEND_RESPONSE: 0x1c, 31 | REMOVE_FRIEND_RESPONSE: 0x1d, 32 | GET_FRIENDS_LIST_RESPONSE: 0x1e, 33 | UPDATE_FRIEND_NOTIFY: 0x1f, 34 | ADD_IGNORE_RESPONSE: 0x20, 35 | REMOVE_IGNORE_RESPONSE: 0x21, 36 | GET_IGNORE_LIST_RESPONSE: 0x22, 37 | TEAM_INVITE: 0x23, 38 | TEAM_INVITE_INITIAL_RESPONSE: 0x24, 39 | GUILD_CREATE_RESPONSE: 0x25, 40 | GUILD_GET_STATUS_RESPONSE: 0x26, 41 | GUILD_INVITE: 0x27, 42 | GUILD_INVITE_INITIAL_RESPONSE: 0x28, 43 | GUILD_INVITE_FINAL_RESPONSE: 0x29, 44 | GUILD_INVITE_CONFIRM: 0x2a, 45 | GUILD_ADD_PLAYER: 0x2b, 46 | GUILD_REMOVE_PLAYER: 0x2c, 47 | GUILD_LOGIN_LOGOUT: 0x2d, 48 | GUILD_RANK_CHANGE: 0x2e, 49 | GUILD_DATA: 0x2f, 50 | GUILD_STATUS: 0x30, 51 | MAIL: 0x31, 52 | DB_PROXY_RESULT: 0x32, 53 | SHOW_ALL_RESPONSE: 0x33, 54 | WHO_RESPONSE: 0x34, 55 | SEND_CANNED_TEXT: 0x35, 56 | UPDATE_CHARACTER_NAME: 0x36, 57 | SET_NETWORK_SIMULATOR: 0x37, 58 | INVALID_CHAT_MESSAGE: 0x38, 59 | MINIMUM_CHAT_MODE_RESPONSE: 0x39, 60 | MINIMUM_CHAT_MODE_RESPONSE_PRIVATE: 0x3a, 61 | CHAT_MODERATION_STRING: 0x3b, 62 | UGC_MANIFEST_RESPONSE: 0x3c, 63 | IN_LOGIN_QUEUE: 0x3d, 64 | SERVER_STATES: 0x3e, 65 | GM_CLOSE_TARGET_CHAT_WINDOW: 0x3f, 66 | GENERAL_TEXT_FOR_LOCALIZATION: 0x40, 67 | UPDATE_FREE_TRIAL_STATUS: 0x41, 68 | key: function (value) { 69 | for (const prop in this) { 70 | if (this.hasOwnProperty.call(prop)) { 71 | if (this[prop] === value) return prop; 72 | } 73 | } 74 | } 75 | }; 76 | -------------------------------------------------------------------------------- /src/LU/Message Types/LUGeneralMessageType.ts: -------------------------------------------------------------------------------- 1 | export const LUGeneralMessageType = { 2 | MSG_SERVER_VERSION_CONFIRM: 0, 3 | MSG_SERVER_DISCONNECT_NOTIFY: 1, 4 | MSG_SERVER_GENERAL_NOTIFY: 2, 5 | key: function (value) { 6 | for (const prop in this) { 7 | if (this.hasOwnProperty.call(prop)) { 8 | if (this[prop] === value) return prop; 9 | } 10 | } 11 | } 12 | }; 13 | -------------------------------------------------------------------------------- /src/LU/Message Types/LURemoteConnectionType.ts: -------------------------------------------------------------------------------- 1 | export const LURemoteConnectionType = { 2 | general: 0, 3 | authentication: 1, 4 | chat: 2, 5 | internal: 3, 6 | server: 4, 7 | client: 5, 8 | key: function (value) { 9 | for (const prop in this) { 10 | if (this.hasOwnProperty.call(prop)) { 11 | if (this[prop] === value) return prop; 12 | } 13 | } 14 | } 15 | }; 16 | -------------------------------------------------------------------------------- /src/LU/Message Types/LUServerMessageType.ts: -------------------------------------------------------------------------------- 1 | export const LUServerMessageType = { 2 | MSG_WORLD_CLIENT_VALIDATION: 0x01, 3 | MSG_WORLD_CLIENT_CHARACTER_LIST_REQUEST: 0x02, 4 | MSG_WORLD_CLIENT_CHARACTER_CREATE_REQUEST: 0x03, 5 | MSG_WORLD_CLIENT_LOGIN_REQUEST: 0x04, 6 | MSG_WORLD_CLIENT_GAME_MSG: 0x05, 7 | MSG_WORLD_CLIENT_CHARACTER_DELETE_REQUEST: 0x06, 8 | MSG_WORLD_CLIENT_CHARACTER_RENAME_REQUEST: 0x07, 9 | MSG_WORLD_CLIENT_HAPPY_FLOWER_MODE_NOTIFY: 0x08, 10 | MSG_WORLD_CLIENT_SLASH_RELOAD_MAP: 0x09, 11 | MSG_WORLD_CLIENT_SLASH_PUSH_MAP_REQUEST: 0x0a, 12 | MSG_WORLD_CLIENT_SLASH_PUSH_MAP: 0x0b, 13 | MSG_WORLD_CLIENT_SLASH_PULL_MAP: 0x0c, 14 | MSG_WORLD_CLIENT_LOCK_MAP_REQUEST: 0x0d, 15 | MSG_WORLD_CLIENT_GENERAL_CHAT_MESSAGE: 0x0e, 16 | MSG_WORLD_CLIENT_HTTP_MONITOR_INFO_REQUEST: 0x0f, 17 | MSG_WORLD_CLIENT_SLASH_DEBUG_SCRIPTS: 0x10, 18 | MSG_WORLD_CLIENT_MODELS_CLEAR: 0x11, 19 | MSG_WORLD_CLIENT_EXHIBIT_INSERT_MODEL: 0x12, 20 | MSG_WORLD_CLIENT_LEVEL_LOAD_COMPLETE: 0x13, 21 | MSG_WORLD_CLIENT_TMP_GUILD_CREATE: 0x14, 22 | MSG_WORLD_CLIENT_ROUTE_PACKET: 0x15, 23 | MSG_WORLD_CLIENT_POSITION_UPDATE: 0x16, 24 | MSG_WORLD_CLIENT_MAIL: 0x17, 25 | MSG_WORLD_CLIENT_WORD_CHECK: 0x18, 26 | MSG_WORLD_CLIENT_STRING_CHECK: 0x19, 27 | MSG_WORLD_CLIENT_GET_PLAYERS_IN_ZONE: 0x1a, 28 | MSG_WORLD_CLIENT_REQUEST_UGC_MANIFEST_INFO: 0x1b, 29 | MSG_WORLD_CLIENT_BLUEPRINT_GET_ALL_DATA_REQUEST: 0x1c, 30 | MSG_WORLD_CLIENT_CANCEL_MAP_QUEUE: 0x1d, 31 | MSG_WORLD_CLIENT_HANDLE_FUNNESS: 0x1e, 32 | MSG_WORLD_CLIENT_FAKE_PRG_CSR_MESSAGE: 0x1f, 33 | MSG_WORLD_CLIENT_REQUEST_FREE_TRIAL_REFRESH: 0x20, 34 | MSG_WORLD_CLIENT_GM_SET_FREE_TRIAL_STATUS: 0x21, 35 | MSG_WORLD_CLIENT_TOP_FIVE_ISSUES_REQUEST: 0x22, 36 | MSG_WORLD_CLIENT_MAYBE_UGC_DOWNLOAD_FAILED: 0x23, 37 | MSG_WORLD_CLIENT_UGC_DOWNLOAD_FAILED: 0x78, 38 | key: function (value) { 39 | for (const prop in this) { 40 | if (this.hasOwnProperty.call(prop)) { 41 | if (this[prop] === value) return prop; 42 | } 43 | } 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /src/LU/Message.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | 3 | export default abstract class Message { 4 | abstract serialize(stream: BitStream): void; 5 | 6 | abstract deserialize(stream: BitStream): void; 7 | } 8 | -------------------------------------------------------------------------------- /src/LU/Messages/DisconnectNotify.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | import Message from '../Message'; 3 | 4 | export const DisconnectNotifyReason = { 5 | UNKNOWN_SERVER_ERROR: 0x00, 6 | DUPLICATE_LOGIN: 0x04, 7 | SERVER_SHUTDOWN: 0x05, 8 | SERVER_UNABLE_TO_LOAD_MAP: 0x06, 9 | INVALID_SESSION_KEY: 0x07, 10 | ACCOUNT_NOT_PENDING: 0x08, 11 | CHARACTER_NOT_FOUND: 0x09, 12 | CORRUPT_CHARACTER: 0xa, 13 | KICK: 0x0b, 14 | FREE_TRIAL_EXPIRED: 0x0d, 15 | OUT_OF_PLAY_TIME: 0x0e 16 | }; 17 | 18 | export class DisconnectNotify extends Message { 19 | reason: number; 20 | 21 | constructor() { 22 | super(); 23 | this.reason = DisconnectNotifyReason.UNKNOWN_SERVER_ERROR; 24 | } 25 | 26 | deserialize(stream: BitStream): void { 27 | this.reason = stream.readLong(); 28 | } 29 | 30 | serialize(stream: BitStream): void { 31 | stream.writeLong(this.reason); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/LU/Messages/LoadStaticZone.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | import Message from '../Message'; 3 | import Vector3f from '../../geometry/Vector3f'; 4 | 5 | const Checksum = { 6 | 1000: 548931708, 7 | 1001: 644352572, 8 | 1100: 1230132497, 9 | 1101: 1401033954, 10 | 1102: 265552858, 11 | 1150: 265552858, 12 | 1151: 176751363, 13 | 1200: 3659426608, 14 | 1201: 1198396208, 15 | 1203: 284951810, 16 | 1204: 131334744, 17 | 1250: 93127057, 18 | 1251: 156173405, 19 | 1260: 404031453, 20 | 1300: 317375120, 21 | 1302: 192348911, 22 | 1303: 355338122, 23 | 1350: 79036764, 24 | 1400: 2233038349, 25 | 1402: 49611143, 26 | 1403: 2172981070, 27 | 1450: 66060582, 28 | 1600: 130155246, 29 | 1601: 36831494, 30 | 1602: 127075199, 31 | 1603: 70975917, 32 | 1604: 404031453, 33 | 1700: 33816888, 34 | 1800: 1259840409, 35 | 1900: 2655712316, 36 | 2000: 1298738292, 37 | 2001: 166396143 38 | }; 39 | 40 | export class LoadStaticZone extends Message { 41 | zoneID: number; 42 | instanceID: number; 43 | cloneID: number; 44 | unknown1: number; 45 | location: Vector3f; 46 | worldState: number; 47 | checksum: number; 48 | 49 | constructor() { 50 | super(); 51 | this.zoneID = 0; 52 | this.instanceID = 0; 53 | this.cloneID = 0; 54 | this.unknown1 = 0; 55 | this.location = new Vector3f(); 56 | this.worldState = 0; 57 | this.checksum = 0; 58 | } 59 | 60 | deserialize(stream: BitStream): void { 61 | this.zoneID = stream.readShort(); 62 | this.cloneID = stream.readShort(); 63 | this.instanceID = stream.readLong(); 64 | this.checksum = stream.readLong(); 65 | this.unknown1 = stream.readShort(); 66 | this.location.x = stream.readFloat(); 67 | this.location.y = stream.readFloat(); 68 | this.location.z = stream.readFloat(); 69 | this.worldState = stream.readLong(); 70 | } 71 | 72 | serialize(stream: BitStream): void { 73 | stream.writeShort(this.zoneID); 74 | stream.writeShort(this.instanceID); 75 | stream.writeLong(this.instanceID); 76 | stream.writeLong(Checksum[this.zoneID]); 77 | stream.writeShort(this.unknown1); 78 | stream.writeFloat(this.location.x); 79 | stream.writeFloat(this.location.y); 80 | stream.writeFloat(this.location.z); 81 | stream.writeLong(this.worldState); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/LU/Messages/LoginInfo.ts: -------------------------------------------------------------------------------- 1 | import Message from '../Message'; 2 | import BitStream from 'node-raknet/structures/BitStream'; 3 | 4 | export const LoginCodes = { 5 | failedOne: 0, 6 | success: 1, 7 | banned: 2, 8 | failedTwo: 3, 9 | failedThree: 4, 10 | badPermissions: 5, 11 | badPassword: 6, 12 | accountLocked: 7, 13 | badUsername: 8, 14 | activationPending: 9, 15 | accountDisabled: 10, 16 | noMoreGameTime: 11, 17 | freeTrialEnded: 12, 18 | playScheduleNotAllowed: 13, 19 | accountNotActivated: 14 20 | }; 21 | 22 | class Stamp { 23 | id: number; 24 | num1: number; 25 | num2: number; 26 | num3: number; 27 | } 28 | 29 | export class LoginInfo extends Message { 30 | code: number; 31 | password: string; 32 | string: string; 33 | clientVersionMajor: number; 34 | clientVersionCurrent: number; 35 | clientVersionMinor: number; 36 | session: string; 37 | redirectIP: string; 38 | chatIP: string; 39 | redirectPort: number; 40 | chatPort: number; 41 | altIP: string; 42 | guid: string; 43 | unknown: number; 44 | localization: string; 45 | firstSubscription: boolean; 46 | freeToPlay: boolean; 47 | unknown2: number; 48 | customErrorMessage: string; 49 | stamps: Array; 50 | 51 | constructor() { 52 | super(); 53 | this.code = 0; 54 | this.password = 'Talk_Like_A_Pirate'; 55 | this.string = ''; 56 | this.clientVersionMajor = 0; 57 | this.clientVersionCurrent = 0; 58 | this.clientVersionMinor = 0; 59 | this.session = ''; 60 | this.redirectIP = ''; 61 | this.chatIP = ''; 62 | this.redirectPort = 0; 63 | this.chatPort = 0; 64 | this.altIP = ''; 65 | this.guid = '00000000-0000-0000-0000-000000000000'; 66 | this.unknown = 0; 67 | this.localization = ''; 68 | this.firstSubscription = false; 69 | this.freeToPlay = false; 70 | this.unknown2 = 0; 71 | this.customErrorMessage = ''; 72 | this.stamps = []; 73 | } 74 | 75 | /** 76 | * 77 | * @param {BitStream} stream 78 | */ 79 | deserialize(stream) { 80 | this.code = stream.readByte(); 81 | this.password = stream.readString(); 82 | 83 | this.string = stream.readString(); 84 | this.string = stream.readString(); 85 | this.string = stream.readString(); 86 | this.string = stream.readString(); 87 | this.string = stream.readString(); 88 | this.string = stream.readString(); 89 | this.string = stream.readString(); 90 | 91 | this.clientVersionMajor = stream.readShort(); 92 | this.clientVersionCurrent = stream.readShort(); 93 | this.clientVersionMinor = stream.readShort(); 94 | 95 | this.session = stream.readWString(); 96 | this.redirectIP = stream.readString(); 97 | this.chatIP = stream.readString(); 98 | this.redirectPort = stream.readShort(); 99 | this.chatPort = stream.readShort(); 100 | this.altIP = stream.readString(); 101 | this.guid = stream.readString(); 102 | this.unknown = stream.readLong(); 103 | this.localization = stream.readString(); 104 | this.firstSubscription = stream.readByte(); 105 | this.freeToPlay = stream.readByte(); 106 | this.unknown2 = stream.readLongLong(); 107 | const customErrorMessageLength = stream.readShort(); 108 | this.customErrorMessage = stream.readWString(customErrorMessageLength); 109 | const stampsLength = stream.readLong(); 110 | for (let i = 0; i < stampsLength; i++) { 111 | // push back stamps to this.stamps 112 | } 113 | } 114 | 115 | /** 116 | * 117 | * @param {BitStream} stream 118 | */ 119 | serialize(stream) { 120 | stream.writeByte(this.code); 121 | stream.writeString(this.password); 122 | 123 | stream.writeString(this.string); 124 | stream.writeString(this.string); 125 | stream.writeString(this.string); 126 | stream.writeString(this.string); 127 | stream.writeString(this.string); 128 | stream.writeString(this.string); 129 | stream.writeString(this.string); 130 | 131 | stream.writeShort(this.clientVersionMajor); 132 | stream.writeShort(this.clientVersionCurrent); 133 | stream.writeShort(this.clientVersionMinor); 134 | 135 | stream.writeWString(this.session); 136 | 137 | stream.writeString(this.redirectIP); 138 | stream.writeString(this.chatIP); 139 | 140 | stream.writeShort(this.redirectPort); 141 | stream.writeShort(this.chatPort); 142 | 143 | stream.writeString(this.altIP); 144 | 145 | stream.writeString(this.guid, 37); 146 | 147 | stream.writeLong(this.unknown); 148 | stream.writeString(this.localization, 3); 149 | stream.writeByte(this.firstSubscription ? 1 : 0); 150 | stream.writeByte(this.freeToPlay ? 1 : 0); 151 | stream.writeLongLong(this.unknown2); 152 | stream.writeShort(this.customErrorMessage.length); 153 | stream.writeWString( 154 | this.customErrorMessage, 155 | this.customErrorMessage.length 156 | ); 157 | stream.writeLong(this.stamps.length * 16 + 4); 158 | for (let i = 0; i < this.stamps.length; i++) { 159 | stream.writeLong(this.stamps[i].id); 160 | stream.writeLong(this.stamps[i].num1); 161 | stream.writeLong(this.stamps[i].num2); 162 | stream.writeLong(this.stamps[i].num3); 163 | } 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /src/LU/Messages/MinifigCreateRequest.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | import Message from '../Message'; 3 | 4 | export class MinifigCreateRequest extends Message { 5 | name: string; 6 | firstName: number; 7 | middleName: number; 8 | lastName: number; 9 | unknown1: string; 10 | shirtColor: number; 11 | shirtStyle: number; 12 | pantsColor: number; 13 | hairStyle: number; 14 | hairColor: number; 15 | lh: number; 16 | rh: number; 17 | eyebrows: number; 18 | eyes: number; 19 | mouth: number; 20 | unknown2: number; 21 | 22 | constructor() { 23 | super(); 24 | this.name = ''; 25 | this.firstName = 0; 26 | this.middleName = 0; 27 | this.lastName = 0; 28 | this.unknown1 = ''; 29 | this.shirtColor = 0; 30 | this.shirtStyle = 0; 31 | this.pantsColor = 0; 32 | this.hairStyle = 0; 33 | this.hairColor = 0; 34 | this.lh = 0; 35 | this.rh = 0; 36 | this.eyebrows = 0; 37 | this.eyes = 0; 38 | this.mouth = 0; 39 | this.unknown2 = 0; 40 | } 41 | 42 | deserialize(stream: BitStream): void { 43 | this.name = stream.readWString(); 44 | this.firstName = stream.readLong(); 45 | this.middleName = stream.readLong(); 46 | this.lastName = stream.readLong(); 47 | this.unknown1 = stream.readString(9); 48 | this.shirtColor = stream.readLong(); 49 | this.shirtStyle = stream.readLong(); 50 | this.pantsColor = stream.readLong(); 51 | this.hairStyle = stream.readLong(); 52 | this.hairColor = stream.readLong(); 53 | this.lh = stream.readLong(); 54 | this.rh = stream.readLong(); 55 | this.eyebrows = stream.readLong(); 56 | this.eyes = stream.readLong(); 57 | this.mouth = stream.readLong(); 58 | this.unknown2 = stream.readByte(); 59 | } 60 | 61 | serialize(stream: BitStream): void { 62 | stream.writeWString(this.name); 63 | stream.writeLong(this.firstName); 64 | stream.writeLong(this.middleName); 65 | stream.writeLong(this.lastName); 66 | stream.writeString(this.unknown1, 9); 67 | stream.writeLong(this.shirtColor); 68 | stream.writeLong(this.shirtStyle); 69 | stream.writeLong(this.pantsColor); 70 | stream.writeLong(this.hairStyle); 71 | stream.writeLong(this.hairColor); 72 | stream.writeLong(this.lh); 73 | stream.writeLong(this.rh); 74 | stream.writeLong(this.eyebrows); 75 | stream.writeLong(this.eyes); 76 | stream.writeLong(this.mouth); 77 | stream.writeByte(this.unknown2); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/LU/Messages/MinifigCreateResponse.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | import Message from '../Message'; 3 | 4 | export const CreationResponse = { 5 | SUCCESS: 0, 6 | DOES_NOT_WORK: 1, 7 | NAME_NOT_ALLOWED: 2, 8 | PREDEFINED_NAME_IN_USE: 3, 9 | CUSTOM_NAME_IN_USE: 4 10 | }; 11 | 12 | export class MinifigCreateResponse extends Message { 13 | id: number; 14 | 15 | constructor() { 16 | super(); 17 | this.id = CreationResponse.NAME_NOT_ALLOWED; 18 | } 19 | 20 | deserialize(stream: BitStream): void { 21 | this.id = stream.readByte(); 22 | } 23 | 24 | serialize(stream: BitStream): void { 25 | stream.writeByte(this.id); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/LU/Messages/MinifigDeleteResponse.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | import Message from '../Message'; 3 | 4 | export const DeletionResponse = { 5 | SUCCESS: 1 6 | }; 7 | 8 | export class MinifigDeleteResponse extends Message { 9 | id: number; 10 | 11 | constructor() { 12 | super(); 13 | this.id = DeletionResponse.SUCCESS; 14 | } 15 | 16 | deserialize(stream: BitStream): void { 17 | this.id = stream.readByte(); 18 | } 19 | 20 | serialize(stream: BitStream): void { 21 | stream.writeByte(this.id); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/LU/Messages/MinifigList.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | import Message from '../Message'; 3 | 4 | class Character { 5 | id: bigint; 6 | unknown1: number; 7 | name: string; 8 | unapprovedName: string; 9 | nameRejected: number; 10 | freeToPlay: number; 11 | unknown2: string; 12 | shirtColor: number; 13 | shirtStyle: number; 14 | pantsColor: number; 15 | hairStyle: number; 16 | hairColor: number; 17 | lh: number; 18 | rh: number; 19 | eyebrows: number; 20 | eyes: number; 21 | mouth: number; 22 | unknown3: number; 23 | zone: number; 24 | instance: number; 25 | clone: number; 26 | last_log: bigint; 27 | items: Array; 28 | } 29 | 30 | export class MinifigList extends Message { 31 | characters; 32 | front: number; 33 | 34 | constructor() { 35 | super(); 36 | this.characters = []; 37 | this.front = 0; 38 | } 39 | 40 | deserialize(stream: BitStream): void { 41 | const num = stream.readByte(); 42 | this.front = stream.readByte(); 43 | for (let i = 0; i < num; i++) { 44 | const character = new Character(); 45 | character.id = stream.readLongLong(); 46 | character.unknown1 = stream.readLong(); 47 | character.name = stream.readWString(); 48 | character.unapprovedName = stream.readWString(); 49 | character.nameRejected = stream.readByte(); 50 | character.freeToPlay = stream.readByte(); 51 | character.unknown2 = stream.readString(10); 52 | character.shirtColor = stream.readLong(); 53 | character.shirtStyle = stream.readLong(); 54 | character.pantsColor = stream.readLong(); 55 | character.hairStyle = stream.readLong(); 56 | character.hairColor = stream.readLong(); 57 | character.lh = stream.readLong(); 58 | character.rh = stream.readLong(); 59 | character.eyebrows = stream.readLong(); 60 | character.eyes = stream.readLong(); 61 | character.mouth = stream.readLong(); 62 | character.unknown3 = stream.readLong(); 63 | character.zone = stream.readShort(); 64 | character.instance = stream.readShort(); 65 | character.clone = stream.readLong(); 66 | character.last_log = stream.readLongLong(); 67 | character.items = []; 68 | const numItems = stream.readShort(); 69 | for (let j = 0; j < numItems; j++) { 70 | character.items.push(stream.readLong()); 71 | } 72 | } 73 | } 74 | 75 | serialize(stream: BitStream): void { 76 | stream.writeByte(this.characters.length); 77 | stream.writeByte(this.front); // TODO: This needs to be the index of the last used character 78 | for (let i = 0; i < this.characters.length; i++) { 79 | const character = this.characters[i]; 80 | character.id.serialize(stream); 81 | stream.writeLong(character.unknown1); 82 | stream.writeWString(character.name); 83 | stream.writeWString(character.unapprovedName); 84 | stream.writeByte(character.nameRejected); 85 | stream.writeByte(character.freeToPlay); 86 | stream.writeString(character.unknown2, 10); 87 | stream.writeLong(character.shirtColor); // Works 88 | stream.writeLong(character.shirtStyle); 89 | stream.writeLong(character.pantsColor); // Works 90 | stream.writeLong(character.hairStyle); // Works 91 | stream.writeLong(character.hairColor); // Works 92 | stream.writeLong(character.lh); 93 | stream.writeLong(character.rh); 94 | stream.writeLong(character.eyebrows); // Works 95 | stream.writeLong(character.eyes); // Works 96 | stream.writeLong(character.mouth); // Doesn't 97 | stream.writeLong(character.unknown3); 98 | stream.writeShort(character.zone); 99 | stream.writeShort(character.instance); 100 | stream.writeLong(character.clone); 101 | stream.writeLongLong(character.last_log); 102 | stream.writeShort(character.items.length); 103 | for (let j = 0; j < character.items.length; j++) { 104 | stream.writeLong(character.items[j]); 105 | } 106 | } 107 | } 108 | } 109 | 110 | module.exports = MinifigList; 111 | -------------------------------------------------------------------------------- /src/LU/Messages/TransferToWorld.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | import Message from '../Message'; 3 | 4 | export default class TransferToWorld extends Message { 5 | ip: string; 6 | port: number; 7 | mythranShift: boolean; 8 | 9 | constructor() { 10 | super(); 11 | this.ip = ''; 12 | this.port = 0; 13 | this.mythranShift = false; 14 | } 15 | 16 | deserialize(stream: BitStream): void { 17 | this.ip = stream.readString(); 18 | this.port = stream.readShort(); 19 | this.mythranShift = stream.readBoolean(); 20 | } 21 | 22 | serialize(stream: BitStream): void { 23 | stream.writeString(this.ip); 24 | stream.writeShort(this.port); 25 | stream.writeBoolean(this.mythranShift); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/LU/Messages/UserSessionInfo.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | import Message from '../Message'; 3 | 4 | export class UserSessionInfo extends Message { 5 | username: string; 6 | key: string; 7 | hash: string; 8 | 9 | constructor() { 10 | super(); 11 | this.username = undefined; 12 | this.key = undefined; 13 | this.hash = undefined; 14 | } 15 | 16 | deserialize(stream: BitStream): void { 17 | this.username = stream.readWString(); 18 | this.key = stream.readWString(); 19 | this.hash = stream.readString(); 20 | } 21 | 22 | serialize(stream: BitStream): void { 23 | stream.writeWString(this.username); 24 | stream.writeWString(this.key); 25 | stream.writeString(this.hash); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/LU/Messages/VersionConfirm.ts: -------------------------------------------------------------------------------- 1 | import BitStream from 'node-raknet/structures/BitStream'; 2 | import Message from '../Message'; 3 | 4 | class VersionConfirm extends Message { 5 | version: number; 6 | unknown: number; 7 | remoteConnectionType: number; 8 | processID: number; 9 | localPort: number; 10 | localIP: string; 11 | 12 | constructor() { 13 | super(); 14 | this.version = undefined; 15 | this.unknown = undefined; 16 | this.remoteConnectionType = undefined; 17 | this.processID = undefined; 18 | this.localPort = undefined; 19 | this.localIP = undefined; 20 | } 21 | 22 | deserialize(stream: BitStream): void { 23 | this.version = stream.readLong(); 24 | this.unknown = stream.readLong(); 25 | this.remoteConnectionType = stream.readLong(); 26 | this.processID = stream.readLong(); 27 | this.localPort = stream.readShort(); 28 | this.localIP = stream.readString(); 29 | } 30 | 31 | serialize(stream: BitStream): void { 32 | stream.writeLong(this.version); 33 | stream.writeLong(this.unknown); 34 | stream.writeLong(this.remoteConnectionType); 35 | stream.writeLong(this.processID); 36 | stream.writeShort(this.localPort); 37 | stream.writeString(this.localIP); 38 | } 39 | } 40 | 41 | module.exports = VersionConfirm; 42 | -------------------------------------------------------------------------------- /src/LU/Replica/ComponentMask.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * A way to keep track of components for an object 3 | */ 4 | export default class ComponentMask { 5 | #array: Array; 6 | 7 | constructor() { 8 | this.#array = Array(116).fill(false); 9 | } 10 | 11 | /** 12 | * Adds a component to an object 13 | * @param {number} component 14 | */ 15 | addComponent(component: number) { 16 | this.#array[component] = true; 17 | } 18 | 19 | /** 20 | * Remove a component from an object 21 | * @param {Number} component 22 | */ 23 | removeComponent(component: number) { 24 | this.#array[component] = false; 25 | } 26 | 27 | /** 28 | * 29 | * @param {Number}component 30 | * @return {Boolean} 31 | */ 32 | hasComponent(component: number) { 33 | return this.#array[component]; 34 | } 35 | 36 | /** 37 | * Get list of components for an object 38 | * @return {Array} 39 | */ 40 | getComponentsList() { 41 | const list = []; 42 | this.#array.forEach((e, i) => { 43 | if (e) list.push(i); 44 | }); 45 | return list; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/LU/Replica/Components.ts: -------------------------------------------------------------------------------- 1 | export const Components = { 2 | CONTROLABLE_PHYSICS_COMPONENT: 1, 3 | RENDER_COMPONENT: 2, 4 | SIMPLE_PHYSICS_COMPONENT: 3, 5 | CHARACTER_COMPONENT: 4, // only two in table 6 | SCRIPT_COMPONENT: 5, 7 | BOUNCER_COMPONENT: 6, 8 | DESTRUCTABLE_COMPONENT: 7, 9 | 10 | SKILL_COMPONENT: 9, 11 | SPAWNER_COMPONENT: 10, // used for LOT 176, a spawner 12 | ITEM_COMPONENT: 11, // used for quite a few objects, they vary too much to determine what it is 13 | MODULAR_BUILD_COMPONENT: 12, // This is for modular building areas, rocket bays and car bays 14 | REBUILD_START_COMPONENT: 13, // Rebuildables and Thinking hat has this for some reason 15 | REBUILD_ACTIVATOR_COMPONENT: 14, // only one in table, but no object matches that id 16 | ICON_ONLY_RENDER_COMPONENT: 15, // only one in table, but no object matches that id 17 | VENDOR_COMPONENT: 16, 18 | INVENTORY_COMPONENT: 17, 19 | PROJECTILE_PHYSICS_COMPONENT: 18, // this is used by shooting gallery objects 20 | SHOOTING_GALLERY_COMPONENT: 19, // cannon component? Is used by cannon objects 21 | RIGID_BODY_PHANTOM_PHYSICS_COMPONENT: 20, // Is used by objects in racing 22 | 23 | CHEST_COMPONENT: 22, // Only used by a treasure chest 24 | COLLECTIBLE_COMPONENT: 23, // used by collectable spawners 25 | BLUEPRINT_COMPONENT: 24, // used in showing a model in the inventory 26 | MOVING_PLATFORM_COMPONENT: 25, // Is used by moving platforms, could be a moving platform component 27 | PET_COMPONENT: 26, 28 | PLATFORM_BOUNDRY_COMPONENT: 27, // another moving platform component, potentially 29 | MODULE_COMPONENT: 28, // Modular Component? All the objects are pieces to rockets, etc. 30 | JET_PACK_PAD_COMPONENT: 29, // JetpackComponent? All objects using this have to do with jetpacks 31 | VEHICLE_PHYSICS_COMPONENT: 30, 32 | MOVEMENT_AI_COMPONENT: 31, // only enemies have this for the most part 33 | EXHIBIT_COMPONENT: 32, // Exhibit Component? 34 | 35 | MINIFIG_COMPONENT: 35, // All NPC's have this component... 36 | PROPERTY_COMPONENT: 36, // This component is used by property related objects 37 | PET_NEST_COMPONENT: 37, // only one in table, used by LOT 3336, which is described as a nest asset. Possibly a petNestComponent 38 | MODEL_BUILDER_COMPONENT: 38, // only two in table, LWOModelBuilderComponent is listed in the description of LOT 6228 39 | SCRIPTED_ACTIVITY_COMPONENT: 39, 40 | PHANTOM_PHYSICS_COMPONENT: 40, 41 | SPRINGPAD_COMPONENT: 41, // A component for testing "new springpads" LOT 4816 for example 42 | MODEL_BEHAVIORS_COMPONENT: 42, // Models, or something...? 43 | PROPERTY_ENTRANCE_COMPONENT: 43, // Property Lauchpad components 44 | FX_COMPONENT: 44, // Not one object uses this 45 | PROPERTY_MANAGEMENT_COMPONENT: 45, // only one in table, LOT 3315 46 | VEHICLE_PHYSICS_2_COMPONENT: 46, // Flying vehicle tests component 47 | PHYSICS_SYSTEM_COMPONENT: 47, // Used by a lot of LUP freebuild objects, LOT 7138 48 | REBUILD_COMPONENT: 48, 49 | SWITCH_COMPONENT: 49, 50 | MINIGAME_COMPONENT: 50, // only two in table, one is the biplane(LOT 4625), the other is LOT 2365, a zone control object 51 | CHANGLING_COMPONENT: 51, // used by "Kipper Duel" models... 52 | CHOICE_BUILD_COMPONENT: 52, // choice build component? 53 | PACKAGE_COMPONENT: 53, // Loot pack component? 54 | SOUND_REPEATER_COMPONENT: 54, // only two in table, both are sound objects, this must be a sound component 55 | SOUND_AMBIENT_2D_COMPONENT: 55, // only two in table, and those are the player objects 56 | SOUND_AMBIENT_3D_COMPONENT: 56, // only one in table, which is an ambient sound object 57 | PLAYER_FLAGS_COMPONENT: 57, // used in pirate siege... 58 | 59 | CUSTUM_BUILD_ASSEMBLY_COMPONENT: 59, // only one in table, LOT 6398. a test rocket 60 | BASE_COMBAT_AI_COMPONENT: 60, 61 | MODULE_ASSEMBLY_COMPONENT: 61, // used by cars and rockets, modular stuff 62 | SHOWCASE_MODEL_COMPONENT: 62, // showcase component? (LOT 6545) 63 | RACING_MODULE_COMPONENT: 63, // another modular related component 64 | GENERIC_ACTIVATOR_COMPONENT: 64, // only three in table, a middle module component? 65 | PROPERTY_VENDOR_COMPONENT: 65, // only two in table, property selling venders 66 | HF_LIGHT_DIRECTION_COMPONENT: 66, // only one in table, LOT 6968, a light direction component? 67 | ROCKET_LAUNCHPAD_CONTROL_COMPONENT: 67, // launchpad related component 68 | ROCKET_LANDING_COMPONENT: 68, // only two in table, and those are the player objects 69 | TRIGGER_COMPONENT: 69, // I assume Simon pulled this from somewhere(?) 70 | 71 | RACING_CONTROL_COMPONENT: 71, 72 | FACTION_TRIGGER_COMPONENT: 72, // something to do with rank items... maybe to do with skills? 73 | MISSION_NPC_COMPONENT: 73, // missions giver component? 74 | RACING_STATS_COMPONENT: 74, // only two in table, racing car related 75 | LUP_EXHIBIT_COMPONENT: 75, // only one in table, LUP exhibit related, LOT 9461 76 | 77 | SOUND_TRIGGER_COMPONENT: 77, // sound trigger component 78 | PROXIMITY_MONITOR_COMPONENT: 78, // more launchpad related stuff 79 | RACING_SOUND_TRIGGER_COMPONENT: 79, // only two in table, sound trigger related 80 | 81 | USER_CONTROL_COMPONENT: 95, // skateboard component 82 | 83 | UNKOWN_97_COMPONENT: 97, // only two in table, both are Starbase 3001 launcher related 84 | 85 | UNKOWN_100_COMPONENT: 100, // brick donation component 86 | 87 | UNKOWN_102_COMPONENT: 102, // only two in table, commendation vendor component? 88 | UNKOWN_103_COMPONENT: 103, // only two in table, nothing in objects 89 | RAIL_ACTIVATOR_COMPONENT: 104, // rail activator related 90 | UNKOWN_105_COMPONENT: 105, // only three in table, ? I haven't a clue as to what this is supposed to be 91 | UNKOWN_106_COMPONENT: 106, // only one in table, related to skateboard mount, LOT 16684 92 | UNKNOWN_107_COMPONENT: 107, // only one in table, generic player 93 | UNKNOWN_108_COMPONENT: 108, // for vehicles 94 | 95 | UNKOWN_113_COMPONENT: 113, // only one in table, property plaque 96 | UNKOWN_114_COMPONENT: 114, // used by building bays 97 | 98 | UNKOWN_116_COMPONENT: 116, // only one in table, LOT 16512, a culling plane, culls objects behind them 99 | 100 | key: function (value) { 101 | for (const prop in this) { 102 | if (this.hasOwnProperty.call(prop)) { 103 | if (this[prop] === value) return prop; 104 | } 105 | } 106 | } 107 | }; 108 | -------------------------------------------------------------------------------- /src/LU/Replica/Object.ts: -------------------------------------------------------------------------------- 1 | import ReplicaManager from '../Managers/ReplicaManager'; 2 | import Vector3f from '../../geometry/Vector3f'; 3 | import Vector4f from '../../geometry/Vector4f'; 4 | import * as events from 'events'; 5 | import BitStream from 'node-raknet/structures/BitStream'; 6 | import ComponentMask from './ComponentMask'; 7 | import { SerializationType } from './SerializationType'; 8 | 9 | import { ComponentsRegistry } from '../../DB/CDClient'; 10 | 11 | export default class GameObject { 12 | #rm: ReplicaManager; 13 | #netId: number; 14 | #id: bigint; 15 | #template: number; 16 | #pos: Vector3f; 17 | #rot: Vector4f; 18 | #scale: number; 19 | #owner: bigint; 20 | #data: any; 21 | #serializers: Array; 22 | #bus: events.EventEmitter; 23 | components: ComponentMask; 24 | 25 | /** 26 | * 27 | * @param {ReplicaManager} replicaManager 28 | * @param {number} networkId 29 | * @param {bigint} id 30 | * @param {number} objectTemplate 31 | * @param {Vector3f} pos 32 | * @param {Vector4f} rot 33 | * @param {number} scale 34 | * @param {bigint} owner 35 | * @param data 36 | */ 37 | constructor( 38 | replicaManager: ReplicaManager, 39 | networkId: number, 40 | id: bigint, 41 | objectTemplate: number, 42 | pos: Vector3f, 43 | rot: Vector4f, 44 | scale: number, 45 | owner: bigint, 46 | data 47 | ) { 48 | this.#rm = replicaManager; 49 | 50 | this.#netId = networkId; 51 | 52 | // For use of keeping track of the object 53 | this.#id = id; 54 | 55 | // Set the component mask up 56 | this.components = new ComponentMask(); 57 | ComponentsRegistry.findAll({ 58 | where: { 59 | id: objectTemplate 60 | } 61 | }) 62 | .then((components) => { 63 | components.forEach((component) => { 64 | this.components.addComponent(component.component_type); // Add components to mask 65 | }); 66 | }) 67 | .then(() => { 68 | this.#rm.eventBus.emit('new-object-created', this); 69 | }); 70 | 71 | this.#template = objectTemplate; 72 | this.#pos = pos; 73 | this.#rot = rot; 74 | this.#scale = scale; 75 | this.#owner = owner; 76 | this.#data = data; 77 | 78 | this.#serializers = []; 79 | 80 | this.#bus = new events.EventEmitter(); 81 | } 82 | 83 | /** 84 | * Serializes this object to a stream 85 | * @param {number} type 86 | * @param {BitStream} stream 87 | */ 88 | serialize(type: number, stream: BitStream) { 89 | if (type === SerializationType.CREATION) { 90 | stream.writeLongLong(this.ID); 91 | 92 | stream.writeLong(this.LOT); 93 | 94 | stream.writeByte('testing'.length); // TODO: add a name field and serialize it here 95 | stream.writeWString('testing', 'testing'.length); 96 | 97 | stream.writeLong(0); // TODO: Keep track of how long this object has been created on server 98 | stream.writeBit(false); // TODO: Add support for this structure 99 | stream.writeBit(false); // TODO: Add support for Trigger ID 100 | stream.writeBit(false); // TODO: Add support for Spawner ID, set here as a s64 101 | stream.writeBit(false); // TODO: Add support for Spawner Node ID, set here as a u32 102 | stream.writeBit(this.scale !== 1); 103 | if (this.scale !== 1) { 104 | stream.writeFloat(this.scale); 105 | } 106 | stream.writeBit(false); // TODO: Add support for GameObject World State 107 | stream.writeBit(false); // TODO: Add support for GM Level 108 | } 109 | stream.writeBit(false); // TODO: add support for child/parent objects 110 | 111 | const serializers = this.#serializers; 112 | serializers.forEach((serializer) => { 113 | serializer(type, stream); 114 | }); 115 | } 116 | 117 | addSerializer(id: number, method: any) { 118 | this.#serializers[id] = method; 119 | if ( 120 | Object.keys(this.#serializers).length === 121 | this.components.getComponentsList().length 122 | ) { 123 | // this means that we have a serializer for every component that the object has and we are loaded 124 | this.#rm.eventBus.emit('new-object-loaded', this); 125 | } 126 | } 127 | 128 | removeSerializer(id: number) { 129 | delete this.#serializers[id]; 130 | } 131 | 132 | /** 133 | * 134 | * @return {bigint} 135 | * @constructor 136 | */ 137 | get ID(): bigint { 138 | return this.#id; 139 | } 140 | 141 | /** 142 | * 143 | * @return {Number} 144 | * @constructor 145 | */ 146 | get LOT(): number { 147 | return this.#template; 148 | } 149 | 150 | get position(): Vector3f { 151 | return this.#pos; 152 | } 153 | 154 | get rotation(): Vector4f { 155 | return this.#rot; 156 | } 157 | 158 | get scale(): number { 159 | return this.#scale; 160 | } 161 | 162 | get owner(): bigint { 163 | return this.#owner; 164 | } 165 | 166 | get data(): any { 167 | return this.#data; 168 | } 169 | 170 | get netID(): number { 171 | return this.#netId; 172 | } 173 | 174 | /** 175 | * 176 | * @param {Number} id 177 | * @param {LUGameMessage} gm 178 | * @param user 179 | */ 180 | emitGM(id: number, gm: number, user) { 181 | this.#bus.emit(id.toString(), gm, user, this); 182 | } 183 | 184 | /** 185 | * 186 | * @param {Number} id 187 | * @param {Function} callback 188 | */ 189 | addGMListener(id: number, callback: () => void) { 190 | this.#bus.on(id.toString(), callback); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/LU/Replica/SerializationOrder.ts: -------------------------------------------------------------------------------- 1 | import { Components } from './Components'; 2 | 3 | /** 4 | * 5 | * @type {Number[]} 6 | */ 7 | export const SerializationOrder = [ 8 | Components.UNKNOWN_108_COMPONENT, 9 | Components.MODULE_ASSEMBLY_COMPONENT, 10 | Components.CONTROLABLE_PHYSICS_COMPONENT, 11 | Components.SIMPLE_PHYSICS_COMPONENT, 12 | Components.RIGID_BODY_PHANTOM_PHYSICS_COMPONENT, 13 | Components.VEHICLE_PHYSICS_COMPONENT, 14 | Components.PHANTOM_PHYSICS_COMPONENT, 15 | Components.DESTRUCTABLE_COMPONENT, 16 | Components.COLLECTIBLE_COMPONENT, 17 | Components.PET_COMPONENT, 18 | Components.CHARACTER_COMPONENT, 19 | Components.SHOOTING_GALLERY_COMPONENT, 20 | Components.INVENTORY_COMPONENT, 21 | Components.SCRIPT_COMPONENT, 22 | Components.SKILL_COMPONENT, 23 | Components.BASE_COMBAT_AI_COMPONENT, 24 | Components.REBUILD_COMPONENT, 25 | Components.MOVING_PLATFORM_COMPONENT, 26 | Components.SWITCH_COMPONENT, 27 | Components.VENDOR_COMPONENT, 28 | Components.BOUNCER_COMPONENT, 29 | Components.SCRIPTED_ACTIVITY_COMPONENT, 30 | Components.RACING_CONTROL_COMPONENT, 31 | Components.LUP_EXHIBIT_COMPONENT, 32 | Components.MODEL_BEHAVIORS_COMPONENT, 33 | Components.RENDER_COMPONENT, 34 | Components.MINIGAME_COMPONENT, 35 | Components.UNKNOWN_107_COMPONENT, 36 | Components.TRIGGER_COMPONENT, 37 | 38 | // Below here are the components that aren't actually serialized, but we need them for the object to think it is ready to go 39 | Components.ROCKET_LANDING_COMPONENT, 40 | Components.SOUND_AMBIENT_2D_COMPONENT 41 | ]; 42 | -------------------------------------------------------------------------------- /src/LU/Replica/SerializationType.ts: -------------------------------------------------------------------------------- 1 | export const SerializationType = { 2 | CREATION: 0, 3 | SERIALIZATION: 1, 4 | DESTRUCTION: 2 5 | }; 6 | -------------------------------------------------------------------------------- /src/geometry/Vector3f.ts: -------------------------------------------------------------------------------- 1 | export default class Vector3f { 2 | x: number; 3 | y: number; 4 | z: number; 5 | 6 | constructor(x = 0, y = 0, z = 0) { 7 | this.x = x; 8 | this.y = y; 9 | this.z = z; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /src/geometry/Vector4f.ts: -------------------------------------------------------------------------------- 1 | export default class Vector4f { 2 | x: number; 3 | y: number; 4 | z: number; 5 | w: number; 6 | 7 | constructor(x: number, y: number, z: number, w: number) { 8 | this.x = x; 9 | this.y = y; 10 | this.z = z; 11 | this.w = w; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/server/Commands.ts: -------------------------------------------------------------------------------- 1 | import * as readline from 'readline'; 2 | import { User, sequelize } from '../DB/LUJS'; 3 | import * as util from 'util'; 4 | import * as bcrypt from 'bcryptjs'; 5 | import { servers } from './ServerManager'; 6 | const bcryptHash = util.promisify(bcrypt.hash); 7 | 8 | let instance; 9 | 10 | export default class Commands { 11 | #read: readline.Interface; 12 | 13 | /** 14 | * 15 | * @returns {Commands} 16 | */ 17 | static instance() { 18 | if (instance === undefined) { 19 | instance = new Commands(); 20 | } 21 | 22 | return instance; 23 | } 24 | 25 | constructor() { 26 | this.#read = readline.createInterface(process.stdin); 27 | 28 | this.#read.on('line', (input) => { 29 | this.handleInput(input); 30 | }); 31 | } 32 | 33 | /** 34 | * 35 | * @param {String} input 36 | */ 37 | handleInput(input) { 38 | if (input.charAt(0) === '/') { 39 | this.parseCommand(input.substring(1)); 40 | } 41 | } 42 | 43 | /** 44 | * 45 | * @param {String} input 46 | */ 47 | parseCommand(input) { 48 | const commandAndArgs = input.split(' '); 49 | this.handleCommand(commandAndArgs[0], commandAndArgs.splice(1)); 50 | } 51 | 52 | /** 53 | * TODO: actually use some event handler or something instead of a switch statement 54 | * @param {String} command 55 | * @param {Array} args 56 | */ 57 | handleCommand(command, args) { 58 | switch (command) { 59 | case 'create-account': 60 | bcryptHash(args[1], 10) 61 | .then((hash) => { 62 | User.create({ 63 | username: args[0], 64 | password: hash, 65 | email: '', 66 | first_name: '', 67 | last_name: '', 68 | birthdate: '' 69 | }) 70 | .then(() => { 71 | console.log(`Created user: ${args[0]} ${hash}`); 72 | }) 73 | .catch(() => { 74 | console.error('Failed to create account: DB Error'); 75 | }); 76 | }) 77 | .catch(() => { 78 | console.error('Failed to create account: Hash failed'); 79 | }); 80 | break; 81 | case 'list-servers': 82 | servers.forEach((server) => { 83 | console.log( 84 | `Server: ${server.rakServer.ip}:${server.rakServer.port} Zone: ${server.zoneID}` 85 | ); 86 | }); 87 | break; 88 | case 'rebuild-database': 89 | sequelize.sync({ force: true }); 90 | break; 91 | } 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /src/server/Handles/MessageHandles/ID_CONNECTION_REQUEST.ts: -------------------------------------------------------------------------------- 1 | import RakMessages from 'node-raknet/RakMessages'; 2 | import BitStream from 'node-raknet/structures/BitStream'; 3 | import * as inetAton from 'inet-aton'; 4 | import { Reliability } from 'node-raknet/ReliabilityLayer'; 5 | import { RakServerExtended } from '../../Server'; 6 | 7 | /** 8 | * 9 | * @param {RakServerExtended} server 10 | */ 11 | export default function ID_CONNECTION_REQUEST(server: RakServerExtended) { 12 | server.on(String(RakMessages.ID_CONNECTION_REQUEST), function (packet, user) { 13 | const client = this.getClient(user.address); 14 | let password = ''; 15 | while (!packet.allRead()) { 16 | password += String.fromCharCode(packet.readByte()); 17 | } 18 | 19 | if (password === this.password) { 20 | const response = new BitStream(); 21 | response.writeByte(RakMessages.ID_CONNECTION_REQUEST_ACCEPTED); 22 | 23 | const remoteAddress = inetAton(user.address); 24 | response.writeByte(remoteAddress[0]); 25 | response.writeByte(remoteAddress[1]); 26 | response.writeByte(remoteAddress[2]); 27 | response.writeByte(remoteAddress[3]); 28 | 29 | response.writeShort(user.port); 30 | response.writeShort(0); 31 | 32 | const localAddress = inetAton(this.ip); 33 | response.writeByte(localAddress[0]); 34 | response.writeByte(localAddress[1]); 35 | response.writeByte(localAddress[2]); 36 | response.writeByte(localAddress[3]); 37 | 38 | response.writeShort(this.server.address().port); 39 | client.send(response, Reliability.RELIABLE); 40 | } 41 | }); 42 | } 43 | -------------------------------------------------------------------------------- /src/server/Handles/MessageHandles/ID_DISCONNECTION_NOTIFICATION.ts: -------------------------------------------------------------------------------- 1 | import RakMessages from 'node-raknet/RakMessages'; 2 | import { ServerManager } from '../../ServerManager'; 3 | import * as config from 'config'; 4 | import { RakServerExtended } from '../../Server'; 5 | 6 | /** 7 | * 8 | * @param {RakServerExtended} server 9 | */ 10 | export default function ID_DISCONNECTION_NOTIFICATION( 11 | server: RakServerExtended 12 | ) { 13 | server.on(String(RakMessages.ID_DISCONNECTION_NOTIFICATION), function ( 14 | packet, 15 | user 16 | ) { 17 | console.log( 18 | `Client ${user.address} has disconnected from ${server.parent.ip}:${ 19 | server.port 20 | }` 21 | ); 22 | 23 | server.userMessageHandler.removeAllListeners( 24 | `user-authenticated-${user.address}-${user.port}` 25 | ); 26 | 27 | // TODO: Save users info from memory to DB here... 28 | 29 | const servers = []; 30 | config.servers.forEach((server_) => { 31 | servers.push(server_.port); 32 | }); 33 | 34 | if (server.connections.length === 0 && !servers.includes(server.port)) { 35 | // if the server is now empty 36 | server.timeout = setTimeout(() => { 37 | console.log(`Closing server ${server.port}`); 38 | 39 | ServerManager.remove(server.parent); 40 | }, config.timeout); 41 | } 42 | }); 43 | } 44 | -------------------------------------------------------------------------------- /src/server/Handles/MessageHandles/ID_INTERNAL_PING.ts: -------------------------------------------------------------------------------- 1 | import RakMessages from 'node-raknet/RakMessages'; 2 | import BitStream from 'node-raknet/structures/BitStream'; 3 | import { Reliability } from 'node-raknet/ReliabilityLayer'; 4 | import { RakServerExtended } from '../../Server'; 5 | 6 | /** 7 | * 8 | * @param {RakServerExtended} server 9 | */ 10 | export default function ID_INTERNAL_PING(server: RakServerExtended) { 11 | server.on(String(RakMessages.ID_INTERNAL_PING), function (packet, user) { 12 | const client = this.getClient(user.address); 13 | const ping = packet.readLong(); 14 | 15 | const response = new BitStream(); 16 | response.writeByte(RakMessages.ID_CONNECTED_PONG); 17 | response.writeLong(ping); 18 | response.writeLong(Date.now() - this.startTime); 19 | client.send(response, Reliability.UNRELIABLE); 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /src/server/Handles/MessageHandles/ID_NEW_INCOMING_CONNECTION.ts: -------------------------------------------------------------------------------- 1 | import RakMessages from 'node-raknet/RakMessages'; 2 | import { RakServerExtended } from '../../Server'; 3 | 4 | /** 5 | * 6 | * @param {RakServerExtended} server 7 | */ 8 | export default function ID_NEW_INCOMING_CONNECTION(server: RakServerExtended) { 9 | server.on(String(RakMessages.ID_NEW_INCOMING_CONNECTION), function ( 10 | packet, 11 | user 12 | ) { 13 | console.log( 14 | `Got new connection from ${user.address} for ${server.parent.ip}:${ 15 | server.port 16 | }` 17 | ); 18 | 19 | // prevent the server from shutting down 20 | clearTimeout(server.timeout); 21 | }); 22 | } 23 | -------------------------------------------------------------------------------- /src/server/Handles/MessageHandles/ID_USER_PACKET_ENUM.ts: -------------------------------------------------------------------------------- 1 | import RakMessages from 'node-raknet/RakMessages'; 2 | import { LURemoteConnectionType } from '../../../LU/Message Types/LURemoteConnectionType'; 3 | import { LUGeneralMessageType } from '../../../LU/Message Types/LUGeneralMessageType'; 4 | import { LUAuthenticationMessageType } from '../../../LU/Message Types/LUAuthenticationMessageType'; 5 | import { LUChatMessageType } from '../../../LU/Message Types/LUChatMessageType'; 6 | import { LUServerMessageType } from '../../../LU/Message Types/LUServerMessageType'; 7 | import { LUClientMessageType } from '../../../LU/Message Types/LUClientMessageType'; 8 | import { RakServerExtended } from '../../Server'; 9 | import * as path from 'path'; 10 | import * as fs from 'fs'; 11 | 12 | /** 13 | * 14 | * @param {RakServerExtended} server 15 | */ 16 | export default function ID_USER_PACKET_ENUM(server: RakServerExtended) { 17 | // Each module is responsible for registering for the event 18 | const normalizedPath = path.join(__dirname, '../UserHandles'); 19 | fs.readdirSync(normalizedPath).forEach(function (file) { 20 | import('../UserHandles/' + file).then((handle) => { 21 | handle(server.userMessageHandler); 22 | }); 23 | }); 24 | 25 | server.on(String(RakMessages.ID_USER_PACKET_ENUM), function (packet, user) { 26 | const remoteConnectionType = packet.readShort(); 27 | const packetID = packet.readLong(); 28 | const alwaysZero = packet.readByte(); 29 | if (alwaysZero !== 0) { 30 | throw new Error('Malformed Packet: Not always zero'); 31 | } 32 | 33 | if ( 34 | this.userMessageHandler.listenerCount( 35 | [remoteConnectionType, packetID].join() 36 | ) > 0 37 | ) { 38 | this.userMessageHandler.emit( 39 | [remoteConnectionType, packetID].join(), 40 | this, 41 | packet, 42 | user 43 | ); 44 | } else { 45 | let string = ''; 46 | switch (remoteConnectionType) { 47 | case LURemoteConnectionType.general: 48 | string = LUGeneralMessageType.key(packetID); 49 | break; 50 | case LURemoteConnectionType.client: 51 | string = LUClientMessageType.key(packetID); 52 | break; 53 | case LURemoteConnectionType.authentication: 54 | string = LUAuthenticationMessageType.key(packetID); 55 | break; 56 | case LURemoteConnectionType.chat: 57 | string = LUChatMessageType.key(packetID); 58 | break; 59 | case LURemoteConnectionType.server: 60 | string = LUServerMessageType.key(packetID); 61 | break; 62 | case LURemoteConnectionType.internal: 63 | break; 64 | } 65 | console.log( 66 | `No listeners found for: ${[ 67 | LURemoteConnectionType.key(remoteConnectionType), 68 | string 69 | ].join(', ')}` 70 | ); 71 | } 72 | }); 73 | } 74 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/LOAD_STATIC_ZONE.js: -------------------------------------------------------------------------------- 1 | const RakMessages = require('node-raknet/RakMessages.js'); 2 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 3 | .LURemoteConnectionType; 4 | const LUServerMessageType = require('../../../LU/Message Types/LUServerMessageType') 5 | .LUServerMessageType; 6 | const LUClientMessageType = require('../../../LU/Message Types/LUClientMessageType') 7 | .LUClientMessageType; 8 | const BitStream = require('node-raknet/structures/BitStream'); 9 | const { Reliability } = require('node-raknet/ReliabilityLayer.js'); 10 | const LoadStaticZone = require('../../../LU/Messages/LoadStaticZone'); 11 | 12 | function LOAD_STATIC_ZONE (handler) { 13 | handler.on( 14 | [ 15 | LURemoteConnectionType.server, 16 | LUServerMessageType.MSG_WORLD_CLIENT_VALIDATION 17 | ].join(), 18 | function (server, packet, user) { 19 | const client = server.getClient(user.address); 20 | 21 | if (server.getServer().zoneID > 0) { 22 | // They are connected to a world server, time to send them some information 23 | const response = new LoadStaticZone(); 24 | 25 | response.zoneID = server.getServer().zoneID; 26 | 27 | const send = new BitStream(); 28 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 29 | send.writeShort(LURemoteConnectionType.client); 30 | send.writeLong(LUClientMessageType.LOAD_STATIC_ZONE); 31 | send.writeByte(0); 32 | response.serialize(send); 33 | client.send(send, Reliability.RELIABLE_ORDERED); 34 | } 35 | } 36 | ); 37 | } 38 | 39 | module.exports = LOAD_STATIC_ZONE; 40 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_AUTH_LOGIN_REQUEST.js: -------------------------------------------------------------------------------- 1 | const RakMessages = require('node-raknet/RakMessages.js'); 2 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 3 | .LURemoteConnectionType; 4 | const LUAuthenticationMessageType = require('../../../LU/Message Types/LUAuthenticationMessageType') 5 | .LUAuthenticationMessageType; 6 | const LUClientMessageType = require('../../../LU/Message Types/LUClientMessageType') 7 | .LUClientMessageType; 8 | const BitStream = require('node-raknet/structures/BitStream'); 9 | const { Reliability } = require('node-raknet/ReliabilityLayer.js'); 10 | const { LoginInfo, LoginCodes } = require('../../../LU/Messages/LoginInfo'); 11 | const bcrypt = require('bcryptjs'); 12 | const { Session, User, HardwareSurvey } = require('../../../DB/LUJS'); 13 | const { ServerManager } = require('../../ServerManager'); 14 | const util = require('util'); 15 | const bcryptHash = util.promisify(bcrypt.hash); 16 | 17 | function rand (size) { 18 | const chars = 'abcdefghijklmnopqrstuvwxyz1234567890'; 19 | let ret = ''; 20 | for (let i = 0; i < size; i++) { 21 | ret += chars.charAt(Math.floor(Math.random() * chars.length)); 22 | } 23 | 24 | return ret; 25 | } 26 | 27 | function MSG_AUTH_LOGIN_REQUEST (handler) { 28 | handler.on( 29 | [ 30 | LURemoteConnectionType.authentication, 31 | LUAuthenticationMessageType.MSG_AUTH_LOGIN_REQUEST 32 | ].join(), 33 | function (server, packet, user) { 34 | const client = server.getClient(user.address); 35 | 36 | const username = packet.readWString(); 37 | const password = packet.readWString(41); 38 | packet.readShort(); // language 39 | packet.readByte(); // unknown 40 | const processInformation = packet.readWString(256); // process information 41 | const graphicsInformation = packet.readWString(128); // graphics information 42 | const numberOfProcessors = packet.readLong(); // number of processors 43 | const processorType = packet.readShort(); // processor type 44 | const processorLevel = packet.readShort(); // processor level 45 | packet.readLong(); // unknown 2 46 | 47 | if (!packet.allRead()) { 48 | packet.readLong(); // os major 49 | packet.readLong(); // os minor 50 | packet.readLong(); // os build 51 | packet.readLong(); // os platform 52 | } 53 | 54 | HardwareSurvey.create({ 55 | process_information: processInformation, 56 | graphics_information: graphicsInformation, 57 | number_of_processors: numberOfProcessors, 58 | processor_type: processorType, 59 | processor_level: processorLevel 60 | }); 61 | 62 | User.findOne({ 63 | where: { username: username } 64 | }).then(userModel => { 65 | const response = new LoginInfo(); 66 | 67 | if (userModel === null) { 68 | // The user was not found 69 | response.code = LoginCodes.badUsername; 70 | } else { 71 | // This is how to generate passwords.. 72 | bcryptHash(password, 10) 73 | .then(hash => { 74 | console.log(hash); 75 | }) 76 | .catch(() => { 77 | console.error('Failed to generate password hash'); 78 | }); 79 | 80 | // TODO: make this async... smh 81 | if (bcrypt.compareSync(password, userModel.password)) { 82 | response.code = LoginCodes.success; 83 | 84 | // Find the world server acting as the char server 85 | ServerManager.request(0).then(server => { 86 | const redirect = server; 87 | response.redirectIP = redirect.ip; 88 | response.redirectPort = redirect.port; 89 | response.chatIP = redirect.ip; 90 | response.chatPort = redirect.port; 91 | response.altIP = redirect.ip; 92 | 93 | // Session stuff. 94 | // TODO: Check if the user already has a login from this ip, if so kill that session (and log out the other user logged in at some point) 95 | // TODO: Check to see if there is already an existing session for this user, if so, use it 96 | // TODO: Actually store this in the DB 97 | response.session = rand(32); 98 | const today = new Date(); 99 | 100 | Session.create({ 101 | key: response.session, 102 | start_time: today, 103 | end_time: new Date(today.getTime() + 24 * 60 * 60 * 1000), 104 | ip: user.address, 105 | user_id: userModel.id 106 | }); 107 | 108 | response.clientVersionMajor = 1; 109 | response.clientVersionCurrent = 10; 110 | response.clientVersionMinor = 64; 111 | response.localization = 'US'; 112 | // TODO: Actually get this from the DB 113 | response.firstSubscription = false; 114 | // TODO: Actually get this from the DB 115 | response.freeToPlay = false; 116 | 117 | const send = new BitStream(); 118 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 119 | send.writeShort(LURemoteConnectionType.client); 120 | send.writeLong(LUClientMessageType.LOGIN_RESPONSE); 121 | send.writeByte(0); 122 | response.serialize(send); 123 | client.send(send, Reliability.RELIABLE_ORDERED); 124 | }); 125 | 126 | return; 127 | } else { 128 | response.code = LoginCodes.badPassword; 129 | } 130 | } 131 | 132 | response.clientVersionMajor = 1; 133 | response.clientVersionCurrent = 10; 134 | response.clientVersionMinor = 64; 135 | response.localization = 'US'; 136 | // TODO: Actually get this from the DB 137 | response.firstSubscription = false; 138 | // TODO: Actually get this from the DB 139 | response.freeToPlay = false; 140 | 141 | const send = new BitStream(); 142 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 143 | send.writeShort(LURemoteConnectionType.client); 144 | send.writeLong(LUClientMessageType.LOGIN_RESPONSE); 145 | send.writeByte(0); 146 | response.serialize(send); 147 | client.send(send, Reliability.RELIABLE_ORDERED); 148 | }); 149 | } 150 | ); 151 | } 152 | 153 | module.exports = MSG_AUTH_LOGIN_REQUEST; 154 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_SERVER_VERSION_CONFIRM.js: -------------------------------------------------------------------------------- 1 | const RakMessages = require('node-raknet/RakMessages.js'); 2 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 3 | .LURemoteConnectionType; 4 | const LUGeneralMessageType = require('../../../LU/Message Types/LUGeneralMessageType') 5 | .LUGeneralMessageType; 6 | const VersionConfirm = require('../../../LU/Messages/VersionConfirm'); 7 | const BitStream = require('node-raknet/structures/BitStream'); 8 | const { Reliability } = require('node-raknet/ReliabilityLayer.js'); 9 | 10 | function MSG_SERVER_VERSION_CONFIRM (handler) { 11 | handler.on( 12 | [ 13 | LURemoteConnectionType.general, 14 | LUGeneralMessageType.MSG_SERVER_VERSION_CONFIRM 15 | ].join(), 16 | function (server, packet, user) { 17 | const client = server.getClient(user.address); 18 | const message = new VersionConfirm(); 19 | message.deserialize(packet); 20 | message.unknown = 0x93; 21 | message.remoteConnectionType = 4; 22 | if (server.port === 1001) { 23 | // If this is an auth server 24 | message.remoteConnectionType = 1; 25 | } 26 | message.processID = process.pid; 27 | message.localPort = 0xffff; 28 | message.localIP = server.ip; 29 | 30 | // TODO: This needs to be brought into a method inside something 31 | const send = new BitStream(); 32 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 33 | send.writeShort(LURemoteConnectionType.general); 34 | send.writeLong(LUGeneralMessageType.MSG_SERVER_VERSION_CONFIRM); 35 | send.writeByte(0); 36 | message.serialize(send); 37 | client.send(send, Reliability.RELIABLE_ORDERED); 38 | } 39 | ); 40 | } 41 | 42 | module.exports = MSG_SERVER_VERSION_CONFIRM; 43 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_WORLD_CLIENT_CHARACTER_CREATE_REQUEST.js: -------------------------------------------------------------------------------- 1 | const RakMessages = require('node-raknet/RakMessages.js'); 2 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 3 | .LURemoteConnectionType; 4 | const LUServerMessageType = require('../../../LU/Message Types/LUServerMessageType') 5 | .LUServerMessageType; 6 | const LUClientMessageType = require('../../../LU/Message Types/LUClientMessageType') 7 | .LUClientMessageType; 8 | const BitStream = require('node-raknet/structures/BitStream'); 9 | const { Reliability } = require('node-raknet/ReliabilityLayer.js'); 10 | const MinifigCreateRequest = require('../../../LU/Messages/MinifigCreateRequest'); 11 | const { 12 | MinifigCreateResponse, 13 | CreationResponse 14 | } = require('../../../LU/Messages/MinifigCreateResponse'); 15 | const Sequelize = require('sequelize'); 16 | 17 | const { Character, InventoryItem } = require('../../../DB/LUJS'); 18 | const { ComponentsRegistry, ItemComponent } = require('../../../DB/CDClient'); 19 | 20 | function MSG_WORLD_CLIENT_CHARACTER_CREATE_REQUEST (handler) { 21 | handler.on( 22 | [ 23 | LURemoteConnectionType.server, 24 | LUServerMessageType.MSG_WORLD_CLIENT_CHARACTER_CREATE_REQUEST 25 | ].join(), 26 | function (server, packet, user) { 27 | const client = server.getClient(user.address); 28 | 29 | const minifig = new MinifigCreateRequest(); 30 | minifig.deserialize(packet); 31 | 32 | Character.create({ 33 | name: 34 | minifig.firstName + ' ' + minifig.middleName + ' ' + minifig.lastName, 35 | unapproved_name: minifig.name, 36 | shirt_color: minifig.shirtColor, 37 | shirt_style: minifig.shirtStyle, 38 | pants_color: minifig.pantsColor, 39 | hair_style: minifig.hairStyle, 40 | hair_color: minifig.hairColor, 41 | lh: minifig.lh, 42 | rh: minifig.rh, 43 | eyebrows: minifig.eyebrows, 44 | eyes: minifig.eyes, 45 | mouth: minifig.mouth, 46 | user_id: client.user_id 47 | }).then(function (character) { 48 | const Op = Sequelize.Op; 49 | 50 | const promises = []; 51 | 52 | // Find the shirt... 53 | promises.push( 54 | ItemComponent.findOne({ 55 | where: { 56 | [Op.and]: [ 57 | { color1: minifig.shirtColor }, 58 | { decal: minifig.shirtStyle }, 59 | { equipLocation: 'chest' } 60 | ] 61 | } 62 | }).then(function (inventoryComponent) { 63 | promises.push( 64 | ComponentsRegistry.findOne({ 65 | where: { 66 | [Op.and]: [ 67 | { component_type: 11 }, 68 | { component_id: inventoryComponent.id } 69 | ] 70 | } 71 | }).then(function (component) { 72 | InventoryItem.create({ 73 | character_id: character.id, 74 | lot: component.id, 75 | slot: 0, 76 | count: 1, 77 | type: 0, 78 | is_equipped: 1, 79 | is_linked: 1 80 | }); 81 | }) 82 | ); 83 | }) 84 | ); 85 | 86 | // Find pants... 87 | promises.push( 88 | ItemComponent.findOne({ 89 | where: { 90 | [Op.and]: [ 91 | { color1: minifig.pantsColor }, 92 | { equipLocation: 'legs' } 93 | ] 94 | } 95 | }).then(function (inventoryComponent) { 96 | promises.push( 97 | ComponentsRegistry.findOne({ 98 | where: { 99 | [Op.and]: [ 100 | { component_type: 11 }, 101 | { component_id: inventoryComponent.id } 102 | ] 103 | } 104 | }).then(function (component) { 105 | InventoryItem.create({ 106 | character_id: character.id, 107 | lot: component.id, 108 | slot: 1, 109 | count: 1, 110 | type: 0, 111 | is_equipped: 1, 112 | is_linked: 1 113 | }); 114 | }) 115 | ); 116 | }) 117 | ); 118 | 119 | Promise.all(promises).then(function () { 120 | const response = new MinifigCreateResponse(); 121 | response.id = CreationResponse.SUCCESS; 122 | 123 | const send = new BitStream(); 124 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 125 | send.writeShort(LURemoteConnectionType.client); 126 | send.writeLong(LUClientMessageType.CHARACTER_CREATE_RESPONSE); 127 | send.writeByte(0); 128 | response.serialize(send); 129 | client.send(send, Reliability.RELIABLE_ORDERED); 130 | 131 | // Send the minifig list again 132 | handler.emit( 133 | [ 134 | LURemoteConnectionType.server, 135 | LUServerMessageType.MSG_WORLD_CLIENT_CHARACTER_LIST_REQUEST 136 | ].join(), 137 | server, 138 | undefined, 139 | user 140 | ); 141 | }); 142 | }); 143 | } 144 | ); 145 | } 146 | 147 | module.exports = MSG_WORLD_CLIENT_CHARACTER_CREATE_REQUEST; 148 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_WORLD_CLIENT_CHARACTER_DELETE_REQUEST.js: -------------------------------------------------------------------------------- 1 | const RakMessages = require('node-raknet/RakMessages.js'); 2 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 3 | .LURemoteConnectionType; 4 | const LUServerMessageType = require('../../../LU/Message Types/LUServerMessageType') 5 | .LUServerMessageType; 6 | const LUClientMessageType = require('../../../LU/Message Types/LUClientMessageType') 7 | .LUClientMessageType; 8 | const BitStream = require('node-raknet/structures/BitStream'); 9 | const { Reliability } = require('node-raknet/ReliabilityLayer.js'); 10 | const { 11 | MinifigDeleteResponse 12 | } = require('../../../LU/Messages/MinifigDeleteResponse'); 13 | 14 | const { Character } = require('../../../DB/LUJS'); 15 | 16 | function MSG_WORLD_CLIENT_CHARACTER_DELETE_REQUEST (handler) { 17 | handler.on( 18 | [ 19 | LURemoteConnectionType.server, 20 | LUServerMessageType.MSG_WORLD_CLIENT_CHARACTER_DELETE_REQUEST 21 | ].join(), 22 | function (server, packet, user) { 23 | const client = server.getClient(user.address); 24 | 25 | const characterID = packet.readLongLong(); 26 | 27 | Character.destroy({ 28 | where: { 29 | id: characterID 30 | } 31 | }).then(function () { 32 | const response = new MinifigDeleteResponse(); 33 | 34 | const send = new BitStream(); 35 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 36 | send.writeShort(LURemoteConnectionType.client); 37 | send.writeLong(LUClientMessageType.DELETE_CHARACTER_RESPONSE); 38 | send.writeByte(0); 39 | response.serialize(send); 40 | client.send(send, Reliability.RELIABLE_ORDERED); 41 | }); 42 | } 43 | ); 44 | } 45 | 46 | module.exports = MSG_WORLD_CLIENT_CHARACTER_DELETE_REQUEST; 47 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_WORLD_CLIENT_CHARACTER_LIST_REQUEST.js: -------------------------------------------------------------------------------- 1 | const RakMessages = require('node-raknet/RakMessages.js'); 2 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 3 | .LURemoteConnectionType; 4 | const LUServerMessageType = require('../../../LU/Message Types/LUServerMessageType') 5 | .LUServerMessageType; 6 | const LUClientMessageType = require('../../../LU/Message Types/LUClientMessageType') 7 | .LUClientMessageType; 8 | const BitStream = require('node-raknet/structures/BitStream'); 9 | const { Reliability } = require('node-raknet/ReliabilityLayer.js'); 10 | const MinifigList = require('../../../LU/Messages/MinifigList'); 11 | const Sequelize = require('sequelize'); 12 | 13 | const { Character, InventoryItem } = require('../../../DB/LUJS'); 14 | 15 | function MSG_WORLD_CLIENT_CHARACTER_LIST_REQUEST (handler) { 16 | handler.on( 17 | [ 18 | LURemoteConnectionType.server, 19 | LUServerMessageType.MSG_WORLD_CLIENT_CHARACTER_LIST_REQUEST 20 | ].join(), 21 | function (server, packet, user) { 22 | const client = server.getClient(user.address); 23 | 24 | const list = function () { 25 | const Op = Sequelize.Op; 26 | 27 | Character.findAll({ 28 | where: { 29 | user_id: client.user_id 30 | } 31 | }).then(characters => { 32 | const promises = []; 33 | const response = new MinifigList(); 34 | characters.forEach(function (character) { 35 | const id = 0x1de0b6b500000000n + BigInt(character.id); 36 | const char = { 37 | id: id, 38 | unknown1: 0, 39 | name: character.name, 40 | unapprovedName: character.unapproved_name, 41 | nameRejected: false, 42 | freeToPlay: false, 43 | unknown2: '', 44 | shirtColor: character.shirt_color, 45 | shirtStyle: character.shirt_style, 46 | pantsColor: character.pants_color, 47 | hairStyle: character.hair_style, 48 | hairColor: character.hair_color, 49 | lh: character.lh, 50 | rh: character.rh, 51 | eyebrows: character.eyebrows, 52 | eyes: character.eyes, 53 | mouth: character.mouth, 54 | unknown3: 0, 55 | zone: character.zone, 56 | instance: character.instance, 57 | clone: character.clone, 58 | last_log: character.last_log, 59 | items: [] 60 | }; 61 | 62 | promises.push( 63 | InventoryItem.findAll({ 64 | where: { 65 | [Op.and]: [ 66 | { character_id: character.id }, 67 | { is_equipped: true } 68 | ] 69 | } 70 | }).then(function (items) { 71 | items.forEach(function (item) { 72 | char.items.push(item.lot); 73 | }); 74 | response.characters.push(char); 75 | }) 76 | ); 77 | }); 78 | Promise.all(promises).then(function () { 79 | const send = new BitStream(); 80 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 81 | send.writeShort(LURemoteConnectionType.client); 82 | send.writeLong(LUClientMessageType.CHARACTER_LIST_RESPONSE); 83 | send.writeByte(0); 84 | response.serialize(send); 85 | client.send(send, Reliability.RELIABLE_ORDERED); 86 | }); 87 | }); 88 | }; 89 | 90 | handler.on(`user-authenticated-${user.address}-${user.port}`, list); 91 | } 92 | ); 93 | } 94 | 95 | module.exports = MSG_WORLD_CLIENT_CHARACTER_LIST_REQUEST; 96 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_WORLD_CLIENT_GAME_MSG.js: -------------------------------------------------------------------------------- 1 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 2 | .LURemoteConnectionType; 3 | const LUServerMessageType = require('../../../LU/Message Types/LUServerMessageType') 4 | .LUServerMessageType; 5 | const GameMessageKey = require('lugamemessages/GameMessages').GameMessageKey; 6 | const GameMessageFactory = require('../../../LU/GameMessageFactory'); 7 | 8 | function MSG_WORLD_CLIENT_GAME_MSG (handler) { 9 | handler.on( 10 | [ 11 | LURemoteConnectionType.server, 12 | LUServerMessageType.MSG_WORLD_CLIENT_GAME_MSG 13 | ].join(), 14 | function (server, packet, user) { 15 | const client = server.getClient(user.address); 16 | const targetID = packet.readLongLong(); 17 | const id = packet.readShort(); 18 | 19 | console.log( 20 | `Game Message with ID ${GameMessageKey.key(id)}(${id.toString( 21 | 16 22 | )}) for object ${targetID.toString(16)}` 23 | ); 24 | 25 | // TODO: route these to a game message manager as an event 26 | 27 | const gm = GameMessageFactory.generateMessageFromBitStream(id, packet); 28 | 29 | const obj = server 30 | .getServer() 31 | .manager.getManager('replica') 32 | .getObject(targetID); 33 | if (obj !== undefined) obj.emitGM(id, gm, client); 34 | else console.log(`Target not found ${targetID.toString(16)}`); 35 | } 36 | ); 37 | } 38 | 39 | module.exports = MSG_WORLD_CLIENT_GAME_MSG; 40 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_WORLD_CLIENT_LEVEL_LOAD_COMPLETE.js: -------------------------------------------------------------------------------- 1 | const zlib = require('zlib'); 2 | const RakMessages = require('node-raknet/RakMessages.js'); 3 | const BitStream = require('node-raknet/structures/BitStream'); 4 | const { Reliability } = require('node-raknet/ReliabilityLayer.js'); 5 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 6 | .LURemoteConnectionType; 7 | const LUServerMessageType = require('../../../LU/Message Types/LUServerMessageType') 8 | .LUServerMessageType; 9 | const LUClientMessageType = require('../../../LU/Message Types/LUClientMessageType') 10 | .LUClientMessageType; 11 | const LDF = require('../../../LU/LDF'); 12 | const GameMessageFactory = require('../../../LU/GameMessageFactory'); 13 | const GameMessageKey = require('lugamemessages/GameMessages').GameMessageKey; 14 | const SerializationType = require('../../../LU/Replica/SerializationType'); 15 | const CharData = require('../../../LU/CharData'); 16 | const util = require('util'); 17 | 18 | const { Character } = require('../../../DB/LUJS'); 19 | 20 | const zlibDeflate = util.promisify(zlib.deflate); 21 | 22 | function MSG_WORLD_CLIENT_LOGIN_REQUEST (handler) { 23 | handler.on( 24 | [ 25 | LURemoteConnectionType.server, 26 | LUServerMessageType.MSG_WORLD_CLIENT_LEVEL_LOAD_COMPLETE 27 | ].join(), 28 | (server, packet, user) => { 29 | const client = server.getClient(user.address); 30 | 31 | Character.findByPk(client.session.character_id).then(character => { 32 | const charData = new BitStream(); 33 | charData.writeByte(RakMessages.ID_USER_PACKET_ENUM); 34 | charData.writeShort(LURemoteConnectionType.client); 35 | charData.writeLong(LUClientMessageType.CREATE_CHARACTER); 36 | charData.writeByte(0); 37 | 38 | const id = 0x1de0b6b500000000n + BigInt(character.id); 39 | const cont = new BitStream(); 40 | const ldf = new LDF(); 41 | ldf.addLWOOBJID('objid', id); 42 | ldf.addSignedLong('template', 1); 43 | 44 | const charDataXml = new CharData(); 45 | charDataXml.setCharacterData(character.id, 0, 0, 0); 46 | charDataXml.setMinifigureData( 47 | character.hair_color, 48 | character.hair_style, 49 | character.shirt_color, 50 | character.pants_color, 51 | character.lh, 52 | character.rh, 53 | character.eyebrows, 54 | character.eyes, 55 | character.mouth 56 | ); 57 | charDataXml.setLevelInformation(1); 58 | 59 | return character 60 | .getItems() 61 | .then(items => { 62 | items.forEach(item => { 63 | charDataXml.addItem( 64 | item.lot, 65 | (0x1000000100000000n + BigInt(item.id)).toString(), 66 | item.slot, 67 | item.count, 68 | item.is_equipped, 69 | item.is_linked 70 | ); 71 | }); 72 | ldf.addByteString('xmlData', charDataXml.xml.toString()); 73 | 74 | console.log(charDataXml.xml.toString()); 75 | ldf.serialize(cont); 76 | }) 77 | .then(() => { 78 | return zlibDeflate(cont.data); 79 | }) 80 | .then(buffer => { 81 | const compressedData = new BitStream(buffer); 82 | charData.writeLong(compressedData.length() + 9); 83 | charData.writeBoolean(true); 84 | charData.writeLong(cont.length()); 85 | charData.writeLong(compressedData.length()); 86 | charData.concat(compressedData); 87 | // charData.toFile((Date.now() / 1000 | 0) + "_chardata.bin"); 88 | return client.send(charData, Reliability.RELIABLE_ORDERED); 89 | }) 90 | .then(() => { 91 | const lwoobjid = new LWOOBJID(0x1de0b6b5, character.id); 92 | return server 93 | .getServer() 94 | .manager.getManager('replica') 95 | .loadObject( 96 | 1, 97 | { x: character.x, y: character.y, z: character.z }, 98 | { 99 | x: character.rotation_x, 100 | y: character.rotation_y, 101 | z: character.rotation_z, 102 | w: character.rotation_w 103 | }, 104 | 1, 105 | undefined, 106 | undefined, 107 | lwoobjid 108 | ); 109 | }) 110 | .then(object => { 111 | const characterManager = server 112 | .getServer() 113 | .manager.getManager('character'); 114 | const charCompData = characterManager.getObjectData(object.ID); 115 | charCompData.accountID = character.ID; 116 | charCompData.hairColor = character.hair_color; 117 | charCompData.hairStyle = character.hair_style; 118 | charCompData.shirtColor = character.shirt_color; 119 | charCompData.pantsColor = character.pants_color; 120 | charCompData.eyebrows = character.eyebrows; 121 | charCompData.eyes = character.eyes; 122 | charCompData.mouth = character.mouth; 123 | characterManager.setObjectData(object.ID, charCompData); 124 | 125 | return character.getItems().then(items => { 126 | const inv = { 127 | inventory: [] 128 | }; 129 | items.forEach(item => { 130 | const invItem = {}; 131 | invItem.id = 0x1000000100000000n + BigInt(item.id); 132 | invItem.lot = item.lot; 133 | invItem.count = item.count; 134 | invItem.slot = item.slot; 135 | 136 | inv.inventory.push(invItem); 137 | }); 138 | 139 | const inventoryManager = server 140 | .getServer() 141 | .manager.getManager('inventory'); 142 | inventoryManager.setObjectData(object.ID, inv); 143 | return object; 144 | }); 145 | }) 146 | .then(object => { 147 | const stream = new BitStream(); 148 | stream.writeByte(RakMessages.ID_REPLICA_MANAGER_CONSTRUCTION); 149 | stream.writeBit(true); 150 | stream.writeShort(object.netID); 151 | object.serialize(SerializationType.CREATION, stream); 152 | 153 | // stream.toFile((Date.now() / 1000 | 0) + "_[24]_[01-00]_(1).bin"); 154 | 155 | return client.send(stream, Reliability.RELIABLE).then(() => { 156 | return object; 157 | }); 158 | }) 159 | .then(object => { 160 | const stream = new BitStream(); 161 | stream.writeByte(RakMessages.ID_USER_PACKET_ENUM); 162 | stream.writeShort(LURemoteConnectionType.client); 163 | stream.writeLong(LUClientMessageType.GAME_MSG); 164 | stream.writeByte(0); 165 | stream.writeLongLong(object.ID); 166 | GameMessageFactory.makeMessage( 167 | GameMessageKey.serverDoneLoadingAllObjects, 168 | {} 169 | ).serialize(stream); 170 | return client.send(stream, Reliability.RELIABLE).then(() => { 171 | return object; 172 | }); 173 | }) 174 | .then(object => { 175 | // attach the chat command listener to the player... 176 | object.addGMListener( 177 | GameMessageKey.parseChatMessage, 178 | (gm, user, obj) => { 179 | server.getServer().eventBus.emit('chat', gm, user, obj); 180 | } 181 | ); 182 | 183 | object.addGMListener( 184 | GameMessageKey.playerLoaded, 185 | (gm, user, obj) => { 186 | const stream = new BitStream(); 187 | stream.writeByte(RakMessages.ID_USER_PACKET_ENUM); 188 | stream.writeShort(LURemoteConnectionType.client); 189 | stream.writeLong(LUClientMessageType.GAME_MSG); 190 | stream.writeByte(0); 191 | object.ID.serialize(stream); 192 | GameMessageFactory.makeMessage( 193 | GameMessageKey.playerReady, 194 | {} 195 | ).serialize(stream); 196 | client.send(stream, Reliability.RELIABLE); 197 | } 198 | ); 199 | }); 200 | }); 201 | } 202 | ); 203 | } 204 | 205 | module.exports = MSG_WORLD_CLIENT_LOGIN_REQUEST; 206 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_WORLD_CLIENT_LOGIN_REQUEST.js: -------------------------------------------------------------------------------- 1 | const RakMessages = require('node-raknet/RakMessages.js'); 2 | const BitStream = require('node-raknet/structures/BitStream'); 3 | const { Reliability } = require('node-raknet/ReliabilityLayer.js'); 4 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 5 | .LURemoteConnectionType; 6 | const LUServerMessageType = require('../../../LU/Message Types/LUServerMessageType') 7 | .LUServerMessageType; 8 | const LUClientMessageType = require('../../../LU/Message Types/LUClientMessageType') 9 | .LUClientMessageType; 10 | const TransferToWorld = require('../../../LU/Messages/TransferToWorld'); 11 | const { ServerManager } = require('../../ServerManager'); 12 | const { Character } = require('../../../DB/LUJS'); 13 | 14 | function MSG_WORLD_CLIENT_LOGIN_REQUEST (handler) { 15 | handler.on( 16 | [ 17 | LURemoteConnectionType.server, 18 | LUServerMessageType.MSG_WORLD_CLIENT_LOGIN_REQUEST 19 | ].join(), 20 | function (server, packet, user) { 21 | const client = server.getClient(user.address); 22 | 23 | let characterID = packet.readLongLong(); 24 | 25 | client.session.character_id = Number(characterID); 26 | client.session.save(); 27 | 28 | Character.findOne({ 29 | where: { 30 | id: characterID.low 31 | } 32 | }).then(function (character) { 33 | if (character.zone === 0) { 34 | character.zone = 1000; 35 | } 36 | 37 | ServerManager.request(character.zone).then(server => { 38 | const zone = server; 39 | character.x = zone.luz.spawnX; 40 | character.y = zone.luz.spawnY; 41 | character.z = zone.luz.spawnZ; 42 | character.rotation_x = zone.luz.spawnrX; 43 | character.rotation_y = zone.luz.spawnrY; 44 | character.rotation_z = zone.luz.spawnrZ; 45 | character.rotation_w = zone.luz.spawnrW; 46 | character.save(); 47 | 48 | const response = new TransferToWorld(); 49 | response.ip = zone.ip; 50 | response.port = zone.port; 51 | response.mythranShift = false; 52 | 53 | const send = new BitStream(); 54 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 55 | send.writeShort(LURemoteConnectionType.client); 56 | send.writeLong(LUClientMessageType.TRANSFER_TO_WORLD); 57 | send.writeByte(0); 58 | response.serialize(send); 59 | client.send(send, Reliability.RELIABLE_ORDERED); 60 | }); 61 | }); 62 | } 63 | ); 64 | } 65 | 66 | module.exports = MSG_WORLD_CLIENT_LOGIN_REQUEST; 67 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_WORLD_CLIENT_POSITION_UPDATE.js: -------------------------------------------------------------------------------- 1 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 2 | .LURemoteConnectionType; 3 | const LUServerMessageType = require('../../../LU/Message Types/LUServerMessageType') 4 | .LUServerMessageType; 5 | 6 | function MSG_WORLD_CLIENT_GAME_MSG (handler) { 7 | handler.on( 8 | [ 9 | LURemoteConnectionType.server, 10 | LUServerMessageType.MSG_WORLD_CLIENT_POSITION_UPDATE 11 | ].join(), 12 | function (server, packet, user) { 13 | // I don't really care about this for now, but this is where I would run validation checks? 14 | // put this here to prevent the log from clogging up 15 | } 16 | ); 17 | } 18 | 19 | module.exports = MSG_WORLD_CLIENT_GAME_MSG; 20 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_WORLD_CLIENT_ROUTE_PACKET.js: -------------------------------------------------------------------------------- 1 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 2 | .LURemoteConnectionType; 3 | const LUServerMessageType = require('../../../LU/Message Types/LUServerMessageType') 4 | .LUServerMessageType; 5 | 6 | function MSG_WORLD_CLIENT_ROUTE_PACKET (handler) { 7 | handler.on( 8 | [ 9 | LURemoteConnectionType.server, 10 | LUServerMessageType.MSG_WORLD_CLIENT_ROUTE_PACKET 11 | ].join(), 12 | (server, packet, user) => { 13 | // TODO: something I guess? 14 | } 15 | ); 16 | } 17 | 18 | module.exports = MSG_WORLD_CLIENT_ROUTE_PACKET; 19 | -------------------------------------------------------------------------------- /src/server/Handles/UserHandles/MSG_WORLD_CLIENT_VALIDATION.js: -------------------------------------------------------------------------------- 1 | const RakMessages = require('node-raknet/RakMessages.js'); 2 | const LURemoteConnectionType = require('../../../LU/Message Types/LURemoteConnectionType') 3 | .LURemoteConnectionType; 4 | const LUServerMessageType = require('../../../LU/Message Types/LUServerMessageType') 5 | .LUServerMessageType; 6 | const LUGeneralMessageType = require('../../../LU/Message Types/LUGeneralMessageType') 7 | .LUGeneralMessageType; 8 | const BitStream = require('node-raknet/structures/BitStream'); 9 | const { Reliability } = require('node-raknet/ReliabilityLayer.js'); 10 | const UserSessionInfo = require('../../../LU/Messages/UserSessionInfo'); 11 | const { 12 | DisconnectNotify, 13 | DisconnectNotifyReason 14 | } = require('../../../LU/Messages/DisconnectNotify'); 15 | const Sequelize = require('sequelize'); 16 | 17 | const { Session, User } = require('../../../DB/LUJS'); 18 | 19 | function MSG_WORLD_CLIENT_VALIDATION (handler) { 20 | handler.on( 21 | [ 22 | LURemoteConnectionType.server, 23 | LUServerMessageType.MSG_WORLD_CLIENT_VALIDATION 24 | ].join(), 25 | function (server, packet, user) { 26 | const client = server.getClient(user.address); 27 | 28 | const sessionInfo = new UserSessionInfo(); 29 | sessionInfo.deserialize(packet); 30 | 31 | User.findOne({ 32 | where: { 33 | username: sessionInfo.username 34 | } 35 | }).then(userDB => { 36 | if (userDB !== null) { 37 | const Op = Sequelize.Op; 38 | Session.findOne({ 39 | where: { 40 | [Op.and]: [ 41 | { user_id: userDB.id }, 42 | { start_time: { [Op.lt]: new Date() } }, 43 | { end_time: { [Op.gt]: new Date() } }, 44 | { key: sessionInfo.key }, 45 | { ip: user.address } 46 | ] 47 | } 48 | }).then(session => { 49 | if (session === null) { 50 | // We didn't find a valid session for this user... Time to disconnect them... 51 | const response = new DisconnectNotify(); // TODO: Investigate error... 52 | response.reason = DisconnectNotifyReason.INVALID_SESSION_KEY; 53 | 54 | const send = new BitStream(); 55 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 56 | send.writeShort(LURemoteConnectionType.general); 57 | send.writeLong(LUGeneralMessageType.MSG_SERVER_DISCONNECT_NOTIFY); 58 | send.writeByte(0); 59 | response.serialize(send); 60 | client.send(send, Reliability.RELIABLE_ORDERED); 61 | } else { 62 | client.user_id = userDB.id; 63 | client.session = session; 64 | handler.emit(`user-authenticated-${user.address}-${user.port}`); 65 | } 66 | }); 67 | } else { 68 | const response = new DisconnectNotify(); // TODO: Investigate error... 69 | response.reason = DisconnectNotifyReason.INVALID_SESSION_KEY; 70 | 71 | const send = new BitStream(); 72 | send.writeByte(RakMessages.ID_USER_PACKET_ENUM); 73 | send.writeShort(LURemoteConnectionType.general); 74 | send.writeLong(LUGeneralMessageType.MSG_SERVER_DISCONNECT_NOTIFY); 75 | send.writeByte(0); 76 | response.serialize(send); 77 | client.send(send, Reliability.RELIABLE_ORDERED); 78 | } 79 | }); 80 | } 81 | ); 82 | } 83 | 84 | module.exports = MSG_WORLD_CLIENT_VALIDATION; 85 | -------------------------------------------------------------------------------- /src/server/Loader.ts: -------------------------------------------------------------------------------- 1 | import { servers, ServerManager } from './ServerManager'; 2 | import * as fs from 'fs'; 3 | import * as path from 'path'; 4 | import * as util from 'util'; 5 | import Commands from './Commands'; 6 | import PluginLoader from './PluginLoader'; 7 | import * as config from 'config'; 8 | 9 | const readdir = util.promisify(fs.readdir); 10 | 11 | export default class Loader { 12 | static setup() { 13 | const normalizedPath = path.join(__dirname, config.get('handlers')); 14 | readdir(normalizedPath) 15 | .then((files) => { 16 | const handles = []; 17 | files.forEach((file) => { 18 | if(file.split('.')[file.split('.').length - 1] !== 'js') return; 19 | 20 | import(config.get('handlers') + file).then((handle) => { 21 | handles.push(handle.default); 22 | }); 23 | }); 24 | 25 | this.startServersFromConfig(handles); 26 | }) 27 | .then(() => { 28 | Commands.instance(); 29 | return PluginLoader.load(config, servers); 30 | }); 31 | } 32 | 33 | static startServersFromConfig(handles) { 34 | config.get('servers').forEach(function (server) { 35 | ServerManager.startServer( 36 | server.ip, 37 | server.port, 38 | server.password, 39 | server.zone 40 | ); 41 | }); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/server/Plugin.ts: -------------------------------------------------------------------------------- 1 | import { Server } from './Server'; 2 | 3 | export default abstract class Plugin { 4 | abstract register(server: Server): void; 5 | abstract unregister(server: Server): void; 6 | abstract load(): void; 7 | abstract unload(): void; 8 | } 9 | -------------------------------------------------------------------------------- /src/server/PluginLoader.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as md5file from 'md5-file'; 3 | import * as unzip from 'extract-zip'; 4 | import * as pathlib from 'path'; 5 | import Plugin from './Plugin'; 6 | import * as util from 'util'; 7 | 8 | const readdir = util.promisify(fs.readdir); 9 | 10 | class PluginWrapper { 11 | name: string; 12 | plugin: Plugin; 13 | registered: boolean; 14 | 15 | constructor(name, plugin, registered) { 16 | this.name = name; 17 | this.plugin = plugin; 18 | this.registered = registered; 19 | } 20 | } 21 | 22 | export default class PluginLoader { 23 | plugins: Array; 24 | 25 | constructor() { 26 | this.plugins = []; 27 | } 28 | 29 | static getPlugins() { 30 | return loader.plugins; 31 | } 32 | 33 | static load(config, servers) { 34 | return readdir(config.pluginPath, { withFileTypes: true }) 35 | .then((files) => { 36 | const foundFiles = []; 37 | files.forEach((file) => { 38 | if (file.isFile() && file.name.match(/.*\.zip$/g)) { 39 | foundFiles.push(file); 40 | } 41 | }); 42 | return foundFiles; 43 | }) 44 | .then((files) => { 45 | const promises = []; 46 | for (let i = 0; i < files.length; i++) { 47 | const file = files[i]; 48 | promises.push( 49 | this.isAlreadyUnzipped(config.pluginPath, file.name).then( 50 | (isUnzipped) => { 51 | if (!isUnzipped) { 52 | return this.unzip(config.pluginPath, file.name).then(() => { 53 | return this.loadPluginFromFile( 54 | config.pluginPath, 55 | file.name 56 | ); 57 | }); 58 | } else { 59 | return this.loadPluginFromFile(config.pluginPath, file.name); 60 | } 61 | } 62 | ) 63 | ); 64 | } 65 | return Promise.all(promises); 66 | }) 67 | .then(() => { 68 | loader.plugins.forEach((plugin) => { 69 | servers.forEach((server) => { 70 | plugin.plugin.register(server); 71 | }); 72 | console.log(`Registered ${this.getUniformName(plugin.name)}`); 73 | }); 74 | }); 75 | } 76 | 77 | static getUniformName(name) { 78 | return name.toLowerCase().replace(/ /g, '-'); 79 | } 80 | 81 | static reloadPlugin(pluginName) { 82 | this.unloadPlugin(pluginName); 83 | this.loadPlugin(pluginName); 84 | } 85 | 86 | static unloadPlugin(pluginName) { 87 | loader.plugins.forEach((plugin) => { 88 | if (pluginName === this.getUniformName(plugin.name)) { 89 | plugin.plugin.unload(); 90 | } 91 | }); 92 | } 93 | 94 | static loadPlugin(pluginName) { 95 | loader.plugins.forEach((plugin) => { 96 | if (pluginName === this.getUniformName(plugin.name)) { 97 | plugin.plugin.load(); 98 | } 99 | }); 100 | } 101 | 102 | static reregisterPlugin(pluginName, servers) { 103 | this.unregisterPlugin(pluginName, servers); 104 | this.registerPlugin(pluginName, servers); 105 | } 106 | 107 | static registerPlugin(pluginName, servers) { 108 | servers.forEach((server) => { 109 | loader.plugins.forEach((plugin) => { 110 | if (pluginName === this.getUniformName(plugin.name)) { 111 | plugin.plugin.register(server); 112 | } 113 | }); 114 | }); 115 | } 116 | 117 | static unregisterPlugin(pluginName, servers) { 118 | servers.forEach((server) => { 119 | loader.plugins.forEach((plugin) => { 120 | if (pluginName === this.getUniformName(plugin.name)) { 121 | plugin.plugin.unregister(server); 122 | } 123 | }); 124 | }); 125 | } 126 | 127 | static loadPluginFromFile(path, zipName) { 128 | return this.getHash(path, zipName).then((hash) => { 129 | return this.loadInfoFromFile(`${path}unpacked/${hash}/info.json`).then( 130 | (info) => { 131 | import(`${path}unpacked/${hash}/index.js`).then((PluginClass) => { 132 | const plugin = new PluginClass(); 133 | plugin.load(); 134 | loader.plugins.push(new PluginWrapper(info, plugin, false)); 135 | }); 136 | } 137 | ); 138 | }); 139 | } 140 | 141 | static loadInfoFromFile(file) { 142 | return new Promise((resolve, reject) => { 143 | fs.readFile(file, { encoding: 'utf8', flag: 'r' }, (err, data) => { 144 | if (err) reject(err); 145 | else { 146 | resolve(JSON.parse(data)); 147 | } 148 | }); 149 | }); 150 | } 151 | 152 | static unzip(path, zipName) { 153 | return this.getHash(path, zipName).then((hash) => { 154 | return unzip(path + zipName, { 155 | dir: pathlib.resolve(__dirname, `${path}unpacked/${hash}`) 156 | }); 157 | }); 158 | } 159 | 160 | static getHash(path, fileName) { 161 | return new Promise((resolve, reject) => { 162 | md5file(path + fileName, (err, hash) => { 163 | if (err) reject(err); 164 | 165 | resolve(hash); 166 | }); 167 | }); 168 | } 169 | 170 | static isAlreadyUnzipped(path, fileName) { 171 | return this.getHash(path, fileName).then((hash) => { 172 | return new Promise((resolve, reject) => { 173 | fs.readdir( 174 | `${path}unpacked/${hash}`, 175 | { withFileTypes: true }, 176 | (err, files) => { 177 | if (err) resolve(false); 178 | 179 | let found = false; 180 | 181 | if (files === undefined) resolve(found); 182 | else { 183 | files.forEach((file) => { 184 | if (file.isFile() && file.name.match(/index\.js$/g)) { 185 | found = true; 186 | } 187 | }); 188 | resolve(found); 189 | } 190 | } 191 | ); 192 | }); 193 | }); 194 | } 195 | } 196 | 197 | const loader = new PluginLoader(); 198 | -------------------------------------------------------------------------------- /src/server/Server.ts: -------------------------------------------------------------------------------- 1 | import * as fs from 'fs'; 2 | import * as util from 'util'; 3 | import * as config from 'config'; 4 | 5 | const readFile = util.promisify(fs.readFile); 6 | 7 | import LUZ from '../LU/Level/luz'; 8 | import BitStream from 'node-raknet/structures/BitStream'; 9 | import ReplicaManager from '../LU/Managers/ReplicaManager'; 10 | import LWOOBJIDManager from '../LU/Managers/LWOOBJIDManager'; 11 | import ChatManager from '../LU/Managers/ChatManager'; 12 | 13 | // Replica component managers 14 | import CharacterManager from '../LU/Managers/ReplicaManagers/CharacterManager'; 15 | import ControllablePhysicsManager from '../LU/Managers/ReplicaManagers/ControllablePhysicsManager'; 16 | import DestructibleManager from '../LU/Managers/ReplicaManagers/DestructibleManager'; 17 | import InventoryManager from '../LU/Managers/ReplicaManagers/InventoryManager'; 18 | import RenderManager from '../LU/Managers/ReplicaManagers/RenderManager'; 19 | import RocketLandingManager from '../LU/Managers/ReplicaManagers/RocketLandingManager'; 20 | import SkillManager from '../LU/Managers/ReplicaManagers/SkillManager'; 21 | import SoundAmbient2DManager from '../LU/Managers/ReplicaManagers/SoundAmbient2DManager'; 22 | import Unknown107Manager from '../LU/Managers/ReplicaManagers/Unknown107Manager'; 23 | 24 | import { ZoneTable } from '../DB/CDClient'; 25 | 26 | import RakServer from 'node-raknet/RakServer'; 27 | import * as events from 'events'; 28 | import Manager from '../LU/Managers/Manager'; 29 | 30 | export class RakServerExtended extends RakServer { 31 | userMessageHandler: events.EventEmitter; 32 | parent: Server; 33 | timeout; 34 | 35 | constructor(ip: string, port: number, password: string) { 36 | super(ip, port, password); 37 | 38 | this.userMessageHandler = new events.EventEmitter(); 39 | } 40 | } 41 | 42 | /** 43 | * A server instance 44 | */ 45 | export class Server { 46 | #rakserver: RakServerExtended; 47 | #ip: string; 48 | #port: number; 49 | #zoneID: number; 50 | #manager: Manager; 51 | #luz; 52 | 53 | /** 54 | * 55 | * @param {RakServerExtended} rakserver The rakserver instance to attach to this server 56 | * @param {String} ip 57 | * @param {Number} port 58 | * @param {Number} zoneID the zone ID of the zone you want to load 59 | */ 60 | constructor( 61 | rakserver: RakServerExtended, 62 | ip: string, 63 | port: number, 64 | zoneID: number 65 | ) { 66 | this.#rakserver = rakserver; 67 | this.#ip = ip; 68 | this.#port = port; 69 | this.#zoneID = zoneID; 70 | 71 | this.#rakserver.parent = this; 72 | 73 | // if(this.zoneID > 0) { 74 | // this.loadLUZ(this.zoneID); 75 | // } 76 | 77 | // Attach managers 78 | this.#manager = new Manager(); 79 | this.#manager.attachManager('replica', new ReplicaManager(this)); 80 | this.#manager.attachManager('lwoobjid', new LWOOBJIDManager(this)); 81 | this.#manager.attachManager('chat', new ChatManager(this)); 82 | 83 | // Replica Components 84 | this.#manager.attachManager('character', new CharacterManager(this)); 85 | this.#manager.attachManager( 86 | 'controllable-physics', 87 | new ControllablePhysicsManager(this) 88 | ); 89 | this.#manager.attachManager('destructible', new DestructibleManager(this)); 90 | this.#manager.attachManager('inventory', new InventoryManager(this)); 91 | this.#manager.attachManager('render', new RenderManager(this)); 92 | this.#manager.attachManager( 93 | 'rocket-landing', 94 | new RocketLandingManager(this) 95 | ); 96 | this.#manager.attachManager('skill', new SkillManager(this)); 97 | this.#manager.attachManager( 98 | 'sound-ambient-2d', 99 | new SoundAmbient2DManager(this) 100 | ); 101 | this.#manager.attachManager('unknown-127', new Unknown107Manager(this)); 102 | } 103 | 104 | /** 105 | * Closes this server 106 | * @returns {Promise<>} 107 | */ 108 | close() { 109 | return new Promise((resolve, reject) => { 110 | this.rakServer.server.close(() => { 111 | resolve(); 112 | }); 113 | }); 114 | } 115 | 116 | /** 117 | * Returns the rakserver instance associated to this server 118 | * @return {RakServerExtended} 119 | */ 120 | get rakServer(): RakServerExtended { 121 | return this.#rakserver; 122 | } 123 | 124 | /** 125 | * Returns the IP of this server to redirect to 126 | * @returns {string} 127 | */ 128 | get ip(): string { 129 | return this.#ip; 130 | } 131 | 132 | /** 133 | * Returns this port of this server to redirect to 134 | * @returns {number} 135 | */ 136 | get port(): number { 137 | return this.#port; 138 | } 139 | 140 | /** 141 | * Returns the event bus for this server 142 | * @return {events.EventEmitter} 143 | */ 144 | get eventBus(): events.EventEmitter { 145 | return this.#rakserver.userMessageHandler; 146 | } 147 | 148 | /** 149 | * Returns the zone ID associated with this server 150 | * @return {number} 151 | */ 152 | get zoneID() { 153 | return this.#zoneID; 154 | } 155 | 156 | /** 157 | * Returns the root Manager of this server 158 | * @return {Manager} 159 | */ 160 | get manager() { 161 | return this.#manager; 162 | } 163 | 164 | /** 165 | * Returns the LUZ of this server 166 | * @return {LUZ} 167 | */ 168 | get luz() { 169 | return this.#luz; 170 | } 171 | 172 | /** 173 | * Loads an LUZ file given a zone ID 174 | * @param {Number} zoneID 175 | * @return {Promise<>} 176 | */ 177 | loadLUZ(zoneID) { 178 | return ZoneTable.findByPk(zoneID) 179 | .then((zone) => { 180 | return readFile(config.get('mapsFolder') + zone.zoneName); 181 | }) 182 | .then((file) => { 183 | //server.#luz = new LUZ(new BitStream(file)); 184 | }); 185 | } 186 | 187 | /** 188 | * Broadcasts message to all clients connected 189 | * @param {BitStream} stream 190 | * @param {Number} reliability 191 | */ 192 | broadcast(stream, reliability) { 193 | this.#rakserver.connections.forEach((client) => { 194 | client.send(stream, reliability); 195 | }); 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /src/server/ServerManager.ts: -------------------------------------------------------------------------------- 1 | import * as config from 'config'; 2 | import * as util from 'util'; 3 | import * as fs from 'fs'; 4 | import * as path from 'path'; 5 | import * as events from 'events'; 6 | import { Server, RakServerExtended } from './Server'; 7 | 8 | const readdir = util.promisify(fs.readdir); 9 | 10 | export const servers: Array = []; 11 | 12 | const startPort = 3000; 13 | // const poolSize = 5; TODO: use this 14 | let currentPort = startPort; 15 | const ip = config.globalIP; 16 | const password = '3.25 ND'; 17 | 18 | export class ServerManager { 19 | /** 20 | * 21 | * @param {Server} server 22 | */ 23 | static add(server) { 24 | servers.push(server); 25 | } 26 | 27 | /** 28 | * 29 | * @param ip 30 | * @param port 31 | * @param password 32 | * @param zoneID 33 | * @returns {Promise} 34 | */ 35 | static startServer( 36 | ip: string, 37 | port: number, 38 | password: string, 39 | zoneID: number 40 | ): Promise { 41 | return new Promise((resolve) => { 42 | const normalizedPath = path.join(__dirname, config.get('handlers')); 43 | readdir(normalizedPath) 44 | .then((files) => { 45 | const handles = []; 46 | files.forEach(function (file) { 47 | if(file.split('.')[file.split('.').length - 1] !== 'js') return; 48 | 49 | import(config.get('handlers') + file).then((handle) => { 50 | handles.push(handle.default); 51 | }); 52 | }); 53 | 54 | return handles; 55 | }) 56 | .then((handles) => { 57 | const rakServer = new RakServerExtended('0.0.0.0', port, password); 58 | 59 | rakServer.server.on('listening', () => { 60 | handles.forEach(function (handle) { 61 | handle(rakServer); 62 | }); 63 | }); 64 | 65 | const server = new Server(rakServer, ip, port, zoneID); 66 | ServerManager.add(server); 67 | 68 | if (zoneID > 0) { 69 | server.loadLUZ(zoneID).then(() => { 70 | resolve(server); 71 | }); 72 | } else { 73 | resolve(server); 74 | } 75 | }); 76 | }); 77 | } 78 | 79 | /** 80 | * 81 | * @param {Server} server 82 | */ 83 | static remove(server: Server): void { 84 | let index = -1; 85 | servers.forEach((server_, i) => { 86 | if (server_.rakServer.port === server.rakServer.port) { 87 | index = i; 88 | } 89 | }); 90 | 91 | servers[index].close().then(() => { 92 | servers.splice(index); 93 | }); 94 | } 95 | 96 | /** 97 | * @param {Number} zoneID 98 | * @return {Promise} 99 | */ 100 | static request(zoneID: number): Promise { 101 | return new Promise((resolve, reject) => { 102 | // find an existing server 103 | for (const server of servers) { 104 | if (server.zoneID === zoneID) { 105 | resolve(server); 106 | return; 107 | } 108 | } 109 | 110 | // or start one 111 | this.startServer(ip, currentPort, password, zoneID).then((server) => { 112 | resolve(server); 113 | currentPort++; 114 | }); 115 | }); 116 | } 117 | } 118 | -------------------------------------------------------------------------------- /src/server/index.ts: -------------------------------------------------------------------------------- 1 | // TODO: Add something that automatically updates files from the repo 2 | 3 | // Run the LU server... 4 | import Loader from './Loader'; 5 | Loader.setup(); 6 | 7 | // TODO: At some point I want an API server running... 8 | 9 | // TODO: I also want Discord Rich Presence Integration? 10 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "esnext", 5 | "sourceMap": true 6 | }, 7 | "exclude": [ 8 | "node_modules" 9 | ], 10 | "lib": [ 11 | "esnext" 12 | ] 13 | } --------------------------------------------------------------------------------