From 67030d35b22096c6d76161651d3363c400789735 Mon Sep 17 00:00:00 2001 From: Ollie <69084614+olijeffers0n@users.noreply.github.com> Date: Sat, 18 Mar 2023 20:10:43 +0000 Subject: [PATCH 01/14] Initial Camera Port --- package-lock.json | 872 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 3 +- rustplus.js | 232 +++++++++++- rustplus.proto | 111 +++--- 4 files changed, 1168 insertions(+), 50 deletions(-) diff --git a/package-lock.json b/package-lock.json index 6284743..4609892 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "command-line-args": "^5.2.0", "command-line-usage": "^6.1.1", "express": "^4.17.1", + "jimp": "^0.22.7", "protobufjs": "^7.1.2", "push-receiver": "^2.1.0", "uuid": "^9.0.0", @@ -23,6 +24,395 @@ "rustplus": "cli/index.js" } }, + "node_modules/@jimp/bmp": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/bmp/-/bmp-0.22.7.tgz", + "integrity": "sha512-0cfBPvugURS7G+60vRBL+penDRst8x40alS5Rhn2nlGsgsBHljFDw7+H4o5r6gldw9nv9PR9JA90Wloy7KMZdQ==", + "dependencies": { + "@jimp/utils": "^0.22.7", + "bmp-js": "^0.1.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/core": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/core/-/core-0.22.7.tgz", + "integrity": "sha512-lg4z+pw23v2Gp9LWQur0NqYtnmoNWnyN/Or96elhJgeEJskrDGwROdajortHCCOI1xDnUZSirg8sFvStC8BIlg==", + "dependencies": { + "@jimp/utils": "^0.22.7", + "any-base": "^1.1.0", + "buffer": "^5.2.0", + "exif-parser": "^0.1.12", + "file-type": "^16.5.4", + "isomorphic-fetch": "^3.0.0", + "mkdirp": "^2.1.3", + "pixelmatch": "^4.0.2", + "tinycolor2": "^1.6.0" + } + }, + "node_modules/@jimp/custom": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/custom/-/custom-0.22.7.tgz", + "integrity": "sha512-n+1+ZVDNumB1E+sL7KdGKAJ6MbgniX1/v/xOEFEQ46WDZ4cRTqP4+tXjHTuHSlOXiANH+K9zD6qgzqmgO6mCVw==", + "dependencies": { + "@jimp/core": "^0.22.7" + } + }, + "node_modules/@jimp/gif": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/gif/-/gif-0.22.7.tgz", + "integrity": "sha512-PGZMS8sYFnDcqg+t8IT3RaSJLrqB+3GzhI0hU5D4mmSuJ5UO/6Bdgu8nrwh3uFPxw0ZH6h9ozYk88cz0pKEhLQ==", + "dependencies": { + "@jimp/utils": "^0.22.7", + "gifwrap": "^0.9.2", + "omggif": "^1.0.9" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/jpeg": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/jpeg/-/jpeg-0.22.7.tgz", + "integrity": "sha512-ptwWyX/7RPcREy8SpPN/8IlywbwyPXiuXmoHwM6m4iKcyaCmmnfCdZwLNXYliJzFAFLLOWDuOrwO3cZSkH6Czg==", + "dependencies": { + "@jimp/utils": "^0.22.7", + "jpeg-js": "^0.4.4" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blit": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blit/-/plugin-blit-0.22.7.tgz", + "integrity": "sha512-8oXcBTSd/sBmTQATrCxQ1ZBER31Lge8vXzWqNCbC3b1ZvRggCcqnDzRRH1+JiI4i+jPRo3Fi6/sdvEUyQ5LY3g==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-blur": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-blur/-/plugin-blur-0.22.7.tgz", + "integrity": "sha512-M+0I5CKFIpnIQE27j8o8NECBsOFBd4z7C95ydy2UohYopugFq+hSVtMs1D4pQgb0RW1DJPiXD/4PHqb+lzV5mA==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-circle": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-circle/-/plugin-circle-0.22.7.tgz", + "integrity": "sha512-zfZKKpOhlyiDeFjGW5JB9K4h/kvbdaAJWUEwmKrvvGar67G3j8dKu46AX0MeWRNZ1yk/lfz+JIa7TzKfxEBf6w==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-color": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-color/-/plugin-color-0.22.7.tgz", + "integrity": "sha512-OXro9pdB0twQjV4LgW0bTEXaX1VgBsTBcFoDAs8q9mtQzD5p3UQmJ+ykCiQ5rTPxNN1Buc44tcCIfp8haB1ZVQ==", + "dependencies": { + "@jimp/utils": "^0.22.7", + "tinycolor2": "^1.6.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-contain": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-contain/-/plugin-contain-0.22.7.tgz", + "integrity": "sha512-GwUxZp4jMA0O0qbknUPDONJAfHFaTRs8kK+jgRtUfgb1Xi96l5RN/PMMDv4owZCUiPVAON80X1BMj7nSQWNVUw==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-cover": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-cover/-/plugin-cover-0.22.7.tgz", + "integrity": "sha512-PVXeQyofGepMoJaQ5XapLwCcZfsOF1IoAotHosh8AOP8niCP/Erm8T6ZWf5tf0sMJiLHQMPUyns186H5isqEMQ==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5", + "@jimp/plugin-scale": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-crop": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-crop/-/plugin-crop-0.22.7.tgz", + "integrity": "sha512-XXvUU+hPdodtTBSgyUJUnzh7JgKMVlS1GxjcQsjYU8iGr1dbpuazKMTQxc76ChVmy8ue4goi8bGstacWUHpl/Q==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-displace": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-displace/-/plugin-displace-0.22.7.tgz", + "integrity": "sha512-CCNAkmm2OS4QQtNRfQvXqoAMxNE0maSlVEV5DNdioHOUKycy02EJ5hNYR3l0FG+NraQHOuqv9XV37sGRl6QzMA==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-dither": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-dither/-/plugin-dither-0.22.7.tgz", + "integrity": "sha512-ndCW5MIGMdh3aBvvgRCO7el9cIPG29kU7xQYlOs5+3JsDk3Vf7X30QGPjzxABOY95qLUNUjf5Qe/p/tqv/vbcw==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-fisheye": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-fisheye/-/plugin-fisheye-0.22.7.tgz", + "integrity": "sha512-boI1QowhZRfb6OF+ZPWtiSJP1GATsTHjd5Oy/lJ+n0L4rp439ZOTB1Elzcgc44O2C1mgZDdybRPQQvYdPF8slA==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-flip": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-flip/-/plugin-flip-0.22.7.tgz", + "integrity": "sha512-/jkbgtvQPcKadAEV5ZXyoEpSdd7GEvGs/Ya/f48+LNszc+S24u4UXtuP3QPRJ5FHm0Re1t4uztM7xa6IPklAOA==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-rotate": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-gaussian": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-gaussian/-/plugin-gaussian-0.22.7.tgz", + "integrity": "sha512-OB1sdnjzq2rfUHmx9Rvi3SJIDbQAgWFgYEw6KhN3TSVOdrJHvwrQkEnwR9PoUzQg992VIpGcVc9Y1s/SOU2oCA==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-invert": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-invert/-/plugin-invert-0.22.7.tgz", + "integrity": "sha512-dX/TqACJ/M5uXDIEJlVPPwietMD6EWUeA/CV4uvhLz9EMjTgHociJ3TWqGCY/70phhIBLbhLcHUVBL/q65ynfQ==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-mask": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-mask/-/plugin-mask-0.22.7.tgz", + "integrity": "sha512-rfKHKJLAtJG7qbB4zYAMcQ9ue3CIFRuAJ3xX0lzCxC0fGvCVuXlcxiAEauBxqaTWqiKMnahqpR3/Ah679K2FKQ==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-normalize": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-normalize/-/plugin-normalize-0.22.7.tgz", + "integrity": "sha512-t8x2jjKDmvUAZB4Wbeagr4D0BvoVCIWquy94mpglvSZ8ujKLt0aQBl3CBEIbXFAoVqNif+G36NtxPHNsjxIXOg==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-print": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-print/-/plugin-print-0.22.7.tgz", + "integrity": "sha512-kx0+cPeinki1IFg9cJy7LC4uVuOEOa8TIrcERioB6PVgJ7EDzCAfatTKULZ+t4uSs2K/lQF97wPYlbiyxs/Hzg==", + "dependencies": { + "@jimp/utils": "^0.22.7", + "load-bmfont": "^1.4.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-resize": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-resize/-/plugin-resize-0.22.7.tgz", + "integrity": "sha512-pg7i0JIYt7x7ag+CoD/yG70Xvwm1sKRfcFjQh954yestiin14uppPgXchAmTBmctecBjLNdsVlqSXbPvU4Jvxw==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-rotate": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-rotate/-/plugin-rotate-0.22.7.tgz", + "integrity": "sha512-Uh3Gb18IY8uXWk6E1bzMopum2GP+xwohbnMIDE0MSWmLaz7LXrfnvgXFba1uRGgn73CJz8UDS4fC1KIJMuxQZA==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blit": ">=0.3.5", + "@jimp/plugin-crop": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-scale": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-scale/-/plugin-scale-0.22.7.tgz", + "integrity": "sha512-3uHUrk5Rl6MCxuoJtHTSeJjSHIxHWqOOgmD2caKIvyxds0Zmofu/Fva+N4V/m80E4q4G2RXNsUplFpFGhUM7hw==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-shadow": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-shadow/-/plugin-shadow-0.22.7.tgz", + "integrity": "sha512-NKEq5VR8U/d0OKf0hxFtrrbMCuNv7by31V+Kwgxb1oTP+j+zZEaww+m3YgEwIwRe7E8/yeDSHa5bJ+CmuyFZjw==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-blur": ">=0.3.5", + "@jimp/plugin-resize": ">=0.3.5" + } + }, + "node_modules/@jimp/plugin-threshold": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugin-threshold/-/plugin-threshold-0.22.7.tgz", + "integrity": "sha512-BH4aLwfmnqjRVhdzMIqUns4ycZ6QoHHFR6Qz+X2iSpH5a33xFA4DRbd3Ehtrs4Gk7XiCjWkUyM6wjmH7l/1hNQ==", + "dependencies": { + "@jimp/utils": "^0.22.7" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5", + "@jimp/plugin-color": ">=0.8.0", + "@jimp/plugin-resize": ">=0.8.0" + } + }, + "node_modules/@jimp/plugins": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/plugins/-/plugins-0.22.7.tgz", + "integrity": "sha512-AJmzTG/sa+CDpvle/UE89hjHR85gnRGSwLuQqPbhlY6GFCmC3uqHRJz9O5I8A4zdi9+e8LsBphuTlKV7RbuXOw==", + "dependencies": { + "@jimp/plugin-blit": "^0.22.7", + "@jimp/plugin-blur": "^0.22.7", + "@jimp/plugin-circle": "^0.22.7", + "@jimp/plugin-color": "^0.22.7", + "@jimp/plugin-contain": "^0.22.7", + "@jimp/plugin-cover": "^0.22.7", + "@jimp/plugin-crop": "^0.22.7", + "@jimp/plugin-displace": "^0.22.7", + "@jimp/plugin-dither": "^0.22.7", + "@jimp/plugin-fisheye": "^0.22.7", + "@jimp/plugin-flip": "^0.22.7", + "@jimp/plugin-gaussian": "^0.22.7", + "@jimp/plugin-invert": "^0.22.7", + "@jimp/plugin-mask": "^0.22.7", + "@jimp/plugin-normalize": "^0.22.7", + "@jimp/plugin-print": "^0.22.7", + "@jimp/plugin-resize": "^0.22.7", + "@jimp/plugin-rotate": "^0.22.7", + "@jimp/plugin-scale": "^0.22.7", + "@jimp/plugin-shadow": "^0.22.7", + "@jimp/plugin-threshold": "^0.22.7", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/png": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/png/-/png-0.22.7.tgz", + "integrity": "sha512-LxD3O9FKEwVv+j+HcUV7ez72Miy+823EjhtFZbBYXNp9qjHtHFBpgcSJBftUOCei8OlmmVgULYn9XjyfPsDgGw==", + "dependencies": { + "@jimp/utils": "^0.22.7", + "pngjs": "^6.0.0" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/tiff": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/tiff/-/tiff-0.22.7.tgz", + "integrity": "sha512-/oE8kLumzBfU1Z6h4TrDXYCGQNc4CjbZQvPssjImEqNLr5vbefpIpoy1fVMpsyuHZHsGovsBhBHxTJaRLO4+Og==", + "dependencies": { + "utif2": "^4.0.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/types": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/types/-/types-0.22.7.tgz", + "integrity": "sha512-1T8BxwDh5HJvBh3tt6HUd8r7ir5Ge3JWATXC8O3Y9QYwOaERjA2+FVhGSjtoo5xCeJvLRjSzEtfZ8heowMBL4w==", + "dependencies": { + "@jimp/bmp": "^0.22.7", + "@jimp/gif": "^0.22.7", + "@jimp/jpeg": "^0.22.7", + "@jimp/png": "^0.22.7", + "@jimp/tiff": "^0.22.7", + "timm": "^1.6.1" + }, + "peerDependencies": { + "@jimp/custom": ">=0.3.5" + } + }, + "node_modules/@jimp/utils": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/@jimp/utils/-/utils-0.22.7.tgz", + "integrity": "sha512-4ax4IOWLIERx4yz9y3fNXKvQaPOY23yJF5h4sizxVkQUObkZHWE0kL0TVHodBt3rS8ksdbCL8Jkz4GeNP/Katg==", + "dependencies": { + "regenerator-runtime": "^0.13.3" + } + }, "node_modules/@protobufjs/aspromise": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", @@ -77,6 +467,11 @@ "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.0.tgz", "integrity": "sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==" }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==" + }, "node_modules/@types/long": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/@types/long/-/long-4.0.2.tgz", @@ -125,6 +520,11 @@ "node": ">=4" } }, + "node_modules/any-base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/any-base/-/any-base-1.1.0.tgz", + "integrity": "sha512-uMgjozySS8adZZYePpaWs8cxB9/kdzmpX6SgJZ+wbz1K5eYk5QMYDVJaZKhxyIHUdnnJkfR7SVgStgH7LkGUyg==" + }, "node_modules/array-back": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", @@ -182,6 +582,25 @@ "proxy-from-env": "^1.1.0" } }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", @@ -195,6 +614,11 @@ "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==" }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==" + }, "node_modules/body-parser": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.1.tgz", @@ -218,6 +642,37 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-equal": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.1.tgz", + "integrity": "sha512-RgSV6InVQ9ODPdLWJ5UAqBqJBOg370Nz6ZQtRzpt6nUjc8v0St97uJ4PYC6NztqIScrAXafKM3mZPMygSe1ggA==", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/bytes": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", @@ -438,6 +893,11 @@ "npm": "1.2.8000 || >= 1.4.16" } }, + "node_modules/dom-walk": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", + "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" + }, "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", @@ -484,6 +944,11 @@ "node": ">= 0.6" } }, + "node_modules/exif-parser": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", + "integrity": "sha512-c2bQfLNbMzLPmzQuOr8fy0csy84WmwnER81W88DzTp9CYNPJ6yzOj2EZAh9pywYpqHnshVLHQJ8WzldAyfY+Iw==" + }, "node_modules/express": { "version": "4.18.2", "resolved": "https://registry.npmjs.org/express/-/express-4.18.2.tgz", @@ -548,6 +1013,22 @@ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, + "node_modules/file-type": { + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", + "dependencies": { + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, "node_modules/finalhandler": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.2.0.tgz", @@ -658,6 +1139,24 @@ "assert-plus": "^1.0.0" } }, + "node_modules/gifwrap": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/gifwrap/-/gifwrap-0.9.4.tgz", + "integrity": "sha512-MDMwbhASQuVeD4JKd1fKgNgCRL3fGqMM4WaqpNhWO0JiMOAjbQdumbs4BbBZEy9/M00EHEjKN3HieVhCUlwjeQ==", + "dependencies": { + "image-q": "^4.0.0", + "omggif": "^1.0.10" + } + }, + "node_modules/global": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", + "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", + "dependencies": { + "min-document": "^2.19.0", + "process": "^0.11.10" + } + }, "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", @@ -760,6 +1259,38 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/image-q": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/image-q/-/image-q-4.0.0.tgz", + "integrity": "sha512-PfJGVgIfKQJuq3s0tTDOKtztksibuUEbJQIYT3by6wctQo+Rdlh7ef4evJ5NCdxY4CfMbvFkocEwbl4BF8RlJw==", + "dependencies": { + "@types/node": "16.9.1" + } + }, + "node_modules/image-q/node_modules/@types/node": { + "version": "16.9.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.1.tgz", + "integrity": "sha512-QpLcX9ZSsq3YYUUnD3nFDY8H7wctAhQj/TFKL8Ya8v5fMm3CFXxo8zStsLAl780ltoYoo1WvKUVGBQK+1ifr7g==" + }, "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", @@ -787,6 +1318,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-function": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-function/-/is-function-1.0.2.tgz", + "integrity": "sha512-lw7DUp0aWXYg+CBCN+JKkcE0Q2RayZnSvnZBlwgxHBQhqt5pZNVy4Ri7H9GmmXkdu7LUthszM+Tor1u/2iBcpQ==" + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -803,11 +1339,36 @@ "node": ">=8" } }, + "node_modules/isomorphic-fetch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz", + "integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==", + "dependencies": { + "node-fetch": "^2.6.1", + "whatwg-fetch": "^3.4.1" + } + }, "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" }, + "node_modules/jimp": { + "version": "0.22.7", + "resolved": "https://registry.npmjs.org/jimp/-/jimp-0.22.7.tgz", + "integrity": "sha512-TJCTJ4ZcFUw6W8XZnR6ajdEu8vSyPi3AuoChs+zLHalXnhAPZgwkzwcXnxey4LNjh1p9dfIUkg8YSQ+q8pBW0A==", + "dependencies": { + "@jimp/custom": "^0.22.7", + "@jimp/plugins": "^0.22.7", + "@jimp/types": "^0.22.7", + "regenerator-runtime": "^0.13.3" + } + }, + "node_modules/jpeg-js": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/jpeg-js/-/jpeg-js-0.4.4.tgz", + "integrity": "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==" + }, "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -851,6 +1412,21 @@ "marky": "^1.2.2" } }, + "node_modules/load-bmfont": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", + "integrity": "sha512-8UyQoYmdRDy81Brz6aLAUhfZLwr5zV0L3taTQ4hju7m6biuwiWiJXjPhBJxbUQJA8PrkvJ/7Enqmwk2sM14soA==", + "dependencies": { + "buffer-equal": "0.0.1", + "mime": "^1.3.4", + "parse-bmfont-ascii": "^1.0.3", + "parse-bmfont-binary": "^1.0.5", + "parse-bmfont-xml": "^1.1.4", + "phin": "^2.9.1", + "xhr": "^2.0.1", + "xtend": "^4.0.0" + } + }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", @@ -922,6 +1498,28 @@ "node": ">= 0.6" } }, + "node_modules/min-document": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", + "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", + "dependencies": { + "dom-walk": "^0.1.0" + } + }, + "node_modules/mkdirp": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-2.1.5.tgz", + "integrity": "sha512-jbjfql+shJtAPrFoKxHOXip4xS+kul9W3OzfzzrqueWK2QMGon2bFH2opl6W9EagBThjEz+iysyi/swOoVfB/w==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", @@ -935,6 +1533,25 @@ "node": ">= 0.6" } }, + "node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/oauth-sign": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", @@ -951,6 +1568,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/omggif": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/omggif/-/omggif-1.0.10.tgz", + "integrity": "sha512-LMJTtvgc/nugXj0Vcrrs68Mn2D1r0zf630VNtqtpI1FEO7e+O9FP4gqs9AcnBaSEeoHIPm28u6qgPR0oyEpGSw==" + }, "node_modules/on-finished": { "version": "2.4.1", "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", @@ -962,6 +1584,35 @@ "node": ">= 0.8" } }, + "node_modules/pako": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" + }, + "node_modules/parse-bmfont-ascii": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-ascii/-/parse-bmfont-ascii-1.0.6.tgz", + "integrity": "sha512-U4RrVsUFCleIOBsIGYOMKjn9PavsGOXxbvYGtMOEfnId0SVNsgehXh1DxUdVPLoxd5mvcEtvmKs2Mmf0Mpa1ZA==" + }, + "node_modules/parse-bmfont-binary": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/parse-bmfont-binary/-/parse-bmfont-binary-1.0.6.tgz", + "integrity": "sha512-GxmsRea0wdGdYthjuUeWTMWPqm2+FAd4GI8vCvhgJsFnoGhTrLhXDDupwTo7rXVAgaLIGoVHDZS9p/5XbSqeWA==" + }, + "node_modules/parse-bmfont-xml": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/parse-bmfont-xml/-/parse-bmfont-xml-1.1.4.tgz", + "integrity": "sha512-bjnliEOmGv3y1aMEfREMBJ9tfL3WR0i0CKPj61DnSLaoxWR3nLrsQrEbCId/8rF4NyRF0cCqisSVXyQYWM+mCQ==", + "dependencies": { + "xml-parse-from-string": "^1.0.0", + "xml2js": "^0.4.5" + } + }, + "node_modules/parse-headers": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/parse-headers/-/parse-headers-2.0.5.tgz", + "integrity": "sha512-ft3iAoLOB/MlwbNXgzy43SWGP6sQki2jQvAyBg/zDFAgr9bfNWZIUj42Kw2eJIl8kEi4PbgE6U1Zau/HwI75HA==" + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -975,11 +1626,63 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" }, + "node_modules/peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==", + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" }, + "node_modules/phin": { + "version": "2.9.3", + "resolved": "https://registry.npmjs.org/phin/-/phin-2.9.3.tgz", + "integrity": "sha512-CzFr90qM24ju5f88quFC/6qohjC144rehe5n6DH900lgXmUe86+xCKc10ev56gRKC4/BkHUoG4uSiQgBiIXwDA==" + }, + "node_modules/pixelmatch": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-4.0.2.tgz", + "integrity": "sha512-J8B6xqiO37sU/gkcMglv6h5Jbd9xNER7aHzpfRdNmV4IbQBzBpe4l9XmbG+xPF/znacgu2jfEw+wHffaq/YkXA==", + "dependencies": { + "pngjs": "^3.0.0" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/pngjs": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-6.0.0.tgz", + "integrity": "sha512-TRzzuFRRmEoSW/p1KVAmiOgPco2Irlah+bGFCeNfJXxxYGwSw7YwAOAcd7X28K/m5bjBWKsC29KyoMfHbypayg==", + "engines": { + "node": ">=12.13.0" + } + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, "node_modules/protobufjs": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.1.2.tgz", @@ -1129,6 +1832,34 @@ "node": ">= 0.8" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/readable-web-to-node-stream": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.2.tgz", + "integrity": "sha512-ePeK6cc1EcKLEhJFt/AebMCLL+GgSKhuygrZ/GLaKZYEecIgIECf4UaUuaByiGtzckwR4ain9VzUh95T1exYGw==", + "dependencies": { + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/reduce-flatten": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", @@ -1137,6 +1868,11 @@ "node": ">=6" } }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==" + }, "node_modules/request": { "version": "2.88.2", "resolved": "https://registry.npmjs.org/request/-/request-2.88.2.tgz", @@ -1254,6 +1990,11 @@ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==" + }, "node_modules/send": { "version": "0.18.0", "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz", @@ -1354,6 +2095,30 @@ "node": ">=0.10.0" } }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strtok3": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -1395,6 +2160,16 @@ "node": ">=8" } }, + "node_modules/timm": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/timm/-/timm-1.7.1.tgz", + "integrity": "sha512-IjZc9KIotudix8bMaBW6QvMuq64BrJWFs1+4V0lXwWGQZwH+LnX87doAYhem4caOEusRP9/g6jVDQmZ8XOk1nw==" + }, + "node_modules/tinycolor2": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.6.0.tgz", + "integrity": "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw==" + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1403,6 +2178,22 @@ "node": ">=0.6" } }, + "node_modules/token-types": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", + "dependencies": { + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", @@ -1415,6 +2206,11 @@ "node": ">=0.8" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" + }, "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", @@ -1472,6 +2268,19 @@ "resolved": "https://registry.npmjs.org/urlsafe-base64/-/urlsafe-base64-1.0.0.tgz", "integrity": "sha512-RtuPeMy7c1UrHwproMZN9gN6kiZ0SvJwRaEzwZY0j9MypEkFqyBaKv176jvlPtg58Zh36bOkS0NFABXMHvvGCA==" }, + "node_modules/utif2": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/utif2/-/utif2-4.0.1.tgz", + "integrity": "sha512-KMaD76dbzK1VjbwsckHJiqDjhP3pbpwyV+FdqkY6XFQenc2o/HS6pjPSYdu4+NQMHf2NLTW+nVP/eFP1CvOYQQ==", + "dependencies": { + "pako": "^1.0.11" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/utils-merge": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", @@ -1509,6 +2318,25 @@ "extsprintf": "^1.2.0" } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" + }, + "node_modules/whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/wordwrapjs": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", @@ -1548,6 +2376,50 @@ "optional": true } } + }, + "node_modules/xhr": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/xhr/-/xhr-2.6.0.tgz", + "integrity": "sha512-/eCGLb5rxjx5e3mF1A7s+pLlR6CGyqWN91fv1JgER5mVWg1MZmlhBvy9kjcsOdRk8RrIujotWyJamfyrp+WIcA==", + "dependencies": { + "global": "~4.4.0", + "is-function": "^1.0.1", + "parse-headers": "^2.0.0", + "xtend": "^4.0.0" + } + }, + "node_modules/xml-parse-from-string": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/xml-parse-from-string/-/xml-parse-from-string-1.0.1.tgz", + "integrity": "sha512-ErcKwJTF54uRzzNMXq2X5sMIy88zJvfN2DmdoQvy7PAFJ+tPRU6ydWuOKNMyfmOjdyBQTFREi60s0Y0SyI0G0g==" + }, + "node_modules/xml2js": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.23.tgz", + "integrity": "sha512-ySPiMjM0+pLDftHgXY4By0uswI3SPKLDw/i3UXbnO8M/p28zqexCUoPmQFrYD+/1BzhGJSs2i1ERWKJAtiLrug==", + "dependencies": { + "sax": ">=0.6.0", + "xmlbuilder": "~11.0.0" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/xmlbuilder": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz", + "integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "engines": { + "node": ">=0.4" + } } } } diff --git a/package.json b/package.json index 3178985..a2be41a 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@liamcottle/rustplus.js", - "version": "2.3.0", + "version": "2.3.1", "description": "Unofficial NodeJS library for controlling Smart Switches in the PC game Rust", "main": "rustplus.js", "bin": { @@ -34,6 +34,7 @@ "command-line-args": "^5.2.0", "command-line-usage": "^6.1.1", "express": "^4.17.1", + "jimp": "^0.22.7", "protobufjs": "^7.1.2", "push-receiver": "^2.1.0", "uuid": "^9.0.0", diff --git a/rustplus.js b/rustplus.js index 492d536..3edf4bc 100644 --- a/rustplus.js +++ b/rustplus.js @@ -4,6 +4,9 @@ const path = require('path'); const WebSocket = require('ws'); const protobuf = require("protobufjs"); const { EventEmitter } = require('events'); +const Jimp = require('jimp'); + + class RustPlus extends EventEmitter { @@ -316,18 +319,237 @@ class RustPlus extends EventEmitter { /** * Get CCTV Camera frame * @param identifier CCTV Camera Identifier, such as OILRIG1 (or custom name) - * @param frame integer that should be incremented for each frame request. Otherwise a cached frame is returned * @param callback */ - getCameraFrame(identifier, frame, callback) { + subscribeToCamera(identifier, callback) { + this.sendRequest({ + cameraSubscribe: { + cameraId: identifier, + }, + }, callback); + } + + /** + * Sends camera movement to the server (mouse movement) + * @param buttons The buttons that are currently pressed + * @param x The x delta of the mouse movement + * @param y The y delta of the mouse movement + * @param callback + */ + sendCameraMovement(buttons, x, y, callback) { this.sendRequest({ - getCameraFrame: { - identifier: identifier, - frame: frame, + cameraInput: { + buttons: buttons, + mouseDelta: { + x: x, + y: y, + } }, }, callback); } + /** + * Render a camera frame to a PNG image buffer + * @param frames the frame data to render + * @param width the width of the frame + * @param height the height of the frame + * @param callback the callback to call with the rendered image buffer + */ + renderCameraFrame(frames, width, height, callback) { + + // First we populate the samplePositionBuffer with the positions of each sample + const samplePositionBuffer = new Int16Array(width * height * 2); + for (let w = 0, _ = 0; _ < height; _++) + for (let g = 0; g < width; g++) { + samplePositionBuffer[w] = g; + samplePositionBuffer[++w] = _; + w++; + } + + for (let B = new IndexGenerator(1337), R = width * height - 1; R >= 1; R--) { + let C = 2 * R, + I = 2 * B.nextInt(R + 1), + P = samplePositionBuffer[C], + k = samplePositionBuffer[C + 1], + A = samplePositionBuffer[I], + F = samplePositionBuffer[I + 1]; + samplePositionBuffer[I] = P; + samplePositionBuffer[I + 1] = k; + samplePositionBuffer[C] = A; + samplePositionBuffer[C + 1] = F; + } + + // Create the output buffer + const output = new Array(width * height); + // Loop through each frame + for (let frame of frames) { + + // Reset some look back and pointer variables + let sampleOffset = 2 * frame.sampleOffset; + let dataPointer = 0; + let rayLookback = new Array(64); + for (let r = 0; r < 64; r++) rayLookback[r] = [0, 0, 0]; + + const rayData = frame.rayData; + + // Loop through the ray data + while (true) { + if (dataPointer >= rayData.length - 1) + break; + + // Get the first byte and set some variables + var t, r, i, n = rayData[dataPointer++]; + + // Ray Decoding Logic + if (255 === n) { + var l = rayData[dataPointer++], + o = rayData[dataPointer++], + s = rayData[dataPointer++], + u = (3 * (((t = (l << 2) | (o >> 6)) / 128) | 0) + 5 * (((r = 63 & o) / 16) | 0) + 7 * (i = s)) & 63, + f = rayLookback[u]; + f[0] = t; + f[1] = r; + f[2] = i; + } else { + var c = 192 & n; + + if (0 === c) { + var h = 63 & n, y = rayLookback[h]; + t = y[0]; + r = y[1]; + i = y[2]; + } else if (64 === c) { + var p = 63 & n, + v = rayLookback[p], + b = v[0], + w = v[1], + _ = v[2], + g = rayData[dataPointer++]; + t = b + ((g >> 3) - 15); + r = w + ((7 & g) - 3); + i = _; + } else if (128 === c) { + var R = 63 & n, + C = rayLookback[R], + I = C[0], + P = C[1], + k = C[2]; + t = I + (rayData[dataPointer++] - 127); + r = P; + i = k; + } else { + var A = rayData[dataPointer++], + F = rayData[dataPointer++], + D = (3 * (((t = (A << 2) | (F >> 6)) / 128) | 0) + 5 * (((r = 63 & F) / 16) | 0) + 7 * (i = 63 & n)) & 63, + E = rayLookback[D]; + E[0] = t; + E[1] = r; + E[2] = i; + } + } + + sampleOffset %= 2 * width * height; + const index = samplePositionBuffer[sampleOffset++] + samplePositionBuffer[sampleOffset++] * width; + output[index] = [t / 1023, r / 63, i]; + } + } + + const colours = [ + [0.5, 0.5, 0.5], [0.8, 0.7, 0.7], [0.3, 0.7, 1], [0.6, 0.6, 0.6], + [0.7, 0.7, 0.7], [0.8, 0.6, 0.4], [1, 0.4, 0.4], [1, 0.1, 0.1], + ] + + const image = new Jimp(width, height); + + for (let i = 0; i < output.length; i++) { + let ray = output[i]; + if (!ray) { + continue; + } + + let distance = ray[0] + let alignment = ray[1] + let material = ray[2] + + let target_colour; + + if (distance === 1 && alignment === 0 && material === 0) { + target_colour = [208, 230, 252]; + } else { + let colour = colours[material]; + target_colour = [(alignment * colour[0] * 255), (alignment * colour[1] * 255), (alignment * colour[2] * 255)] + } + + let x = i % width; + let y = height - 1 - Math.floor(i / width); + image.setPixelColor(Jimp.rgbaToInt(target_colour[0], target_colour[1], target_colour[2], 255), x, y); + } + + image.getBuffer(Jimp.MIME_PNG, (err, buffer) => { + if (err) + console.log(err); + callback(buffer) + }); + } + + /** + * These represent the possible inputs that can be sent to the server. + */ + static MovementControls = { + + FORWARD : 2, + BACKWARD : 4, + LEFT : 8, + RIGHT : 16, + JUMP : 32, + DUCK : 64, + SPRINT : 128, + USE : 256, + FIRE_PRIMARY : 1024, + FIRE_SECONDARY : 2048, + RELOAD : 8192, + FIRE_THIRD : 134217728, + } + + /** + * These represent the possible camera movement options that can be sent to the server. + * For example, Static CCTV cameras will not have any movement options. + */ + static CameraMovementOptions = { + NONE : 0, + MOVEMENT : 1, + MOUSE : 2, + SPRINT_AND_DUCK : 4, + FIRE : 8, + RELOAD : 16, + CROSSHAIR : 32, + } + +} + +class IndexGenerator { + constructor(e) { + this.state = 0 | e; + this.nextState(); + } + + nextInt(e) { + var t = ((this.nextState() * (0 | e)) / 4294967295) | 0; + if (t < 0) t = e + t - 1; + return 0 | t; + } + + nextState() { + var e = this.state, + t = e; + e = ((e = ((e = (e ^ ((e << 13) | 0)) | 0) ^ ((e >>> 17) | 0)) | 0) ^ ((e << 5) | 0)) | 0; + this.state = e; + return j(t); + } +} + +function j(e) { + return e >= 0 ? e : 4294967295 + e - 1; } module.exports = RustPlus; diff --git a/rustplus.proto b/rustplus.proto index 6005c0c..b5e6db6 100644 --- a/rustplus.proto +++ b/rustplus.proto @@ -1,7 +1,10 @@ syntax = "proto2"; package rustplus; -// This took me a while to craft by hand 😴 +message Vector2 { + optional float x = 1; + optional float y = 2; +} message Vector3 { optional float x = 1; @@ -9,17 +12,6 @@ message Vector3 { optional float z = 3; } -message Half3 { - optional uint32 x = 1; - optional uint32 y = 2; - optional uint32 z = 3; -} - -message Ray { - optional Vector3 origin = 1; - optional Vector3 direction = 2; -} - message Vector4 { optional float x = 1; optional float y = 2; @@ -27,6 +19,12 @@ message Vector4 { optional float w = 4; } +message Half3 { + optional float x = 1; + optional float y = 2; + optional float z = 3; +} + message Color { optional float r = 1; optional float g = 2; @@ -34,9 +32,9 @@ message Color { optional float a = 4; } -message Vector2 { - optional float x = 1; - optional float y = 2; +message Ray { + optional Vector3 origin = 1; + optional Vector3 direction = 2; } message ClanActionResult { @@ -47,20 +45,19 @@ message ClanActionResult { } message ClanInfo { - required int64 clanId = 1; required string name = 2; required int64 created = 3; - required uint64 creator = 4; + required int64 creator = 4; optional string motd = 5; optional int64 motdTimestamp = 6; - optional uint64 motdAuthor = 7; + optional int64 motdAuthor = 7; optional bytes logo = 8; optional sint32 color = 9; repeated ClanInfo.Role roles = 10; repeated ClanInfo.Member members = 11; repeated ClanInfo.Invite invites = 12; - repeated int32 maxMemberCount = 13; + optional int32 maxMemberCount = 13; message Role { required int32 roleId = 1; @@ -77,7 +74,7 @@ message ClanInfo { } message Member { - required uint64 steamId = 1; + required int64 steamId = 1; required int32 roleId = 2; required int64 joined = 3; required int64 lastSeen = 4; @@ -86,15 +83,13 @@ message ClanInfo { } message Invite { - required uint64 steamId = 1; - required uint64 recruiter = 2; + required int64 steamId = 1; + required int64 recruiter = 2; required int64 timestamp = 3; } - } message ClanLog { - required int64 clanId = 1; repeated ClanLog.Entry logEntries = 2; @@ -106,19 +101,16 @@ message ClanLog { optional string arg3 = 5; optional string arg4 = 6; } - } message ClanInvitations { - repeated ClanInvitations.Invitation invitations = 1; message Invitation { required int64 clanId = 1; - required uint64 recruiter = 2; + required int64 recruiter = 2; required int64 timestamp = 3; } - } enum AppEntityType { @@ -140,11 +132,9 @@ enum AppMarkerType { } message AppRequest { - required uint32 seq = 1; required uint64 playerId = 2; required int32 playerToken = 3; - optional uint32 entityId = 4; optional AppEmpty getInfo = 8; optional AppEmpty getTime = 9; @@ -163,7 +153,9 @@ message AppRequest { optional AppEmpty getClanChat = 23; optional AppSendMessage sendClanMessage = 24; optional AppGetNexusAuth getNexusAuth = 25; - + optional AppCameraSubscribe cameraSubscribe = 30; + optional AppEmpty cameraUnsubscribe = 31; + optional AppCameraInput cameraInput = 32; } message AppMessage { @@ -172,7 +164,7 @@ message AppMessage { } message AppResponse { - required uint32 seq = 1; + required int32 seq = 1; optional AppSuccess success = 4; optional AppError error = 5; optional AppInfo info = 6; @@ -186,18 +178,19 @@ message AppResponse { optional AppClanInfo clanInfo = 15; optional AppClanChat clanChat = 16; optional AppNexusAuth nexusAuth = 17; + optional AppCameraInfo cameraSubscribeInfo = 20; } message AppBroadcast { optional AppTeamChanged teamChanged = 4; - optional AppNewTeamMessage teamMessage = 5; + optional AppNewTeamMessage newTeamMessage = 5; optional AppEntityChanged entityChanged = 6; optional AppClanChanged clanChanged = 7; optional AppNewClanMessage clanMessage = 8; + optional AppCameraRays cameraRays = 10; } message AppEmpty { - } message AppSendMessage { @@ -209,7 +202,7 @@ message AppSetEntityValue { } message AppPromoteToLeader { - required uint64 steamId = 1; + required int64 steamId = 1; } message AppGetNexusAuth { @@ -217,7 +210,6 @@ message AppGetNexusAuth { } message AppSuccess { - } message AppError { @@ -255,7 +247,6 @@ message AppTime { } message AppMap { - required uint32 width = 1; required uint32 height = 2; required bytes jpgImage = 3; @@ -268,7 +259,6 @@ message AppMap { required float x = 2; required float y = 3; } - } message AppEntityInfo { @@ -277,7 +267,6 @@ message AppEntityInfo { } message AppEntityPayload { - optional bool value = 1; repeated AppEntityPayload.Item items = 2; optional int32 capacity = 3; @@ -289,11 +278,9 @@ message AppEntityPayload { required int32 quantity = 2; required bool itemIsBlueprint = 3; } - } message AppTeamInfo { - required uint64 leaderSteamId = 1; repeated AppTeamInfo.Member members = 2; repeated AppTeamInfo.Note mapNotes = 3; @@ -315,7 +302,6 @@ message AppTeamInfo { required float x = 3; required float y = 4; } - } message AppTeamMessage { @@ -331,7 +317,6 @@ message AppTeamChat { } message AppMarker { - required uint32 id = 1; required AppMarkerType type = 2; required float x = 3; @@ -357,7 +342,6 @@ message AppMarker { optional float itemCondition = 8; optional float itemConditionMax = 9; } - } message AppMapMarkers { @@ -406,3 +390,42 @@ message AppNewClanMessage { required int64 clanId = 1; required AppClanMessage message = 2; } + +message AppCameraSubscribe { + required string cameraId = 1; +} + +message AppCameraInput { + required int32 buttons = 1; + required Vector2 mouseDelta = 2; +} + +message AppCameraInfo { + required int32 width = 1; + required int32 height = 2; + required float nearPlane = 3; + required float farPlane = 4; + required int32 controlFlags = 5; +} + +message AppCameraRays { + required float verticalFov = 1; + required int32 sampleOffset = 2; + required bytes rayData = 3; + required float distance = 4; + repeated AppCameraRays.Entity entities = 5; + + enum EntityType { + Tree = 1; + Player = 2; + } + + message Entity { + required uint32 entityId = 1; + required EntityType type = 2; + required Vector3 position = 3; + required Vector3 rotation = 4; + required Vector3 size = 5; + optional string name = 6; + } +} From a7545a9d391733f355d9788939eec8a350e12589 Mon Sep 17 00:00:00 2001 From: Ollie <69084614+olijeffers0n@users.noreply.github.com> Date: Mon, 20 Mar 2023 11:44:48 +0000 Subject: [PATCH 02/14] Update Protobuf --- rustplus.proto | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/rustplus.proto b/rustplus.proto index b5e6db6..243f806 100644 --- a/rustplus.proto +++ b/rustplus.proto @@ -48,10 +48,10 @@ message ClanInfo { required int64 clanId = 1; required string name = 2; required int64 created = 3; - required int64 creator = 4; + required uint64 creator = 4; optional string motd = 5; optional int64 motdTimestamp = 6; - optional int64 motdAuthor = 7; + optional uint64 motdAuthor = 7; optional bytes logo = 8; optional sint32 color = 9; repeated ClanInfo.Role roles = 10; @@ -74,7 +74,7 @@ message ClanInfo { } message Member { - required int64 steamId = 1; + required uint64 steamId = 1; required int32 roleId = 2; required int64 joined = 3; required int64 lastSeen = 4; @@ -83,8 +83,8 @@ message ClanInfo { } message Invite { - required int64 steamId = 1; - required int64 recruiter = 2; + required uint64 steamId = 1; + required uint64 recruiter = 2; required int64 timestamp = 3; } } @@ -108,7 +108,7 @@ message ClanInvitations { message Invitation { required int64 clanId = 1; - required int64 recruiter = 2; + required uint64 recruiter = 2; required int64 timestamp = 3; } } @@ -164,7 +164,7 @@ message AppMessage { } message AppResponse { - required int32 seq = 1; + required uint32 seq = 1; optional AppSuccess success = 4; optional AppError error = 5; optional AppInfo info = 6; @@ -183,7 +183,7 @@ message AppResponse { message AppBroadcast { optional AppTeamChanged teamChanged = 4; - optional AppNewTeamMessage newTeamMessage = 5; + optional AppNewTeamMessage teamMessage = 5; optional AppEntityChanged entityChanged = 6; optional AppClanChanged clanChanged = 7; optional AppNewClanMessage clanMessage = 8; @@ -202,7 +202,7 @@ message AppSetEntityValue { } message AppPromoteToLeader { - required int64 steamId = 1; + required uint64 steamId = 1; } message AppGetNexusAuth { @@ -429,3 +429,4 @@ message AppCameraRays { optional string name = 6; } } +U \ No newline at end of file From b837619fd35a16e5b966026a964d07cb8d39df12 Mon Sep 17 00:00:00 2001 From: Ollie <69084614+olijeffers0n@users.noreply.github.com> Date: Mon, 20 Mar 2023 11:45:52 +0000 Subject: [PATCH 03/14] Remove My Fat Finger Mistakes --- rustplus.proto | 1 - 1 file changed, 1 deletion(-) diff --git a/rustplus.proto b/rustplus.proto index 243f806..d893865 100644 --- a/rustplus.proto +++ b/rustplus.proto @@ -429,4 +429,3 @@ message AppCameraRays { optional string name = 6; } } -U \ No newline at end of file From 44fae85d35b5306f2990161efce7a46922a14fd3 Mon Sep 17 00:00:00 2001 From: Ollie <69084614+olijeffers0n@users.noreply.github.com> Date: Mon, 20 Mar 2023 12:04:06 +0000 Subject: [PATCH 04/14] Add example and also Add Unsubscription --- examples/7_camera_operations.js | 43 +++++++++++++++++++++++++++++++++ rustplus.js | 27 +++++++++++++-------- 2 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 examples/7_camera_operations.js diff --git a/examples/7_camera_operations.js b/examples/7_camera_operations.js new file mode 100644 index 0000000..8cfd4b8 --- /dev/null +++ b/examples/7_camera_operations.js @@ -0,0 +1,43 @@ +const RustPlus = require('../rustplus.js'); +const {read} = require("jimp"); +var rustplus = new RustPlus('ip', 'port', 'playerId', 'playerToken'); + +rustplus.on('connected', () => { + + /** + * We must first subscribe to the camera. Frame data works on a subscription basis, + * where after subscription the server will send data to your client for around 10-15 seconds + * where you must then re-subscribe. + */ + rustplus.subscribeToCamera("DOME1", cameraData => { + if (!cameraData.cameraSubscribeInfo) return; + const subData = cameraData.response.cameraSubscribeInfo; + + let cameraRays = []; + + // Listen for broadcasts containing the camera rays + rustplus.on("message", broadcast => { + if (broadcast.broadcast && broadcast.broadcast.cameraRays) { + // This means it was a rays message. + cameraRays.push(broadcast.broadcast.cameraRays); + } + + // Wait until we have 5 Broadcasts to build an Image: + if (cameraRays.length >= 5) { + // Remove the first rayData + cameraRays.shift() + // Render the image and then save it with jimp + rustplus.renderCameraFrame(cameraRays, subData.width, subData.height, image => { + read(image, (err, image) => { + if (err) throw err + image.write("camera.png") + }) + }) + } + }) + }) + +}); + +// connect to rust server +rustplus.connect(); \ No newline at end of file diff --git a/rustplus.js b/rustplus.js index 3edf4bc..123fa17 100644 --- a/rustplus.js +++ b/rustplus.js @@ -7,7 +7,6 @@ const { EventEmitter } = require('events'); const Jimp = require('jimp'); - class RustPlus extends EventEmitter { /** @@ -329,6 +328,19 @@ class RustPlus extends EventEmitter { }, callback); } + /** + * Unsubscribes from a CCTV Camera + * @param identifier CCTV Camera Identifier + * @param callback + */ + unsubscribeFromCamera(identifier, callback) { + this.sendRequest({ + cameraUnsubscribe: { + + } + }, callback) + } + /** * Sends camera movement to the server (mouse movement) * @param buttons The buttons that are currently pressed @@ -350,7 +362,7 @@ class RustPlus extends EventEmitter { /** * Render a camera frame to a PNG image buffer - * @param frames the frame data to render + * @param frames the frame data to render. This will be an iterable of camera rays from the server. * @param width the width of the frame * @param height the height of the frame * @param callback the callback to call with the rendered image buffer @@ -495,7 +507,7 @@ class RustPlus extends EventEmitter { /** * These represent the possible inputs that can be sent to the server. */ - static MovementControls = { + static MovementInputControls = { FORWARD : 2, BACKWARD : 4, @@ -540,16 +552,11 @@ class IndexGenerator { } nextState() { - var e = this.state, - t = e; + var e = this.state, t = e; e = ((e = ((e = (e ^ ((e << 13) | 0)) | 0) ^ ((e >>> 17) | 0)) | 0) ^ ((e << 5) | 0)) | 0; this.state = e; - return j(t); + return t >= 0 ? t : 4294967295 + t - 1; } } -function j(e) { - return e >= 0 ? e : 4294967295 + e - 1; -} - module.exports = RustPlus; From 28a177035efb630f391dbc767cfc158f85d28873 Mon Sep 17 00:00:00 2001 From: Ollie <69084614+olijeffers0n@users.noreply.github.com> Date: Mon, 20 Mar 2023 12:17:47 +0000 Subject: [PATCH 05/14] Extend example and Update README --- README.md | 2 ++ examples/7_camera_operations.js | 35 +++++++++++++++++++++++++-------- rustplus.js | 18 ++++++++--------- 3 files changed, 38 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 72bfaf1..241ed8e 100644 --- a/README.md +++ b/README.md @@ -329,6 +329,8 @@ SetEntityValue: 1 SetSubscription: 1 TeamInfo: 1 Time: 1 +Camera Subscription: 1 +Camera Movement: 0.01 ``` ## Hey Facepunch! diff --git a/examples/7_camera_operations.js b/examples/7_camera_operations.js index 8cfd4b8..faaa58f 100644 --- a/examples/7_camera_operations.js +++ b/examples/7_camera_operations.js @@ -9,7 +9,7 @@ rustplus.on('connected', () => { * where after subscription the server will send data to your client for around 10-15 seconds * where you must then re-subscribe. */ - rustplus.subscribeToCamera("DOME1", cameraData => { + rustplus.subscribeToCamera("A_PTZ_CAMERA", cameraData => { if (!cameraData.cameraSubscribeInfo) return; const subData = cameraData.response.cameraSubscribeInfo; @@ -25,17 +25,36 @@ rustplus.on('connected', () => { // Wait until we have 5 Broadcasts to build an Image: if (cameraRays.length >= 5) { // Remove the first rayData - cameraRays.shift() + cameraRays.shift(); // Render the image and then save it with jimp rustplus.renderCameraFrame(cameraRays, subData.width, subData.height, image => { read(image, (err, image) => { - if (err) throw err - image.write("camera.png") - }) - }) + if (err) throw err; + image.write("camera.png"); + }); + }); } - }) - }) + }); + + // Lets check that we have the ability to zoom the camera: + // This can be done with a bitwise AND operation and checking it is equal to the constant + if ((subData.controlFlags & RustPlus.CameraMovementOptions.FIRE) === RustPlus.CameraMovementOptions.FIRE) { + // This means this flag is enabled. Now we are going to send a zoom command every 5 seconds + setInterval(() => { + // Use a bitwise OR on all the operations you need. The 0 is for demonstration only + const buttons = 0 | RustPlus.MovementInputControls.FIRE_PRIMARY; + rustplus.sendCameraMovement(buttons, 0, 0); + // Once the input is sent, you must then "clear" the movement. If you imagine clicking down + // And then releasing the button + rustplus.sendCameraMovement(0, 0, 0); + }, 5000); + } + + // Resubscribe to the camera every 10 seconds + setInterval(() => { + rustplus.subscribeToCamera("A_PTZ_CAMERA") + }, 10_000) + }); }); diff --git a/rustplus.js b/rustplus.js index 123fa17..b347109 100644 --- a/rustplus.js +++ b/rustplus.js @@ -410,11 +410,11 @@ class RustPlus extends EventEmitter { break; // Get the first byte and set some variables - var t, r, i, n = rayData[dataPointer++]; + let t, r, i, n = rayData[dataPointer++]; // Ray Decoding Logic if (255 === n) { - var l = rayData[dataPointer++], + let l = rayData[dataPointer++], o = rayData[dataPointer++], s = rayData[dataPointer++], u = (3 * (((t = (l << 2) | (o >> 6)) / 128) | 0) + 5 * (((r = 63 & o) / 16) | 0) + 7 * (i = s)) & 63, @@ -423,15 +423,15 @@ class RustPlus extends EventEmitter { f[1] = r; f[2] = i; } else { - var c = 192 & n; + let c = 192 & n; if (0 === c) { - var h = 63 & n, y = rayLookback[h]; + let h = 63 & n, y = rayLookback[h]; t = y[0]; r = y[1]; i = y[2]; } else if (64 === c) { - var p = 63 & n, + let p = 63 & n, v = rayLookback[p], b = v[0], w = v[1], @@ -441,7 +441,7 @@ class RustPlus extends EventEmitter { r = w + ((7 & g) - 3); i = _; } else if (128 === c) { - var R = 63 & n, + let R = 63 & n, C = rayLookback[R], I = C[0], P = C[1], @@ -450,7 +450,7 @@ class RustPlus extends EventEmitter { r = P; i = k; } else { - var A = rayData[dataPointer++], + let A = rayData[dataPointer++], F = rayData[dataPointer++], D = (3 * (((t = (A << 2) | (F >> 6)) / 128) | 0) + 5 * (((r = 63 & F) / 16) | 0) + 7 * (i = 63 & n)) & 63, E = rayLookback[D]; @@ -546,13 +546,13 @@ class IndexGenerator { } nextInt(e) { - var t = ((this.nextState() * (0 | e)) / 4294967295) | 0; + let t = ((this.nextState() * (0 | e)) / 4294967295) | 0; if (t < 0) t = e + t - 1; return 0 | t; } nextState() { - var e = this.state, t = e; + let e = this.state, t = e; e = ((e = ((e = (e ^ ((e << 13) | 0)) | 0) ^ ((e >>> 17) | 0)) | 0) ^ ((e << 5) | 0)) | 0; this.state = e; return t >= 0 ? t : 4294967295 + t - 1; From cec2bd33d33a9de42d1952ba7dc78077685b8726 Mon Sep 17 00:00:00 2001 From: Ollie <69084614+olijeffers0n@users.noreply.github.com> Date: Mon, 20 Mar 2023 12:20:40 +0000 Subject: [PATCH 06/14] Semi Colons --- examples/7_camera_operations.js | 4 ++-- rustplus.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/7_camera_operations.js b/examples/7_camera_operations.js index faaa58f..e51b97f 100644 --- a/examples/7_camera_operations.js +++ b/examples/7_camera_operations.js @@ -52,8 +52,8 @@ rustplus.on('connected', () => { // Resubscribe to the camera every 10 seconds setInterval(() => { - rustplus.subscribeToCamera("A_PTZ_CAMERA") - }, 10_000) + rustplus.subscribeToCamera("A_PTZ_CAMERA"); + }, 10_000); }); }); diff --git a/rustplus.js b/rustplus.js index b347109..45c3a64 100644 --- a/rustplus.js +++ b/rustplus.js @@ -469,7 +469,7 @@ class RustPlus extends EventEmitter { const colours = [ [0.5, 0.5, 0.5], [0.8, 0.7, 0.7], [0.3, 0.7, 1], [0.6, 0.6, 0.6], [0.7, 0.7, 0.7], [0.8, 0.6, 0.4], [1, 0.4, 0.4], [1, 0.1, 0.1], - ] + ]; const image = new Jimp(width, height); @@ -500,7 +500,7 @@ class RustPlus extends EventEmitter { image.getBuffer(Jimp.MIME_PNG, (err, buffer) => { if (err) console.log(err); - callback(buffer) + callback(buffer); }); } From a71e0043593c470d0526f172548780323e30ef20 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 21 Mar 2023 15:20:52 +1300 Subject: [PATCH 07/14] move camera rendering to dedicated class --- camera.js | 374 ++++++++++++++++++++++++++++++++++++++++++++++++++++ rustplus.js | 220 +++---------------------------- 2 files changed, 391 insertions(+), 203 deletions(-) create mode 100644 camera.js diff --git a/camera.js b/camera.js new file mode 100644 index 0000000..b5690ce --- /dev/null +++ b/camera.js @@ -0,0 +1,374 @@ +"use strict"; + +const { EventEmitter } = require("events"); +const Jimp = require("jimp"); + +class Camera extends EventEmitter { + + /** + * These represent the possible buttons that can be sent to the server. + */ + static Buttons = { + FORWARD: 2, + BACKWARD: 4, + LEFT: 8, + RIGHT: 16, + JUMP: 32, + DUCK: 64, + SPRINT: 128, + USE: 256, + FIRE_PRIMARY: 1024, + FIRE_SECONDARY: 2048, + RELOAD: 8192, + FIRE_THIRD: 134217728, + } + + /** + * These represent the possible control flags that can be sent to the server. + * For example, Static CCTV cameras will not support movement. + */ + static ControlFlags = { + NONE: 0, + MOVEMENT: 1, + MOUSE: 2, + SPRINT_AND_DUCK: 4, + FIRE: 8, + RELOAD: 16, + CROSSHAIR: 32, + } + + /** + * @param rustplus An existing RustPlus instance + * @param identifier Camera Identifier, such as OILRIG1 (or custom name) + * + * Events emitted by the Camera class instance + * - subscribing: When we are subscribing to the Camera. + * - subscribed: When we are subscribed to the Camera. + * - unsubscribing: When we are unsubscribing from the Camera. + * - unsubscribed: When we are unsubscribed from the Camera. + * - render: When a camera frame has been rendered. A png image buffer will be provided. + */ + constructor(rustplus, identifier) { + + super(); + + this.rustplus = rustplus; + this.identifier = identifier; + this.isSubscribed = false; + + this.cameraRays = []; + this.cameraSubscribeInfo = null; + this.subscribeInterval = null; + + // listen to camera message broadcasts + this.rustplus.on('message', async (message) => { + await this._onMessage(message); + }); + + // unsubscribe when rustplus is disconnected (to prevent hanging due to intervals still running) + this.rustplus.on('disconnected', async () => { + if(this.isSubscribed){ + await this.unsubscribe(); + } + }); + + } + + async _onMessage(message) { + + // do nothing if not subscribed + if(!this.isSubscribed){ + return; + } + + if(message.broadcast && message.broadcast.cameraRays){ + await this._onCameraRays(message.broadcast.cameraRays); + } + + } + + async _onCameraRays(cameraRays) { + + // do nothing if not subscribed + if(!this.isSubscribed){ + return; + } + + // add new camera rays to cache + this.cameraRays.push(cameraRays); + + // wait until we have enough camera rays to render an image + if(this.cameraRays.length > 10){ + + // remove first oldest rayData + this.cameraRays.shift(); + + // render to png + const frame = await this._renderCameraFrame(this.cameraRays, this.cameraSubscribeInfo.width, this.cameraSubscribeInfo.height); + + // fire callback + await this._onRender(frame); + + } + + } + + async _onRender(image) { + + // do nothing if not subscribed + if(!this.isSubscribed){ + return; + } + + this.emit('render', image); + + } + + /** + * Render a camera frame to a PNG image buffer + * @param frames the frame data to render. This will be an array of camera rays from the server. + * @param width the width of the frame + * @param height the height of the frame + */ + async _renderCameraFrame(frames, width, height) { + + // First we populate the samplePositionBuffer with the positions of each sample + const samplePositionBuffer = new Int16Array(width * height * 2); + for (let w = 0, _ = 0; _ < height; _++) + for (let g = 0; g < width; g++) { + samplePositionBuffer[w] = g; + samplePositionBuffer[++w] = _; + w++; + } + + for (let B = new IndexGenerator(1337), R = width * height - 1; R >= 1; R--) { + let C = 2 * R, + I = 2 * B.nextInt(R + 1), + P = samplePositionBuffer[C], + k = samplePositionBuffer[C + 1], + A = samplePositionBuffer[I], + F = samplePositionBuffer[I + 1]; + samplePositionBuffer[I] = P; + samplePositionBuffer[I + 1] = k; + samplePositionBuffer[C] = A; + samplePositionBuffer[C + 1] = F; + } + + // Create the output buffer + const output = new Array(width * height); + // Loop through each frame + for (let frame of frames) { + + // Reset some look back and pointer variables + let sampleOffset = 2 * frame.sampleOffset; + let dataPointer = 0; + let rayLookback = new Array(64); + for (let r = 0; r < 64; r++) rayLookback[r] = [0, 0, 0]; + + const rayData = frame.rayData; + + // Loop through the ray data + while (true) { + if (dataPointer >= rayData.length - 1) + break; + + // Get the first byte and set some variables + let t, r, i, n = rayData[dataPointer++]; + + // Ray Decoding Logic + if (255 === n) { + let l = rayData[dataPointer++], + o = rayData[dataPointer++], + s = rayData[dataPointer++], + u = (3 * (((t = (l << 2) | (o >> 6)) / 128) | 0) + 5 * (((r = 63 & o) / 16) | 0) + 7 * (i = s)) & 63, + f = rayLookback[u]; + f[0] = t; + f[1] = r; + f[2] = i; + } else { + let c = 192 & n; + + if (0 === c) { + let h = 63 & n, y = rayLookback[h]; + t = y[0]; + r = y[1]; + i = y[2]; + } else if (64 === c) { + let p = 63 & n, + v = rayLookback[p], + b = v[0], + w = v[1], + _ = v[2], + g = rayData[dataPointer++]; + t = b + ((g >> 3) - 15); + r = w + ((7 & g) - 3); + i = _; + } else if (128 === c) { + let R = 63 & n, + C = rayLookback[R], + I = C[0], + P = C[1], + k = C[2]; + t = I + (rayData[dataPointer++] - 127); + r = P; + i = k; + } else { + let A = rayData[dataPointer++], + F = rayData[dataPointer++], + D = (3 * (((t = (A << 2) | (F >> 6)) / 128) | 0) + 5 * (((r = 63 & F) / 16) | 0) + 7 * (i = 63 & n)) & 63, + E = rayLookback[D]; + E[0] = t; + E[1] = r; + E[2] = i; + } + } + + sampleOffset %= 2 * width * height; + const index = samplePositionBuffer[sampleOffset++] + samplePositionBuffer[sampleOffset++] * width; + output[index] = [t / 1023, r / 63, i]; + } + } + + const colours = [ + [0.5, 0.5, 0.5], [0.8, 0.7, 0.7], [0.3, 0.7, 1], [0.6, 0.6, 0.6], + [0.7, 0.7, 0.7], [0.8, 0.6, 0.4], [1, 0.4, 0.4], [1, 0.1, 0.1], + ]; + + const image = new Jimp(width, height); + + for (let i = 0; i < output.length; i++) { + let ray = output[i]; + if (!ray) { + continue; + } + + let distance = ray[0] + let alignment = ray[1] + let material = ray[2] + + let target_colour; + + if (distance === 1 && alignment === 0 && material === 0) { + target_colour = [208, 230, 252]; + } else { + let colour = colours[material]; + target_colour = [(alignment * colour[0] * 255), (alignment * colour[1] * 255), (alignment * colour[2] * 255)] + } + + let x = i % width; + let y = height - 1 - Math.floor(i / width); + image.setPixelColor(Jimp.rgbaToInt(target_colour[0], target_colour[1], target_colour[2], 255), x, y); + } + + // return png buffer + return image.getBufferAsync(Jimp.MIME_PNG); + + } + + async _subscribe() { + + // subscribe to camera + const response = await this.rustplus.sendRequestAsync({ + cameraSubscribe: { + cameraId: this.identifier, + }, + }); + + // update camera subscribe info + this.cameraSubscribeInfo = response.cameraSubscribeInfo; + this.isSubscribed = true; + + } + + async subscribe() { + + this.emit('subscribing'); + + // subscribe to camera + await this._subscribe(); + + this.emit('subscribed'); + + // automatically resubscribe to the camera every 10 seconds + this.subscribeInterval = setInterval(async () => { + if(this.isSubscribed){ + await this._subscribe(); + } + }, 10_000); + + } + + async unsubscribe() { + + this.emit('unsubscribing'); + + this.isSubscribed = false; + + // stop automatically resubscribing + clearInterval(this.subscribeInterval); + + // release memory + this.cameraRays = []; + this.cameraSubscribeInfo = null; + this.subscribeInterval = null; + + // unsubscribe from camera on server (if connected) + if(this.rustplus.isConnected()){ + try { + await this.rustplus.sendRequestAsync({ + cameraUnsubscribe: { + + }, + }); + } catch (error) { + // ignore errors unsubscribing from camera + } + } + + this.emit('unsubscribed'); + + } + + /** + * Sends camera movement to the server (mouse movement) + * @param buttons The buttons that are currently pressed + * @param x The x delta of the mouse movement + * @param y The y delta of the mouse movement + */ + async move(buttons, x, y) { + return await this.rustplus.sendRequestAsync({ + cameraInput: { + buttons: buttons, + mouseDelta: { + x: x, + y: y, + } + }, + }); + } + +} + +class IndexGenerator { + + constructor(e) { + this.state = 0 | e; + this.nextState(); + } + + nextInt(e) { + let t = ((this.nextState() * (0 | e)) / 4294967295) | 0; + if (t < 0) t = e + t - 1; + return 0 | t; + } + + nextState() { + let e = this.state, t = e; + e = ((e = ((e = (e ^ ((e << 13) | 0)) | 0) ^ ((e >>> 17) | 0)) | 0) ^ ((e << 5) | 0)) | 0; + this.state = e; + return t >= 0 ? t : 4294967295 + t - 1; + } + +} + +module.exports = Camera; \ No newline at end of file diff --git a/rustplus.js b/rustplus.js index 45c3a64..6dc256d 100644 --- a/rustplus.js +++ b/rustplus.js @@ -4,8 +4,7 @@ const path = require('path'); const WebSocket = require('ws'); const protobuf = require("protobufjs"); const { EventEmitter } = require('events'); -const Jimp = require('jimp'); - +const Camera = require('./camera'); class RustPlus extends EventEmitter { @@ -121,6 +120,14 @@ class RustPlus extends EventEmitter { } } + /** + * Check if RustPlus is connected to the server. + * @returns {boolean} + */ + isConnected() { + return this.websocket != null; + } + /** * Send a Request to the Rust Server with an optional callback when a Response is received. * @param data this should contain valid data for the AppRequest packet in the rustplus.proto schema file @@ -316,8 +323,8 @@ class RustPlus extends EventEmitter { } /** - * Get CCTV Camera frame - * @param identifier CCTV Camera Identifier, such as OILRIG1 (or custom name) + * Subscribes to a Camera + * @param identifier Camera Identifier, such as OILRIG1 (or custom name) * @param callback */ subscribeToCamera(identifier, callback) { @@ -329,8 +336,8 @@ class RustPlus extends EventEmitter { } /** - * Unsubscribes from a CCTV Camera - * @param identifier CCTV Camera Identifier + * Unsubscribes from a Camera + * @param identifier Camera Identifier * @param callback */ unsubscribeFromCamera(identifier, callback) { @@ -342,13 +349,13 @@ class RustPlus extends EventEmitter { } /** - * Sends camera movement to the server (mouse movement) + * Sends camera input to the server (mouse movement) * @param buttons The buttons that are currently pressed * @param x The x delta of the mouse movement * @param y The y delta of the mouse movement * @param callback */ - sendCameraMovement(buttons, x, y, callback) { + sendCameraInput(buttons, x, y, callback) { this.sendRequest({ cameraInput: { buttons: buttons, @@ -360,203 +367,10 @@ class RustPlus extends EventEmitter { }, callback); } - /** - * Render a camera frame to a PNG image buffer - * @param frames the frame data to render. This will be an iterable of camera rays from the server. - * @param width the width of the frame - * @param height the height of the frame - * @param callback the callback to call with the rendered image buffer - */ - renderCameraFrame(frames, width, height, callback) { - - // First we populate the samplePositionBuffer with the positions of each sample - const samplePositionBuffer = new Int16Array(width * height * 2); - for (let w = 0, _ = 0; _ < height; _++) - for (let g = 0; g < width; g++) { - samplePositionBuffer[w] = g; - samplePositionBuffer[++w] = _; - w++; - } - - for (let B = new IndexGenerator(1337), R = width * height - 1; R >= 1; R--) { - let C = 2 * R, - I = 2 * B.nextInt(R + 1), - P = samplePositionBuffer[C], - k = samplePositionBuffer[C + 1], - A = samplePositionBuffer[I], - F = samplePositionBuffer[I + 1]; - samplePositionBuffer[I] = P; - samplePositionBuffer[I + 1] = k; - samplePositionBuffer[C] = A; - samplePositionBuffer[C + 1] = F; - } - - // Create the output buffer - const output = new Array(width * height); - // Loop through each frame - for (let frame of frames) { - - // Reset some look back and pointer variables - let sampleOffset = 2 * frame.sampleOffset; - let dataPointer = 0; - let rayLookback = new Array(64); - for (let r = 0; r < 64; r++) rayLookback[r] = [0, 0, 0]; - - const rayData = frame.rayData; - - // Loop through the ray data - while (true) { - if (dataPointer >= rayData.length - 1) - break; - - // Get the first byte and set some variables - let t, r, i, n = rayData[dataPointer++]; - - // Ray Decoding Logic - if (255 === n) { - let l = rayData[dataPointer++], - o = rayData[dataPointer++], - s = rayData[dataPointer++], - u = (3 * (((t = (l << 2) | (o >> 6)) / 128) | 0) + 5 * (((r = 63 & o) / 16) | 0) + 7 * (i = s)) & 63, - f = rayLookback[u]; - f[0] = t; - f[1] = r; - f[2] = i; - } else { - let c = 192 & n; - - if (0 === c) { - let h = 63 & n, y = rayLookback[h]; - t = y[0]; - r = y[1]; - i = y[2]; - } else if (64 === c) { - let p = 63 & n, - v = rayLookback[p], - b = v[0], - w = v[1], - _ = v[2], - g = rayData[dataPointer++]; - t = b + ((g >> 3) - 15); - r = w + ((7 & g) - 3); - i = _; - } else if (128 === c) { - let R = 63 & n, - C = rayLookback[R], - I = C[0], - P = C[1], - k = C[2]; - t = I + (rayData[dataPointer++] - 127); - r = P; - i = k; - } else { - let A = rayData[dataPointer++], - F = rayData[dataPointer++], - D = (3 * (((t = (A << 2) | (F >> 6)) / 128) | 0) + 5 * (((r = 63 & F) / 16) | 0) + 7 * (i = 63 & n)) & 63, - E = rayLookback[D]; - E[0] = t; - E[1] = r; - E[2] = i; - } - } - - sampleOffset %= 2 * width * height; - const index = samplePositionBuffer[sampleOffset++] + samplePositionBuffer[sampleOffset++] * width; - output[index] = [t / 1023, r / 63, i]; - } - } - - const colours = [ - [0.5, 0.5, 0.5], [0.8, 0.7, 0.7], [0.3, 0.7, 1], [0.6, 0.6, 0.6], - [0.7, 0.7, 0.7], [0.8, 0.6, 0.4], [1, 0.4, 0.4], [1, 0.1, 0.1], - ]; - - const image = new Jimp(width, height); - - for (let i = 0; i < output.length; i++) { - let ray = output[i]; - if (!ray) { - continue; - } - - let distance = ray[0] - let alignment = ray[1] - let material = ray[2] - - let target_colour; - - if (distance === 1 && alignment === 0 && material === 0) { - target_colour = [208, 230, 252]; - } else { - let colour = colours[material]; - target_colour = [(alignment * colour[0] * 255), (alignment * colour[1] * 255), (alignment * colour[2] * 255)] - } - - let x = i % width; - let y = height - 1 - Math.floor(i / width); - image.setPixelColor(Jimp.rgbaToInt(target_colour[0], target_colour[1], target_colour[2], 255), x, y); - } - - image.getBuffer(Jimp.MIME_PNG, (err, buffer) => { - if (err) - console.log(err); - callback(buffer); - }); - } - - /** - * These represent the possible inputs that can be sent to the server. - */ - static MovementInputControls = { - - FORWARD : 2, - BACKWARD : 4, - LEFT : 8, - RIGHT : 16, - JUMP : 32, - DUCK : 64, - SPRINT : 128, - USE : 256, - FIRE_PRIMARY : 1024, - FIRE_SECONDARY : 2048, - RELOAD : 8192, - FIRE_THIRD : 134217728, - } - - /** - * These represent the possible camera movement options that can be sent to the server. - * For example, Static CCTV cameras will not have any movement options. - */ - static CameraMovementOptions = { - NONE : 0, - MOVEMENT : 1, - MOUSE : 2, - SPRINT_AND_DUCK : 4, - FIRE : 8, - RELOAD : 16, - CROSSHAIR : 32, + getCamera(identifier) { + return new Camera(this, identifier); } } -class IndexGenerator { - constructor(e) { - this.state = 0 | e; - this.nextState(); - } - - nextInt(e) { - let t = ((this.nextState() * (0 | e)) / 4294967295) | 0; - if (t < 0) t = e + t - 1; - return 0 | t; - } - - nextState() { - let e = this.state, t = e; - e = ((e = ((e = (e ^ ((e << 13) | 0)) | 0) ^ ((e >>> 17) | 0)) | 0) ^ ((e << 5) | 0)) | 0; - this.state = e; - return t >= 0 ? t : 4294967295 + t - 1; - } -} - module.exports = RustPlus; From cb4dc4e12eb00b287fac39eec5790f19d631b9df Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 21 Mar 2023 15:27:33 +1300 Subject: [PATCH 08/14] add camera render example --- examples/8_render_camera.js | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 examples/8_render_camera.js diff --git a/examples/8_render_camera.js b/examples/8_render_camera.js new file mode 100644 index 0000000..0a01b52 --- /dev/null +++ b/examples/8_render_camera.js @@ -0,0 +1,34 @@ +const RustPlus = require('@liamcottle/rustplus.js'); +const fs = require("fs"); +var rustplus = new RustPlus('ip', 'port', 'playerId', 'playerToken'); + +rustplus.on('connected', async () => { + + console.log("connected"); + + // get a camera + const camera = rustplus.getCamera("DOME1"); + + // listen for events when a camera frame has been rendered, you will get a png image buffer + camera.on('render', async (frame) => { + + console.log("on render"); + + // save camera frame to disk + fs.writeFileSync(`camera.png`, frame); + + // unsubscribe from camera to allow others to control it + await camera.unsubscribe(); + + // disconnect from server after a single render + rustplus.disconnect(); + + }); + + // subscribe to camera + await camera.subscribe(); + +}); + +// connect to rust server +rustplus.connect(); \ No newline at end of file From 3c820c164bf3c8897550a0017b6ae74506491d9b Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 21 Mar 2023 15:48:30 +1300 Subject: [PATCH 09/14] add example for moving ptz camera --- camera.js | 1 + examples/9_move_ptz_camera.js | 56 +++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 examples/9_move_ptz_camera.js diff --git a/camera.js b/camera.js index b5690ce..1873a01 100644 --- a/camera.js +++ b/camera.js @@ -9,6 +9,7 @@ class Camera extends EventEmitter { * These represent the possible buttons that can be sent to the server. */ static Buttons = { + NONE: 0, FORWARD: 2, BACKWARD: 4, LEFT: 8, diff --git a/examples/9_move_ptz_camera.js b/examples/9_move_ptz_camera.js new file mode 100644 index 0000000..c8ce98d --- /dev/null +++ b/examples/9_move_ptz_camera.js @@ -0,0 +1,56 @@ +const RustPlus = require('../rustplus'); +const Camera = require('../camera'); +var rustplus = new RustPlus('ip', 'port', 'playerId', 'playerToken'); + +function delay(time) { + return new Promise(resolve => setTimeout(resolve, time)); +} + +rustplus.on('connected', async () => { + + console.log("connected"); + + // get a camera (must support PTZ) + const camera = rustplus.getCamera("PTZ1"); + + // wait until subscribed to camera + camera.on('subscribed', async () => { + + console.log("subscribed to camera"); + + // move camera up 10 times + for(let i = 0; i < 10; i++){ + await camera.move(Camera.Buttons.NONE, 0, 1); + await delay(100); + } + + // move camera down 10 times + for(let i = 0; i < 10; i++){ + await camera.move(Camera.Buttons.NONE, 0, -1); + await delay(100); + } + + // move camera left 10 times + for(let i = 0; i < 10; i++){ + await camera.move(Camera.Buttons.NONE, -1, 0); + await delay(100); + } + + // move camera right 10 times + for(let i = 0; i < 10; i++){ + await camera.move(Camera.Buttons.NONE, 1, 0); + await delay(100); + } + + console.log("disconnecting"); + await rustplus.disconnect(); + + }); + + // subscribe to camera + await camera.subscribe(); + +}); + +// connect to rust server +rustplus.connect(); \ No newline at end of file From 5153280385703825ee422258d845f33253687a53 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 21 Mar 2023 15:59:28 +1300 Subject: [PATCH 10/14] add example for zooming ptz camera --- camera.js | 15 ++++++++++++++ examples/9_zoom_ptz_camera.js | 37 +++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 examples/9_zoom_ptz_camera.js diff --git a/camera.js b/camera.js index 1873a01..dc73110 100644 --- a/camera.js +++ b/camera.js @@ -348,6 +348,21 @@ class Camera extends EventEmitter { }); } + /** + * Zooms a PTZ camera in by 1 level. + * PTZ cameras have 4 zoom levels. + * If the PTZ camera is already at max zoom (level 4), it zooms out as far as it can (level 1). + */ + async zoom() { + + // press left mouse button to zoom in + await this.move(Camera.Buttons.FIRE_PRIMARY, 0, 0); + + // release all mouse buttons + await this.move(Camera.Buttons.NONE, 0, 0); + + } + } class IndexGenerator { diff --git a/examples/9_zoom_ptz_camera.js b/examples/9_zoom_ptz_camera.js new file mode 100644 index 0000000..ff034e2 --- /dev/null +++ b/examples/9_zoom_ptz_camera.js @@ -0,0 +1,37 @@ +const RustPlus = require('../rustplus'); +var rustplus = new RustPlus('ip', 'port', 'playerId', 'playerToken'); + +function delay(time) { + return new Promise(resolve => setTimeout(resolve, time)); +} + +rustplus.on('connected', async () => { + + console.log("connected"); + + // get a camera (must support PTZ) + const camera = rustplus.getCamera("PTZ1"); + + // wait until subscribed to camera + camera.on('subscribed', async () => { + + console.log("subscribed to camera"); + + // zoom camera every 1 second, 8 times + for(let i = 0; i < 8; i++){ + await camera.zoom(); + await delay(1000); + } + + console.log("disconnecting"); + await rustplus.disconnect(); + + }); + + // subscribe to camera + await camera.subscribe(); + +}); + +// connect to rust server +rustplus.connect(); \ No newline at end of file From ed53e2f9c8727fa6dfd7068978fca615905fe1c0 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 21 Mar 2023 16:23:24 +1300 Subject: [PATCH 11/14] remove old example --- examples/7_camera_operations.js | 62 --------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 examples/7_camera_operations.js diff --git a/examples/7_camera_operations.js b/examples/7_camera_operations.js deleted file mode 100644 index e51b97f..0000000 --- a/examples/7_camera_operations.js +++ /dev/null @@ -1,62 +0,0 @@ -const RustPlus = require('../rustplus.js'); -const {read} = require("jimp"); -var rustplus = new RustPlus('ip', 'port', 'playerId', 'playerToken'); - -rustplus.on('connected', () => { - - /** - * We must first subscribe to the camera. Frame data works on a subscription basis, - * where after subscription the server will send data to your client for around 10-15 seconds - * where you must then re-subscribe. - */ - rustplus.subscribeToCamera("A_PTZ_CAMERA", cameraData => { - if (!cameraData.cameraSubscribeInfo) return; - const subData = cameraData.response.cameraSubscribeInfo; - - let cameraRays = []; - - // Listen for broadcasts containing the camera rays - rustplus.on("message", broadcast => { - if (broadcast.broadcast && broadcast.broadcast.cameraRays) { - // This means it was a rays message. - cameraRays.push(broadcast.broadcast.cameraRays); - } - - // Wait until we have 5 Broadcasts to build an Image: - if (cameraRays.length >= 5) { - // Remove the first rayData - cameraRays.shift(); - // Render the image and then save it with jimp - rustplus.renderCameraFrame(cameraRays, subData.width, subData.height, image => { - read(image, (err, image) => { - if (err) throw err; - image.write("camera.png"); - }); - }); - } - }); - - // Lets check that we have the ability to zoom the camera: - // This can be done with a bitwise AND operation and checking it is equal to the constant - if ((subData.controlFlags & RustPlus.CameraMovementOptions.FIRE) === RustPlus.CameraMovementOptions.FIRE) { - // This means this flag is enabled. Now we are going to send a zoom command every 5 seconds - setInterval(() => { - // Use a bitwise OR on all the operations you need. The 0 is for demonstration only - const buttons = 0 | RustPlus.MovementInputControls.FIRE_PRIMARY; - rustplus.sendCameraMovement(buttons, 0, 0); - // Once the input is sent, you must then "clear" the movement. If you imagine clicking down - // And then releasing the button - rustplus.sendCameraMovement(0, 0, 0); - }, 5000); - } - - // Resubscribe to the camera every 10 seconds - setInterval(() => { - rustplus.subscribeToCamera("A_PTZ_CAMERA"); - }, 10_000); - }); - -}); - -// connect to rust server -rustplus.connect(); \ No newline at end of file From 1bc7095522f3a99a2e8a9f54915357318fdc1714 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 21 Mar 2023 16:23:48 +1300 Subject: [PATCH 12/14] rename examples --- examples/{8_render_camera.js => 7_render_camera.js} | 0 examples/{9_move_ptz_camera.js => 8_move_ptz_camera.js} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename examples/{8_render_camera.js => 7_render_camera.js} (100%) rename examples/{9_move_ptz_camera.js => 8_move_ptz_camera.js} (100%) diff --git a/examples/8_render_camera.js b/examples/7_render_camera.js similarity index 100% rename from examples/8_render_camera.js rename to examples/7_render_camera.js diff --git a/examples/9_move_ptz_camera.js b/examples/8_move_ptz_camera.js similarity index 100% rename from examples/9_move_ptz_camera.js rename to examples/8_move_ptz_camera.js From 418b2fdba52c20a3d6272af250ac81719fe1a9f2 Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 21 Mar 2023 16:55:58 +1300 Subject: [PATCH 13/14] add example for shooting auto turrets --- camera.js | 35 +++++++++++ examples/10_shoot_autoturret.js | 100 ++++++++++++++++++++++++++++++++ 2 files changed, 135 insertions(+) create mode 100644 examples/10_shoot_autoturret.js diff --git a/camera.js b/camera.js index dc73110..85765d5 100644 --- a/camera.js +++ b/camera.js @@ -363,6 +363,41 @@ class Camera extends EventEmitter { } + /** + * Shoots a PTZ controllable Auto Turret. + */ + async shoot() { + + // press left mouse button to shoot + await this.move(Camera.Buttons.FIRE_PRIMARY, 0, 0); + + // release all mouse buttons + await this.move(Camera.Buttons.NONE, 0, 0); + + } + + /** + * Reloads a PTZ controllable Auto Turret + */ + async reload() { + + // press reload button to reload turret + await this.move(Camera.Buttons.RELOAD, 0, 0); + + // release all mouse buttons + await this.move(Camera.Buttons.NONE, 0, 0); + + } + + /** + * Check if camera is an auto turret + * @returns {boolean} + */ + isAutoTurret() { + const crosshairControlFlag = Camera.ControlFlags.CROSSHAIR; + return (this.cameraSubscribeInfo?.controlFlags & crosshairControlFlag) === crosshairControlFlag; + } + } class IndexGenerator { diff --git a/examples/10_shoot_autoturret.js b/examples/10_shoot_autoturret.js new file mode 100644 index 0000000..56c0513 --- /dev/null +++ b/examples/10_shoot_autoturret.js @@ -0,0 +1,100 @@ +const RustPlus = require('../rustplus'); +const Camera = require("../camera"); +var rustplus = new RustPlus('ip', 'port', 'playerId', 'playerToken'); + +function delay(time) { + return new Promise(resolve => setTimeout(resolve, time)); +} + +rustplus.on('connected', async () => { + + console.log("connected"); + + // get a camera (should be an autoturret for this example) + const turret = rustplus.getCamera("TURRET1"); + + // wait until subscribed to autoturret camera + turret.on('subscribed', async () => { + + // check if camera is an auto turret + if(!turret.isAutoTurret()){ + console.log("Camera is not an auto turret!"); + await rustplus.disconnect(); + return; + } + + console.log("subscribed to autoturret camera"); + await delay(500); + + const shootCount = 3; + const shootDelay = 250; + const moveDelay = 500; + const moveAmount = 5; + + // shoot autoturret + console.log("shooting"); + for(let i = 0; i < shootCount; i++){ + await delay(shootDelay); + await turret.shoot(); + } + + // move autoturret left + console.log("moving left"); + await delay(moveDelay); + await turret.move(Camera.Buttons.NONE, -moveAmount, 0); + + // shoot autoturret + console.log("shooting"); + for(let i = 0; i < shootCount; i++){ + await delay(shootDelay); + await turret.shoot(); + } + + // move autoturret up + console.log("moving up"); + await delay(moveDelay); + await turret.move(Camera.Buttons.NONE, 0, moveAmount); + + // shoot autoturret + console.log("shooting"); + for(let i = 0; i < shootCount; i++){ + await delay(shootDelay); + await turret.shoot(); + } + + // move autoturret right + console.log("moving right"); + await delay(moveDelay); + await turret.move(Camera.Buttons.NONE, moveAmount, 0); + + // shoot autoturret + console.log("shooting"); + for(let i = 0; i < shootCount; i++){ + await delay(shootDelay); + await turret.shoot(); + } + + // move autoturret down + console.log("moving down"); + await delay(moveDelay); + await turret.move(Camera.Buttons.NONE, 0, -moveAmount); + + // shoot autoturret + console.log("shooting"); + for(let i = 0; i < shootCount; i++){ + await delay(shootDelay); + await turret.shoot(); + } + + console.log("disconnecting"); + await rustplus.disconnect(); + + }); + + // subscribe to autoturret camera + await turret.subscribe(); + +}); + +// connect to rust server +rustplus.connect(); \ No newline at end of file From 2fbcdb79d5e877fb8b5701035364979c61dd7fca Mon Sep 17 00:00:00 2001 From: liamcottle Date: Tue, 21 Mar 2023 17:00:45 +1300 Subject: [PATCH 14/14] add doc block for getCamera --- rustplus.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/rustplus.js b/rustplus.js index 6dc256d..63c0991 100644 --- a/rustplus.js +++ b/rustplus.js @@ -367,6 +367,11 @@ class RustPlus extends EventEmitter { }, callback); } + /** + * Get a camera instance for controlling CCTV Cameras, PTZ Cameras and Auto Turrets + * @param identifier Camera Identifier, such as DOME1, OILRIG1L1, (or a custom camera id) + * @returns {Camera} + */ getCamera(identifier) { return new Camera(this, identifier); }