diff --git a/package-lock.json b/package-lock.json index 408fe16..ba1cac6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,9 @@ "license": "MIT", "dependencies": { "minecraft-protocol": "^1.30.0" + }, + "devDependencies": { + "prismarine-proxy": "^1.1.1" } }, "node_modules/@azure/msal-common": { @@ -278,6 +281,12 @@ "resolved": "https://registry.npmjs.org/minecraft-folder-path/-/minecraft-folder-path-1.2.0.tgz", "integrity": "sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==" }, + "node_modules/minecraft-packets": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/minecraft-packets/-/minecraft-packets-1.5.0.tgz", + "integrity": "sha512-my9kZoqdv3kLt8YTjmPkCSCM1An+NLxuCFhi6j8NTY2BF5JYHh69EHs/RghwxPUH5XPM0aaSAvs3/UCrOL/+hg==", + "dev": true + }, "node_modules/minecraft-protocol": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/minecraft-protocol/-/minecraft-protocol-1.30.0.tgz", @@ -357,6 +366,18 @@ "protodef": "^1.9.0" } }, + "node_modules/prismarine-proxy": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prismarine-proxy/-/prismarine-proxy-1.1.1.tgz", + "integrity": "sha512-A+Kn2jm80R7GVJaUwWIFCY8iqxWeF4YU7L+h+DwQ6L185etpFUmHqP7wXgkzcDAyMOm3uikz6YpBMopH2YJy/A==", + "dev": true, + "dependencies": { + "debug": "^4.3.1", + "minecraft-data": "^2.94.0", + "minecraft-packets": "^1.5.0", + "minecraft-protocol": "^1.13.0" + } + }, "node_modules/protodef": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/protodef/-/protodef-1.14.0.tgz", @@ -730,6 +751,12 @@ "resolved": "https://registry.npmjs.org/minecraft-folder-path/-/minecraft-folder-path-1.2.0.tgz", "integrity": "sha512-qaUSbKWoOsH9brn0JQuBhxNAzTDMwrOXorwuRxdJKKKDYvZhtml+6GVCUrY5HRiEsieBEjCUnhVpDuQiKsiFaw==" }, + "minecraft-packets": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/minecraft-packets/-/minecraft-packets-1.5.0.tgz", + "integrity": "sha512-my9kZoqdv3kLt8YTjmPkCSCM1An+NLxuCFhi6j8NTY2BF5JYHh69EHs/RghwxPUH5XPM0aaSAvs3/UCrOL/+hg==", + "dev": true + }, "minecraft-protocol": { "version": "1.30.0", "resolved": "https://registry.npmjs.org/minecraft-protocol/-/minecraft-protocol-1.30.0.tgz", @@ -795,6 +822,18 @@ "protodef": "^1.9.0" } }, + "prismarine-proxy": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/prismarine-proxy/-/prismarine-proxy-1.1.1.tgz", + "integrity": "sha512-A+Kn2jm80R7GVJaUwWIFCY8iqxWeF4YU7L+h+DwQ6L185etpFUmHqP7wXgkzcDAyMOm3uikz6YpBMopH2YJy/A==", + "dev": true, + "requires": { + "debug": "^4.3.1", + "minecraft-data": "^2.94.0", + "minecraft-packets": "^1.5.0", + "minecraft-protocol": "^1.13.0" + } + }, "protodef": { "version": "1.14.0", "resolved": "https://registry.npmjs.org/protodef/-/protodef-1.14.0.tgz", diff --git a/package.json b/package.json index 31b412e..eabc4c4 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "@solar-tweaks/minecraft-protocol-lunarclient", - "version": "1.0.1", + "version": "1.1.0", "description": "Custom packets used by Lunar Client for node-minecraft-protocol", "main": "src/index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "node ./test/proxy.js" }, "repository": { "type": "git", @@ -28,5 +28,8 @@ }, "publishConfig": { "registry": "https://npm.pkg.github.com" + }, + "devDependencies": { + "prismarine-proxy": "^1.1.1" } } diff --git a/src/LCPlayer.js b/src/LCPlayer.js new file mode 100644 index 0000000..4fd2915 --- /dev/null +++ b/src/LCPlayer.js @@ -0,0 +1,111 @@ +const scheme = require('./scheme'); + +class LCPlayer { + constructor(client, channel = 'lunarclient:pm') { + this.client = client; + this.channel = channel; + this.waypoints = []; + this.teammates = []; + + this.client.write('custom_payload', { + channel: 'REGISTER', + data: Buffer.from(this.channel), + }); + this.client.registerChannel(this.channel, scheme); + } + + addWaypoint(waypoint) { + if (this.waypoints.find((w) => w.name === waypoint.name)) return false; + this.waypoints.push(waypoint); + this.client.writeChannel(this.channel, { + id: 'waypoint_add', + name: waypoint.name, + world: '', + color: waypoint.color, + x: waypoint.x, + y: waypoint.y, + z: waypoint.z, + forced: waypoint.forced, + visible: waypoint.visible, + }); + return true; + } + + removeWaypoint(waypoint) { + const _waypoint = typeof waypoint === 'string' ? waypoint : waypoint.name; + if (!this.waypoints.find((w) => w.name === _waypoint)) return false; + this.waypoints = this.waypoints.filter((w) => w.name !== _waypoint); + this.client.writeChannel(this.channel, { + id: 'waypoint_remove', + name: _waypoint, + world: '', + }); + return true; + } + + removeAllWaypoints() { + this.waypoints.forEach((waypoint) => { + this.removeWaypoint(waypoint.name); + }); + this.waypoints = []; + } + + sendNotification(message, durationMs, level = 'info') { + this.client.writeChannel(this.channel, { + id: 'notification', + message, + durationMs, + level, + }); + } + + addTeammate(uuid) { + if (this.teammates.find((t) => t === uuid)) return false; + this.teammates.push(uuid); + this.#sendTeammateList(); + return true; + } + + removeTeammate(uuid) { + if (!this.teammates.find((t) => t === uuid)) return false; + this.teammates = this.teammates.filter((t) => t !== uuid); + this.#sendTeammateList(); + return true; + } + + #sendTeammateList() { + const players = []; + this.teammates.forEach((uuid) => { + players.push({ + player: uuid, + posMap: [ + { key: 'x', value: 0 }, + { key: 'y', value: 0 }, + { key: 'z', value: 0 }, + ], + }); + }); + this.client.writeChannel(this.channel, { + id: 'teammates', + leader: this.client.uuid, + lastMs: 0, + players, + }); + } +} + +const WaypointColor = { + RED: 0xff0000, + BLUE: 0x0000ff, + GREEN: 0x00ff00, + YELLOW: 0xffff00, + AQUA: 0x00ffff, + WHITE: 0xffffff, + PINK: 0xff00ff, + GRAY: 0x808080, +}; + +module.exports = { + LCPlayer, + WaypointColor, +}; diff --git a/src/index.d.ts b/src/index.d.ts index 8f8f16c..6765922 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -1,8 +1,125 @@ import { Client } from 'minecraft-protocol'; +/** + * The protocol scheme used by [`protodef`](https://www.npmjs.com/package/protodef) to define the protocol between the client and the server + */ export declare const scheme: { [key: string]: any }; -export declare const channel: string; -export declare function registerClient( - client: Client, - altChannel?: boolean -): void; + +export declare class LCPlayer { + /** + * Creates a new LunarClient player + * @param client Node Minecraft Protocol Client + * @param channel LC Plugin Channel to use + */ + constructor( + client: Client, + channel: 'lunarclient:pm' | 'Lunar-Client' = 'lunarclient:pm' + ); + + /** + * Node Minecraft Protocol Client + * @readonly + */ + client: Client; + /** + * Plugin channel used + * @readonly + */ + channel: 'lunarclient:pm' | 'Lunar-Client'; + /** + * Waypoints loaded by the client + */ + waypoints: Waypoint[]; + /** + * Current teammates of the client (Array of UUIDs) + */ + teammates: string[]; + + /** + * Add a waypoint to the client + * @param waypoint Waypoint to add + * @returns True if successful + */ + addWaypoint(waypoint: Waypoint): boolean; + /** + * Remove a waypoint from the client + * @param waypoint Waypoint object or waypoint name + * @returns True if successful + */ + removeWaypoint(waypoint: Waypoint | string): boolean; + /** + * Remove all waypoints loaded by the player + */ + removeAllWaypoints(): void; + /** + * Send a notification to the client. This packet is not supported by the client by default! Solar Tweaks does implement this packet, so use this packet with that in mind. + * @param message Message to send + * @param durationMs Duration of the message in milliseconds + * @param level Message level, set to `info` by default + */ + sendNotification( + message: string, + durationMs: number, + level: 'error' | 'info' | 'success' | 'warning' = 'info' + ): void; + /** + * Add a teammate to the client. The TeamView mod must be enabled for this to work. + * @param uuid UUID of the teammate to add (must be a valid UUID with dashes) + * @returns True if successful + */ + addTeammate(uuid: string): boolean; + /** + * Remove a teammate from the client. The TeamView mod must be enabled for this to work. + * @param uuid UUID of the teammate to remove (must be a valid UUID with dashes) + * @returns True if successful + */ + removeTeammate(uuid: string): boolean; +} + +/** + * Some colors that can be used as a waypoint color + */ +export declare enum WaypointColor { + RED = 0xff0000, + BLUE = 0x0000ff, + GREEN = 0x00ff00, + YELLOW = 0xffff00, + AQUA = 0x00ffff, + WHITE = 0xffffff, + PINK = 0xff00ff, + GRAY = 0x808080, +} + +/** + * Waypoint object + */ +export interface Waypoint { + /** + * Name of the waypoint + */ + name: string; + /** + * X coordinate of the waypoint + */ + x: number; + /** + * Y coordinate of the waypoint + */ + y: number; + /** + * Z coordinate of the waypoint + */ + z: number; + /** + * Color of the waypoint + */ + color: WaypointColor | number; + /** + * I don't really know what this is, if you know please tell me or open a pull request with this comment changed + */ + forced: boolean; + /** + * If the waypoint is visible + */ + visible: boolean; +} diff --git a/src/index.js b/src/index.js index 0b0f0ae..89565ab 100644 --- a/src/index.js +++ b/src/index.js @@ -1,62 +1,5 @@ -const { Client } = require('minecraft-protocol'); - -const scheme = [ - 'container', - [ - { - name: 'id', - type: ['mapper', require('./packets/mapper')], - }, - { - anon: true, - type: [ - 'switch', - { - compareTo: 'id', - fields: { - /* Server */ - client_voice: require('./packets/server/client_voice'), - voice_channel_switch: require('./packets/server/voice_channel_switch'), - voice_mute: require('./packets/server/voice_mute'), - voice: require('./packets/server/voice'), - voice_channel: require('./packets/server/voice_channel'), - voice_channel_remove: require('./packets/server/voice_channel_remove'), - voice_channel_update: require('./packets/server/voice_channel_update'), - /* Client */ - cooldown: require('./packets/client/cooldown'), - hologram: require('./packets/client/hologram'), - hologram_update: require('./packets/client/hologram_update'), - hologram_remove: require('./packets/client/hologram_remove'), - nametags_override: require('./packets/client/nametags_override'), - nametags_update: require('./packets/client/nametags_update'), - notification: require('./packets/client/notification'), - server_rule: require('./packets/client/server_rule'), - server_update: require('./packets/client/server_update'), - staff_mods: require('./packets/client/staff_mods'), - teammates: require('./packets/client/teammates'), - title: require('./packets/client/title'), - update_world: require('./packets/client/update_world'), - world_border: require('./packets/client/world_border'), - world_border_remove: require('./packets/client/world_border_remove'), - world_border_update: require('./packets/client/world_border_update'), - ghost: require('./packets/client/ghost'), - boss_bar: require('./packets/client/boss_bar'), - world_border_create_new: require('./packets/client/world_border_create_new'), - world_border_update_new: require('./packets/client/world_border_update_new'), - mod_settings: require('./packets/client/mod_settings'), - /* Shared */ - emote_broadcast: require('./packets/shared/emote_broadcast'), - waypoint_add: require('./packets/shared/waypoint_add'), - waypoint_remove: require('./packets/shared/waypoint_remove'), - }, - default: 'void', - }, - ], - }, - ], -]; - -const channel = 'lunarclient:pm'; +const scheme = require('./scheme'); +const { LCPlayer, WaypointColor } = require('./LCPlayer'); // const util = require('util'); // console.log( @@ -64,32 +7,7 @@ const channel = 'lunarclient:pm'; // ); module.exports = { - /** - * The protocol scheme used by [`protodef`](https://www.npmjs.com/package/protodef) to define the protocol between the client and the server - */ scheme, - /** - * Name of the channel Lunar Client use - * - * The client also use `Lunar-Client` you can choose between the two (`lunarclient:pm` is used by the [official Bukkit plugin](https://github.com/LunarClient/BukkitAPI)) - * @type string - */ - channel, - /** - * Register the Lunar Client protocol to the given client - * - * See full protocol on their [GitHub repository](https://github.com/LunarClient/BukkitAPI-NetHandler) - * @example registerClient(client); - * @param {Client} client Client to register to - * @param {boolean} altChannel Use the alternate channel name (`Lunar-Client`) - * @returns {void} - */ - registerClient: (client, altChannel = false) => { - const channelName = altChannel ? 'Lunar-Client' : channel; - client.write('custom_payload', { - channel: 'REGISTER', - data: Buffer.from(channelName, 'utf8'), - }); - client.registerChannel(channelName, scheme); - }, + LCPlayer, + WaypointColor, }; diff --git a/src/scheme.js b/src/scheme.js new file mode 100644 index 0000000..a56515d --- /dev/null +++ b/src/scheme.js @@ -0,0 +1,57 @@ +const scheme = [ + 'container', + [ + { + name: 'id', + type: ['mapper', require('./packets/mapper')], + }, + { + anon: true, + type: [ + 'switch', + { + compareTo: 'id', + fields: { + /* Server */ + client_voice: require('./packets/server/client_voice'), + voice_channel_switch: require('./packets/server/voice_channel_switch'), + voice_mute: require('./packets/server/voice_mute'), + voice: require('./packets/server/voice'), + voice_channel: require('./packets/server/voice_channel'), + voice_channel_remove: require('./packets/server/voice_channel_remove'), + voice_channel_update: require('./packets/server/voice_channel_update'), + /* Client */ + cooldown: require('./packets/client/cooldown'), + hologram: require('./packets/client/hologram'), + hologram_update: require('./packets/client/hologram_update'), + hologram_remove: require('./packets/client/hologram_remove'), + nametags_override: require('./packets/client/nametags_override'), + nametags_update: require('./packets/client/nametags_update'), + notification: require('./packets/client/notification'), + server_rule: require('./packets/client/server_rule'), + server_update: require('./packets/client/server_update'), + staff_mods: require('./packets/client/staff_mods'), + teammates: require('./packets/client/teammates'), + title: require('./packets/client/title'), + update_world: require('./packets/client/update_world'), + world_border: require('./packets/client/world_border'), + world_border_remove: require('./packets/client/world_border_remove'), + world_border_update: require('./packets/client/world_border_update'), + ghost: require('./packets/client/ghost'), + boss_bar: require('./packets/client/boss_bar'), + world_border_create_new: require('./packets/client/world_border_create_new'), + world_border_update_new: require('./packets/client/world_border_update_new'), + mod_settings: require('./packets/client/mod_settings'), + /* Shared */ + emote_broadcast: require('./packets/shared/emote_broadcast'), + waypoint_add: require('./packets/shared/waypoint_add'), + waypoint_remove: require('./packets/shared/waypoint_remove'), + }, + default: 'void', + }, + ], + }, + ], +]; + +module.exports = scheme; diff --git a/test/proxy.js b/test/proxy.js new file mode 100644 index 0000000..5291a79 --- /dev/null +++ b/test/proxy.js @@ -0,0 +1,43 @@ +const { InstantConnectProxy } = require('prismarine-proxy'); +const { LCPlayer, WaypointColor } = require('../src'); + +const proxy = new InstantConnectProxy({ + loginHandler: (client) => { + return { username: client.username, auth: 'microsoft' }; + }, + serverOptions: { + version: '1.8.9', + }, + clientOptions: { + version: '1.8.9', + host: 'hypixel.net', + }, +}); + +proxy.on('incoming', (data, meta, toClient, toServer) => { + toClient.write(meta.name, data); +}); + +proxy.on('outgoing', (data, meta, toClient, toServer) => { + toServer.write(meta.name, data); +}); + +proxy.on('start', (client) => { + const player = new LCPlayer(client); + player.addWaypoint({ + name: 'Spawn', + color: WaypointColor.PINK, + x: 0, + y: 64, + z: 0, + forced: false, + visible: true, + }); + + player.addTeammate('64fb990d-5c85-43cd-a3b1-98a44b385493'); + + setTimeout(() => { + player.removeTeammate('64fb990d-5c85-43cd-a3b1-98a44b385493'); + player.removeAllWaypoints(); + }, 5000); +});